@prisma-next/extension-supabase 0.13.0-dev.2 → 0.13.0-dev.21

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.
@@ -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.2",
3
+ "version": "0.13.0-dev.21",
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.2",
9
- "@prisma-next/contract-authoring": "0.13.0-dev.2",
10
- "@prisma-next/family-sql": "0.13.0-dev.2",
11
- "@prisma-next/framework-components": "0.13.0-dev.2",
12
- "@prisma-next/migration-tools": "0.13.0-dev.2",
13
- "@prisma-next/sql-contract": "0.13.0-dev.2",
14
- "@prisma-next/sql-contract-ts": "0.13.0-dev.2",
15
- "@prisma-next/sql-operations": "0.13.0-dev.2",
16
- "@prisma-next/sql-relational-core": "0.13.0-dev.2",
17
- "@prisma-next/sql-runtime": "0.13.0-dev.2",
18
- "@prisma-next/sql-schema-ir": "0.13.0-dev.2",
19
- "@prisma-next/utils": "0.13.0-dev.2",
8
+ "@prisma-next/adapter-postgres": "0.13.0-dev.21",
9
+ "@prisma-next/contract": "0.13.0-dev.21",
10
+ "@prisma-next/driver-postgres": "0.13.0-dev.21",
11
+ "@prisma-next/postgres": "0.13.0-dev.21",
12
+ "@prisma-next/sql-builder": "0.13.0-dev.21",
13
+ "@prisma-next/sql-orm-client": "0.13.0-dev.21",
14
+ "@prisma-next/target-postgres": "0.13.0-dev.21",
15
+ "jose": "^5",
16
+ "pg": "8.21.0",
17
+ "@prisma-next/contract-authoring": "0.13.0-dev.21",
18
+ "@prisma-next/family-sql": "0.13.0-dev.21",
19
+ "@prisma-next/framework-components": "0.13.0-dev.21",
20
+ "@prisma-next/migration-tools": "0.13.0-dev.21",
21
+ "@prisma-next/sql-contract": "0.13.0-dev.21",
22
+ "@prisma-next/sql-contract-ts": "0.13.0-dev.21",
23
+ "@prisma-next/sql-operations": "0.13.0-dev.21",
24
+ "@prisma-next/sql-relational-core": "0.13.0-dev.21",
25
+ "@prisma-next/sql-runtime": "0.13.0-dev.21",
26
+ "@prisma-next/sql-schema-ir": "0.13.0-dev.21",
27
+ "@prisma-next/utils": "0.13.0-dev.21",
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.2",
25
- "@prisma-next/cli": "0.13.0-dev.2",
26
- "@prisma-next/driver-postgres": "0.13.0-dev.2",
27
- "@prisma-next/operations": "0.13.0-dev.2",
28
- "@prisma-next/postgres": "0.13.0-dev.2",
29
- "@prisma-next/sql-contract-psl": "0.13.0-dev.2",
30
- "@prisma-next/target-postgres": "0.13.0-dev.2",
31
- "@prisma-next/test-utils": "0.13.0-dev.2",
32
- "@prisma-next/tsconfig": "0.13.0-dev.2",
33
- "@prisma-next/tsdown": "0.13.0-dev.2",
32
+ "@prisma-next/cli": "0.13.0-dev.21",
33
+ "@prisma-next/operations": "0.13.0-dev.21",
34
+ "@prisma-next/sql-contract-psl": "0.13.0-dev.21",
35
+ "@prisma-next/test-utils": "0.13.0-dev.21",
36
+ "@prisma-next/tsconfig": "0.13.0-dev.21",
37
+ "@prisma-next/tsdown": "0.13.0-dev.21",
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.2",
44
+ "@prisma-next/adapter-postgres": "0.13.0-dev.21",
42
45
  "typescript": ">=5.9"
43
46
  },
