@resonatehq/supabase 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: "npm"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
@@ -0,0 +1,29 @@
1
+ name: cd
2
+
3
+ on:
4
+ release:
5
+ types: [released]
6
+
7
+ jobs:
8
+ run:
9
+ name: release
10
+
11
+ permissions:
12
+ id-token: write
13
+
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "20"
22
+ registry-url: "https://registry.npmjs.org"
23
+
24
+ # Ensure npm 11.5.1 or later is installed
25
+ - name: Update npm
26
+ run: npm install -g npm@latest
27
+ - run: npm install
28
+ - run: npm run build
29
+ - run: npm publish
@@ -0,0 +1,39 @@
1
+ name: ci
2
+
3
+ permissions:
4
+ contents: read
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ push:
9
+ branches: [main]
10
+ paths-ignore:
11
+ - README.md
12
+ pull_request:
13
+ branches: [main]
14
+ paths-ignore:
15
+ - README.md
16
+
17
+ jobs:
18
+ run:
19
+ runs-on: ${{ matrix.os }}
20
+ timeout-minutes: 25
21
+
22
+ strategy:
23
+ fail-fast: true
24
+ matrix:
25
+ os: [ubuntu-latest, macos-latest]
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - name: Set up Bun
31
+ uses: oven-sh/setup-bun@v2
32
+ with:
33
+ bun-version: 1.3.0
34
+
35
+ - name: install
36
+ run: bun install
37
+
38
+ - name: check linting
39
+ run: bun run check
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Resonate = void 0;
4
4
  const sdk_1 = require("@resonatehq/sdk");
5
5
  const encryptor_1 = require("@resonatehq/sdk/dist/src/encryptor");
6
+ const options_1 = require("@resonatehq/sdk/dist/src/options");
7
+ const tracer_1 = require("@resonatehq/sdk/dist/src/tracer");
6
8
  class Resonate {
7
9
  constructor({ verbose = false, encryptor = undefined, } = {}) {
8
10
  this.registry = new sdk_1.Registry();
@@ -27,7 +29,6 @@ class Resonate {
27
29
  });
28
30
  }
29
31
  const url = buildForwardedURL(req);
30
- console.log("URL", url);
31
32
  const body = await req.json();
