Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test and lint
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

on:
push:
branches: [main]
pull_request:
branches: ["**"]

jobs:
check:
name: Test and lint
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5

- name: Node setup
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache-dependency-path: package.json
node-version: "20.x"
cache: "npm"

- name: Install and build
run: |
npm i
npm run build
- name: Publish package for testing branch
run: npx pkg-pr-new publish || echo "Have you set up pkg-pr-new for this repo?"
- name: Test
run: |
npm run test
npm run typecheck
npm run lint
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ dist-ssr
explorations
node_modules
.eslintcache
# components are libraries!
.package-lock.json

# this is a package-json-redirect stub dir, see https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file
frontend/package.json
# npm pack output
*.tgz
*.tsbuildinfo
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"trailingComma": "all",
"proseWrap": "always"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## 0.2.0

- Adds /test and /\_generated/component.js entrypoints
- Drops commonjs support
- Improves source mapping for generated files
- Changes to a statically generated component API
40 changes: 40 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Developing guide

## Running locally

```sh
npm i
npm run dev
```

## Testing

```sh
npm run clean
npm run build
npm run typecheck
npm run lint
npm run test
```

## Deploying

### Building a one-off package

```sh
npm run clean
npm ci
npm pack
```

### Deploying a new version

```sh
npm run release
```

or for alpha release:

