@prisma-next/extension-supabase 0.14.0-dev.3 → 0.14.0-dev.30
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/contract-DClE1snt.mjs +419 -0
- package/dist/contract-DClE1snt.mjs.map +1 -0
- package/dist/contract.d.mts +18 -0
- package/dist/contract.d.mts.map +1 -1
- package/dist/pack.mjs +1 -413
- package/dist/pack.mjs.map +1 -1
- package/dist/runtime.d.mts +639 -4
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +60 -14
- package/dist/runtime.mjs.map +1 -1
- package/dist/test/utils.d.mts +6 -6
- package/dist/test/utils.mjs +28 -9
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +26 -26
- package/src/contract/contract.d.ts +73 -4
- package/src/contract/contract.json +2 -1
- package/src/exports/runtime.ts +2 -0
- package/src/runtime/ext-contract-type.ts +9 -0
- package/src/runtime/supabase.ts +107 -15
- package/dist/package-DfQpSTWM.mjs +0 -6
- package/dist/package-DfQpSTWM.mjs.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Contract } from '../contract/contract.d';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Supabase extension's own emitted contract type (`auth`, `storage`
|
|
5
|
+
* namespaces), re-exported under a distinct name so the runtime facade can
|
|
6
|
+
* reference it alongside the framework `Contract` (`@prisma-next/contract/types`)
|
|
7
|
+
* without an aliased import. Backs the `service_role` `.supabase` secondary root.
|
|
8
|
+
*/
|
|
9
|
+
export type SupabaseExtensionContract = Contract;
|
package/src/runtime/supabase.ts
CHANGED
|
@@ -32,7 +32,9 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
32
32
|
import { createRemoteJWKSet, type JWTVerifyResult, jwtVerify } from 'jose';
|
|
33
33
|
import type { Client } from 'pg';
|
|
34
34
|
import { Pool } from 'pg';
|
|
35
|
+
import extensionContractJson from '../contract/contract.json' with { type: 'json' };
|
|
35
36
|
import { supabaseRuntimeDescriptor } from './descriptor';
|
|
37
|
+
import type { SupabaseExtensionContract } from './ext-contract-type';
|
|
36
38
|
import type { SupabaseRoleBinding, SupabaseRuntime } from './supabase-runtime';
|
|
37
39
|
import { SupabaseRuntimeImpl } from './supabase-runtime';
|
|
38
40
|
|
|
@@ -71,12 +73,40 @@ export interface RoleBoundDb<TContract extends Contract<SqlStorage>> {
|
|
|
71
73
|
transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R>;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Query surface for the Supabase-internal contract (`auth`, `storage`). Exposed
|
|
78
|
+
* as a separate secondary root — never merged into the app contract — and only
|
|
79
|
+
* reachable through `service_role`, the one role with grants on those schemas
|
|
80
|
+
* over a direct Postgres connection.
|
|
81
|
+
*
|
|
82
|
+
* Deliberately omits `transaction` (which {@link RoleBoundDb} has): the primary
|
|
83
|
+
* app root and this secondary root are served by separate runtimes that do not
|
|
84
|
+
* share one pinned connection, so a transaction spanning both is out of scope for v1.
|
|
85
|
+
*/
|
|
86
|
+
export interface SupabaseInternalDb {
|
|
87
|
+
readonly sql: Db<SupabaseExtensionContract>;
|
|
88
|
+
readonly orm: OrmClient<SupabaseExtensionContract>;
|
|
89
|
+
execute<Row>(
|
|
90
|
+
plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },
|
|
91
|
+
options?: RuntimeExecuteOptions,
|
|
92
|
+
): AsyncIterableResult<Row>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The `service_role` db: the app-contract role-bound surface (its `.sql` / `.orm`
|
|
97
|
+
* are app-only, exactly like `asUser` / `asAnon`), plus a `.supabase` secondary
|
|
98
|
+
* root for the Supabase-internal namespaces.
|
|
99
|
+
*/
|
|
100
|
+
export type ServiceRoleDb<TContract extends Contract<SqlStorage>> = RoleBoundDb<TContract> & {
|
|
101
|
+
readonly supabase: SupabaseInternalDb;
|
|
102
|
+
};
|
|
103
|
+
|
|
74
104
|
export interface SupabaseDb<TContract extends Contract<SqlStorage>> {
|
|
75
105
|
readonly context: ExecutionContext<TContract>;
|
|
76
106
|
readonly stack: SqlExecutionStackWithDriver<SupabaseTargetId>;
|
|
77
107
|
asUser(jwt: string): Promise<RoleBoundDb<TContract>>;
|
|
78
108
|
asAnon(): RoleBoundDb<TContract>;
|
|
79
|
-
asServiceRole():
|
|
109
|
+
asServiceRole(): ServiceRoleDb<TContract>;
|
|
80
110
|
close(): Promise<void>;
|
|
81
111
|
[Symbol.asyncDispose](): Promise<void>;
|
|
82
112
|
}
|
|
@@ -138,11 +168,13 @@ const contractSerializer = new PostgresContractSerializer();
|
|
|
138
168
|
function resolveContract<TContract extends Contract<SqlStorage>>(
|
|
139
169
|
options: SupabaseOptions<TContract>,
|
|
140
170
|
): TContract {
|
|
141
|
-
const
|
|
171
|
+
const contractJson = hasContractJson(options)
|
|
172
|
+
? options.contractJson
|
|
173
|
+
: contractSerializer.serializeContract(options.contract);
|
|
142
174
|
return blindCast<
|
|
143
175
|
TContract,
|
|
144
176
|
'contractSerializer.deserializeContract returns a validated TContract'
|
|
145
|
-
>(contractSerializer.deserializeContract(
|
|
177
|
+
>(contractSerializer.deserializeContract(contractJson));
|
|
146
178
|
}
|
|
147
179
|
|
|
148
180
|
function resolveKeyMaterial<TContract extends Contract<SqlStorage>>(
|
|
@@ -197,6 +229,20 @@ function withSupabaseDescriptor(
|
|
|
197
229
|
: [...packs, supabaseRuntimeDescriptor];
|
|
198
230
|
}
|
|
199
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Deserializes the Supabase extension's own emitted contract into a runtime
|
|
234
|
+
* contract: namespaces hydrate into `PostgresSchema` instances (with
|
|
235
|
+
* `qualifyTable`), and `typeRef` columns (`Timestamptz`, `Uuid`) resolve
|
|
236
|
+
* through the codec registry. Exposed only via `service_role`'s `.supabase`
|
|
237
|
+
* secondary root — never merged into the app contract.
|
|
238
|
+
*/
|
|
239
|
+
function buildExtensionContract(): SupabaseExtensionContract {
|
|
240
|
+
return blindCast<
|
|
241
|
+
SupabaseExtensionContract,
|
|
242
|
+
'deserializeContract hydrates JSON namespaces into PostgresSchema instances with qualifyTable'
|
|
243
|
+
>(contractSerializer.deserializeContract(extensionContractJson));
|
|
244
|
+
}
|
|
245
|
+
|
|
200
246
|
export default async function supabase<TContract extends Contract<SqlStorage>>(
|
|
201
247
|
options: SupabaseOptionsWithContract<TContract>,
|
|
202
248
|
): Promise<SupabaseDb<TContract>>;
|
|
@@ -254,18 +300,20 @@ export default async function supabase<TContract extends Contract<SqlStorage>>(
|
|
|
254
300
|
}
|
|
255
301
|
}
|
|
256
302
|
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
303
|
+
function buildRoleBoundDbWithContext<C extends Contract<SqlStorage>>(
|
|
304
|
+
binding: SupabaseRoleBinding,
|
|
305
|
+
roleContext: ExecutionContext<C>,
|
|
306
|
+
roleRuntime: SupabaseRuntime & SupabaseRuntimeImpl<C>,
|
|
307
|
+
): RoleBoundDb<C> {
|
|
308
|
+
const roleSql: Db<C> = sql<C>({ context: roleContext, rawCodecInferer });
|
|
309
|
+
const roleOrm: OrmClient<C> = orm({
|
|
260
310
|
runtime: {
|
|
261
311
|
execute(plan) {
|
|
262
|
-
return
|
|
312
|
+
return roleRuntime.executeWithRole(plan, binding);
|
|
263
313
|
},
|
|
264
|
-
|
|
265
|
-
// operations (mutations, includes) — every statement runs role-bound.
|
|
266
|
-
connection: () => runtime.openRoleSession(binding),
|
|
314
|
+
connection: () => roleRuntime.openRoleSession(binding),
|
|
267
315
|
},
|
|
268
|
-
context,
|
|
316
|
+
context: roleContext,
|
|
269
317
|
});
|
|
270
318
|
|
|
271
319
|
return {
|
|
@@ -276,14 +324,57 @@ export default async function supabase<TContract extends Contract<SqlStorage>>(
|
|
|
276
324
|
plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },
|
|
277
325
|
execOptions?: RuntimeExecuteOptions,
|
|
278
326
|
): AsyncIterableResult<Row> {
|
|
279
|
-
return
|
|
327
|
+
return roleRuntime.executeWithRole<Row>(plan, binding, execOptions);
|
|
280
328
|
},
|
|
281
329
|
transaction<R>(fn: (tx: TransactionContext) => PromiseLike<R>): Promise<R> {
|
|
282
|
-
return withTransaction({ connection: () =>
|
|
330
|
+
return withTransaction({ connection: () => roleRuntime.openRoleSession(binding) }, fn);
|
|
283
331
|
},
|
|
284
332
|
};
|
|
285
333
|
}
|
|
286
334
|
|
|
335
|
+
function buildRoleBoundDb(binding: SupabaseRoleBinding): RoleBoundDb<TContract> {
|
|
336
|
+
return buildRoleBoundDbWithContext(binding, context, runtime);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const serviceRoleBinding: SupabaseRoleBinding = { role: 'service_role', claims: {} };
|
|
340
|
+
|
|
341
|
+
// The Supabase-internal contract (auth/storage) as a separate secondary root.
|
|
342
|
+
// It is contract-bound: a plan built against it carries the extension's
|
|
343
|
+
// storageHash, so it must run on a runtime bound to the extension contract —
|
|
344
|
+
// the app runtime would reject it (PLAN.HASH_MISMATCH). This runtime shares
|
|
345
|
+
// the same driver (one pool, no second connection) and disables marker
|
|
346
|
+
// verification: the extension contract is external and owns no app-space
|
|
347
|
+
// marker, so its hashes must not be checked against the DB marker.
|
|
348
|
+
const extContract = buildExtensionContract();
|
|
349
|
+
const extContext = createExecutionContext({ contract: extContract, stack });
|
|
350
|
+
const extRuntime: SupabaseRuntime & SupabaseRuntimeImpl<SupabaseExtensionContract> =
|
|
351
|
+
new SupabaseRuntimeImpl({
|
|
352
|
+
context: extContext,
|
|
353
|
+
adapter: stackInstance.adapter,
|
|
354
|
+
driver,
|
|
355
|
+
verifyMarker: false,
|
|
356
|
+
...ifDefined('middleware', options.middleware),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const supabaseInternal: SupabaseInternalDb = {
|
|
360
|
+
sql: sql<SupabaseExtensionContract>({ context: extContext, rawCodecInferer }),
|
|
361
|
+
orm: orm({
|
|
362
|
+
runtime: {
|
|
363
|
+
execute(plan) {
|
|
364
|
+
return extRuntime.executeWithRole(plan, serviceRoleBinding);
|
|
365
|
+
},
|
|
366
|
+
connection: () => extRuntime.openRoleSession(serviceRoleBinding),
|
|
367
|
+
},
|
|
368
|
+
context: extContext,
|
|
369
|
+
}),
|
|
370
|
+
execute<Row>(
|
|
371
|
+
plan: (SqlExecutionPlan<Row> | SqlQueryPlan<Row>) & { readonly _row?: Row },
|
|
372
|
+
execOptions?: RuntimeExecuteOptions,
|
|
373
|
+
): AsyncIterableResult<Row> {
|
|
374
|
+
return extRuntime.executeWithRole<Row>(plan, serviceRoleBinding, execOptions);
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
287
378
|
async function closeDb(): Promise<void> {
|
|
288
379
|
if (closed) return;
|
|
289
380
|
closed = true;
|
|
@@ -313,8 +404,9 @@ export default async function supabase<TContract extends Contract<SqlStorage>>(
|
|
|
313
404
|
return buildRoleBoundDb({ role: 'anon', claims: {} });
|
|
314
405
|
},
|
|
315
406
|
|
|
316
|
-
asServiceRole():
|
|
317
|
-
|
|
407
|
+
asServiceRole(): ServiceRoleDb<TContract> {
|
|
408
|
+
const roleBound = buildRoleBoundDbWithContext(serviceRoleBinding, context, runtime);
|
|
409
|
+
return { ...roleBound, supabase: supabaseInternal };
|
|
318
410
|
},
|
|
319
411
|
|
|
320
412
|
close: closeDb,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package-DfQpSTWM.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|