32
33
  if (!req.body) {
33
34
  return new Response(JSON.stringify({
@@ -46,6 +47,8 @@ class Resonate {
46
47
  });
47
48
  }
48
49
  const encoder = new sdk_1.JsonEncoder();
50
+ const clock = new sdk_1.WallClock();
51
+ const tracer = new tracer_1.NoopTracer();
49
52
  const network = new sdk_1.HttpNetwork({
50
53
  headers: {},
51
54
  timeout: 60 * 1000, // 60s
@@ -53,22 +56,25 @@ class Resonate {
53
56
  verbose: this.verbose,
54
57
  });
55
58
  const resonateInner = new sdk_1.ResonateInner({
56
- anycastNoPreference: url,
57
- anycastPreference: url,
58
- clock: new sdk_1.WallClock(),
59
- dependencies: this.dependencies,
60
- handler: new sdk_1.Handler(network, encoder, this.encryptor),
61
- heartbeat: new sdk_1.NoopHeartbeat(),
62
- network,
59
+ unicast: url,
60
+ anycast: url,
63
61
  pid: `pid-${Math.random().toString(36).substring(7)}`,
62
+ ttl: 30 * 1000,
63
+ clock,
64
+ network,
65
+ handler: new sdk_1.Handler(network, encoder, this.encryptor),
64
66
  registry: this.registry,
65
- ttl: 30 * 1000, // 30s
66
- unicast: url,
67
+ heartbeat: new sdk_1.NoopHeartbeat(),
68
+ dependencies: this.dependencies,
69
+ optsBuilder: new options_1.OptionsBuilder({
70
+ match: (_) => url,
71
+ }),
67
72
  verbose: this.verbose,
73
+ tracer,
68
74
  });
69
75
  const task = { kind: "unclaimed", task: body.task };
70
76
  const completion = new Promise((resolve) => {
71
- resonateInner.process(task, (error, status) => {
77
+ resonateInner.process(tracer.startSpan(task.task.rootPromiseId, clock.now()), task, (error, status) => {
72
78
  if (error || !status) {
73
79
  resolve(new Response(JSON.stringify({
74
80
  error: "Task processing failed",
@@ -110,9 +116,30 @@ class Resonate {
110
116
  }
111
117
  exports.Resonate = Resonate;
112
118
  function buildForwardedURL(req) {
113
- const proto = req.headers.get("x-forwarded-proto") ?? "http";
114
- const host = req.headers.get("x-forwarded-host") ?? req.headers.get("host");
115
- const port = req.headers.get("x-forwarded-port");
116
- const path = req.headers.get("x-forwarded-path");
117
- return `${proto}://${host}${port ? `:${port}` : ""}${path}`;
119
+ const headers = req.headers;
120
+ const url = new URL(req.url);
121
+ // 1. Hostname Logic
122
+ // Dev: "x-forwarded-host" is present (e.g., 127.0.0.1)
123
+ // Prod: "x-forwarded-host" is missing, so we use url.hostname (e.g., project.supabase.co)
124
+ const forwardedHost = headers.get("x-forwarded-host");
125
+ const host = forwardedHost ?? url.hostname;
126
+ // 2. Protocol Logic
127
+ // Always prefer "x-forwarded-proto" (usually https in prod), fallback to "http"
128
+ const proto = headers.get("x-forwarded-proto") ?? "http";
129
+ // 3. Port Logic
130
+ // Dev: We need the port (e.g., :54321).
131
+ // Prod: We rarely need :443 explicitly in the URL string.
132
+ const forwardedPort = headers.get("x-forwarded-port");
133
+ const port = forwardedHost && forwardedPort ? `:${forwardedPort}` : "";
134
+ // 4. Path Logic
135
+ // Dev: "x-forwarded-path" contains the full path (/functions/v1/hello-world)
136
+ // Prod: We must use url.pathname.
137
+ let path = headers.get("x-forwarded-path") ?? url.pathname;
138
+ // 5. Production Path Fix
139
+ // In Prod, the internal req.url often strips '/functions/v1'.
140
+ // We re-add it if we are in Prod (no forwardedHost) and it's missing.
141
+ if (!forwardedHost && !path.startsWith("/functions/v1")) {
142
+ path = `/functions/v1${path}`;
143
+ }
144
+ return `${proto}://${host}${port}${path}`;
118
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resonatehq/supabase",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Resonate FaaS handler for Supabase Edge Functions (TypeScript)",
5
5
  "repository": {
6
6
  "url": "https://github.com/resonatehq/resonate-faas-supabase-ts"
@@ -29,6 +29,6 @@
29
29
  "typescript": "^5.9.3"
30
30
  },
31
31
  "dependencies": {
32
- "@resonatehq/sdk": "^0.8.6"
32
+ "@resonatehq/sdk": "^0.9.3"
33
33
  }
34
34
  }
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  type Encryptor,
14
14
  NoopEncryptor,
15
15
  } from "@resonatehq/sdk/dist/src/encryptor";
16
+ import { OptionsBuilder } from "@resonatehq/sdk/dist/src/options";
17
+ import { NoopTracer } from "@resonatehq/sdk/dist/src/tracer";
16
18
 
17
19
  export class Resonate {
18
20
  private registry = new Registry();
@@ -77,7 +79,6 @@ export class Resonate {
77
79
  }
78
80
 
79
81
  const url = buildForwardedURL(req);
80
- console.log("URL", url);
81
82
  const body: any = await req.json();
82
83
 
83
84
  if (!req.body) {
@@ -108,6 +109,8 @@ export class Resonate {
108
109
  }
109
110
 
110
111
  const encoder = new JsonEncoder();
112
+ const clock = new WallClock();
113
+ const tracer = new NoopTracer();
111
114
  const network = new HttpNetwork({
112
115
  headers: {},
113
116
  timeout: 60 * 1000, // 60s
@@ -116,68 +119,75 @@ export class Resonate {
116
119
  });
117
120
 
118
121
  const resonateInner = new ResonateInner({
119
- anycastNoPreference: url,
120
- anycastPreference: url,
121
- clock: new WallClock(),
122
- dependencies: this.dependencies,
123
- handler: new Handler(network, encoder, this.encryptor),
124
- heartbeat: new NoopHeartbeat(),
125
- network,
122
+ unicast: url,
123
+ anycast: url,
126
124
  pid: `pid-${Math.random().toString(36).substring(7)}`,
125
+ ttl: 30 * 1000,
126
+ clock,
127
+ network,
128
+ handler: new Handler(network, encoder, this.encryptor),
127
129
  registry: this.registry,
128
- ttl: 30 * 1000, // 30s
129
- unicast: url,
130
+ heartbeat: new NoopHeartbeat(),
131
+ dependencies: this.dependencies,
132
+ optsBuilder: new OptionsBuilder({
133
+ match: (_: string): string => url,
134
+ }),
130
135
  verbose: this.verbose,
136
+ tracer,
131
137
  });
132
138
 
133
139
  const task: Task = { kind: "unclaimed", task: body.task };
134
140
 
135
141
  const completion: Promise<Response> = new Promise((resolve) => {
136
- resonateInner.process(task, (error, status) => {
137
- if (error || !status) {
138
- resolve(
139
- new Response(
140
- JSON.stringify({
141
- error: "Task processing failed",
142
- details: { error, status },
143
- }),
144
- {
145
- status: 500,
146
- },
147
- ),
148
- );
149
- return;
150
- }
151
-
152
- if (status.kind === "completed") {
153
- resolve(
154
- new Response(
155
- JSON.stringify({
156
- status: "completed",
157
- result: status.promise.value,
158
- requestUrl: url,
159
- }),
160
- {
161
- status: 200,
162
- },
163
- ),
164
- );
165
- return;
166
- } else if (status.kind === "suspended") {
167
- resolve(
168
- new Response(
169
- JSON.stringify({
170
- status: "suspended",
171
- requestUrl: url,
172
- }),
173
- {
174
- status: 200,
175
- },
176
- ),
177
- );
178
- return;
179
- }
180
- });
142
+ resonateInner.process(
143
+ tracer.startSpan(task.task.rootPromiseId, clock.now()),
144
+ task,
145
+ (error, status) => {
146
+ if (error || !status) {
147
+ resolve(
148
+ new Response(
149
+ JSON.stringify({
150
+ error: "Task processing failed",
151
+ details: { error, status },
152
+ }),
153
+ {
154
+ status: 500,
155
+ },
156
+ ),
157
+ );
158
+ return;
159
+ }
160
+
161
+ if (status.kind === "completed") {
162
+ resolve(
163
+ new Response(
164
+ JSON.stringify({
165
+ status: "completed",
166
+ result: status.promise.value,
167
+ requestUrl: url,
168
+ }),
169
+ {
170
+ status: 200,
171
+ },
172
+ ),
173
+ );
174
+ return;
175
+ } else if (status.kind === "suspended") {
176
+ resolve(
177
+ new Response(
178
+ JSON.stringify({
179
+ status: "suspended",
180
+ requestUrl: url,
181
+ }),
182
+ {
183
+ status: 200,
184
+ },
185
+ ),
186
+ );
187
+ return;
188
+ }
189
+ },
190
+ );
181
191
  });
182
192
  return completion;
183
193
  } catch (error) {
@@ -192,10 +202,36 @@ export class Resonate {
192
202
  }
193
203
 
194
204
  function buildForwardedURL(req: Request) {
195
- const proto = req.headers.get("x-forwarded-proto") ?? "http";
196
- const host = req.headers.get("x-forwarded-host") ?? req.headers.get("host");
197
- const port = req.headers.get("x-forwarded-port");
198
- const path = req.headers.get("x-forwarded-path");
205
+ const headers = req.headers;
206
+ const url = new URL(req.url);
207
+
208
+ // 1. Hostname Logic
209
+ // Dev: "x-forwarded-host" is present (e.g., 127.0.0.1)
210
+ // Prod: "x-forwarded-host" is missing, so we use url.hostname (e.g., project.supabase.co)
211
+ const forwardedHost = headers.get("x-forwarded-host");
212
+ const host = forwardedHost ?? url.hostname;
213
+
214
+ // 2. Protocol Logic
215
+ // Always prefer "x-forwarded-proto" (usually https in prod), fallback to "http"
216
+ const proto = headers.get("x-forwarded-proto") ?? "http";
217
+
218
+ // 3. Port Logic
219
+ // Dev: We need the port (e.g., :54321).
220
+ // Prod: We rarely need :443 explicitly in the URL string.
221
+ const forwardedPort = headers.get("x-forwarded-port");
222
+ const port = forwardedHost && forwardedPort ? `:${forwardedPort}` : "";
223
+
224
+ // 4. Path Logic
225
+ // Dev: "x-forwarded-path" contains the full path (/functions/v1/hello-world)
226
+ // Prod: We must use url.pathname.
227
+ let path = headers.get("x-forwarded-path") ?? url.pathname;
228
+
229
+ // 5. Production Path Fix
230
+ // In Prod, the internal req.url often strips '/functions/v1'.
231
+ // We re-add it if we are in Prod (no forwardedHost) and it's missing.
232
+ if (!forwardedHost && !path.startsWith("/functions/v1")) {
233
+ path = `/functions/v1${path}`;
234
+ }
199
235
 
200
- return `${proto}://${host}${port ? `:${port}` : ""}${path}`;
236
+ return `${proto}://${host}${port}${path}`;
201
237
  }