```sh
npm run alpha
```
58 changes: 39 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ export const sendSms = internalAction({

### Twilio Phone Number

Create a Twilio account and, if you haven't already, create a [Twilio Phone Number](https://www.twilio.com/docs/phone-numbers).
Create a Twilio account and, if you haven't already, create a
[Twilio Phone Number](https://www.twilio.com/docs/phone-numbers).

Note the **Phone Number SID** of the phone number you'll be using, you'll need it in a moment.
Note the **Phone Number SID** of the phone number you'll be using, you'll need
it in a moment.

### Convex App

You'll need a Convex App to use the component. Follow any of the [Convex quickstarts](https://docs.convex.dev/home) to set one up.
You'll need a Convex App to use the component. Follow any of the
[Convex quickstarts](https://docs.convex.dev/home) to set one up.

## Installation

Expand All @@ -44,12 +47,13 @@ Install the component package:
npm install @convex-dev/twilio
```

Create a `convex.config.ts` file in your app's `convex/` folder and install the component by calling `use`:
Create a `convex.config.ts` file in your app's `convex/` folder and install the
component by calling `use`:

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import twilio from "@convex-dev/twilio/convex.config";
import twilio from "@convex-dev/twilio/convex.config.js";

const app = defineApp();
app.use(twilio);
Expand Down Expand Up @@ -78,7 +82,8 @@ export const twilio = new Twilio(components.twilio, {
});
```

Register Twilio webhook handlers by creating an `http.ts` file in your `convex/` folder and use the client you've exported above:
Register Twilio webhook handlers by creating an `http.ts` file in your `convex/`
folder and use the client you've exported above:

```ts
// http.ts
Expand Down Expand Up @@ -110,21 +115,30 @@ export const sendSms = internalAction({
});
```

By querying the message (see [below](#querying-messages)) you can check for the status ([Twilio Statuses](https://www.twilio.com/docs/messaging/api/message-resource#message-status-values)). The component subscribes to status updates and writes the most up-to-date status into the database.
By querying the message (see [below](#querying-messages)) you can check for the
status
([Twilio Statuses](https://www.twilio.com/docs/messaging/api/message-resource#message-status-values)).
The component subscribes to status updates and writes the most up-to-date status
into the database.

## Receiving Messages

To receive messages, you will associate a webhook handler provided by the component with the Twilio phone number you'd like to use.
To receive messages, you will associate a webhook handler provided by the
component with the Twilio phone number you'd like to use.

`twilio.registerRoutes` registers two webhook HTTP handlers in your your Convex app's deployment:
`twilio.registerRoutes` registers two webhook HTTP handlers in your your Convex
app's deployment:

- `YOUR_CONVEX_SITE_URL/twilio/message-status` - capture and store delivery status of messages you **send**.
- `YOUR_CONVEX_SITE_URL/twilio/incoming-message` - capture and store messages **sent to** your Twilio phone number.
- `YOUR_CONVEX_SITE_URL/twilio/message-status` - capture and store delivery
status of messages you **send**.
- `YOUR_CONVEX_SITE_URL/twilio/incoming-message` - capture and store messages
**sent to** your Twilio phone number.

Note: You may pass a custom `httpPrefix` to `Twilio` if you want to
route Twilio endpoints somewhere other than `YOUR_CONVEX_SITE_URL/twilio/*`.
Note: You may pass a custom `httpPrefix` to `Twilio` if you want to route Twilio
endpoints somewhere other than `YOUR_CONVEX_SITE_URL/twilio/*`.

For instance, to route to `YOUR_CONVEX_SITE_URL/custom-twilio/message-status`, set:
For instance, to route to `YOUR_CONVEX_SITE_URL/custom-twilio/message-status`,
set:

```ts
export const twilio = new Twilio(components.twilio, {
Expand All @@ -134,9 +148,12 @@ export const twilio = new Twilio(components.twilio, {

You can associate it with your Twilio phone number in two ways:

1. Using the [Twilio console](https://console.twilio.com/) in the "Configure" tab of the phone number, under "Messaging Configuration" -> "A messsage comes in" -> "URL".
1. Using the [Twilio console](https://console.twilio.com/) in the "Configure"
tab of the phone number, under "Messaging Configuration" -> "A messsage comes
in" -> "URL".

2. By calling `registerIncomingSmsHandler` exposed by the component client, passing it the phone number's SID:
2. By calling `registerIncomingSmsHandler` exposed by the component client,
passing it the phone number's SID:

```ts
export const registerIncomingSmsHandler = internalAction({
Expand All @@ -149,9 +166,11 @@ export const registerIncomingSmsHandler = internalAction({
});
```

Once it is configured, incoming messages will be captured by the component and logged in the `messages` table.
Once it is configured, incoming messages will be captured by the component and
logged in the `messages` table.

You can execute your own logic upon receiving an incoming message, by providing a callback when instantiating the Twilio Component client:
You can execute your own logic upon receiving an incoming message, by providing
a callback when instantiating the Twilio Component client:

```ts
// convex/example.ts
Expand Down Expand Up @@ -181,7 +200,8 @@ but you can replay them manually from the Twilio "Error logs" console.

To list all the mssages, use the `list` method in your Convex function.

To list all the incoming or outgoing messages, use `listIncoming` and `listOutgoing` methods:
To list all the incoming or outgoing messages, use `listIncoming` and
`listOutgoing` methods:

```ts
// convex/messages.ts
Expand Down
8 changes: 0 additions & 8 deletions commonjs.json

This file was deleted.

7 changes: 7 additions & 0 deletions convex.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/convex/schemas/convex.schema.json",
"functions": "example/convex",
"codegen": {
"legacyComponentApi": false
}
}
67 changes: 59 additions & 8 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";

export default [
{ files: ["src/**/*.{js,mjs,cjs,ts,tsx}"] },
{
ignores: [
"dist/**",
"eslint.config.js",
"vitest.config.ts",
"**/_generated/",
"node10stubs.mjs",
],
},
{
files: ["src/**/*.{js,mjs,cjs,ts,tsx}", "example/**/*.{js,mjs,cjs,ts,tsx}"],
languageOptions: {
globals: globals.worker,
parser: tseslint.parser,

parserOptions: {
project: true,
tsconfigRootDir: ".",
project: ["./tsconfig.json", "./example/convex/tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
// Convex code - Worker environment
{
files: ["src/**/*.{ts,tsx}", "example/convex/**/*.{ts,tsx}"],
ignores: ["src/react/**"],
languageOptions: {
globals: globals.worker,
},
rules: {
"@typescript-eslint/no-floating-promises": "error",
"eslint-comments/no-unused-disable": "off",

// allow (_arg: number) => {} and const _foo = 1;
"@typescript-eslint/no-explicit-any": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
Expand All @@ -39,6 +44,52 @@ export default [
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-unused-expressions": [
"error",
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
},
},
// React app code - Browser environment
{
files: ["src/react/**/*.{ts,tsx}", "example/src/**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
},
},
// Example config files (vite.config.ts, etc.) - Node environment
{
files: ["example/vite.config.ts", "example/**/*.config.{js,ts}"],
languageOptions: {
globals: {
...globals.node,
...globals.browser,
},
},
},
];
8 changes: 0 additions & 8 deletions esm.json

This file was deleted.

16 changes: 0 additions & 16 deletions example/.gitignore

This file was deleted.

Loading
Loading