@prisma-next/extension-supabase 0.13.0-dev.11 → 0.13.0-dev.13

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/dist/pack.mjs CHANGED
@@ -1,7 +1,5 @@
1
+ import { t as version } from "./package-jUTMEltU.mjs";
1
2
  import { blindCast } from "@prisma-next/utils/casts";
2
- //#region package.json
3
- var version = "0.13.0-dev.11";
4
- //#endregion
5
3
  //#region src/contract/contract.json
6
4
  var contract_default = {
7
5
  schemaVersion: "1",
package/dist/pack.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pack.mjs","names":["contractJson","packageJson.version"],"sources":["../package.json","../src/contract/contract.json","../src/pack/index.ts"],"sourcesContent":["","","import type { SqlControlExtensionDescriptor } from '@prisma-next/family-sql/control';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport packageJson from '../../package.json' with { type: 'json' };\nimport type { Contract } from '../contract/contract.d';\nimport contractJson from '../contract/contract.json' with { type: 'json' };\n\nconst SUPABASE_SPACE_ID = 'supabase' as const;\n\nfunction buildContractSpace(contractOverride?: unknown) {\n const contract = blindCast<\n Contract,\n 'JSON import narrowed to emitted Contract type; assertDescriptorSelfConsistency verifies the storageHash at load time'\n >(contractOverride ?? contractJson);\n return {\n contractJson: contract,\n migrations: [] as const,\n headRef: { hash: contract.storage.storageHash, invariants: [] as const },\n };\n}\n\nconst supabaseContractSpace = buildContractSpace();\n\nconst supabasePackBase = {\n kind: 'extension' as const,\n id: SUPABASE_SPACE_ID,\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n version: packageJson.version,\n contractSpace: supabaseContractSpace,\n create: () => ({\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n }),\n} satisfies SqlControlExtensionDescriptor<'postgres'>;\n\nexport const supabasePack: SqlControlExtensionDescriptor<'postgres'> = supabasePackBase;\n\n/**\n * Returns a pack using `contractOverride` in place of the shipped\n * `contract.json` when provided, otherwise returns the default pack.\n *\n * Intended for tests that need to drive the framework with a synthetic\n * contract while still exercising the full descriptor wiring.\n */\nexport function supabasePackWith(options?: {\n contractOverride?: unknown;\n}): SqlControlExtensionDescriptor<'postgres'> {\n if (options?.contractOverride === undefined) return supabasePack;\n return {\n ...supabasePackBase,\n contractSpace: buildContractSpace(options.contractOverride),\n };\n}\n\nexport default supabasePack;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEMA,MAAM,oBAAoB;AAE1B,SAAS,mBAAmB,kBAA4B;CACtD,MAAM,WAAW,UAGf,oBAAoBA,gBAAY;CAClC,OAAO;EACL,cAAc;EACd,YAAY,CAAC;EACb,SAAS;GAAE,MAAM,SAAS,QAAQ;GAAa,YAAY,CAAC;EAAW;CACzE;AACF;AAIA,MAAM,mBAAmB;CACvB,MAAM;CACN,IAAI;CACJ,UAAU;CACV,UAAU;CACDC;CACT,eAR4B,mBAQb;CACf,eAAe;EACb,UAAU;EACV,UAAU;CACZ;AACF;AAEA,MAAa,eAA0D;;;;;;;;AASvE,SAAgB,iBAAiB,SAEa;CAC5C,IAAI,SAAS,qBAAqB,KAAA,GAAW,OAAO;CACpD,OAAO;EACL,GAAG;EACH,eAAe,mBAAmB,QAAQ,gBAAgB;CAC5D;AACF"}
1
+ {"version":3,"file":"pack.mjs","names":["contractJson","packageJson.version"],"sources":["../src/contract/contract.json","../src/pack/index.ts"],"sourcesContent":["","import type { SqlControlExtensionDescriptor } from '@prisma-next/family-sql/control';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport packageJson from '../../package.json' with { type: 'json' };\nimport type { Contract } from '../contract/contract.d';\nimport contractJson from '../contract/contract.json' with { type: 'json' };\n\nconst SUPABASE_SPACE_ID = 'supabase' as const;\n\nfunction buildContractSpace(contractOverride?: unknown) {\n const contract = blindCast<\n Contract,\n 'JSON import narrowed to emitted Contract type; assertDescriptorSelfConsistency verifies the storageHash at load time'\n >(contractOverride ?? contractJson);\n return {\n contractJson: contract,\n migrations: [] as const,\n headRef: { hash: contract.storage.storageHash, invariants: [] as const },\n };\n}\n\nconst supabaseContractSpace = buildContractSpace();\n\nconst supabasePackBase = {\n kind: 'extension' as const,\n id: SUPABASE_SPACE_ID,\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n version: packageJson.version,\n contractSpace: supabaseContractSpace,\n create: () => ({\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n }),\n} satisfies SqlControlExtensionDescriptor<'postgres'>;\n\nexport const supabasePack: SqlControlExtensionDescriptor<'postgres'> = supabasePackBase;\n\n/**\n * Returns a pack using `contractOverride` in place of the shipped\n * `contract.json` when provided, otherwise returns the default pack.\n *\n * Intended for tests that need to drive the framework with a synthetic\n * contract while still exercising the full descriptor wiring.\n */\nexport function supabasePackWith(options?: {\n contractOverride?: unknown;\n}): SqlControlExtensionDescriptor<'postgres'> {\n if (options?.contractOverride === undefined) return supabasePack;\n return {\n ...supabasePackBase,\n contractSpace: buildContractSpace(options.contractOverride),\n };\n}\n\nexport default supabasePack;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACMA,MAAM,oBAAoB;AAE1B,SAAS,mBAAmB,kBAA4B;CACtD,MAAM,WAAW,UAGf,oBAAoBA,gBAAY;CAClC,OAAO;EACL,cAAc;EACd,YAAY,CAAC;EACb,SAAS;GAAE,MAAM,SAAS,QAAQ;GAAa,YAAY,CAAC;EAAW;CACzE;AACF;AAIA,MAAM,mBAAmB;CACvB,MAAM;CACN,IAAI;CACJ,UAAU;CACV,UAAU;CACDC;CACT,eAR4B,mBAQb;CACf,eAAe;EACb,UAAU;EACV,UAAU;CACZ;AACF;AAEA,MAAa,eAA0D;;;;;;;;AASvE,SAAgB,iBAAiB,SAEa;CAC5C,IAAI,SAAS,qBAAqB,KAAA,GAAW,OAAO;CACpD,OAAO;EACL,GAAG;EACH,eAAe,mBAAmB,QAAQ,gBAAgB;CAC5D;AACF"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.13.0-dev.13";
3
+ //#endregion
4
+ export { version as t };
5
+
6
+ //# sourceMappingURL=package-jUTMEltU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-jUTMEltU.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,7 +1,114 @@
1
- import { SqlRuntimeExtensionDescriptor } from "@prisma-next/sql-runtime";
1
+ import { orm } from "@prisma-next/sql-orm-client";
2
+ import { RawSqlTag } from "@prisma-next/sql-relational-core/expression";
3
+ import { ExecutionContext, RuntimeConnection, SqlExecutionStackWithDriver, SqlMiddleware, SqlRuntimeExtensionDescriptor, TransactionContext, VerifyMarkerOption } from "@prisma-next/sql-runtime";
4
+ import { Client, Pool } from "pg";
5
+ import { AsyncIterableResult, RuntimeExecuteOptions } from "@prisma-next/framework-components/runtime";
6
+ import { PostgresRuntime, PostgresRuntimeImpl } from "@prisma-next/postgres/runtime";
7
+ import { Contract } from "@prisma-next/contract/types";
8
+ import { Db } from "@prisma-next/sql-builder/types";
9
+ import { SqlStorage } from "@prisma-next/sql-contract/types";
10
+ import { SqlExecutionPlan, SqlQueryPlan } from "@prisma-next/sql-relational-core/plan";
2
11
 
3
- //#region src/exports/runtime.d.ts
12
+ //#region src/runtime/descriptor.d.ts
13
+ /**
14
+ * Tells the runtime that the Supabase pack's runtime component is available.
15
+ *
16
+ * When a contract declares the Supabase pack, the runtime checks that a
17
+ * matching descriptor has been registered. Without this, loading a Supabase
18
+ * contract errors with "pack runtime component missing". The `supabase()`
19
+ * factory registers this descriptor automatically — app code never needs to
20
+ * reference it directly.
21
+ */
4
22
  declare const supabaseRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'>;
5
23
  //#endregion
6
- export { supabaseRuntimeDescriptor as default };
24
+ //#region src/runtime/supabase.d.ts
25
+ type SupabaseTargetId = 'postgres';
26
+ type OrmClient<TContract extends Contract<SqlStorage>> = ReturnType<typeof orm<TContract>>;
27
+ declare class SupabaseConfigError extends Error {
28
+ readonly name = "SupabaseConfigError";
29
+ constructor(message: string);
30
+ }
31
+ declare class InvalidJwtError extends Error {
32
+ readonly name = "InvalidJwtError";
33
+ readonly reason: string;
34
+ constructor(reason: string);
35
+ }
36
+ interface RoleBoundDb<TContract extends Contract<SqlStorage>> {
37
+ readonly sql: Db<TContract>;
38
+ readonly orm: OrmClient<TContract>;
39
+ readonly raw: RawSqlTag;
40
+ execute<Row>(plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & {
41
+ readonly _row?: Row;
42
+ }, options?: RuntimeExecuteOptions): AsyncIterableResult<Row>;
43
+ transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R>;
44
+ }
45
+ interface SupabaseDb<TContract extends Contract<SqlStorage>> {
46
+ readonly context: ExecutionContext<TContract>;
47
+ readonly stack: SqlExecutionStackWithDriver<SupabaseTargetId>;
48
+ asUser(jwt: string): Promise<RoleBoundDb<TContract>>;
49
+ asAnon(): RoleBoundDb<TContract>;
50
+ asServiceRole(): RoleBoundDb<TContract>;
51
+ close(): Promise<void>;
52
+ [Symbol.asyncDispose](): Promise<void>;
53
+ }
54
+ interface SupabaseOptionsBase {
55
+ readonly extensions?: readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[];
56
+ readonly middleware?: readonly SqlMiddleware[];
57
+ readonly verifyMarker?: VerifyMarkerOption;
58
+ readonly poolOptions?: {
59
+ readonly connectionTimeoutMillis?: number;
60
+ readonly idleTimeoutMillis?: number;
61
+ };
62
+ }
63
+ interface SupabaseBindingOptions {
64
+ readonly url?: string;
65
+ readonly pg?: Pool | Client;
66
+ }
67
+ type JwtSecretOption = {
68
+ readonly jwtSecret: string;
69
+ readonly jwksUrl?: never;
70
+ };
71
+ type JwksUrlOption = {
72
+ readonly jwksUrl: string;
73
+ readonly jwtSecret?: never;
74
+ };
75
+ type SupabaseOptionsWithContract<TContract extends Contract<SqlStorage>> = SupabaseBindingOptions & SupabaseOptionsBase & (JwtSecretOption | JwksUrlOption) & {
76
+ readonly contract: TContract;
77
+ readonly contractJson?: never;
78
+ };
79
+ type SupabaseOptionsWithContractJson<TContract extends Contract<SqlStorage>> = SupabaseBindingOptions & SupabaseOptionsBase & (JwtSecretOption | JwksUrlOption) & {
80
+ readonly contractJson: unknown;
81
+ readonly contract?: never;
82
+ readonly _contract?: TContract;
83
+ };
84
+ type SupabaseOptions<TContract extends Contract<SqlStorage>> = SupabaseOptionsWithContract<TContract> | SupabaseOptionsWithContractJson<TContract>;
85
+ declare function supabase<TContract extends Contract<SqlStorage>>(options: SupabaseOptionsWithContract<TContract>): Promise<SupabaseDb<TContract>>;
86
+ declare function supabase<TContract extends Contract<SqlStorage>>(options: SupabaseOptionsWithContractJson<TContract>): Promise<SupabaseDb<TContract>>;
87
+ //#endregion
88
+ //#region src/runtime/supabase-runtime.d.ts
89
+ interface SupabaseRuntime extends PostgresRuntime {}
90
+ interface SupabaseRoleBinding {
91
+ readonly role: 'anon' | 'authenticated' | 'service_role';
92
+ readonly claims?: Record<string, unknown>;
93
+ }
94
+ /**
95
+ * A connection with a Supabase role already bound via session-scoped set_config.
96
+ * Implements `RuntimeConnection` so it plugs into ORM scope machinery and `withTransaction`.
97
+ */
98
+ interface RoleSession extends RuntimeConnection {}
99
+ declare class SupabaseRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> extends PostgresRuntimeImpl<TContract> {
100
+ /**
101
+ * Opens a raw connection and applies role + JWT claims via session-scoped set_config.
102
+ * On bind failure, destroys the connection before rethrowing — no leaked connections.
103
+ * Not on the `SupabaseRuntime` interface; consumed by the facade, not by app code.
104
+ */
105
+ openRoleSession(binding: SupabaseRoleBinding): Promise<RoleSession>;
106
+ /**
107
+ * Opens a role session, executes the plan, then releases after the stream drains.
108
+ * On mid-stream error, destroys the session instead of releasing.
109
+ */
110
+ executeWithRole<Row>(plan: SqlExecutionPlan<Row> | SqlQueryPlan<Row>, binding: SupabaseRoleBinding, options?: RuntimeExecuteOptions): AsyncIterableResult<Row>;
111
+ }
112
+ //#endregion
113
+ export { InvalidJwtError, type RoleBoundDb, type RoleSession, SupabaseConfigError, type SupabaseDb, type SupabaseOptions, type SupabaseOptionsWithContract, type SupabaseOptionsWithContractJson, type SupabaseRoleBinding, type SupabaseRuntime, SupabaseRuntimeImpl, type SupabaseTargetId, supabaseRuntimeDescriptor as default, supabase };
7
114
  //# sourceMappingURL=runtime.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/exports/runtime.ts"],"mappings":";;;cAeM,yBAAA,EAA2B,6BAA6B"}
1
+ {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime/descriptor.ts","../src/runtime/supabase.ts","../src/runtime/supabase-runtime.ts"],"mappings":"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"}
package/dist/runtime.mjs CHANGED
@@ -1,8 +1,32 @@
1
- //#region src/exports/runtime.ts
1
+ import { t as version } from "./package-jUTMEltU.mjs";
2
+ import { blindCast } from "@prisma-next/utils/casts";
3
+ import postgresAdapter from "@prisma-next/adapter-postgres/runtime";
4
+ import postgresDriver from "@prisma-next/driver-postgres/runtime";
5
+ import { instantiateExecutionStack } from "@prisma-next/framework-components/execution";
6
+ import { sql } from "@prisma-next/sql-builder/runtime";
7
+ import { orm } from "@prisma-next/sql-orm-client";
8
+ import { createRawSql } from "@prisma-next/sql-relational-core/expression";
9
+ import { createExecutionContext, createSqlExecutionStack, withTransaction } from "@prisma-next/sql-runtime";
10
+ import postgresTarget, { PostgresContractSerializer } from "@prisma-next/target-postgres/runtime";
11
+ import { ifDefined } from "@prisma-next/utils/defined";
12
+ import { createRemoteJWKSet, jwtVerify } from "jose";
13
+ import { Pool } from "pg";
14
+ import { AsyncIterableResult } from "@prisma-next/framework-components/runtime";
15
+ import { PostgresRuntimeImpl } from "@prisma-next/postgres/runtime";
16
+ //#region src/runtime/descriptor.ts
17
+ /**
18
+ * Tells the runtime that the Supabase pack's runtime component is available.
19
+ *
20
+ * When a contract declares the Supabase pack, the runtime checks that a
21
+ * matching descriptor has been registered. Without this, loading a Supabase
22
+ * contract errors with "pack runtime component missing". The `supabase()`
23
+ * factory registers this descriptor automatically — app code never needs to
24
+ * reference it directly.
25
+ */
2
26
  const supabaseRuntimeDescriptor = {
3
27
  kind: "extension",
4
28
  id: "supabase",
5
- version: "0.12.0",
29
+ version,
6
30
  familyId: "sql",
7
31
  targetId: "postgres",
8
32
  codecs: () => [],
@@ -14,6 +38,257 @@ const supabaseRuntimeDescriptor = {
14
38
  }
15
39
  };
16
40
  //#endregion
17
- export { supabaseRuntimeDescriptor as default };
41
+ //#region src/runtime/supabase-runtime.ts
42
+ var SupabaseRuntimeImpl = class extends PostgresRuntimeImpl {
43
+ /**
44
+ * Opens a raw connection and applies role + JWT claims via session-scoped set_config.
45
+ * On bind failure, destroys the connection before rethrowing — no leaked connections.
46
+ * Not on the `SupabaseRuntime` interface; consumed by the facade, not by app code.
47
+ */
48
+ async openRoleSession(binding) {
49
+ const conn = await this.acquireRawConnection();
50
+ try {
51
+ await conn.query("SELECT set_config($1, $2, false)", ["role", binding.role]);
52
+ await conn.query("SELECT set_config($1, $2, false)", ["request.jwt.claims", JSON.stringify(binding.claims ?? {})]);
53
+ } catch (err) {
54
+ await conn.destroy(err).catch(() => void 0);
55
+ throw err;
56
+ }
57
+ const self = this;
58
+ return {
59
+ execute(plan, options) {
60
+ return self.executeAgainstQueryable(plan, conn, {
61
+ ...options,
62
+ scope: "connection"
63
+ });
64
+ },
65
+ executePrepared(ps, params, options) {
66
+ return self.executePreparedAgainstQueryable(blindCast(ps), blindCast(params), conn, {
67
+ ...options,
68
+ scope: "connection"
69
+ });
70
+ },
71
+ async transaction() {
72
+ const tx = await conn.beginTransaction();
73
+ return {
74
+ async commit() {
75
+ await tx.commit();
76
+ },
77
+ async rollback() {
78
+ await tx.rollback();
79
+ },
80
+ execute(plan, options) {
81
+ return self.executeAgainstQueryable(plan, tx, {
82
+ ...options,
83
+ scope: "transaction"
84
+ });
85
+ },
86
+ executePrepared(ps, params, options) {
87
+ return self.executePreparedAgainstQueryable(blindCast(ps), blindCast(params), tx, {
88
+ ...options,
89
+ scope: "transaction"
90
+ });
91
+ }
92
+ };
93
+ },
94
+ /**
95
+ * Resets all session-local config then releases the connection back to the pool.
96
+ * If RESET ALL fails, destroys the connection instead — pool-poisoning guarantee.
97
+ */
98
+ async release() {
99
+ try {
100
+ await conn.query("RESET ALL");
101
+ await conn.release();
102
+ } catch (resetError) {
103
+ await conn.destroy(resetError).catch(() => void 0);
104
+ }
105
+ },
106
+ async destroy(reason) {
107
+ await conn.destroy(reason);
108
+ }
109
+ };
110
+ }
111
+ /**
112
+ * Opens a role session, executes the plan, then releases after the stream drains.
113
+ * On mid-stream error, destroys the session instead of releasing.
114
+ */
115
+ executeWithRole(plan, binding, options) {
116
+ const self = this;
117
+ const generator = async function* () {
118
+ const session = await self.openRoleSession(binding);
119
+ let errored = false;
120
+ try {
121
+ for await (const row of session.execute(plan, options)) yield row;
122
+ } catch (err) {
123
+ errored = true;
124
+ await session.destroy(err).catch(() => void 0);
125
+ throw err;
126
+ } finally {
127
+ if (!errored) await session.release();
128
+ }
129
+ };
130
+ return new AsyncIterableResult(generator());
131
+ }
132
+ };
133
+ //#endregion
134
+ //#region src/runtime/supabase.ts
135
+ var SupabaseConfigError = class extends Error {
136
+ name = "SupabaseConfigError";
137
+ constructor(message) {
138
+ super(message);
139
+ }
140
+ };
141
+ var InvalidJwtError = class extends Error {
142
+ name = "InvalidJwtError";
143
+ reason;
144
+ constructor(reason) {
145
+ super(`Invalid JWT: ${reason}`);
146
+ this.reason = reason;
147
+ }
148
+ };
149
+ function hasContractJson(options) {
150
+ return "contractJson" in options;
151
+ }
152
+ const contractSerializer = new PostgresContractSerializer();
153
+ function resolveContract(options) {
154
+ const contractInput = hasContractJson(options) ? options.contractJson : options.contract;
155
+ return blindCast(contractSerializer.deserializeContract(contractInput));
156
+ }
157
+ function resolveKeyMaterial(options) {
158
+ const jwtSecret = "jwtSecret" in options ? options.jwtSecret : void 0;
159
+ const jwksUrl = "jwksUrl" in options ? options.jwksUrl : void 0;
160
+ if (jwtSecret !== void 0 && jwksUrl !== void 0) throw new SupabaseConfigError("Provide either jwtSecret or jwksUrl, not both");
161
+ if (jwtSecret === void 0 && jwksUrl === void 0) throw new SupabaseConfigError("Either jwtSecret or jwksUrl is required");
162
+ if (jwtSecret !== void 0) return {
163
+ kind: "secret",
164
+ key: new TextEncoder().encode(jwtSecret)
165
+ };
166
+ if (jwksUrl !== void 0) return {
167
+ kind: "jwks",
168
+ keyset: createRemoteJWKSet(new URL(jwksUrl))
169
+ };
170
+ throw new SupabaseConfigError("Either jwtSecret or jwksUrl is required");
171
+ }
172
+ function toPool(options) {
173
+ if (options.pg instanceof Pool) return {
174
+ pool: options.pg,
175
+ owned: false
176
+ };
177
+ if (typeof options.url === "string") return {
178
+ pool: new Pool({
179
+ connectionString: options.url,
180
+ connectionTimeoutMillis: options.poolOptions?.connectionTimeoutMillis ?? 2e4,
181
+ idleTimeoutMillis: options.poolOptions?.idleTimeoutMillis ?? 3e4
182
+ }),
183
+ owned: true
184
+ };
185
+ }
186
+ function withSupabaseDescriptor(extensions) {
187
+ const packs = extensions ?? [];
188
+ return packs.some((pack) => pack.id === supabaseRuntimeDescriptor.id) ? packs : [...packs, supabaseRuntimeDescriptor];
189
+ }
190
+ async function supabase(options) {
191
+ const keyMaterial = resolveKeyMaterial(options);
192
+ const contract = resolveContract(options);
193
+ const stack = createSqlExecutionStack({
194
+ target: postgresTarget,
195
+ adapter: postgresAdapter,
196
+ driver: postgresDriver,
197
+ extensionPacks: withSupabaseDescriptor(options.extensions)
198
+ });
199
+ const context = createExecutionContext({
200
+ contract,
201
+ stack
202
+ });
203
+ const rawCodecInferer = stack.adapter.rawCodecInferer;
204
+ const rawSqlTag = createRawSql(rawCodecInferer);
205
+ const poolEntry = toPool(options);
206
+ let closed = false;
207
+ const stackInstance = instantiateExecutionStack(stack);
208
+ const driverDescriptor = stack.driver;
209
+ if (!driverDescriptor) throw new Error("Driver descriptor missing from execution stack");
210
+ const driver = driverDescriptor.create({ cursor: { disabled: true } });
211
+ if (poolEntry) await driver.connect({
212
+ kind: "pgPool",
213
+ pool: poolEntry.pool
214
+ });
215
+ const runtime = new SupabaseRuntimeImpl({
216
+ context,
217
+ adapter: stackInstance.adapter,
218
+ driver,
219
+ ...ifDefined("verifyMarker", options.verifyMarker),
220
+ ...ifDefined("middleware", options.middleware)
221
+ });
222
+ async function verifyJwt(jwt) {
223
+ try {
224
+ if (keyMaterial.kind === "secret") return await jwtVerify(jwt, keyMaterial.key);
225
+ return await jwtVerify(jwt, keyMaterial.keyset);
226
+ } catch (err) {
227
+ throw new InvalidJwtError(err instanceof Error ? err.message : String(err));
228
+ }
229
+ }
230
+ function buildRoleBoundDb(binding) {
231
+ return {
232
+ sql: sql({
233
+ context,
234
+ rawCodecInferer
235
+ }),
236
+ orm: orm({
237
+ runtime: {
238
+ execute(plan) {
239
+ return runtime.executeWithRole(plan, binding);
240
+ },
241
+ connection: () => runtime.openRoleSession(binding)
242
+ },
243
+ context
244
+ }),
245
+ raw: rawSqlTag,
246
+ execute(plan, execOptions) {
247
+ return runtime.executeWithRole(plan, binding, execOptions);
248
+ },
249
+ transaction(fn) {
250
+ return withTransaction({ connection: () => runtime.openRoleSession(binding) }, fn);
251
+ }
252
+ };
253
+ }
254
+ async function closeDb() {
255
+ if (closed) return;
256
+ closed = true;
257
+ await runtime.close();
258
+ if (poolEntry?.owned) await poolEntry.pool.end().catch(() => void 0);
259
+ }
260
+ return {
261
+ context,
262
+ stack,
263
+ async asUser(jwt) {
264
+ const { payload } = await verifyJwt(jwt);
265
+ const rawRole = payload["role"];
266
+ const roleStr = typeof rawRole === "string" ? rawRole : "authenticated";
267
+ return buildRoleBoundDb({
268
+ role: roleStr === "anon" || roleStr === "authenticated" || roleStr === "service_role" ? roleStr : "authenticated",
269
+ claims: payload
270
+ });
271
+ },
272
+ asAnon() {
273
+ return buildRoleBoundDb({
274
+ role: "anon",
275
+ claims: {}
276
+ });
277
+ },
278
+ asServiceRole() {
279
+ return buildRoleBoundDb({
280
+ role: "service_role",
281
+ claims: {}
282
+ });
283
+ },
284
+ close: closeDb,
285
+ [Symbol.asyncDispose]: closeDb
286
+ };
287
+ }
288
+ //#endregion
289
+ //#region src/exports/runtime.ts
290
+ var runtime_default = supabaseRuntimeDescriptor;
291
+ //#endregion
292
+ export { InvalidJwtError, SupabaseConfigError, SupabaseRuntimeImpl, runtime_default as default, supabase };
18
293
 
19
294
  //# sourceMappingURL=runtime.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/exports/runtime.ts"],"sourcesContent":["/**\n * Minimal M1 runtime descriptor for the Supabase extension.\n *\n * The Supabase pack contributes no runtime codec types or query operations in\n * M1 — `auth.*`/`storage.*` are external tables accessed via the stock\n * postgres runtime, not through a custom codec or operation surface. This\n * descriptor exists so the postgres runtime's contract-requirements check\n * (which verifies every `extensionPacks` entry in the emitted `contract.json`\n * has a matching runtime component) passes.\n *\n * TODO(M2): Replace with the real SupabaseRuntime that adds\n * `asUser()`/`asAnon()` role-binding and the Supabase auth surface.\n */\nimport type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';\n\nconst supabaseRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {\n kind: 'extension' as const,\n id: 'supabase',\n version: '0.12.0',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => [],\n create() {\n return {\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n };\n },\n};\n\nexport default supabaseRuntimeDescriptor;\n"],"mappings":";AAeA,MAAM,4BAAuE;CAC3E,MAAM;CACN,IAAI;CACJ,SAAS;CACT,UAAU;CACV,UAAU;CACV,cAAc,CAAC;CACf,SAAS;EACP,OAAO;GACL,UAAU;GACV,UAAU;EACZ;CACF;AACF"}
1
+ {"version":3,"file":"runtime.mjs","names":["packageJson.version"],"sources":["../src/runtime/descriptor.ts","../src/runtime/supabase-runtime.ts","../src/runtime/supabase.ts","../src/exports/runtime.ts"],"sourcesContent":["import type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';\nimport packageJson from '../../package.json' with { type: 'json' };\n\n/**\n * Tells the runtime that the Supabase pack's runtime component is available.\n *\n * When a contract declares the Supabase pack, the runtime checks that a\n * matching descriptor has been registered. Without this, loading a Supabase\n * contract errors with \"pack runtime component missing\". The `supabase()`\n * factory registers this descriptor automatically — app code never needs to\n * reference it directly.\n */\nexport const supabaseRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {\n kind: 'extension' as const,\n id: 'supabase',\n version: packageJson.version,\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => [],\n create() {\n return {\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n };\n },\n};\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { RuntimeExecuteOptions } from '@prisma-next/framework-components/runtime';\nimport { AsyncIterableResult } from '@prisma-next/framework-components/runtime';\nimport { type PostgresRuntime, PostgresRuntimeImpl } from '@prisma-next/postgres/runtime';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';\nimport type {\n PreparedStatement,\n PreparedStatementImpl,\n RuntimeConnection,\n RuntimeTransaction,\n} from '@prisma-next/sql-runtime';\nimport { blindCast } from '@prisma-next/utils/casts';\n\nexport interface SupabaseRuntime extends PostgresRuntime {}\n\nexport interface SupabaseRoleBinding {\n // TODO(TML-2501): role names move to the Supabase extension contract (roles as first-class IR) when postgres-rls lands.\n readonly role: 'anon' | 'authenticated' | 'service_role';\n readonly claims?: Record<string, unknown>;\n}\n\n/**\n * A connection with a Supabase role already bound via session-scoped set_config.\n * Implements `RuntimeConnection` so it plugs into ORM scope machinery and `withTransaction`.\n */\nexport interface RoleSession extends RuntimeConnection {}\n\nexport class SupabaseRuntimeImpl<\n TContract extends Contract<SqlStorage> = Contract<SqlStorage>,\n> extends PostgresRuntimeImpl<TContract> {\n /**\n * Opens a raw connection and applies role + JWT claims via session-scoped set_config.\n * On bind failure, destroys the connection before rethrowing — no leaked connections.\n * Not on the `SupabaseRuntime` interface; consumed by the facade, not by app code.\n */\n async openRoleSession(binding: SupabaseRoleBinding): Promise<RoleSession> {\n const conn = await this.acquireRawConnection();\n\n try {\n await conn.query('SELECT set_config($1, $2, false)', ['role', binding.role]);\n await conn.query('SELECT set_config($1, $2, false)', [\n 'request.jwt.claims',\n JSON.stringify(binding.claims ?? {}),\n ]);\n } catch (err) {\n await conn.destroy(err).catch(() => undefined);\n throw err;\n }\n\n const self = this;\n\n const session: RoleSession = {\n execute<Row>(\n plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n return self.executeAgainstQueryable<Row>(plan, conn, { ...options, scope: 'connection' });\n },\n\n executePrepared<Params, Row>(\n ps: PreparedStatement<Params, Row>,\n params: Params,\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n return self.executePreparedAgainstQueryable(\n blindCast<\n PreparedStatementImpl<Params, Row>,\n 'PreparedStatement is PreparedStatementImpl; the impl class is the only concrete form'\n >(ps),\n blindCast<\n Record<string, unknown>,\n 'params are structurally Record<string, unknown> at runtime'\n >(params),\n conn,\n { ...options, scope: 'connection' },\n );\n },\n\n async transaction(): Promise<RuntimeTransaction> {\n const tx = await conn.beginTransaction();\n return {\n async commit(): Promise<void> {\n await tx.commit();\n },\n async rollback(): Promise<void> {\n await tx.rollback();\n },\n execute<Row>(\n plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n return self.executeAgainstQueryable<Row>(plan, tx, {\n ...options,\n scope: 'transaction',\n });\n },\n executePrepared<Params, Row>(\n ps: PreparedStatement<Params, Row>,\n params: Params,\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n return self.executePreparedAgainstQueryable(\n blindCast<\n PreparedStatementImpl<Params, Row>,\n 'PreparedStatement is PreparedStatementImpl; the impl class is the only concrete form'\n >(ps),\n blindCast<\n Record<string, unknown>,\n 'params are structurally Record<string, unknown> at runtime'\n >(params),\n tx,\n { ...options, scope: 'transaction' },\n );\n },\n };\n },\n\n /**\n * Resets all session-local config then releases the connection back to the pool.\n * If RESET ALL fails, destroys the connection instead — pool-poisoning guarantee.\n */\n async release(): Promise<void> {\n try {\n await conn.query('RESET ALL');\n await conn.release();\n } catch (resetError) {\n await conn.destroy(resetError).catch(() => undefined);\n }\n },\n\n async destroy(reason?: unknown): Promise<void> {\n await conn.destroy(reason);\n },\n };\n\n return session;\n }\n\n /**\n * Opens a role session, executes the plan, then releases after the stream drains.\n * On mid-stream error, destroys the session instead of releasing.\n */\n executeWithRole<Row>(\n plan: SqlExecutionPlan<Row> | SqlQueryPlan<Row>,\n binding: SupabaseRoleBinding,\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n const self = this;\n\n const generator = async function* (): AsyncGenerator<Row, void, unknown> {\n const session = await self.openRoleSession(binding);\n let errored = false;\n try {\n for await (const row of session.execute(plan, options)) {\n yield row;\n }\n } catch (err) {\n errored = true;\n await session.destroy(err).catch(() => undefined);\n throw err;\n } finally {\n if (!errored) {\n await session.release();\n }\n }\n };\n\n return new AsyncIterableResult(generator());\n }\n}\n","import postgresAdapter from '@prisma-next/adapter-postgres/runtime';\nimport type { Contract } from '@prisma-next/contract/types';\nimport postgresDriver from '@prisma-next/driver-postgres/runtime';\nimport { instantiateExecutionStack } from '@prisma-next/framework-components/execution';\nimport type {\n AsyncIterableResult,\n RuntimeExecuteOptions,\n} from '@prisma-next/framework-components/runtime';\nimport { sql } from '@prisma-next/sql-builder/runtime';\nimport type { Db } from '@prisma-next/sql-builder/types';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport { orm } from '@prisma-next/sql-orm-client';\nimport type { RawSqlTag } from '@prisma-next/sql-relational-core/expression';\nimport { createRawSql } from '@prisma-next/sql-relational-core/expression';\nimport type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';\nimport type {\n ExecutionContext,\n SqlExecutionStackWithDriver,\n SqlMiddleware,\n SqlRuntimeExtensionDescriptor,\n TransactionContext,\n VerifyMarkerOption,\n} from '@prisma-next/sql-runtime';\nimport {\n createExecutionContext,\n createSqlExecutionStack,\n withTransaction,\n} from '@prisma-next/sql-runtime';\nimport postgresTarget, { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { createRemoteJWKSet, type JWTVerifyResult, jwtVerify } from 'jose';\nimport type { Client } from 'pg';\nimport { Pool } from 'pg';\nimport { supabaseRuntimeDescriptor } from './descriptor';\nimport type { SupabaseRoleBinding, SupabaseRuntime } from './supabase-runtime';\nimport { SupabaseRuntimeImpl } from './supabase-runtime';\n\nexport type SupabaseTargetId = 'postgres';\n\ntype OrmClient<TContract extends Contract<SqlStorage>> = ReturnType<typeof orm<TContract>>;\n\nexport class SupabaseConfigError extends Error {\n override readonly name = 'SupabaseConfigError';\n constructor(message: string) {\n super(message);\n }\n}\n\nexport class InvalidJwtError extends Error {\n override readonly name = 'InvalidJwtError';\n readonly reason: string;\n constructor(reason: string) {\n super(`Invalid JWT: ${reason}`);\n this.reason = reason;\n }\n}\n\ntype KeyMaterial =\n | { readonly kind: 'secret'; readonly key: Uint8Array }\n | { readonly kind: 'jwks'; readonly keyset: ReturnType<typeof createRemoteJWKSet> };\n\nexport interface RoleBoundDb<TContract extends Contract<SqlStorage>> {\n readonly sql: Db<TContract>;\n readonly orm: OrmClient<TContract>;\n readonly raw: RawSqlTag;\n execute<Row>(\n plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },\n options?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row>;\n transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R>;\n}\n\nexport interface SupabaseDb<TContract extends Contract<SqlStorage>> {\n readonly context: ExecutionContext<TContract>;\n readonly stack: SqlExecutionStackWithDriver<SupabaseTargetId>;\n asUser(jwt: string): Promise<RoleBoundDb<TContract>>;\n asAnon(): RoleBoundDb<TContract>;\n asServiceRole(): RoleBoundDb<TContract>;\n close(): Promise<void>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nexport interface SupabaseOptionsBase {\n readonly extensions?: readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[];\n readonly middleware?: readonly SqlMiddleware[];\n readonly verifyMarker?: VerifyMarkerOption;\n readonly poolOptions?: {\n readonly connectionTimeoutMillis?: number;\n readonly idleTimeoutMillis?: number;\n };\n}\n\nexport interface SupabaseBindingOptions {\n readonly url?: string;\n readonly pg?: Pool | Client;\n}\n\ntype JwtSecretOption = {\n readonly jwtSecret: string;\n readonly jwksUrl?: never;\n};\n\ntype JwksUrlOption = {\n readonly jwksUrl: string;\n readonly jwtSecret?: never;\n};\n\nexport type SupabaseOptionsWithContract<TContract extends Contract<SqlStorage>> =\n SupabaseBindingOptions &\n SupabaseOptionsBase &\n (JwtSecretOption | JwksUrlOption) & {\n readonly contract: TContract;\n readonly contractJson?: never;\n };\n\nexport type SupabaseOptionsWithContractJson<TContract extends Contract<SqlStorage>> =\n SupabaseBindingOptions &\n SupabaseOptionsBase &\n (JwtSecretOption | JwksUrlOption) & {\n readonly contractJson: unknown;\n readonly contract?: never;\n readonly _contract?: TContract;\n };\n\nexport type SupabaseOptions<TContract extends Contract<SqlStorage>> =\n | SupabaseOptionsWithContract<TContract>\n | SupabaseOptionsWithContractJson<TContract>;\n\nfunction hasContractJson<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptions<TContract>,\n): options is SupabaseOptionsWithContractJson<TContract> {\n return 'contractJson' in options;\n}\n\nconst contractSerializer = new PostgresContractSerializer();\n\nfunction resolveContract<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptions<TContract>,\n): TContract {\n const contractInput = hasContractJson(options) ? options.contractJson : options.contract;\n return blindCast<\n TContract,\n 'contractSerializer.deserializeContract returns a validated TContract'\n >(contractSerializer.deserializeContract(contractInput));\n}\n\nfunction resolveKeyMaterial<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptions<TContract>,\n): KeyMaterial {\n const jwtSecret = 'jwtSecret' in options ? options.jwtSecret : undefined;\n const jwksUrl = 'jwksUrl' in options ? options.jwksUrl : undefined;\n\n if (jwtSecret !== undefined && jwksUrl !== undefined) {\n throw new SupabaseConfigError('Provide either jwtSecret or jwksUrl, not both');\n }\n if (jwtSecret === undefined && jwksUrl === undefined) {\n throw new SupabaseConfigError('Either jwtSecret or jwksUrl is required');\n }\n\n if (jwtSecret !== undefined) {\n return { kind: 'secret', key: new TextEncoder().encode(jwtSecret) };\n }\n\n if (jwksUrl !== undefined) {\n return { kind: 'jwks', keyset: createRemoteJWKSet(new URL(jwksUrl)) };\n }\n\n throw new SupabaseConfigError('Either jwtSecret or jwksUrl is required');\n}\n\nfunction toPool<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptions<TContract>,\n): { pool: Pool; owned: boolean } | undefined {\n if (options.pg instanceof Pool) {\n return { pool: options.pg, owned: false };\n }\n if (typeof options.url === 'string') {\n return {\n pool: new Pool({\n connectionString: options.url,\n connectionTimeoutMillis: options.poolOptions?.connectionTimeoutMillis ?? 20_000,\n idleTimeoutMillis: options.poolOptions?.idleTimeoutMillis ?? 30_000,\n }),\n owned: true,\n };\n }\n return undefined;\n}\n\nfunction withSupabaseDescriptor(\n extensions: readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[] | undefined,\n): readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[] {\n const packs = extensions ?? [];\n return packs.some((pack) => pack.id === supabaseRuntimeDescriptor.id)\n ? packs\n : [...packs, supabaseRuntimeDescriptor];\n}\n\nexport default async function supabase<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptionsWithContract<TContract>,\n): Promise<SupabaseDb<TContract>>;\nexport default async function supabase<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptionsWithContractJson<TContract>,\n): Promise<SupabaseDb<TContract>>;\nexport default async function supabase<TContract extends Contract<SqlStorage>>(\n options: SupabaseOptions<TContract>,\n): Promise<SupabaseDb<TContract>> {\n const keyMaterial = resolveKeyMaterial(options);\n const contract = resolveContract(options);\n\n const stack = createSqlExecutionStack({\n target: postgresTarget,\n adapter: postgresAdapter,\n driver: postgresDriver,\n extensionPacks: withSupabaseDescriptor(options.extensions),\n });\n\n const context = createExecutionContext({ contract, stack });\n const rawCodecInferer = stack.adapter.rawCodecInferer;\n const rawSqlTag: RawSqlTag = createRawSql(rawCodecInferer);\n\n const poolEntry = toPool(options);\n let closed = false;\n\n const stackInstance = instantiateExecutionStack(stack);\n const driverDescriptor = stack.driver;\n if (!driverDescriptor) {\n throw new Error('Driver descriptor missing from execution stack');\n }\n const driver = driverDescriptor.create({ cursor: { disabled: true } });\n\n if (poolEntry) {\n await driver.connect({ kind: 'pgPool', pool: poolEntry.pool });\n }\n\n const runtime: SupabaseRuntime & SupabaseRuntimeImpl<TContract> = new SupabaseRuntimeImpl({\n context,\n adapter: stackInstance.adapter,\n driver,\n ...ifDefined('verifyMarker', options.verifyMarker),\n ...ifDefined('middleware', options.middleware),\n });\n\n async function verifyJwt(jwt: string): Promise<JWTVerifyResult> {\n try {\n if (keyMaterial.kind === 'secret') {\n return await jwtVerify(jwt, keyMaterial.key);\n }\n return await jwtVerify(jwt, keyMaterial.keyset);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new InvalidJwtError(reason);\n }\n }\n\n function buildRoleBoundDb(binding: SupabaseRoleBinding): RoleBoundDb<TContract> {\n const roleSql: Db<TContract> = sql<TContract>({ context, rawCodecInferer });\n const roleOrm: OrmClient<TContract> = orm({\n runtime: {\n execute(plan) {\n return runtime.executeWithRole(plan, binding);\n },\n // connection() returns a role session; this is the enforcement path for ORM scope\n // operations (mutations, includes) — every statement runs role-bound.\n connection: () => runtime.openRoleSession(binding),\n },\n context,\n });\n\n return {\n sql: roleSql,\n orm: roleOrm,\n raw: rawSqlTag,\n execute<Row>(\n plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },\n execOptions?: RuntimeExecuteOptions,\n ): AsyncIterableResult<Row> {\n return runtime.executeWithRole<Row>(plan, binding, execOptions);\n },\n transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R> {\n return withTransaction({ connection: () => runtime.openRoleSession(binding) }, fn);\n },\n };\n }\n\n async function closeDb(): Promise<void> {\n if (closed) return;\n closed = true;\n await runtime.close();\n if (poolEntry?.owned) {\n await poolEntry.pool.end().catch(() => undefined);\n }\n }\n\n return {\n context,\n stack,\n\n async asUser(jwt: string): Promise<RoleBoundDb<TContract>> {\n const { payload } = await verifyJwt(jwt);\n const rawRole = payload['role'];\n const roleStr = typeof rawRole === 'string' ? rawRole : 'authenticated';\n const role: SupabaseRoleBinding['role'] =\n roleStr === 'anon' || roleStr === 'authenticated' || roleStr === 'service_role'\n ? roleStr\n : 'authenticated';\n const binding: SupabaseRoleBinding = { role, claims: payload };\n return buildRoleBoundDb(binding);\n },\n\n asAnon(): RoleBoundDb<TContract> {\n return buildRoleBoundDb({ role: 'anon', claims: {} });\n },\n\n asServiceRole(): RoleBoundDb<TContract> {\n return buildRoleBoundDb({ role: 'service_role', claims: {} });\n },\n\n close: closeDb,\n [Symbol.asyncDispose]: closeDb,\n };\n}\n","import { supabaseRuntimeDescriptor } from '../runtime/descriptor';\n\nexport default supabaseRuntimeDescriptor;\n\nexport type {\n RoleBoundDb,\n SupabaseDb,\n SupabaseOptions,\n SupabaseOptionsWithContract,\n SupabaseOptionsWithContractJson,\n SupabaseTargetId,\n} from '../runtime/supabase';\nexport { default as supabase, InvalidJwtError, SupabaseConfigError } from '../runtime/supabase';\nexport type {\n RoleSession,\n SupabaseRoleBinding,\n SupabaseRuntime,\n} from '../runtime/supabase-runtime';\nexport { SupabaseRuntimeImpl } from '../runtime/supabase-runtime';\n"],"mappings":"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"}
@@ -2,10 +2,9 @@ import { Client } from "pg";
2
2
 
3
3
  //#region test/supabase-bootstrap.d.ts
4
4
  /**
5
- * Seeds the database with the external Supabase schemas and tables. The
6
- * caller passes an already-connected `pg.Client` — this function does not
7
- * open or close connections, so the same client can be reused across the
8
- * test's other setup steps.
5
+ * Seeds the database with the external Supabase schemas, tables, roles, and grants.
6
+ * The caller passes an already-connected `pg.Client` — this function does not
7
+ * open or close connections.
9
8
  *
10
9
  * Creates two schemas (`auth`, `storage`) and four tables whose columns
11
10
  * exactly match the `@prisma-next/extension-supabase` contract:
@@ -15,8 +14,10 @@ import { Client } from "pg";
15
14
  * - `storage.buckets` — id text PK, name text, created_at timestamptz, updated_at timestamptz
16
15
  * - `storage.objects` — id uuid PK, bucket_id text, name text, created_at timestamptz, updated_at timestamptz
17
16
  *
18
- * Does NOT create Postgres roles or `auth.*` functions those are added by
19
- * the `postgres-rls` constituent.
17
+ * Creates the three Postgres roles and grants that mirror a real Supabase database.
18
+ * `ALTER DEFAULT PRIVILEGES` covers tables created after the shim runs (e.g. via `dbInit`).
19
+ * WAL grants are guarded by a schema-existence check — a PGlite single-connection
20
+ * accommodation so role-bound sessions can interleave with the WAL drain query.
20
21
  */
21
22
  declare function bootstrapSupabaseShim(client: Client): Promise<void>;
22
23
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../../test/supabase-bootstrap.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBAoDsB,qBAAA,CAAsB,MAAA,EAAQ,MAAA,GAAS,OAAO"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../../test/supabase-bootstrap.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAiDsB,qBAAA,CAAsB,MAAA,EAAQ,MAAA,GAAS,OAAO"}
@@ -1,9 +1,8 @@
1
1
  //#region test/supabase-bootstrap.ts
2
2
  /**
3
- * Seeds the database with the external Supabase schemas and tables. The
4
- * caller passes an already-connected `pg.Client` — this function does not
5
- * open or close connections, so the same client can be reused across the
6
- * test's other setup steps.
3
+ * Seeds the database with the external Supabase schemas, tables, roles, and grants.
4
+ * The caller passes an already-connected `pg.Client` — this function does not
5
+ * open or close connections.
7
6
  *
8
7
  * Creates two schemas (`auth`, `storage`) and four tables whose columns
9
8
  * exactly match the `@prisma-next/extension-supabase` contract:
@@ -13,8 +12,10 @@
13
12
  * - `storage.buckets` — id text PK, name text, created_at timestamptz, updated_at timestamptz
14
13
  * - `storage.objects` — id uuid PK, bucket_id text, name text, created_at timestamptz, updated_at timestamptz
15
14
  *
16
- * Does NOT create Postgres roles or `auth.*` functions those are added by
17
- * the `postgres-rls` constituent.
15
+ * Creates the three Postgres roles and grants that mirror a real Supabase database.
16
+ * `ALTER DEFAULT PRIVILEGES` covers tables created after the shim runs (e.g. via `dbInit`).
17
+ * WAL grants are guarded by a schema-existence check — a PGlite single-connection
18
+ * accommodation so role-bound sessions can interleave with the WAL drain query.
18
19
  */
19
20
  async function bootstrapSupabaseShim(client) {
20
21
  await client.query("CREATE SCHEMA IF NOT EXISTS auth");
@@ -57,6 +58,18 @@ async function bootstrapSupabaseShim(client) {
57
58
  PRIMARY KEY (id)
58
59
  )
59
60
  `);
61
+ await client.query("CREATE ROLE anon NOLOGIN");
62
+ await client.query("CREATE ROLE authenticated NOLOGIN");
63
+ await client.query("CREATE ROLE service_role NOLOGIN BYPASSRLS");
64
+ await client.query("GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role");
65
+ await client.query("GRANT USAGE ON SCHEMA auth, storage TO anon, authenticated, service_role");
66
+ await client.query("GRANT ALL ON ALL TABLES IN SCHEMA auth TO service_role");
67
+ await client.query("GRANT ALL ON ALL TABLES IN SCHEMA storage TO service_role");
68
+ await client.query("GRANT SELECT ON ALL TABLES IN SCHEMA auth TO anon, authenticated");
69
+ await client.query("GRANT SELECT ON ALL TABLES IN SCHEMA storage TO anon, authenticated");
70
+ await client.query("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO service_role");
71
+ await client.query("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, UPDATE ON TABLES TO authenticated");
72
+ await client.query("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO anon");
60
73
  }
61
74
  //#endregion
62
75
  export { bootstrapSupabaseShim };
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../test/supabase-bootstrap.ts"],"sourcesContent":["/**\n * Shared Supabase test fixture — seeds the external schemas and tables.\n *\n * Seeds a Postgres/PGlite database with the external Supabase schemas and\n * tables that the framework verifier expects when a composed contract declares\n * `auth.*` and `storage.*` tables as `external`. Without these tables present,\n * `db init`/`db update` will fail at the verify step because the framework\n * confirms declared `external` tables exist.\n *\n * **M1 scope:** `CREATE SCHEMA auth, storage` + the four tables\n * (`auth.users`, `auth.identities`, `storage.buckets`, `storage.objects`) with\n * columns matching the Supabase extension contract's pinned model table.\n *\n * **Future increments:**\n * - `postgres-rls` constituent adds Postgres roles (`anon`, `authenticated`,\n * `service_role`) and the `auth.uid()`, `auth.jwt()`, `auth.role()` functions.\n * - `cross-contract-refs` constituent seeds `auth.users` rows for FK tests.\n *\n * The caller owns the client lifecycle — pass any already-connected `pg.Client`\n * (e.g. one the test is sharing across setup steps, or one bound to a\n * transaction for isolation). Convenience wrapper for tests that don't already\n * have one:\n *\n * @example\n * ```ts\n * import { withClient } from '@prisma-next/test-utils';\n * import { bootstrapSupabaseShim } from '@prisma-next/extension-supabase/test/utils';\n *\n * await withClient(connectionString, async (client) => {\n * await bootstrapSupabaseShim(client);\n * });\n * ```\n */\nimport type { Client } from 'pg';\n\n/**\n * Seeds the database with the external Supabase schemas and tables. The\n * caller passes an already-connected `pg.Client` — this function does not\n * open or close connections, so the same client can be reused across the\n * test's other setup steps.\n *\n * Creates two schemas (`auth`, `storage`) and four tables whose columns\n * exactly match the `@prisma-next/extension-supabase` contract:\n *\n * - `auth.users` — id uuid PK, email text, created_at timestamptz, updated_at timestamptz\n * - `auth.identities` — id uuid PK, user_id uuid, provider text, created_at timestamptz, updated_at timestamptz\n * - `storage.buckets` — id text PK, name text, created_at timestamptz, updated_at timestamptz\n * - `storage.objects` — id uuid PK, bucket_id text, name text, created_at timestamptz, updated_at timestamptz\n *\n * Does NOT create Postgres roles or `auth.*` functions those are added by\n * the `postgres-rls` constituent.\n */\nexport async function bootstrapSupabaseShim(client: Client): Promise<void> {\n await client.query('CREATE SCHEMA IF NOT EXISTS auth');\n await client.query('CREATE SCHEMA IF NOT EXISTS storage');\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS auth.users (\n id uuid NOT NULL,\n email text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS auth.identities (\n id uuid NOT NULL,\n user_id uuid NOT NULL,\n provider text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS storage.buckets (\n id text NOT NULL,\n name text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS storage.objects (\n id uuid NOT NULL,\n bucket_id text NOT NULL,\n name text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoDA,eAAsB,sBAAsB,QAA+B;CACzE,MAAM,OAAO,MAAM,kCAAkC;CACrD,MAAM,OAAO,MAAM,qCAAqC;CAExD,MAAM,OAAO,MAAM;;;;;;;;GAQlB;CAED,MAAM,OAAO,MAAM;;;;;;;;;GASlB;CAED,MAAM,OAAO,MAAM;;;;;;;;GAQlB;CAED,MAAM,OAAO,MAAM;;;;;;;;;GASlB;AACH"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../test/supabase-bootstrap.ts"],"sourcesContent":["/**\n * Shared Supabase test fixture — seeds the external schemas, tables, roles, and grants.\n *\n * Seeds a Postgres/PGlite database with the external Supabase schemas and\n * tables that the framework verifier expects when a composed contract declares\n * `auth.*` and `storage.*` tables as `external`. Without these tables present,\n * `db init`/`db update` will fail at the verify step because the framework\n * confirms declared `external` tables exist.\n *\n * Also creates the three Postgres roles (`anon`, `authenticated`, `service_role`)\n * with grants that mirror a real Supabase database. `ALTER DEFAULT PRIVILEGES`\n * ensures tables created after the shim runs (e.g. `public.profile` via `dbInit`)\n * are automatically accessible to the roles.\n *\n * The caller owns the client lifecycle — pass any already-connected `pg.Client`\n * (e.g. one the test is sharing across setup steps, or one bound to a\n * transaction for isolation). Convenience wrapper for tests that don't already\n * have one:\n *\n * @example\n * ```ts\n * import { withClient } from '@prisma-next/test-utils';\n * import { bootstrapSupabaseShim } from '@prisma-next/extension-supabase/test/utils';\n *\n * await withClient(connectionString, async (client) => {\n * await bootstrapSupabaseShim(client);\n * });\n * ```\n */\nimport type { Client } from 'pg';\n\n/**\n * Seeds the database with the external Supabase schemas, tables, roles, and grants.\n * The caller passes an already-connected `pg.Client` — this function does not\n * open or close connections.\n *\n * Creates two schemas (`auth`, `storage`) and four tables whose columns\n * exactly match the `@prisma-next/extension-supabase` contract:\n *\n * - `auth.users` — id uuid PK, email text, created_at timestamptz, updated_at timestamptz\n * - `auth.identities` — id uuid PK, user_id uuid, provider text, created_at timestamptz, updated_at timestamptz\n * - `storage.buckets` — id text PK, name text, created_at timestamptz, updated_at timestamptz\n * - `storage.objects` — id uuid PK, bucket_id text, name text, created_at timestamptz, updated_at timestamptz\n *\n * Creates the three Postgres roles and grants that mirror a real Supabase database.\n * `ALTER DEFAULT PRIVILEGES` covers tables created after the shim runs (e.g. via `dbInit`).\n * WAL grants are guarded by a schema-existence check — a PGlite single-connection\n * accommodation so role-bound sessions can interleave with the WAL drain query.\n */\nexport async function bootstrapSupabaseShim(client: Client): Promise<void> {\n await client.query('CREATE SCHEMA IF NOT EXISTS auth');\n await client.query('CREATE SCHEMA IF NOT EXISTS storage');\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS auth.users (\n id uuid NOT NULL,\n email text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS auth.identities (\n id uuid NOT NULL,\n user_id uuid NOT NULL,\n provider text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS storage.buckets (\n id text NOT NULL,\n name text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS storage.objects (\n id uuid NOT NULL,\n bucket_id text NOT NULL,\n name text NOT NULL,\n created_at timestamptz NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (id)\n )\n `);\n\n // Roles + grants mirror a real Supabase database; WAL grants are a\n // PGlite-single-connection test accommodation.\n await client.query('CREATE ROLE anon NOLOGIN');\n await client.query('CREATE ROLE authenticated NOLOGIN');\n await client.query('CREATE ROLE service_role NOLOGIN BYPASSRLS');\n await client.query('GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role');\n await client.query('GRANT USAGE ON SCHEMA auth, storage TO anon, authenticated, service_role');\n await client.query('GRANT ALL ON ALL TABLES IN SCHEMA auth TO service_role');\n await client.query('GRANT ALL ON ALL TABLES IN SCHEMA storage TO service_role');\n await client.query('GRANT SELECT ON ALL TABLES IN SCHEMA auth TO anon, authenticated');\n await client.query('GRANT SELECT ON ALL TABLES IN SCHEMA storage TO anon, authenticated');\n\n // Default privileges cover tables created after this shim runs (e.g. public.profile via dbInit).\n await client.query(\n 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO service_role',\n );\n await client.query(\n 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, UPDATE ON TABLES TO authenticated',\n );\n await client.query('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO anon');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiDA,eAAsB,sBAAsB,QAA+B;CACzE,MAAM,OAAO,MAAM,kCAAkC;CACrD,MAAM,OAAO,MAAM,qCAAqC;CAExD,MAAM,OAAO,MAAM;;;;;;;;GAQlB;CAED,MAAM,OAAO,MAAM;;;;;;;;;GASlB;CAED,MAAM,OAAO,MAAM;;;;;;;;GAQlB;CAED,MAAM,OAAO,MAAM;;;;;;;;;GASlB;CAID,MAAM,OAAO,MAAM,0BAA0B;CAC7C,MAAM,OAAO,MAAM,mCAAmC;CACtD,MAAM,OAAO,MAAM,4CAA4C;CAC/D,MAAM,OAAO,MAAM,mEAAmE;CACtF,MAAM,OAAO,MAAM,0EAA0E;CAC7F,MAAM,OAAO,MAAM,wDAAwD;CAC3E,MAAM,OAAO,MAAM,2DAA2D;CAC9E,MAAM,OAAO,MAAM,kEAAkE;CACrF,MAAM,OAAO,MAAM,qEAAqE;CAGxF,MAAM,OAAO,MACX,+EACF;CACA,MAAM,OAAO,MACX,2FACF;CACA,MAAM,OAAO,MAAM,0EAA0E;AAC/F"}
package/package.json CHANGED
@@ -1,44 +1,47 @@
1
1
  {
2
2
  "name": "@prisma-next/extension-supabase",
3
- "version": "0.13.0-dev.11",
3
+ "version": "0.13.0-dev.13",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "dependencies": {
8
- "@prisma-next/contract": "0.13.0-dev.11",
9
- "@prisma-next/contract-authoring": "0.13.0-dev.11",
10
- "@prisma-next/family-sql": "0.13.0-dev.11",
11
- "@prisma-next/framework-components": "0.13.0-dev.11",
12
- "@prisma-next/migration-tools": "0.13.0-dev.11",
13
- "@prisma-next/sql-contract": "0.13.0-dev.11",
14
- "@prisma-next/sql-contract-ts": "0.13.0-dev.11",
15
- "@prisma-next/sql-operations": "0.13.0-dev.11",
16
- "@prisma-next/sql-relational-core": "0.13.0-dev.11",
17
- "@prisma-next/sql-runtime": "0.13.0-dev.11",
18
- "@prisma-next/sql-schema-ir": "0.13.0-dev.11",
19
- "@prisma-next/utils": "0.13.0-dev.11",
8
+ "@prisma-next/adapter-postgres": "0.13.0-dev.13",
9
+ "@prisma-next/contract": "0.13.0-dev.13",
10
+ "@prisma-next/driver-postgres": "0.13.0-dev.13",
11
+ "@prisma-next/postgres": "0.13.0-dev.13",
12
+ "@prisma-next/sql-builder": "0.13.0-dev.13",
13
+ "@prisma-next/sql-orm-client": "0.13.0-dev.13",
14
+ "@prisma-next/target-postgres": "0.13.0-dev.13",
15
+ "jose": "^5",
16
+ "pg": "8.21.0",
17
+ "@prisma-next/contract-authoring": "0.13.0-dev.13",
18
+ "@prisma-next/family-sql": "0.13.0-dev.13",
19
+ "@prisma-next/framework-components": "0.13.0-dev.13",
20
+ "@prisma-next/migration-tools": "0.13.0-dev.13",
21
+ "@prisma-next/sql-contract": "0.13.0-dev.13",
22
+ "@prisma-next/sql-contract-ts": "0.13.0-dev.13",
23
+ "@prisma-next/sql-operations": "0.13.0-dev.13",
24
+ "@prisma-next/sql-relational-core": "0.13.0-dev.13",
25
+ "@prisma-next/sql-runtime": "0.13.0-dev.13",
26
+ "@prisma-next/sql-schema-ir": "0.13.0-dev.13",
27
+ "@prisma-next/utils": "0.13.0-dev.13",
20
28
  "@standard-schema/spec": "^1.1.0",
21
29
  "arktype": "^2.2.0"
22
30
  },
23
31
  "devDependencies": {
24
- "@prisma-next/adapter-postgres": "0.13.0-dev.11",
25
- "@prisma-next/cli": "0.13.0-dev.11",
26
- "@prisma-next/driver-postgres": "0.13.0-dev.11",
27
- "@prisma-next/operations": "0.13.0-dev.11",
28
- "@prisma-next/postgres": "0.13.0-dev.11",
29
- "@prisma-next/sql-contract-psl": "0.13.0-dev.11",
30
- "@prisma-next/target-postgres": "0.13.0-dev.11",
31
- "@prisma-next/test-utils": "0.13.0-dev.11",
32
- "@prisma-next/tsconfig": "0.13.0-dev.11",
33
- "@prisma-next/tsdown": "0.13.0-dev.11",
32
+ "@prisma-next/cli": "0.13.0-dev.13",
33
+ "@prisma-next/operations": "0.13.0-dev.13",
34
+ "@prisma-next/sql-contract-psl": "0.13.0-dev.13",
35
+ "@prisma-next/test-utils": "0.13.0-dev.13",
36
+ "@prisma-next/tsconfig": "0.13.0-dev.13",
37
+ "@prisma-next/tsdown": "0.13.0-dev.13",
34
38
  "@types/pg": "8.20.0",
35
- "pg": "8.21.0",
36
39
  "tsdown": "0.22.1",
37
40
  "typescript": "5.9.3",
38
41
  "vitest": "4.1.8"
39
42
  },
40
43
  "peerDependencies": {
41
- "@prisma-next/adapter-postgres": "0.13.0-dev.11",
44
+ "@prisma-next/adapter-postgres": "0.13.0-dev.13",
42
45
  "typescript": ">=5.9"
43
46
  },
44
47
  "peerDependenciesMeta": {
@@ -1,31 +1,19 @@
1
- /**
2
- * Minimal M1 runtime descriptor for the Supabase extension.
3
- *
4
- * The Supabase pack contributes no runtime codec types or query operations in
5
- * M1 — `auth.*`/`storage.*` are external tables accessed via the stock
6
- * postgres runtime, not through a custom codec or operation surface. This
7
- * descriptor exists so the postgres runtime's contract-requirements check
8
- * (which verifies every `extensionPacks` entry in the emitted `contract.json`
9
- * has a matching runtime component) passes.
10
- *
11
- * TODO(M2): Replace with the real SupabaseRuntime that adds
12
- * `asUser()`/`asAnon()` role-binding and the Supabase auth surface.
13
- */
14
- import type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';
15
-
16
- const supabaseRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {
17
- kind: 'extension' as const,
18
- id: 'supabase',
19
- version: '0.12.0',
20
- familyId: 'sql' as const,
21
- targetId: 'postgres' as const,
22
- codecs: () => [],
23
- create() {
24
- return {
25
- familyId: 'sql' as const,
26
- targetId: 'postgres' as const,
27
- };
28
- },
29
- };
1
+ import { supabaseRuntimeDescriptor } from '../runtime/descriptor';
30
2
 
31
3
  export default supabaseRuntimeDescriptor;
4
+
5
+ export type {
6
+ RoleBoundDb,
7
+ SupabaseDb,
8
+ SupabaseOptions,
9
+ SupabaseOptionsWithContract,
10
+ SupabaseOptionsWithContractJson,
11
+ SupabaseTargetId,
12
+ } from '../runtime/supabase';
13
+ export { default as supabase, InvalidJwtError, SupabaseConfigError } from '../runtime/supabase';
14
+ export type {
15
+ RoleSession,
16
+ SupabaseRoleBinding,
17
+ SupabaseRuntime,
18
+ } from '../runtime/supabase-runtime';
19
+ export { SupabaseRuntimeImpl } from '../runtime/supabase-runtime';
@@ -0,0 +1,26 @@
1
+ import type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';
2
+ import packageJson from '../../package.json' with { type: 'json' };
3
+
4
+ /**
5
+ * Tells the runtime that the Supabase pack's runtime component is available.
6
+ *
7
+ * When a contract declares the Supabase pack, the runtime checks that a
8
+ * matching descriptor has been registered. Without this, loading a Supabase
9
+ * contract errors with "pack runtime component missing". The `supabase()`
10
+ * factory registers this descriptor automatically — app code never needs to
11
+ * reference it directly.
12
+ */
13
+ export const supabaseRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {
14
+ kind: 'extension' as const,
15
+ id: 'supabase',
16
+ version: packageJson.version,
17
+ familyId: 'sql' as const,
18
+ targetId: 'postgres' as const,
19
+ codecs: () => [],
20
+ create() {
21
+ return {
22
+ familyId: 'sql' as const,
23
+ targetId: 'postgres' as const,
24
+ };
25
+ },
26
+ };
@@ -0,0 +1,171 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type { RuntimeExecuteOptions } from '@prisma-next/framework-components/runtime';
3
+ import { AsyncIterableResult } from '@prisma-next/framework-components/runtime';
4
+ import { type PostgresRuntime, PostgresRuntimeImpl } from '@prisma-next/postgres/runtime';
5
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
6
+ import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
7
+ import type {
8
+ PreparedStatement,
9
+ PreparedStatementImpl,
10
+ RuntimeConnection,
11
+ RuntimeTransaction,
12
+ } from '@prisma-next/sql-runtime';
13
+ import { blindCast } from '@prisma-next/utils/casts';
14
+
15
+ export interface SupabaseRuntime extends PostgresRuntime {}
16
+
17
+ export interface SupabaseRoleBinding {
18
+ // TODO(TML-2501): role names move to the Supabase extension contract (roles as first-class IR) when postgres-rls lands.
19
+ readonly role: 'anon' | 'authenticated' | 'service_role';
20
+ readonly claims?: Record<string, unknown>;
21
+ }
22
+
23
+ /**
24
+ * A connection with a Supabase role already bound via session-scoped set_config.
25
+ * Implements `RuntimeConnection` so it plugs into ORM scope machinery and `withTransaction`.
26
+ */
27
+ export interface RoleSession extends RuntimeConnection {}
28
+
29
+ export class SupabaseRuntimeImpl<
30
+ TContract extends Contract<SqlStorage> = Contract<SqlStorage>,
31
+ > extends PostgresRuntimeImpl<TContract> {
32
+ /**
33
+ * Opens a raw connection and applies role + JWT claims via session-scoped set_config.
34
+ * On bind failure, destroys the connection before rethrowing — no leaked connections.
35
+ * Not on the `SupabaseRuntime` interface; consumed by the facade, not by app code.
36
+ */
37
+ async openRoleSession(binding: SupabaseRoleBinding): Promise<RoleSession> {
38
+ const conn = await this.acquireRawConnection();
39
+
40
+ try {
41
+ await conn.query('SELECT set_config($1, $2, false)', ['role', binding.role]);
42
+ await conn.query('SELECT set_config($1, $2, false)', [
43
+ 'request.jwt.claims',
44
+ JSON.stringify(binding.claims ?? {}),
45
+ ]);
46
+ } catch (err) {
47
+ await conn.destroy(err).catch(() => undefined);
48
+ throw err;
49
+ }
50
+
51
+ const self = this;
52
+
53
+ const session: RoleSession = {
54
+ execute<Row>(
55
+ plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
56
+ options?: RuntimeExecuteOptions,
57
+ ): AsyncIterableResult<Row> {
58
+ return self.executeAgainstQueryable<Row>(plan, conn, { ...options, scope: 'connection' });
59
+ },
60
+
61
+ executePrepared<Params, Row>(
62
+ ps: PreparedStatement<Params, Row>,
63
+ params: Params,
64
+ options?: RuntimeExecuteOptions,
65
+ ): AsyncIterableResult<Row> {
66
+ return self.executePreparedAgainstQueryable(
67
+ blindCast<
68
+ PreparedStatementImpl<Params, Row>,
69
+ 'PreparedStatement is PreparedStatementImpl; the impl class is the only concrete form'
70
+ >(ps),
71
+ blindCast<
72
+ Record<string, unknown>,
73
+ 'params are structurally Record<string, unknown> at runtime'
74
+ >(params),
75
+ conn,
76
+ { ...options, scope: 'connection' },
77
+ );
78
+ },
79
+
80
+ async transaction(): Promise<RuntimeTransaction> {
81
+ const tx = await conn.beginTransaction();
82
+ return {
83
+ async commit(): Promise<void> {
84
+ await tx.commit();
85
+ },
86
+ async rollback(): Promise<void> {
87
+ await tx.rollback();
88
+ },
89
+ execute<Row>(
90
+ plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
91
+ options?: RuntimeExecuteOptions,
92
+ ): AsyncIterableResult<Row> {
93
+ return self.executeAgainstQueryable<Row>(plan, tx, {
94
+ ...options,
95
+ scope: 'transaction',
96
+ });
97
+ },
98
+ executePrepared<Params, Row>(
99
+ ps: PreparedStatement<Params, Row>,
100
+ params: Params,
101
+ options?: RuntimeExecuteOptions,
102
+ ): AsyncIterableResult<Row> {
103
+ return self.executePreparedAgainstQueryable(
104
+ blindCast<
105
+ PreparedStatementImpl<Params, Row>,
106
+ 'PreparedStatement is PreparedStatementImpl; the impl class is the only concrete form'
107
+ >(ps),
108
+ blindCast<
109
+ Record<string, unknown>,
110
+ 'params are structurally Record<string, unknown> at runtime'
111
+ >(params),
112
+ tx,
113
+ { ...options, scope: 'transaction' },
114
+ );
115
+ },
116
+ };
117
+ },
118
+
119
+ /**
120
+ * Resets all session-local config then releases the connection back to the pool.
121
+ * If RESET ALL fails, destroys the connection instead — pool-poisoning guarantee.
122
+ */
123
+ async release(): Promise<void> {
124
+ try {
125
+ await conn.query('RESET ALL');
126
+ await conn.release();
127
+ } catch (resetError) {
128
+ await conn.destroy(resetError).catch(() => undefined);
129
+ }
130
+ },
131
+
132
+ async destroy(reason?: unknown): Promise<void> {
133
+ await conn.destroy(reason);
134
+ },
135
+ };
136
+
137
+ return session;
138
+ }
139
+
140
+ /**
141
+ * Opens a role session, executes the plan, then releases after the stream drains.
142
+ * On mid-stream error, destroys the session instead of releasing.
143
+ */
144
+ executeWithRole<Row>(
145
+ plan: SqlExecutionPlan<Row> | SqlQueryPlan<Row>,
146
+ binding: SupabaseRoleBinding,
147
+ options?: RuntimeExecuteOptions,
148
+ ): AsyncIterableResult<Row> {
149
+ const self = this;
150
+
151
+ const generator = async function* (): AsyncGenerator<Row, void, unknown> {
152
+ const session = await self.openRoleSession(binding);
153
+ let errored = false;
154
+ try {
155
+ for await (const row of session.execute(plan, options)) {
156
+ yield row;
157
+ }
158
+ } catch (err) {
159
+ errored = true;
160
+ await session.destroy(err).catch(() => undefined);
161
+ throw err;
162
+ } finally {
163
+ if (!errored) {
164
+ await session.release();
165
+ }
166
+ }
167
+ };
168
+
169
+ return new AsyncIterableResult(generator());
170
+ }
171
+ }
@@ -0,0 +1,323 @@
1
+ import postgresAdapter from '@prisma-next/adapter-postgres/runtime';
2
+ import type { Contract } from '@prisma-next/contract/types';
3
+ import postgresDriver from '@prisma-next/driver-postgres/runtime';
4
+ import { instantiateExecutionStack } from '@prisma-next/framework-components/execution';
5
+ import type {
6
+ AsyncIterableResult,
7
+ RuntimeExecuteOptions,
8
+ } from '@prisma-next/framework-components/runtime';
9
+ import { sql } from '@prisma-next/sql-builder/runtime';
10
+ import type { Db } from '@prisma-next/sql-builder/types';
11
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
12
+ import { orm } from '@prisma-next/sql-orm-client';
13
+ import type { RawSqlTag } from '@prisma-next/sql-relational-core/expression';
14
+ import { createRawSql } from '@prisma-next/sql-relational-core/expression';
15
+ import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
16
+ import type {
17
+ ExecutionContext,
18
+ SqlExecutionStackWithDriver,
19
+ SqlMiddleware,
20
+ SqlRuntimeExtensionDescriptor,
21
+ TransactionContext,
22
+ VerifyMarkerOption,
23
+ } from '@prisma-next/sql-runtime';
24
+ import {
25
+ createExecutionContext,
26
+ createSqlExecutionStack,
27
+ withTransaction,
28
+ } from '@prisma-next/sql-runtime';
29
+ import postgresTarget, { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime';
30
+ import { blindCast } from '@prisma-next/utils/casts';
31
+ import { ifDefined } from '@prisma-next/utils/defined';
32
+ import { createRemoteJWKSet, type JWTVerifyResult, jwtVerify } from 'jose';
33
+ import type { Client } from 'pg';
34
+ import { Pool } from 'pg';
35
+ import { supabaseRuntimeDescriptor } from './descriptor';
36
+ import type { SupabaseRoleBinding, SupabaseRuntime } from './supabase-runtime';
37
+ import { SupabaseRuntimeImpl } from './supabase-runtime';
38
+
39
+ export type SupabaseTargetId = 'postgres';
40
+
41
+ type OrmClient<TContract extends Contract<SqlStorage>> = ReturnType<typeof orm<TContract>>;
42
+
43
+ export class SupabaseConfigError extends Error {
44
+ override readonly name = 'SupabaseConfigError';
45
+ constructor(message: string) {
46
+ super(message);
47
+ }
48
+ }
49
+
50
+ export class InvalidJwtError extends Error {
51
+ override readonly name = 'InvalidJwtError';
52
+ readonly reason: string;
53
+ constructor(reason: string) {
54
+ super(`Invalid JWT: ${reason}`);
55
+ this.reason = reason;
56
+ }
57
+ }
58
+
59
+ type KeyMaterial =
60
+ | { readonly kind: 'secret'; readonly key: Uint8Array }
61
+ | { readonly kind: 'jwks'; readonly keyset: ReturnType<typeof createRemoteJWKSet> };
62
+
63
+ export interface RoleBoundDb<TContract extends Contract<SqlStorage>> {
64
+ readonly sql: Db<TContract>;
65
+ readonly orm: OrmClient<TContract>;
66
+ readonly raw: RawSqlTag;
67
+ execute<Row>(
68
+ plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },
69
+ options?: RuntimeExecuteOptions,
70
+ ): AsyncIterableResult<Row>;
71
+ transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R>;
72
+ }
73
+
74
+ export interface SupabaseDb<TContract extends Contract<SqlStorage>> {
75
+ readonly context: ExecutionContext<TContract>;
76
+ readonly stack: SqlExecutionStackWithDriver<SupabaseTargetId>;
77
+ asUser(jwt: string): Promise<RoleBoundDb<TContract>>;
78
+ asAnon(): RoleBoundDb<TContract>;
79
+ asServiceRole(): RoleBoundDb<TContract>;
80
+ close(): Promise<void>;
81
+ [Symbol.asyncDispose](): Promise<void>;
82
+ }
83
+
84
+ export interface SupabaseOptionsBase {
85
+ readonly extensions?: readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[];
86
+ readonly middleware?: readonly SqlMiddleware[];
87
+ readonly verifyMarker?: VerifyMarkerOption;
88
+ readonly poolOptions?: {
89
+ readonly connectionTimeoutMillis?: number;
90
+ readonly idleTimeoutMillis?: number;
91
+ };
92
+ }
93
+
94
+ export interface SupabaseBindingOptions {
95
+ readonly url?: string;
96
+ readonly pg?: Pool | Client;
97
+ }
98
+
99
+ type JwtSecretOption = {
100
+ readonly jwtSecret: string;
101
+ readonly jwksUrl?: never;
102
+ };
103
+
104
+ type JwksUrlOption = {
105
+ readonly jwksUrl: string;
106
+ readonly jwtSecret?: never;
107
+ };
108
+
109
+ export type SupabaseOptionsWithContract<TContract extends Contract<SqlStorage>> =
110
+ SupabaseBindingOptions &
111
+ SupabaseOptionsBase &
112
+ (JwtSecretOption | JwksUrlOption) & {
113
+ readonly contract: TContract;
114
+ readonly contractJson?: never;
115
+ };
116
+
117
+ export type SupabaseOptionsWithContractJson<TContract extends Contract<SqlStorage>> =
118
+ SupabaseBindingOptions &
119
+ SupabaseOptionsBase &
120
+ (JwtSecretOption | JwksUrlOption) & {
121
+ readonly contractJson: unknown;
122
+ readonly contract?: never;
123
+ readonly _contract?: TContract;
124
+ };
125
+
126
+ export type SupabaseOptions<TContract extends Contract<SqlStorage>> =
127
+ | SupabaseOptionsWithContract<TContract>
128
+ | SupabaseOptionsWithContractJson<TContract>;
129
+
130
+ function hasContractJson<TContract extends Contract<SqlStorage>>(
131
+ options: SupabaseOptions<TContract>,
132
+ ): options is SupabaseOptionsWithContractJson<TContract> {
133
+ return 'contractJson' in options;
134
+ }
135
+
136
+ const contractSerializer = new PostgresContractSerializer();
137
+
138
+ function resolveContract<TContract extends Contract<SqlStorage>>(
139
+ options: SupabaseOptions<TContract>,
140
+ ): TContract {
141
+ const contractInput = hasContractJson(options) ? options.contractJson : options.contract;
142
+ return blindCast<
143
+ TContract,
144
+ 'contractSerializer.deserializeContract returns a validated TContract'
145
+ >(contractSerializer.deserializeContract(contractInput));
146
+ }
147
+
148
+ function resolveKeyMaterial<TContract extends Contract<SqlStorage>>(
149
+ options: SupabaseOptions<TContract>,
150
+ ): KeyMaterial {
151
+ const jwtSecret = 'jwtSecret' in options ? options.jwtSecret : undefined;
152
+ const jwksUrl = 'jwksUrl' in options ? options.jwksUrl : undefined;
153
+
154
+ if (jwtSecret !== undefined && jwksUrl !== undefined) {
155
+ throw new SupabaseConfigError('Provide either jwtSecret or jwksUrl, not both');
156
+ }
157
+ if (jwtSecret === undefined && jwksUrl === undefined) {
158
+ throw new SupabaseConfigError('Either jwtSecret or jwksUrl is required');
159
+ }
160
+
161
+ if (jwtSecret !== undefined) {
162
+ return { kind: 'secret', key: new TextEncoder().encode(jwtSecret) };
163
+ }
164
+
165
+ if (jwksUrl !== undefined) {
166
+ return { kind: 'jwks', keyset: createRemoteJWKSet(new URL(jwksUrl)) };
167
+ }
168
+
169
+ throw new SupabaseConfigError('Either jwtSecret or jwksUrl is required');
170
+ }
171
+
172
+ function toPool<TContract extends Contract<SqlStorage>>(
173
+ options: SupabaseOptions<TContract>,
174
+ ): { pool: Pool; owned: boolean } | undefined {
175
+ if (options.pg instanceof Pool) {
176
+ return { pool: options.pg, owned: false };
177
+ }
178
+ if (typeof options.url === 'string') {
179
+ return {
180
+ pool: new Pool({
181
+ connectionString: options.url,
182
+ connectionTimeoutMillis: options.poolOptions?.connectionTimeoutMillis ?? 20_000,
183
+ idleTimeoutMillis: options.poolOptions?.idleTimeoutMillis ?? 30_000,
184
+ }),
185
+ owned: true,
186
+ };
187
+ }
188
+ return undefined;
189
+ }
190
+
191
+ function withSupabaseDescriptor(
192
+ extensions: readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[] | undefined,
193
+ ): readonly SqlRuntimeExtensionDescriptor<SupabaseTargetId>[] {
194
+ const packs = extensions ?? [];
195
+ return packs.some((pack) => pack.id === supabaseRuntimeDescriptor.id)
196
+ ? packs
197
+ : [...packs, supabaseRuntimeDescriptor];
198
+ }
199
+
200
+ export default async function supabase<TContract extends Contract<SqlStorage>>(
201
+ options: SupabaseOptionsWithContract<TContract>,
202
+ ): Promise<SupabaseDb<TContract>>;
203
+ export default async function supabase<TContract extends Contract<SqlStorage>>(
204
+ options: SupabaseOptionsWithContractJson<TContract>,
205
+ ): Promise<SupabaseDb<TContract>>;
206
+ export default async function supabase<TContract extends Contract<SqlStorage>>(
207
+ options: SupabaseOptions<TContract>,
208
+ ): Promise<SupabaseDb<TContract>> {
209
+ const keyMaterial = resolveKeyMaterial(options);
210
+ const contract = resolveContract(options);
211
+
212
+ const stack = createSqlExecutionStack({
213
+ target: postgresTarget,
214
+ adapter: postgresAdapter,
215
+ driver: postgresDriver,
216
+ extensionPacks: withSupabaseDescriptor(options.extensions),
217
+ });
218
+
219
+ const context = createExecutionContext({ contract, stack });
220
+ const rawCodecInferer = stack.adapter.rawCodecInferer;
221
+ const rawSqlTag: RawSqlTag = createRawSql(rawCodecInferer);
222
+
223
+ const poolEntry = toPool(options);
224
+ let closed = false;
225
+
226
+ const stackInstance = instantiateExecutionStack(stack);
227
+ const driverDescriptor = stack.driver;
228
+ if (!driverDescriptor) {
229
+ throw new Error('Driver descriptor missing from execution stack');
230
+ }
231
+ const driver = driverDescriptor.create({ cursor: { disabled: true } });
232
+
233
+ if (poolEntry) {
234
+ await driver.connect({ kind: 'pgPool', pool: poolEntry.pool });
235
+ }
236
+
237
+ const runtime: SupabaseRuntime & SupabaseRuntimeImpl<TContract> = new SupabaseRuntimeImpl({
238
+ context,
239
+ adapter: stackInstance.adapter,
240
+ driver,
241
+ ...ifDefined('verifyMarker', options.verifyMarker),
242
+ ...ifDefined('middleware', options.middleware),
243
+ });
244
+
245
+ async function verifyJwt(jwt: string): Promise<JWTVerifyResult> {
246
+ try {
247
+ if (keyMaterial.kind === 'secret') {
248
+ return await jwtVerify(jwt, keyMaterial.key);
249
+ }
250
+ return await jwtVerify(jwt, keyMaterial.keyset);
251
+ } catch (err) {
252
+ const reason = err instanceof Error ? err.message : String(err);
253
+ throw new InvalidJwtError(reason);
254
+ }
255
+ }
256
+
257
+ function buildRoleBoundDb(binding: SupabaseRoleBinding): RoleBoundDb<TContract> {
258
+ const roleSql: Db<TContract> = sql<TContract>({ context, rawCodecInferer });
259
+ const roleOrm: OrmClient<TContract> = orm({
260
+ runtime: {
261
+ execute(plan) {
262
+ return runtime.executeWithRole(plan, binding);
263
+ },
264
+ // connection() returns a role session; this is the enforcement path for ORM scope
265
+ // operations (mutations, includes) — every statement runs role-bound.
266
+ connection: () => runtime.openRoleSession(binding),
267
+ },
268
+ context,
269
+ });
270
+
271
+ return {
272
+ sql: roleSql,
273
+ orm: roleOrm,
274
+ raw: rawSqlTag,
275
+ execute<Row>(
276
+ plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },
277
+ execOptions?: RuntimeExecuteOptions,
278
+ ): AsyncIterableResult<Row> {
279
+ return runtime.executeWithRole<Row>(plan, binding, execOptions);
280
+ },
281
+ transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R> {
282
+ return withTransaction({ connection: () => runtime.openRoleSession(binding) }, fn);
283
+ },
284
+ };
285
+ }
286
+
287
+ async function closeDb(): Promise<void> {
288
+ if (closed) return;
289
+ closed = true;
290
+ await runtime.close();
291
+ if (poolEntry?.owned) {
292
+ await poolEntry.pool.end().catch(() => undefined);
293
+ }
294
+ }
295
+
296
+ return {
297
+ context,
298
+ stack,
299
+
300
+ async asUser(jwt: string): Promise<RoleBoundDb<TContract>> {
301
+ const { payload } = await verifyJwt(jwt);
302
+ const rawRole = payload['role'];
303
+ const roleStr = typeof rawRole === 'string' ? rawRole : 'authenticated';
304
+ const role: SupabaseRoleBinding['role'] =
305
+ roleStr === 'anon' || roleStr === 'authenticated' || roleStr === 'service_role'
306
+ ? roleStr
307
+ : 'authenticated';
308
+ const binding: SupabaseRoleBinding = { role, claims: payload };
309
+ return buildRoleBoundDb(binding);
310
+ },
311
+
312
+ asAnon(): RoleBoundDb<TContract> {
313
+ return buildRoleBoundDb({ role: 'anon', claims: {} });
314
+ },
315
+
316
+ asServiceRole(): RoleBoundDb<TContract> {
317
+ return buildRoleBoundDb({ role: 'service_role', claims: {} });
318
+ },
319
+
320
+ close: closeDb,
321
+ [Symbol.asyncDispose]: closeDb,
322
+ };
323
+ }