tinbase 0.5.0 → 0.6.1
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.
- package/README.md +29 -26
- package/dist/admin/api.d.ts +3 -1
- package/dist/admin/api.js +9 -1
- package/dist/admin/api.js.map +1 -1
- package/dist/admin/ui.d.ts +1 -1
- package/dist/admin/ui.js +7 -7
- package/dist/auth/handler.d.ts +6 -1
- package/dist/auth/handler.js +148 -6
- package/dist/auth/handler.js.map +1 -1
- package/dist/auth/inbox.d.ts +31 -0
- package/dist/auth/inbox.js +112 -0
- package/dist/auth/inbox.js.map +1 -0
- package/dist/auth/qr.d.ts +8 -0
- package/dist/auth/qr.js +376 -0
- package/dist/auth/qr.js.map +1 -0
- package/dist/auth/totp.d.ts +19 -0
- package/dist/auth/totp.js +101 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/cli.js +86 -15
- package/dist/cli.js.map +1 -1
- package/dist/db/bootstrap.d.ts +8 -1
- package/dist/db/bootstrap.js +197 -0
- package/dist/db/bootstrap.js.map +1 -1
- package/dist/db/database.d.ts +21 -0
- package/dist/db/database.js +46 -6
- package/dist/db/database.js.map +1 -1
- package/dist/db/engine.d.ts +2 -0
- package/dist/db/engine.js.map +1 -1
- package/dist/db/inspect.d.ts +13 -0
- package/dist/db/inspect.js +22 -0
- package/dist/db/inspect.js.map +1 -0
- package/dist/db/pgmem-engine.d.ts +22 -0
- package/dist/db/pgmem-engine.js +135 -0
- package/dist/db/pgmem-engine.js.map +1 -0
- package/dist/functions/handler.d.ts +3 -1
- package/dist/functions/handler.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +49 -10
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +7 -0
- package/dist/jwt.js.map +1 -1
- package/dist/log-buffer.d.ts +21 -0
- package/dist/log-buffer.js +20 -0
- package/dist/log-buffer.js.map +1 -0
- package/dist/node/bundle-function.d.ts +7 -0
- package/dist/node/bundle-function.js +90 -0
- package/dist/node/bundle-function.js.map +1 -0
- package/dist/node/db-diff.d.ts +21 -0
- package/dist/node/db-diff.js +42 -0
- package/dist/node/db-diff.js.map +1 -1
- package/dist/node/index.d.ts +4 -3
- package/dist/node/index.js +4 -3
- package/dist/node/index.js.map +1 -1
- package/dist/node/load-functions.d.ts +6 -0
- package/dist/node/load-functions.js +49 -5
- package/dist/node/load-functions.js.map +1 -1
- package/dist/node/server.d.ts +6 -0
- package/dist/node/server.js +27 -1
- package/dist/node/server.js.map +1 -1
- package/dist/realtime/engine.d.ts +10 -0
- package/dist/realtime/engine.js +90 -1
- package/dist/realtime/engine.js.map +1 -1
- package/dist/rest/build.d.ts +5 -1
- package/dist/rest/build.js +17 -8
- package/dist/rest/build.js.map +1 -1
- package/dist/rest/handler.js +22 -9
- package/dist/rest/handler.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle an edge function with esbuild so it runs under Node like it would on
|
|
3
|
+
* Supabase's Deno edge runtime. This resolves what a plain `import()` can't:
|
|
4
|
+
* - TypeScript and multi-file functions (relative imports)
|
|
5
|
+
* - `npm:` / `jsr:` specifiers → rewritten to esm.sh and fetched
|
|
6
|
+
* - `https://` URL imports → fetched (and their transitive deps)
|
|
7
|
+
*
|
|
8
|
+
* Remote modules are fetched at bundle time (first run) and disk-cached, the
|
|
9
|
+
* same trade Deno makes. esbuild is an optional dependency; when it's missing
|
|
10
|
+
* the loader falls back to a plain import (Web-API/Deno.serve functions still
|
|
11
|
+
* work, just not remote/TS imports).
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
/** Map a Deno-style specifier to a fetchable URL. Pure — unit tested. */
|
|
19
|
+
export function rewriteRemoteSpecifier(spec) {
|
|
20
|
+
if (spec.startsWith('npm:'))
|
|
21
|
+
return `https://esm.sh/${spec.slice(4)}`;
|
|
22
|
+
if (spec.startsWith('jsr:'))
|
|
23
|
+
return `https://esm.sh/jsr/${spec.slice(4)}`;
|
|
24
|
+
return spec; // already an http(s) URL
|
|
25
|
+
}
|
|
26
|
+
const HTTP_CACHE = join(tmpdir(), 'tinbase-fn-http');
|
|
27
|
+
async function fetchModule(url) {
|
|
28
|
+
const key = createHash('sha256').update(url).digest('hex');
|
|
29
|
+
const cached = join(HTTP_CACHE, key);
|
|
30
|
+
if (existsSync(cached))
|
|
31
|
+
return readFile(cached, 'utf8');
|
|
32
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
throw new Error(`failed to fetch ${url}: HTTP ${res.status}`);
|
|
35
|
+
const body = await res.text();
|
|
36
|
+
await mkdir(HTTP_CACHE, { recursive: true });
|
|
37
|
+
await writeFile(cached, body);
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
/** esbuild plugin: resolve npm:/jsr:/https imports by fetching from esm.sh. */
|
|
41
|
+
function remotePlugin() {
|
|
42
|
+
return {
|
|
43
|
+
name: 'tinbase-remote',
|
|
44
|
+
setup(build) {
|
|
45
|
+
build.onResolve({ filter: /^(npm:|jsr:|https?:\/\/)/ }, (args) => ({
|
|
46
|
+
path: rewriteRemoteSpecifier(args.path),
|
|
47
|
+
namespace: 'http-url',
|
|
48
|
+
}));
|
|
49
|
+
// imports *inside* a fetched module resolve relative to its URL
|
|
50
|
+
build.onResolve({ filter: /.*/, namespace: 'http-url' }, (args) => ({
|
|
51
|
+
path: new URL(args.path, args.importer).href,
|
|
52
|
+
namespace: 'http-url',
|
|
53
|
+
}));
|
|
54
|
+
build.onLoad({ filter: /.*/, namespace: 'http-url' }, async (args) => ({
|
|
55
|
+
contents: await fetchModule(args.path),
|
|
56
|
+
loader: 'js',
|
|
57
|
+
}));
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Bundle `entryPath` to a single ESM file and return its path (written under
|
|
63
|
+
* the OS temp dir). Throws if esbuild is not installed.
|
|
64
|
+
*/
|
|
65
|
+
export async function bundleFunction(entryPath, name) {
|
|
66
|
+
let esbuild;
|
|
67
|
+
try {
|
|
68
|
+
esbuild = await import('esbuild');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error('esbuild-not-available');
|
|
72
|
+
}
|
|
73
|
+
const outDir = join(tmpdir(), 'tinbase-fn-bundle');
|
|
74
|
+
await mkdir(outDir, { recursive: true });
|
|
75
|
+
const result = await esbuild.build({
|
|
76
|
+
entryPoints: [entryPath],
|
|
77
|
+
bundle: true,
|
|
78
|
+
format: 'esm',
|
|
79
|
+
platform: 'node',
|
|
80
|
+
target: 'node18',
|
|
81
|
+
write: false,
|
|
82
|
+
logLevel: 'silent',
|
|
83
|
+
plugins: [remotePlugin()],
|
|
84
|
+
});
|
|
85
|
+
const code = result.outputFiles[0].text;
|
|
86
|
+
const out = join(outDir, `${name}-${createHash('sha256').update(entryPath).digest('hex').slice(0, 8)}.mjs`);
|
|
87
|
+
await writeFile(out, code);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=bundle-function.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-function.js","sourceRoot":"","sources":["../../src/node/bundle-function.ts"],"names":[],"mappings":"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"}
|
package/dist/node/db-diff.d.ts
CHANGED
|
@@ -11,3 +11,24 @@ export interface DbDiffOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function computeDbDiff(opts: DbDiffOptions): Promise<string[]>;
|
|
13
13
|
export declare function shadowNativeDataDir(): string;
|
|
14
|
+
export interface DbPullOptions extends DbDiffOptions {
|
|
15
|
+
/** directory to write the migration into (usually supabase/migrations); omit to skip writing */
|
|
16
|
+
migrationsDir?: string;
|
|
17
|
+
/** migration name suffix (default 'remote_schema') */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** timestamp version prefix; pass for determinism (default: now as YYYYMMDDHHMMSS) */
|
|
20
|
+
stamp?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DbPullResult {
|
|
23
|
+
ddl: string[];
|
|
24
|
+
version: string | null;
|
|
25
|
+
path: string | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `tinbase db pull` core: like `db diff`, but writes the delta as a migration
|
|
29
|
+
* AND records it as already-applied on the live database — the schema is
|
|
30
|
+
* already there, so a subsequent `tinbase start` must not re-run it. This is
|
|
31
|
+
* how you bring an out-of-migration (or externally-created) schema under
|
|
32
|
+
* version control.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pullSchema(opts: DbPullOptions): Promise<DbPullResult>;
|
package/dist/node/db-diff.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* new migration.
|
|
6
6
|
*/
|
|
7
7
|
import { mkdtempSync } from 'node:fs';
|
|
8
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
8
9
|
import { tmpdir } from 'node:os';
|
|
9
10
|
import { join } from 'node:path';
|
|
10
11
|
import { createBackend } from '../index.js';
|
|
@@ -37,4 +38,45 @@ export async function computeDbDiff(opts) {
|
|
|
37
38
|
export function shadowNativeDataDir() {
|
|
38
39
|
return join(mkdtempSync(join(tmpdir(), 'tinbase-shadow-')), 'pg');
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `tinbase db pull` core: like `db diff`, but writes the delta as a migration
|
|
43
|
+
* AND records it as already-applied on the live database — the schema is
|
|
44
|
+
* already there, so a subsequent `tinbase start` must not re-run it. This is
|
|
45
|
+
* how you bring an out-of-migration (or externally-created) schema under
|
|
46
|
+
* version control.
|
|
47
|
+
*/
|
|
48
|
+
export async function pullSchema(opts) {
|
|
49
|
+
const schema = opts.schema ?? 'public';
|
|
50
|
+
const shadow = await createBackend({
|
|
51
|
+
engine: opts.makeShadowEngine ? await opts.makeShadowEngine() : undefined,
|
|
52
|
+
migrations: opts.migrations,
|
|
53
|
+
});
|
|
54
|
+
const live = await createBackend({
|
|
55
|
+
engine: opts.liveEngine,
|
|
56
|
+
dataDir: opts.liveEngine ? undefined : opts.liveDataDir,
|
|
57
|
+
migrations: opts.migrations,
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const ddl = diffSchemas(await snapshotSchema(shadow.db, schema), await snapshotSchema(live.db, schema), schema);
|
|
61
|
+
if (ddl.length === 0)
|
|
62
|
+
return { ddl, version: null, path: null };
|
|
63
|
+
const stamp = opts.stamp ?? new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
64
|
+
const name = opts.name ?? 'remote_schema';
|
|
65
|
+
const body = ddl.join('\n\n') + '\n';
|
|
66
|
+
let path = null;
|
|
67
|
+
if (opts.migrationsDir) {
|
|
68
|
+
await mkdir(opts.migrationsDir, { recursive: true });
|
|
69
|
+
path = join(opts.migrationsDir, `${stamp}_${name}.sql`);
|
|
70
|
+
await writeFile(path, body);
|
|
71
|
+
}
|
|
72
|
+
// record as already applied on the live DB so `start` won't re-run it
|
|
73
|
+
await live.db.query(`insert into supabase_migrations.schema_migrations (version, name, statements)
|
|
74
|
+
values ($1, $2, $3) on conflict (version) do nothing`, [stamp, `${stamp}_${name}`, [body]]);
|
|
75
|
+
return { ddl, version: stamp, path };
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await shadow.close();
|
|
79
|
+
await live.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
40
82
|
//# sourceMappingURL=db-diff.js.map
|
package/dist/node/db-diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-diff.js","sourceRoot":"","sources":["../../src/node/db-diff.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAclE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAmB;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAA;IAEtC,kCAAkC;IAClC,MAAM,MAAM,GAAmB,MAAM,aAAa,CAAC;QACjD,MAAM,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,SAAS;QACzE,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,oCAAoC;KACrC,CAAC,CAAA;IACF,iFAAiF;IACjF,8EAA8E;IAC9E,MAAM,IAAI,GAAmB,MAAM,aAAa,CAAC;QAC/C,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QACvD,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAA;IAEF,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;QAC1D,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;QACtD,OAAO,WAAW,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IAClD,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACpB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;AACnE,CAAC"}
|
|
1
|
+
{"version":3,"file":"db-diff.js","sourceRoot":"","sources":["../../src/node/db-diff.ts"],"names":[],"mappings":"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"}
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Node-only entrypoints (HTTP server, fs storage, project loading). */
|
|
2
|
-
export { serve, type ServeOptions, type RunningServer } from './server.js';
|
|
2
|
+
export { serve, findAvailablePort, type ServeOptions, type RunningServer } from './server.js';
|
|
3
3
|
export { FsStorageDriver } from './fs-driver.js';
|
|
4
4
|
export { loadSupabaseProject, type SupabaseProject } from './project.js';
|
|
5
5
|
export { acceptWebSocket, type WsConnection } from './ws.js';
|
|
6
6
|
export { createNativeEngine, ensurePostgres, type NativeEngineOptions } from './native/engine.js';
|
|
7
7
|
export { PgWireClient, PgWireError } from './native/wire.js';
|
|
8
|
-
export { loadFunctions } from './load-functions.js';
|
|
8
|
+
export { loadFunctions, loadFunctionEnv } from './load-functions.js';
|
|
9
|
+
export { bundleFunction, rewriteRemoteSpecifier } from './bundle-function.js';
|
|
9
10
|
export { loadOAuthProviders } from './load-oauth.js';
|
|
10
|
-
export { computeDbDiff, shadowNativeDataDir, type DbDiffOptions } from './db-diff.js';
|
|
11
|
+
export { computeDbDiff, pullSchema, shadowNativeDataDir, type DbDiffOptions, type DbPullOptions, type DbPullResult } from './db-diff.js';
|
package/dist/node/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** Node-only entrypoints (HTTP server, fs storage, project loading). */
|
|
2
|
-
export { serve } from './server.js';
|
|
2
|
+
export { serve, findAvailablePort } from './server.js';
|
|
3
3
|
export { FsStorageDriver } from './fs-driver.js';
|
|
4
4
|
export { loadSupabaseProject } from './project.js';
|
|
5
5
|
export { acceptWebSocket } from './ws.js';
|
|
6
6
|
export { createNativeEngine, ensurePostgres } from './native/engine.js';
|
|
7
7
|
export { PgWireClient, PgWireError } from './native/wire.js';
|
|
8
|
-
export { loadFunctions } from './load-functions.js';
|
|
8
|
+
export { loadFunctions, loadFunctionEnv } from './load-functions.js';
|
|
9
|
+
export { bundleFunction, rewriteRemoteSpecifier } from './bundle-function.js';
|
|
9
10
|
export { loadOAuthProviders } from './load-oauth.js';
|
|
10
|
-
export { computeDbDiff, shadowNativeDataDir } from './db-diff.js';
|
|
11
|
+
export { computeDbDiff, pullSchema, shadowNativeDataDir } from './db-diff.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/dist/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EAAE,KAAK,EAAyC,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAyC,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAwB,MAAM,cAAc,CAAA;AACxE,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAA4B,MAAM,oBAAoB,CAAA;AACjG,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,mBAAmB,EAA6D,MAAM,cAAc,CAAA"}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { EdgeFunction } from '../functions/handler.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load edge-function secrets from supabase/functions/.env (KEY=VALUE lines,
|
|
4
|
+
* `#` comments). These are exposed to functions via Deno.env and ctx.env —
|
|
5
|
+
* the local equivalent of `supabase functions serve --env-file`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadFunctionEnv(projectDir: string): Promise<Record<string, string>>;
|
|
2
8
|
export declare function loadFunctions(projectDir: string): Promise<Map<string, EdgeFunction>>;
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
/** Loads edge functions from supabase/functions/<name>/index.{ts,js,mjs} (Node only). */
|
|
2
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { installDenoShim, resetCapturedHandler, takeCapturedHandler } from '../functions/deno-shim.js';
|
|
6
|
+
import { bundleFunction } from './bundle-function.js';
|
|
7
|
+
/**
|
|
8
|
+
* Load edge-function secrets from supabase/functions/.env (KEY=VALUE lines,
|
|
9
|
+
* `#` comments). These are exposed to functions via Deno.env and ctx.env —
|
|
10
|
+
* the local equivalent of `supabase functions serve --env-file`.
|
|
11
|
+
*/
|
|
12
|
+
export async function loadFunctionEnv(projectDir) {
|
|
13
|
+
let text;
|
|
14
|
+
try {
|
|
15
|
+
text = await readFile(join(projectDir, 'supabase', 'functions', '.env'), 'utf8');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const env = {};
|
|
21
|
+
for (const raw of text.split('\n')) {
|
|
22
|
+
const line = raw.trim();
|
|
23
|
+
if (!line || line.startsWith('#'))
|
|
24
|
+
continue;
|
|
25
|
+
const eq = line.indexOf('=');
|
|
26
|
+
if (eq === -1)
|
|
27
|
+
continue;
|
|
28
|
+
const key = line.slice(0, eq).trim();
|
|
29
|
+
let val = line.slice(eq + 1).trim();
|
|
30
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
31
|
+
val = val.slice(1, -1);
|
|
32
|
+
}
|
|
33
|
+
if (key)
|
|
34
|
+
env[key] = val;
|
|
35
|
+
}
|
|
36
|
+
return env;
|
|
37
|
+
}
|
|
6
38
|
export async function loadFunctions(projectDir) {
|
|
7
39
|
const functions = new Map();
|
|
8
40
|
const root = join(projectDir, 'supabase', 'functions');
|
|
@@ -21,7 +53,7 @@ export async function loadFunctions(projectDir) {
|
|
|
21
53
|
const dir = join(root, name);
|
|
22
54
|
if (!(await stat(dir)).isDirectory())
|
|
23
55
|
continue;
|
|
24
|
-
for (const file of ['index.ts', 'index.js', 'index.mjs']) {
|
|
56
|
+
for (const file of ['index.ts', 'index.tsx', 'index.js', 'index.mjs']) {
|
|
25
57
|
const path = join(dir, file);
|
|
26
58
|
try {
|
|
27
59
|
await stat(path);
|
|
@@ -30,8 +62,20 @@ export async function loadFunctions(projectDir) {
|
|
|
30
62
|
continue;
|
|
31
63
|
}
|
|
32
64
|
try {
|
|
65
|
+
// Bundle with esbuild so TS, relative imports, and npm:/jsr:/URL
|
|
66
|
+
// specifiers resolve. If esbuild isn't installed, import the file
|
|
67
|
+
// directly (Web-API / Deno.serve functions still work).
|
|
68
|
+
let importUrl;
|
|
69
|
+
try {
|
|
70
|
+
importUrl = pathToFileURL(await bundleFunction(path, name)).href;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (e.message !== 'esbuild-not-available')
|
|
74
|
+
throw e;
|
|
75
|
+
importUrl = pathToFileURL(path).href;
|
|
76
|
+
}
|
|
33
77
|
resetCapturedHandler();
|
|
34
|
-
const mod = (await import(
|
|
78
|
+
const mod = (await import(importUrl));
|
|
35
79
|
// prefer an explicit default export; otherwise use the handler a
|
|
36
80
|
// Deno.serve() call captured during import
|
|
37
81
|
const denoHandler = takeCapturedHandler();
|
|
@@ -45,8 +89,8 @@ export async function loadFunctions(projectDir) {
|
|
|
45
89
|
}
|
|
46
90
|
catch (e) {
|
|
47
91
|
const msg = e instanceof Error ? e.message : String(e);
|
|
48
|
-
if (
|
|
49
|
-
console.warn(` warning: function "${name}"
|
|
92
|
+
if (/Unknown file extension|Cannot find module/.test(msg)) {
|
|
93
|
+
console.warn(` warning: function "${name}" needs bundling — install esbuild, or run tinbase with tsx/bun`);
|
|
50
94
|
}
|
|
51
95
|
else {
|
|
52
96
|
console.warn(` warning: failed to load function "${name}": ${msg}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-functions.js","sourceRoot":"","sources":["../../src/node/load-functions.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"load-functions.js","sourceRoot":"","sources":["../../src/node/load-functions.ts"],"names":[],"mappings":"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"}
|
package/dist/node/server.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** Node HTTP wrapper: serves the backend's fetch handler and upgrades /realtime/v1 WebSockets. */
|
|
2
2
|
import { type Server } from 'node:http';
|
|
3
3
|
import type { TinbaseBackend } from '../index.js';
|
|
4
|
+
/**
|
|
5
|
+
* First free port at or after `startPort` (scanning up to `maxTries`), or null
|
|
6
|
+
* if none. Used so `tinbase start` skips a port that's already in use (e.g. a
|
|
7
|
+
* tinbase instance already running) instead of crashing with EADDRINUSE.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findAvailablePort(startPort: number, host?: string, maxTries?: number): Promise<number | null>;
|
|
4
10
|
export interface ServeOptions {
|
|
5
11
|
port?: number;
|
|
6
12
|
host?: string;
|
package/dist/node/server.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
/** Node HTTP wrapper: serves the backend's fetch handler and upgrades /realtime/v1 WebSockets. */
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
|
+
import { createServer as createNetServer } from 'node:net';
|
|
3
4
|
import { acceptWebSocket } from './ws.js';
|
|
5
|
+
/** Resolve whether a port is free to bind on `host`. */
|
|
6
|
+
function portIsFree(port, host) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const tester = createNetServer();
|
|
9
|
+
tester.once('error', () => resolve(false));
|
|
10
|
+
tester.once('listening', () => tester.close(() => resolve(true)));
|
|
11
|
+
tester.listen(port, host);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* First free port at or after `startPort` (scanning up to `maxTries`), or null
|
|
16
|
+
* if none. Used so `tinbase start` skips a port that's already in use (e.g. a
|
|
17
|
+
* tinbase instance already running) instead of crashing with EADDRINUSE.
|
|
18
|
+
*/
|
|
19
|
+
export async function findAvailablePort(startPort, host = '127.0.0.1', maxTries = 20) {
|
|
20
|
+
for (let p = startPort; p < startPort + maxTries; p++) {
|
|
21
|
+
if (await portIsFree(p, host))
|
|
22
|
+
return p;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
4
26
|
export async function serve(backend, opts = {}) {
|
|
5
27
|
const host = opts.host ?? '127.0.0.1';
|
|
6
28
|
const server = createServer(async (req, res) => {
|
|
@@ -29,7 +51,11 @@ export async function serve(backend, opts = {}) {
|
|
|
29
51
|
ws.onClose = session.onClose;
|
|
30
52
|
});
|
|
31
53
|
await new Promise((resolve, reject) => {
|
|
32
|
-
server.once('error',
|
|
54
|
+
server.once('error', (err) => {
|
|
55
|
+
reject(err.code === 'EADDRINUSE'
|
|
56
|
+
? new Error(`port ${opts.port ?? 54321} is already in use — pass --port <n> to choose another`)
|
|
57
|
+
: err);
|
|
58
|
+
});
|
|
33
59
|
server.listen(opts.port ?? 54321, host, () => resolve());
|
|
34
60
|
});
|
|
35
61
|
const address = server.address();
|
package/dist/node/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/node/server.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/node/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,9 +22,19 @@ export declare class RealtimeEngine {
|
|
|
22
22
|
/** topic → key → metas */
|
|
23
23
|
private presence;
|
|
24
24
|
private stopCdc;
|
|
25
|
+
private stopDbBroadcast;
|
|
25
26
|
constructor(db: Database, jwtSecret?: string | undefined);
|
|
26
27
|
/** Resolve a subscriber's access token to a role/claims context. */
|
|
27
28
|
private contextFromToken;
|
|
29
|
+
/**
|
|
30
|
+
* Authorize a private channel against RLS on realtime.messages, as the
|
|
31
|
+
* subscriber. read = a SELECT policy lets them receive; write = an INSERT
|
|
32
|
+
* policy lets them broadcast. Runs in a transaction that always rolls back so
|
|
33
|
+
* the probe rows never persist.
|
|
34
|
+
*/
|
|
35
|
+
private authorizePrivate;
|
|
36
|
+
/** Fan a realtime.send() database broadcast out to a topic's subscribers. */
|
|
37
|
+
private dispatchDbBroadcast;
|
|
28
38
|
start(): Promise<void>;
|
|
29
39
|
stop(): void;
|
|
30
40
|
/** Attach a socket. Returns callbacks the transport must wire up. */
|
package/dist/realtime/engine.js
CHANGED
|
@@ -9,6 +9,7 @@ export class RealtimeEngine {
|
|
|
9
9
|
/** topic → key → metas */
|
|
10
10
|
presence = new Map();
|
|
11
11
|
stopCdc = null;
|
|
12
|
+
stopDbBroadcast = null;
|
|
12
13
|
constructor(db, jwtSecret) {
|
|
13
14
|
this.db = db;
|
|
14
15
|
this.jwtSecret = jwtSecret;
|
|
@@ -23,14 +24,78 @@ export class RealtimeEngine {
|
|
|
23
24
|
const role = typeof claims.role === 'string' ? claims.role : 'authenticated';
|
|
24
25
|
return { role, claims };
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Authorize a private channel against RLS on realtime.messages, as the
|
|
29
|
+
* subscriber. read = a SELECT policy lets them receive; write = an INSERT
|
|
30
|
+
* policy lets them broadcast. Runs in a transaction that always rolls back so
|
|
31
|
+
* the probe rows never persist.
|
|
32
|
+
*/
|
|
33
|
+
async authorizePrivate(subTopic, ctx) {
|
|
34
|
+
const claims = ctx.claims ? JSON.stringify(ctx.claims) : '';
|
|
35
|
+
try {
|
|
36
|
+
return await this.db.engine.transaction(async (tx) => {
|
|
37
|
+
// seed a probe row for the topic with RLS bypassed (service_role)
|
|
38
|
+
await tx.query(`select set_config('role', 'service_role', true)`);
|
|
39
|
+
await tx.query(`insert into realtime.messages (topic, extension, event, payload, private) values ($1, 'broadcast', 'authz', '{}'::jsonb, true)`, [subTopic]);
|
|
40
|
+
// become the subscriber and set the topic being authorized
|
|
41
|
+
await tx.query(`select set_config('role', $1, true), set_config('request.jwt.claims', $2, true), set_config('realtime.topic', $3, true)`, [ctx.role, claims, subTopic]);
|
|
42
|
+
// read: does a SELECT policy let them see the probe row for this topic?
|
|
43
|
+
const rd = await tx.query(`select count(*)::int as n from realtime.messages where topic = $1`, [subTopic]);
|
|
44
|
+
const read = (rd.rows[0]?.n ?? 0) > 0;
|
|
45
|
+
// write: does an INSERT policy let them broadcast? (last query — a
|
|
46
|
+
// failure aborts the tx, which we roll back anyway)
|
|
47
|
+
let write = true;
|
|
48
|
+
try {
|
|
49
|
+
await tx.query(`insert into realtime.messages (topic, extension, event, payload, private) values ($1, 'broadcast', 'authz-w', '{}'::jsonb, true)`, [subTopic]);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
write = false;
|
|
53
|
+
}
|
|
54
|
+
throw { __authz: { read, write } };
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
const authz = e.__authz;
|
|
59
|
+
if (authz)
|
|
60
|
+
return authz;
|
|
61
|
+
// realtime schema missing / unexpected error → deny (safe default)
|
|
62
|
+
return { read: false, write: false };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Fan a realtime.send() database broadcast out to a topic's subscribers. */
|
|
66
|
+
dispatchDbBroadcast(msg) {
|
|
67
|
+
const full = `realtime:${msg.topic}`;
|
|
68
|
+
for (const conn of this.connections) {
|
|
69
|
+
const channel = conn.channels.get(full) ?? conn.channels.get(msg.topic);
|
|
70
|
+
if (!channel)
|
|
71
|
+
continue;
|
|
72
|
+
this.send(conn, {
|
|
73
|
+
topic: channel.topic,
|
|
74
|
+
event: 'broadcast',
|
|
75
|
+
payload: { type: 'broadcast', event: msg.event, payload: msg.payload },
|
|
76
|
+
ref: null,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
26
80
|
async start() {
|
|
27
81
|
if (this.stopCdc)
|
|
28
82
|
return;
|
|
29
83
|
this.stopCdc = await this.db.onCdcEvent((e) => this.dispatchCdc(e));
|
|
84
|
+
// broadcast-from-database: realtime.send() → pg_notify → fan out to topic
|
|
85
|
+
this.stopDbBroadcast = await this.db.engine.listen('tinbase_realtime_broadcast', (payload) => {
|
|
86
|
+
try {
|
|
87
|
+
this.dispatchDbBroadcast(JSON.parse(payload));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// malformed payload — drop
|
|
91
|
+
}
|
|
92
|
+
});
|
|
30
93
|
}
|
|
31
94
|
stop() {
|
|
32
95
|
this.stopCdc?.();
|
|
33
96
|
this.stopCdc = null;
|
|
97
|
+
this.stopDbBroadcast?.();
|
|
98
|
+
this.stopDbBroadcast = null;
|
|
34
99
|
for (const conn of this.connections)
|
|
35
100
|
conn.socket.close(1001, 'server shutting down');
|
|
36
101
|
this.connections.clear();
|
|
@@ -126,6 +191,22 @@ export class RealtimeEngine {
|
|
|
126
191
|
}
|
|
127
192
|
async handleJoin(conn, msg) {
|
|
128
193
|
const config = (msg.payload?.config ?? {});
|
|
194
|
+
const ctx = await this.contextFromToken(msg.payload?.access_token);
|
|
195
|
+
const isPrivate = config.private === true;
|
|
196
|
+
let canBroadcast = true;
|
|
197
|
+
// Private channels are RLS-authorized against realtime.messages (skipped on
|
|
198
|
+
// subset engines with no RLS). No read policy → join is rejected.
|
|
199
|
+
if (isPrivate && !this.db.engine.minimalBootstrap) {
|
|
200
|
+
const sub = msg.topic.replace(/^realtime:/, '');
|
|
201
|
+
const authz = await this.authorizePrivate(sub, ctx);
|
|
202
|
+
if (!authz.read) {
|
|
203
|
+
this.reply(conn, msg, 'error', {
|
|
204
|
+
reason: `You do not have permissions to read from this Channel topic: ${sub}`,
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
canBroadcast = authz.write;
|
|
209
|
+
}
|
|
129
210
|
const bindings = [];
|
|
130
211
|
for (const spec of config.postgres_changes ?? []) {
|
|
131
212
|
const binding = {
|
|
@@ -163,7 +244,9 @@ export class RealtimeEngine {
|
|
|
163
244
|
broadcastAck: config.broadcast?.ack ?? false,
|
|
164
245
|
presenceKey: config.presence?.key || crypto.randomUUID(),
|
|
165
246
|
presenceEnabled: config.presence?.enabled ?? true,
|
|
166
|
-
ctx
|
|
247
|
+
ctx,
|
|
248
|
+
private: isPrivate,
|
|
249
|
+
canBroadcast,
|
|
167
250
|
};
|
|
168
251
|
conn.channels.set(msg.topic, channel);
|
|
169
252
|
this.reply(conn, msg, 'ok', {
|
|
@@ -258,6 +341,12 @@ export class RealtimeEngine {
|
|
|
258
341
|
const sender = conn.channels.get(msg.topic);
|
|
259
342
|
if (!sender)
|
|
260
343
|
return;
|
|
344
|
+
// private channels require INSERT (write) authorization to broadcast
|
|
345
|
+
if (sender.private && !sender.canBroadcast) {
|
|
346
|
+
if (sender.broadcastAck && msg.ref)
|
|
347
|
+
this.reply(conn, msg, 'error', { reason: 'unauthorized' });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
261
350
|
const userPayload = msg.payload.payload;
|
|
262
351
|
const binaryFrame = userPayload instanceof Uint8Array
|
|
263
352
|
? encodeUserBroadcast(msg.topic, String(msg.payload.event ?? ''), userPayload)
|