44
47
  "peerDependenciesMeta": {
@@ -31,7 +31,7 @@ import type {
31
31
  } from '@prisma-next/contract/types';
32
32
 
33
33
  export type StorageHash =
34
- StorageHashBase<'sha256:a92ac18f82eb73f747fa9512351caec3e42df1a1a897bce787966af84371638c'>;
34
+ StorageHashBase<'sha256:27dcfcb121eec1827ff7464f6262582a413af76d4d86627d93db97bb2108410a'>;
35
35
  export type ExecutionHash = ExecutionHashBase<string>;
36
36
  export type ProfileHash =
37
37
  ProfileHashBase<'sha256:9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb'>;
@@ -45,14 +45,14 @@ type DefaultLiteralValue<CodecId extends string, _Encoded> = CodecId extends key
45
45
 
46
46
  export type FieldOutputTypes = {
47
47
  readonly AuthIdentity: {
48
- readonly id: CodecTypes['pg/text@1']['output'];
49
- readonly user_id: CodecTypes['pg/text@1']['output'];
48
+ readonly id: CodecTypes['pg/uuid@1']['output'];
49
+ readonly user_id: CodecTypes['pg/uuid@1']['output'];
50
50
  readonly provider: CodecTypes['pg/text@1']['output'];
51
51
  readonly created_at: CodecTypes['pg/timestamptz@1']['output'];
52
52
  readonly updated_at: CodecTypes['pg/timestamptz@1']['output'];
53
53
  };
54
54
  readonly AuthUser: {
55
- readonly id: CodecTypes['pg/text@1']['output'];
55
+ readonly id: CodecTypes['pg/uuid@1']['output'];
56
56
  readonly email: CodecTypes['pg/text@1']['output'];
57
57
  readonly created_at: CodecTypes['pg/timestamptz@1']['output'];
58
58
  readonly updated_at: CodecTypes['pg/timestamptz@1']['output'];
@@ -64,7 +64,7 @@ export type FieldOutputTypes = {
64
64
  readonly updated_at: CodecTypes['pg/timestamptz@1']['output'];
65
65
  };
66
66
  readonly StorageObject: {
67
- readonly id: CodecTypes['pg/text@1']['output'];
67
+ readonly id: CodecTypes['pg/uuid@1']['output'];
68
68
  readonly bucket_id: CodecTypes['pg/text@1']['output'];
69
69
  readonly name: CodecTypes['pg/text@1']['output'];
70
70
  readonly created_at: CodecTypes['pg/timestamptz@1']['output'];
@@ -73,14 +73,14 @@ export type FieldOutputTypes = {
73
73
  };
74
74
  export type FieldInputTypes = {
75
75
  readonly AuthIdentity: {
76
- readonly id: CodecTypes['pg/text@1']['input'];
77
- readonly user_id: CodecTypes['pg/text@1']['input'];
76
+ readonly id: CodecTypes['pg/uuid@1']['input'];
77
+ readonly user_id: CodecTypes['pg/uuid@1']['input'];
78
78
  readonly provider: CodecTypes['pg/text@1']['input'];
79
79
  readonly created_at: CodecTypes['pg/timestamptz@1']['input'];
80
80
  readonly updated_at: CodecTypes['pg/timestamptz@1']['input'];
81
81
  };
82
82
  readonly AuthUser: {
83
- readonly id: CodecTypes['pg/text@1']['input'];
83
+ readonly id: CodecTypes['pg/uuid@1']['input'];
84
84
  readonly email: CodecTypes['pg/text@1']['input'];
85
85
  readonly created_at: CodecTypes['pg/timestamptz@1']['input'];
86
86
  readonly updated_at: CodecTypes['pg/timestamptz@1']['input'];
@@ -92,7 +92,7 @@ export type FieldInputTypes = {
92
92
  readonly updated_at: CodecTypes['pg/timestamptz@1']['input'];
93
93
  };
94
94
  readonly StorageObject: {
95
- readonly id: CodecTypes['pg/text@1']['input'];
95
+ readonly id: CodecTypes['pg/uuid@1']['input'];
96
96
  readonly bucket_id: CodecTypes['pg/text@1']['input'];
97
97
  readonly name: CodecTypes['pg/text@1']['input'];
98
98
  readonly created_at: CodecTypes['pg/timestamptz@1']['input'];
@@ -124,13 +124,13 @@ type ContractBase = Omit<
124
124
  columns: {
125
125
  readonly id: {
126
126
  readonly nativeType: 'uuid';
127
- readonly codecId: 'pg/text@1';
127
+ readonly codecId: 'pg/uuid@1';
128
128
  readonly nullable: false;
129
129
  readonly typeRef: 'Uuid';
130
130
  };
131
131
  readonly user_id: {
132
132
  readonly nativeType: 'uuid';
133
- readonly codecId: 'pg/text@1';
133
+ readonly codecId: 'pg/uuid@1';
134
134
  readonly nullable: false;
135
135
  readonly typeRef: 'Uuid';
136
136
  };
@@ -161,7 +161,7 @@ type ContractBase = Omit<
161
161
  columns: {
162
162
  readonly id: {
163
163
  readonly nativeType: 'uuid';
164
- readonly codecId: 'pg/text@1';
164
+ readonly codecId: 'pg/uuid@1';
165
165
  readonly nullable: false;
166
166
  readonly typeRef: 'Uuid';
167
167
  };
@@ -235,7 +235,7 @@ type ContractBase = Omit<
235
235
  columns: {
236
236
  readonly id: {
237
237
  readonly nativeType: 'uuid';
238
- readonly codecId: 'pg/text@1';
238
+ readonly codecId: 'pg/uuid@1';
239
239
  readonly nullable: false;
240
240
  readonly typeRef: 'Uuid';
241
241
  };
@@ -274,7 +274,7 @@ type ContractBase = Omit<
274
274
  readonly types: {
275
275
  readonly Uuid: {
276
276
  readonly kind: 'codec-instance';
277
- readonly codecId: 'pg/text@1';
277
+ readonly codecId: 'pg/uuid@1';
278
278
  readonly nativeType: 'uuid';
279
279
  readonly typeParams: Record<string, never>;
280
280
  };
@@ -292,11 +292,11 @@ type ContractBase = Omit<
292
292
  readonly fields: {
293
293
  readonly id: {
294
294
  readonly nullable: false;
295
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
295
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
296
296
  };
297
297
  readonly user_id: {
298
298
  readonly nullable: false;
299
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
299
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
300
300
  };
301
301
  readonly provider: {
302
302
  readonly nullable: false;
@@ -328,7 +328,7 @@ type ContractBase = Omit<
328
328
  readonly fields: {
329
329
  readonly id: {
330
330
  readonly nullable: false;
331
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
331
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
332
332
  };
333
333
  readonly email: {
334
334
  readonly nullable: false;
@@ -390,7 +390,7 @@ type ContractBase = Omit<
390
390
  readonly fields: {
391
391
  readonly id: {
392
392
  readonly nullable: false;
393
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
393
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
394
394
  };
395
395
  readonly bucket_id: {
396
396
  readonly nullable: false;
@@ -451,11 +451,11 @@ type ContractBase = Omit<
451
451
  readonly fields: {
452
452
  readonly id: {
453
453
  readonly nullable: false;
454
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
454
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
455
455
  };
456
456
  readonly user_id: {
457
457
  readonly nullable: false;
458
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
458
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
459
459
  };
460
460
  readonly provider: {
461
461
  readonly nullable: false;
@@ -487,7 +487,7 @@ type ContractBase = Omit<
487
487
  readonly fields: {
488
488
  readonly id: {
489
489
  readonly nullable: false;
490
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
490
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
491
491
  };
492
492
  readonly email: {
493
493
  readonly nullable: false;
@@ -553,7 +553,7 @@ type ContractBase = Omit<
553
553
  readonly fields: {
554
554
  readonly id: {
555
555
  readonly nullable: false;
556
- readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
556
+ readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' };
557
557
  };
558
558
  readonly bucket_id: {
559
559
  readonly nullable: false;
@@ -37,7 +37,7 @@
37
37
  "id": {
38
38
  "nullable": false,
39
39
  "type": {
40
- "codecId": "pg/text@1",
40
+ "codecId": "pg/uuid@1",
41
41
  "kind": "scalar"
42
42
  }
43
43
  },
@@ -58,7 +58,7 @@
58
58
  "user_id": {
59
59
  "nullable": false,
60
60
  "type": {
61
- "codecId": "pg/text@1",
61
+ "codecId": "pg/uuid@1",
62
62
  "kind": "scalar"
63
63
  }
64
64
  }
@@ -105,7 +105,7 @@
105
105
  "id": {
106
106
  "nullable": false,
107
107
  "type": {
108
- "codecId": "pg/text@1",
108
+ "codecId": "pg/uuid@1",
109
109
  "kind": "scalar"
110
110
  }
111
111
  },
@@ -211,7 +211,7 @@
211
211
  "id": {
212
212
  "nullable": false,
213
213
  "type": {
214
- "codecId": "pg/text@1",
214
+ "codecId": "pg/uuid@1",
215
215
  "kind": "scalar"
216
216
  }
217
217
  },
@@ -278,7 +278,7 @@
278
278
  "typeRef": "Timestamptz"
279
279
  },
280
280
  "id": {
281
- "codecId": "pg/text@1",
281
+ "codecId": "pg/uuid@1",
282
282
  "nativeType": "uuid",
283
283
  "nullable": false,
284
284
  "typeRef": "Uuid"
@@ -295,7 +295,7 @@
295
295
  "typeRef": "Timestamptz"
296
296
  },
297
297
  "user_id": {
298
- "codecId": "pg/text@1",
298
+ "codecId": "pg/uuid@1",
299
299
  "nativeType": "uuid",
300
300
  "nullable": false,
301
301
  "typeRef": "Uuid"
@@ -324,7 +324,7 @@
324
324
  "nullable": false
325
325
  },
326
326
  "id": {
327
- "codecId": "pg/text@1",
327
+ "codecId": "pg/uuid@1",
328
328
  "nativeType": "uuid",
329
329
  "nullable": false,
330
330
  "typeRef": "Uuid"
@@ -408,7 +408,7 @@
408
408
  "typeRef": "Timestamptz"
409
409
  },
410
410
  "id": {
411
- "codecId": "pg/text@1",
411
+ "codecId": "pg/uuid@1",
412
412
  "nativeType": "uuid",
413
413
  "nullable": false,
414
414
  "typeRef": "Uuid"
@@ -440,7 +440,7 @@
440
440
  "kind": "postgres-schema"
441
441
  }
442
442
  },
443
- "storageHash": "sha256:a92ac18f82eb73f747fa9512351caec3e42df1a1a897bce787966af84371638c",
443
+ "storageHash": "sha256:27dcfcb121eec1827ff7464f6262582a413af76d4d86627d93db97bb2108410a",
444
444
  "types": {
445
445
  "Timestamptz": {
446
446
  "codecId": "pg/timestamptz@1",
@@ -448,7 +448,7 @@
448
448
  "nativeType": "timestamptz"
449
449
  },
450
450
  "Uuid": {
451
- "codecId": "pg/text@1",
451
+ "codecId": "pg/uuid@1",
452
452
  "kind": "codec-instance",
453
453
  "nativeType": "uuid"
454
454
  }
@@ -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
+ }