@selvajs/supabase-provider 0.14.3 → 0.14.4-beta.0

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.
Files changed (86) hide show
  1. package/dist/auth/SupabaseAuthProvider.d.ts +57 -0
  2. package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
  3. package/dist/auth/SupabaseAuthProvider.js +116 -4
  4. package/dist/auth/SupabaseAuthProvider.js.map +1 -1
  5. package/dist/auth/__tests__/token-verification.test.d.ts +13 -0
  6. package/dist/auth/__tests__/token-verification.test.d.ts.map +1 -0
  7. package/dist/auth/__tests__/token-verification.test.js +135 -0
  8. package/dist/auth/__tests__/token-verification.test.js.map +1 -0
  9. package/dist/data/SupabaseComputeServerStore.d.ts +34 -2
  10. package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
  11. package/dist/data/SupabaseComputeServerStore.js +146 -39
  12. package/dist/data/SupabaseComputeServerStore.js.map +1 -1
  13. package/dist/data/SupabaseDataProvider.d.ts +5 -2
  14. package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
  15. package/dist/data/SupabaseDataProvider.js +24 -18
  16. package/dist/data/SupabaseDataProvider.js.map +1 -1
  17. package/dist/data/SupabaseDefinitionStore.d.ts +2 -1
  18. package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
  19. package/dist/data/SupabaseDefinitionStore.js +68 -67
  20. package/dist/data/SupabaseDefinitionStore.js.map +1 -1
  21. package/dist/data/SupabaseInviteStore.d.ts.map +1 -1
  22. package/dist/data/SupabaseInviteStore.js +11 -22
  23. package/dist/data/SupabaseInviteStore.js.map +1 -1
  24. package/dist/data/SupabaseOrgStore.d.ts.map +1 -1
  25. package/dist/data/SupabaseOrgStore.js +49 -86
  26. package/dist/data/SupabaseOrgStore.js.map +1 -1
  27. package/dist/data/SupabaseProjectStore.d.ts.map +1 -1
  28. package/dist/data/SupabaseProjectStore.js +51 -70
  29. package/dist/data/SupabaseProjectStore.js.map +1 -1
  30. package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -1
  31. package/dist/data/SupabaseShareLinkStore.js +13 -27
  32. package/dist/data/SupabaseShareLinkStore.js.map +1 -1
  33. package/dist/data/__tests__/compute-server-conformance.test.js +6 -1
  34. package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -1
  35. package/dist/data/__tests__/compute-server-encryption.test.d.ts +12 -0
  36. package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +1 -0
  37. package/dist/data/__tests__/compute-server-encryption.test.js +143 -0
  38. package/dist/data/__tests__/compute-server-encryption.test.js.map +1 -0
  39. package/dist/data/__tests__/share-link-concurrency.test.d.ts +22 -0
  40. package/dist/data/__tests__/share-link-concurrency.test.d.ts.map +1 -0
  41. package/dist/data/__tests__/share-link-concurrency.test.js +115 -0
  42. package/dist/data/__tests__/share-link-concurrency.test.js.map +1 -0
  43. package/dist/data/client.d.ts +35 -3
  44. package/dist/data/client.d.ts.map +1 -1
  45. package/dist/data/client.js +67 -16
  46. package/dist/data/client.js.map +1 -1
  47. package/dist/data/errors.d.ts +2 -0
  48. package/dist/data/errors.d.ts.map +1 -0
  49. package/dist/data/errors.js +23 -0
  50. package/dist/data/errors.js.map +1 -0
  51. package/dist/data/index.d.ts +7 -2
  52. package/dist/data/index.d.ts.map +1 -1
  53. package/dist/data/index.js +5 -1
  54. package/dist/data/index.js.map +1 -1
  55. package/dist/data/query.d.ts +20 -0
  56. package/dist/data/query.d.ts.map +1 -0
  57. package/dist/data/query.js +13 -0
  58. package/dist/data/query.js.map +1 -0
  59. package/dist/data/rowStamp.d.ts +38 -0
  60. package/dist/data/rowStamp.d.ts.map +1 -0
  61. package/dist/data/rowStamp.js +43 -0
  62. package/dist/data/rowStamp.js.map +1 -0
  63. package/dist/index.d.ts +2 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/package.json +2 -2
  68. package/src/auth/SupabaseAuthProvider.ts +140 -4
  69. package/src/auth/__tests__/token-verification.test.ts +158 -0
  70. package/src/data/SupabaseComputeServerStore.ts +163 -43
  71. package/src/data/SupabaseDataProvider.ts +35 -15
  72. package/src/data/SupabaseDefinitionStore.ts +73 -66
  73. package/src/data/SupabaseInviteStore.ts +13 -25
  74. package/src/data/SupabaseOrgStore.ts +51 -95
  75. package/src/data/SupabaseProjectStore.ts +55 -78
  76. package/src/data/SupabaseShareLinkStore.ts +15 -30
  77. package/src/data/__tests__/compute-server-conformance.test.ts +7 -1
  78. package/src/data/__tests__/compute-server-encryption.test.ts +173 -0
  79. package/src/data/__tests__/share-link-concurrency.test.ts +124 -0
  80. package/src/data/client.ts +95 -20
  81. package/src/data/errors.ts +45 -0
  82. package/src/data/index.ts +19 -2
  83. package/src/data/query.ts +22 -0
  84. package/src/data/rowStamp.ts +48 -0
  85. package/src/index.ts +20 -2
  86. package/supabase/migrations/20260711120000_selva_definition_schema_version.sql +18 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * CI-runnable (no live stack) unit tests for at-rest encryption of
3
+ * `compute_servers.api_key`. Uses a minimal fake of the Supabase query builder
4
+ * that records `insert` payloads and serves canned `select` rows, so we can
5
+ * assert the exact bytes that would reach the DB without a real Postgres.
6
+ *
7
+ * The security property under test: a plaintext apiKey handed to the store is
8
+ * NEVER written verbatim — the DB only ever sees an `enc:v1:` envelope — and it
9
+ * round-trips back to plaintext on read.
10
+ */
11
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
12
+ import { randomBytes } from 'node:crypto';
13
+ import { isEncryptedSecret, decryptSecret } from '@selvajs/platform/computeServer';
14
+ import { SupabaseComputeServerStore } from '../SupabaseComputeServerStore.js';
15
+ const SYSTEM_CTX = { userId: '', system: true };
16
+ const KEY = randomBytes(32);
17
+ /**
18
+ * Records every `insert` payload and returns canned rows for each table's
19
+ * `select`. Only the fragments the store actually chains are implemented; the
20
+ * builder is thenable so `await client.from(...).select(...)` resolves.
21
+ */
22
+ function fakeBundle(selectRows = {}) {
23
+ const inserted = {};
24
+ function builder(table) {
25
+ const result = { data: selectRows[table] ?? [], error: null };
26
+ const chain = {
27
+ select: () => ({
28
+ ...chain,
29
+ eq: () => ({ ...chain, maybeSingle: async () => ({ data: null, error: null }) }),
30
+ then: (resolve) => resolve(result)
31
+ }),
32
+ insert: (rows) => {
33
+ inserted[table] = (inserted[table] ?? []).concat(rows);
34
+ return { error: null };
35
+ },
36
+ delete: () => ({ eq: () => ({ eq: () => ({ error: null }), error: null }) }),
37
+ update: () => ({ eq: () => ({ error: null }) }),
38
+ upsert: () => ({ error: null })
39
+ };
40
+ return chain;
41
+ }
42
+ const client = { from: (t) => builder(t) };
43
+ const bundle = {
44
+ serviceClient: client,
45
+ forRequest: () => client
46
+ };
47
+ return { bundle, inserted };
48
+ }
49
+ describe('SupabaseComputeServerStore — apiKey encryption at rest', () => {
50
+ beforeEach(() => vi.restoreAllMocks());
51
+ it('encrypts apiKey before insert — DB never sees plaintext', async () => {
52
+ const { bundle, inserted } = fakeBundle();
53
+ const store = new SupabaseComputeServerStore(bundle, KEY);
54
+ await store.savePlatformServers(SYSTEM_CTX, [
55
+ {
56
+ id: 's1',
57
+ label: 'prod',
58
+ serverUrl: 'https://compute.example.com',
59
+ apiKey: 'super-secret-key',
60
+ scope: 'platform',
61
+ sharedWith: 'all'
62
+ }
63
+ ], 's1');
64
+ const rows = inserted['compute_servers'];
65
+ expect(rows).toHaveLength(1);
66
+ expect(rows[0].api_key).not.toBe('super-secret-key');
67
+ expect(isEncryptedSecret(rows[0].api_key)).toBe(true);
68
+ // And it's genuinely our plaintext under the envelope.
69
+ expect(decryptSecret(rows[0].api_key, KEY)).toBe('super-secret-key');
70
+ });
71
+ it('decrypts apiKey on read — callers see plaintext', async () => {
72
+ // Seed the "DB" with an already-encrypted row.
73
+ const { encryptSecret } = await import('@selvajs/platform/computeServer');
74
+ const envelope = encryptSecret('round-trip-key', KEY);
75
+ const { bundle } = fakeBundle({
76
+ compute_servers: [
77
+ {
78
+ id: 's1',
79
+ scope: 'platform',
80
+ owner_org_id: null,
81
+ shared_with_all: true,
82
+ label: 'prod',
83
+ server_url: 'https://compute.example.com',
84
+ api_key: envelope,
85
+ timeout_ms: null,
86
+ retry_count: null
87
+ }
88
+ ]
89
+ });
90
+ const store = new SupabaseComputeServerStore(bundle, KEY);
91
+ const config = await store.getConfig(SYSTEM_CTX);
92
+ expect(config.servers).toHaveLength(1);
93
+ expect(config.servers[0].apiKey).toBe('round-trip-key');
94
+ });
95
+ it('throws rather than persist plaintext when no key is configured', async () => {
96
+ const { bundle } = fakeBundle();
97
+ const store = new SupabaseComputeServerStore(bundle); // no key
98
+ await expect(store.savePlatformServers(SYSTEM_CTX, [
99
+ {
100
+ id: 's1',
101
+ label: 'prod',
102
+ serverUrl: 'https://compute.example.com',
103
+ apiKey: 'secret',
104
+ scope: 'platform',
105
+ sharedWith: 'all'
106
+ }
107
+ ], 's1')).rejects.toThrow(/SELVA_AT_REST_KEY/);
108
+ });
109
+ it('verifySecrets flags a plaintext row', async () => {
110
+ const { bundle } = fakeBundle({
111
+ compute_servers: [{ id: 's1', label: 'legacy', api_key: 'raw-plaintext' }]
112
+ });
113
+ const store = new SupabaseComputeServerStore(bundle, KEY);
114
+ const report = await store.verifySecrets();
115
+ expect(report.ok).toBe(false);
116
+ expect(report.plaintextFound).toBe(true);
117
+ expect(report.failures).toEqual([
118
+ { serverId: 's1', serverLabel: 'legacy', reason: 'plaintext_on_disk' }
119
+ ]);
120
+ });
121
+ it('verifySecrets reports key_mismatch when the at-rest key rotated', async () => {
122
+ const { encryptSecret } = await import('@selvajs/platform/computeServer');
123
+ const envelope = encryptSecret('secret', randomBytes(32)); // different key
124
+ const { bundle } = fakeBundle({
125
+ compute_servers: [{ id: 's1', label: 'prod', api_key: envelope }]
126
+ });
127
+ const store = new SupabaseComputeServerStore(bundle, KEY);
128
+ const report = await store.verifySecrets();
129
+ expect(report.ok).toBe(false);
130
+ expect(report.plaintextFound).toBe(false);
131
+ expect(report.failures[0]).toMatchObject({ serverId: 's1', reason: 'key_mismatch' });
132
+ });
133
+ it('verifySecrets returns ok when every apiKey decrypts', async () => {
134
+ const { encryptSecret } = await import('@selvajs/platform/computeServer');
135
+ const { bundle } = fakeBundle({
136
+ compute_servers: [{ id: 's1', label: 'prod', api_key: encryptSecret('k', KEY) }]
137
+ });
138
+ const store = new SupabaseComputeServerStore(bundle, KEY);
139
+ const report = await store.verifySecrets();
140
+ expect(report).toEqual({ ok: true, failures: [], plaintextFound: false });
141
+ });
142
+ });
143
+ //# sourceMappingURL=compute-server-encryption.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-server-encryption.test.js","sourceRoot":"","sources":["../../../src/data/__tests__/compute-server-encryption.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Audit Q4/Q5 — the concurrent-solve guarantee for share-link caps.
3
+ *
4
+ * `tryIncrementSolveCount` is the load-bearing race-sensitive method: the
5
+ * `/api/compute` endpoint calls it BEFORE solving and turns a `null` return
6
+ * (cap hit) into HTTP 429. Under concurrent solves against one capped link, the
7
+ * cap must hold exactly — no overshoot, no lost update handing two solves the
8
+ * same slot. Supabase provides this via the SECURITY DEFINER
9
+ * `try_increment_share_link_solve_count` RPC, which does the check-and-increment
10
+ * in a single statement.
11
+ *
12
+ * This lives in the Supabase suite (not the shared conformance suite or the app
13
+ * route tests) because it is the ONLY backend that actually provides the
14
+ * guarantee: the local provider's whole-file read-modify-write is a documented
15
+ * dev-scale tradeoff and is not concurrency-safe. Skipped without a live stack,
16
+ * matching the other conformance tests here.
17
+ *
18
+ * Complements the sequential cap/count tests at
19
+ * `packages/selva/src/routes/api/compute/__tests__/solve-cap-and-count.test.ts`.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=share-link-concurrency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-link-concurrency.test.d.ts","sourceRoot":"","sources":["../../../src/data/__tests__/share-link-concurrency.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Audit Q4/Q5 — the concurrent-solve guarantee for share-link caps.
3
+ *
4
+ * `tryIncrementSolveCount` is the load-bearing race-sensitive method: the
5
+ * `/api/compute` endpoint calls it BEFORE solving and turns a `null` return
6
+ * (cap hit) into HTTP 429. Under concurrent solves against one capped link, the
7
+ * cap must hold exactly — no overshoot, no lost update handing two solves the
8
+ * same slot. Supabase provides this via the SECURITY DEFINER
9
+ * `try_increment_share_link_solve_count` RPC, which does the check-and-increment
10
+ * in a single statement.
11
+ *
12
+ * This lives in the Supabase suite (not the shared conformance suite or the app
13
+ * route tests) because it is the ONLY backend that actually provides the
14
+ * guarantee: the local provider's whole-file read-modify-write is a documented
15
+ * dev-scale tradeoff and is not concurrency-safe. Skipped without a live stack,
16
+ * matching the other conformance tests here.
17
+ *
18
+ * Complements the sequential cap/count tests at
19
+ * `packages/selva/src/routes/api/compute/__tests__/solve-cap-and-count.test.ts`.
20
+ */
21
+ import { describe, it, expect, beforeEach } from 'vitest';
22
+ import { SupabaseShareLinkStore } from '../SupabaseShareLinkStore.js';
23
+ import { readEnv, resetAllData, seedUser } from './test-helpers.js';
24
+ const envCtx = readEnv();
25
+ if (!envCtx) {
26
+ describe.skip('SupabaseShareLinkStore concurrency (skipped: no live stack)', () => {
27
+ it('populate packages/providers/supabase/.env.test with Supabase creds to run these tests', () => { });
28
+ });
29
+ }
30
+ else {
31
+ const ctx = { userId: '', system: true };
32
+ /** Seed org → project → definition → share link, all via service-role. */
33
+ async function seedCappedLink(maxSolves) {
34
+ const { userId: ownerId } = await seedUser(envCtx, '');
35
+ const admin = envCtx.adminClient;
36
+ const orgId = crypto.randomUUID();
37
+ const projectId = crypto.randomUUID();
38
+ const definitionId = crypto.randomUUID();
39
+ const linkId = crypto.randomUUID();
40
+ const now = new Date().toISOString();
41
+ const { error: orgErr } = await admin.from('orgs').insert({
42
+ id: orgId,
43
+ name: 'Concurrency Org',
44
+ slug: `conc-${orgId.slice(0, 8)}`,
45
+ owner_id: ownerId,
46
+ created_at: now,
47
+ updated_at: now
48
+ });
49
+ if (orgErr)
50
+ throw orgErr;
51
+ const { error: projErr } = await admin.from('projects').insert({
52
+ id: projectId,
53
+ org_id: orgId,
54
+ name: 'Project',
55
+ slug: `proj-${projectId.slice(0, 8)}`,
56
+ visibility: 'public',
57
+ owner_id: ownerId,
58
+ created_at: now,
59
+ updated_at: now
60
+ });
61
+ if (projErr)
62
+ throw projErr;
63
+ const { error: defErr } = await admin.from('definitions').insert({
64
+ guid: definitionId,
65
+ project_id: projectId,
66
+ owner_id: ownerId,
67
+ display_name: 'Def',
68
+ status: 'published',
69
+ created_at: now,
70
+ updated_at: now
71
+ });
72
+ if (defErr)
73
+ throw defErr;
74
+ const link = {
75
+ id: linkId,
76
+ definitionId,
77
+ channel: 'live',
78
+ tokenHash: crypto.randomUUID().replace(/-/g, ''),
79
+ createdBy: ownerId,
80
+ createdAt: now,
81
+ expiresAt: null,
82
+ revokedAt: null,
83
+ allowSolve: true,
84
+ maxSolves,
85
+ solveCount: 0
86
+ };
87
+ const store = new SupabaseShareLinkStore(envCtx.bundle);
88
+ await store.create(ctx, link);
89
+ return { linkId };
90
+ }
91
+ describe('SupabaseShareLinkStore — concurrent solve-cap enforcement', () => {
92
+ beforeEach(async () => {
93
+ await resetAllData(envCtx);
94
+ });
95
+ it('N concurrent solves against a cap-K link admit exactly K, refuse the rest', async () => {
96
+ const CAP = 5;
97
+ const ATTEMPTS = 25;
98
+ const { linkId } = await seedCappedLink(CAP);
99
+ const store = new SupabaseShareLinkStore(envCtx.bundle);
100
+ const outcomes = await Promise.all(Array.from({ length: ATTEMPTS }, () => store.tryIncrementSolveCount(ctx, linkId)));
101
+ const admitted = outcomes.filter((n) => n !== null);
102
+ const refused = outcomes.filter((n) => n === null);
103
+ // Exactly CAP admitted — the atomic RPC never lets a concurrent burst
104
+ // overshoot the cap.
105
+ expect(admitted).toHaveLength(CAP);
106
+ expect(refused).toHaveLength(ATTEMPTS - CAP);
107
+ // Each admitted solve got a distinct slot 1..CAP — no lost update.
108
+ expect([...admitted].sort((a, b) => a - b)).toEqual([1, 2, 3, 4, 5]);
109
+ // The persisted count settles at exactly the cap.
110
+ const finalLink = await store.getById(ctx, linkId);
111
+ expect(finalLink?.solveCount).toBe(CAP);
112
+ });
113
+ });
114
+ }
115
+ //# sourceMappingURL=share-link-concurrency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-link-concurrency.test.js","sourceRoot":"","sources":["../../../src/data/__tests__/share-link-concurrency.test.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,24 @@ import type { RequestContext } from '@selvajs/platform';
8
8
  * `SupabaseClient` (which is pinned to `'public'`).
9
9
  */
10
10
  export type SelvaSchemaClient = SupabaseClient<any, 'public', 'selva'>;
11
+ /** Default schema every engine store targets. */
12
+ export declare const DEFAULT_SCHEMA: "selva";
13
+ /**
14
+ * A client pinned to a caller-chosen schema. When the schema is `'selva'` this
15
+ * is the narrow `SelvaSchemaClient`; for an app-owned schema (e.g. `'public'`)
16
+ * the schema generic is opaque — the store surface (`.from` / `.rpc`) is the
17
+ * same either way.
18
+ */
19
+ export type SchemaClient = SupabaseClient<any, string, any>;
20
+ export interface ForRequestOptions {
21
+ /**
22
+ * Postgres schema this client targets. Defaults to `'selva'` (the engine's
23
+ * tables). Pass `'public'` (or another app schema) to build stores on the
24
+ * consuming app's own tables while keeping the same fail-closed RLS
25
+ * dispatch as the engine stores.
26
+ */
27
+ schema?: string;
28
+ }
11
29
  /**
12
30
  * Build a Supabase client for a given `RequestContext`.
13
31
  *
@@ -33,15 +51,20 @@ export type SelvaSchemaClient = SupabaseClient<any, 'public', 'selva'>;
33
51
  * fetch connection without leaking auth across requests.
34
52
  */
35
53
  export interface ClientBundle {
36
- /** Long-lived service-role client. Bypasses RLS. */
54
+ /** Long-lived service-role client, pinned to the `'selva'` schema. Bypasses RLS. */
37
55
  serviceClient: SelvaSchemaClient;
38
56
  /**
39
- * Produce (or reuse) a client scoped to this request's identity.
57
+ * Produce (or reuse) a client scoped to this request's identity, targeting
58
+ * `opts.schema` (default `'selva'`).
40
59
  * - `ctx.system === true` → service-role (RLS bypassed).
41
60
  * - `ctx.adapterContext.sessionToken` set → user-scoped (RLS active).
42
61
  * - neither → anon client (RLS active, no `auth.uid()`).
62
+ *
63
+ * The schema pin and the RLS dispatch travel together: an app building
64
+ * stores on its own `'public'` tables gets the same fail-closed behavior as
65
+ * engine stores, just aimed at a different schema.
43
66
  */
44
- forRequest(ctx: RequestContext): SelvaSchemaClient;
67
+ forRequest(ctx: RequestContext, opts?: ForRequestOptions): SchemaClient;
45
68
  }
46
69
  export interface BuildClientOptions {
47
70
  supabaseUrl: string;
@@ -49,4 +72,13 @@ export interface BuildClientOptions {
49
72
  serviceRoleKey: string;
50
73
  }
51
74
  export declare function buildClientBundle(opts: BuildClientOptions): ClientBundle;
75
+ /**
76
+ * Build a `ClientBundle` straight from environment variables — the
77
+ * `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY` triple,
78
+ * nothing else. Deliberately does NOT require `SELVA_AT_REST_KEY`: that key
79
+ * only matters for compute-server secret encryption, which a consuming app
80
+ * building its own `'public'` stores never touches. `SupabaseDataProvider.fromEnv`
81
+ * layers the at-rest-key requirement on top of this.
82
+ */
83
+ export declare function clientBundleFromEnv(env: Record<string, string | undefined>): ClientBundle;
52
84
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/data/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,YAAY;IAC5B,oDAAoD;IACpD,aAAa,EAAE,iBAAiB,CAAC;IACjC;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,iBAAiB,CAAC;CACnD;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,YAAY,CAqCxE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/data/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAEvE,iDAAiD;AACjD,eAAO,MAAM,cAAc,EAAG,OAAgB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,YAAY;IAC5B,oFAAoF;IACpF,aAAa,EAAE,iBAAiB,CAAC;IACjC;;;;;;;;;;OAUG;IACH,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,YAAY,CAoExE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,CAQzF"}
@@ -1,39 +1,90 @@
1
1
  import { createClient } from '@supabase/supabase-js';
2
+ /** Default schema every engine store targets. */
3
+ export const DEFAULT_SCHEMA = 'selva';
2
4
  export function buildClientBundle(opts) {
3
- const serviceClient = createClient(opts.supabaseUrl, opts.serviceRoleKey, {
4
- db: { schema: 'selva' },
5
- auth: { persistSession: false, autoRefreshToken: false }
6
- });
7
- // Long-lived anon client — the fail-closed default for ctxes that aren't
8
- // explicitly system and don't carry a session token.
9
- const anonClient = createClient(opts.supabaseUrl, opts.anonKey, {
10
- db: { schema: 'selva' },
11
- auth: { persistSession: false, autoRefreshToken: false }
12
- });
5
+ // Long-lived service-role + anon clients, one per schema. Both are stateless
6
+ // w.r.t. request identity, so a single instance is safe to share across all
7
+ // requests targeting the same schema.
8
+ const serviceBySchema = new Map();
9
+ const anonBySchema = new Map();
10
+ function serviceFor(schema) {
11
+ let client = serviceBySchema.get(schema);
12
+ if (!client) {
13
+ client = createClient(opts.supabaseUrl, opts.serviceRoleKey, {
14
+ db: { schema },
15
+ auth: { persistSession: false, autoRefreshToken: false }
16
+ });
17
+ serviceBySchema.set(schema, client);
18
+ }
19
+ return client;
20
+ }
21
+ function anonFor(schema) {
22
+ let client = anonBySchema.get(schema);
23
+ if (!client) {
24
+ client = createClient(opts.supabaseUrl, opts.anonKey, {
25
+ db: { schema },
26
+ auth: { persistSession: false, autoRefreshToken: false }
27
+ });
28
+ anonBySchema.set(schema, client);
29
+ }
30
+ return client;
31
+ }
32
+ // The `'selva'`-pinned service client is exposed directly on the bundle for
33
+ // engine code that reaches for RLS-bypassing reads without a request context.
34
+ const serviceClient = serviceFor(DEFAULT_SCHEMA);
35
+ // Per-request user-scoped clients, keyed first by context then by schema so
36
+ // a handler that touches both `'selva'` and an app schema reuses one client
37
+ // per (request, schema) without leaking auth across requests.
13
38
  const perRequestCache = new WeakMap();
14
39
  return {
15
40
  serviceClient,
16
- forRequest(ctx) {
41
+ forRequest(ctx, forOpts) {
42
+ const schema = forOpts?.schema ?? DEFAULT_SCHEMA;
17
43
  if (ctx.system)
18
- return serviceClient;
44
+ return serviceFor(schema);
19
45
  const token = extractSessionToken(ctx.adapterContext);
20
46
  if (!token)
21
- return anonClient;
22
- const cached = perRequestCache.get(ctx);
47
+ return anonFor(schema);
48
+ let bySchema = perRequestCache.get(ctx);
49
+ if (!bySchema) {
50
+ bySchema = new Map();
51
+ perRequestCache.set(ctx, bySchema);
52
+ }
53
+ const cached = bySchema.get(schema);
23
54
  if (cached)
24
55
  return cached;
25
56
  const client = createClient(opts.supabaseUrl, opts.anonKey, {
26
- db: { schema: 'selva' },
57
+ db: { schema },
27
58
  auth: { persistSession: false, autoRefreshToken: false },
28
59
  global: {
29
60
  headers: { Authorization: `Bearer ${token}` }
30
61
  }
31
62
  });
32
- perRequestCache.set(ctx, client);
63
+ bySchema.set(schema, client);
33
64
  return client;
34
65
  }
35
66
  };
36
67
  }
68
+ /**
69
+ * Build a `ClientBundle` straight from environment variables — the
70
+ * `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY` triple,
71
+ * nothing else. Deliberately does NOT require `SELVA_AT_REST_KEY`: that key
72
+ * only matters for compute-server secret encryption, which a consuming app
73
+ * building its own `'public'` stores never touches. `SupabaseDataProvider.fromEnv`
74
+ * layers the at-rest-key requirement on top of this.
75
+ */
76
+ export function clientBundleFromEnv(env) {
77
+ const supabaseUrl = env.SUPABASE_URL;
78
+ const anonKey = env.SUPABASE_ANON_KEY;
79
+ const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
80
+ if (!supabaseUrl)
81
+ throw new Error('Missing required env var: SUPABASE_URL');
82
+ if (!anonKey)
83
+ throw new Error('Missing required env var: SUPABASE_ANON_KEY');
84
+ if (!serviceRoleKey)
85
+ throw new Error('Missing required env var: SUPABASE_SERVICE_ROLE_KEY');
86
+ return buildClientBundle({ supabaseUrl, anonKey, serviceRoleKey });
87
+ }
37
88
  function extractSessionToken(adapterContext) {
38
89
  if (!adapterContext || typeof adapterContext !== 'object')
39
90
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/data/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAsD1E,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACzD,MAAM,aAAa,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE;QACzE,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QACvB,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;KACxD,CAAC,CAAC;IAEH,yEAAyE;IACzE,qDAAqD;IACrD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE;QAC/D,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QACvB,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;KACxD,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,OAAO,EAAqC,CAAC;IAEzE,OAAO;QACN,aAAa;QACb,UAAU,CAAC,GAAmB;YAC7B,IAAI,GAAG,CAAC,MAAM;gBAAE,OAAO,aAAa,CAAC;YAErC,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACtD,IAAI,CAAC,KAAK;gBAAE,OAAO,UAAU,CAAC;YAE9B,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAE1B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE;gBAC3D,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;gBACvB,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;gBACxD,MAAM,EAAE;oBACP,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;iBAC7C;aACD,CAAC,CAAC;YACH,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACjC,OAAO,MAAM,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,cAAuB;IACnD,IAAI,CAAC,cAAc,IAAI,OAAO,cAAc,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC5E,MAAM,KAAK,GAAI,cAA6C,CAAC,YAAY,CAAC;IAC1E,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/data/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function mapPostgrestError(e: unknown): Error;
2
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/data/errors.ts"],"names":[],"mappings":"AAyBA,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,KAAK,CAmBnD"}
@@ -0,0 +1,23 @@
1
+ import { ProviderError } from '@selvajs/platform';
2
+ export function mapPostgrestError(e) {
3
+ const pg = e;
4
+ switch (pg?.code) {
5
+ case 'PGRST116':
6
+ return new ProviderError(pg.message ?? 'Not found', 404);
7
+ case '23505':
8
+ return new ProviderError(pg.message ?? 'Duplicate record', 409);
9
+ case '23503':
10
+ return new ProviderError(pg.message ?? 'Foreign key violation', 409);
11
+ }
12
+ if (e instanceof Error)
13
+ return e;
14
+ if (e && typeof e === 'object') {
15
+ const obj = e;
16
+ const msg = obj.message ?? obj.details ?? obj.hint ?? 'Unknown Postgres error';
17
+ const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
18
+ Object.assign(err, obj);
19
+ return err;
20
+ }
21
+ return new Error(String(e));
22
+ }
23
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/data/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAyBlD,MAAM,UAAU,iBAAiB,CAAC,CAAU;IAC3C,MAAM,EAAE,GAAG,CAAwB,CAAC;IACpC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC;QAClB,KAAK,UAAU;YACd,OAAO,IAAI,aAAa,CAAC,EAAE,CAAC,OAAO,IAAI,WAAW,EAAE,GAAG,CAAC,CAAC;QAC1D,KAAK,OAAO;YACX,OAAO,IAAI,aAAa,CAAC,EAAE,CAAC,OAAO,IAAI,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACjE,KAAK,OAAO;YACX,OAAO,IAAI,aAAa,CAAC,EAAE,CAAC,OAAO,IAAI,uBAAuB,EAAE,GAAG,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,YAAY,KAAK;QAAE,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,CAAwB,CAAC;QACrC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,wBAAwB,CAAC;QAC/E,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC"}
@@ -8,6 +8,11 @@ export { SupabaseDataProvider } from './SupabaseDataProvider.js';
8
8
  export { SupabaseEventSink } from './SupabaseEventSink.js';
9
9
  export { SupabaseSolveMetricSink } from './SupabaseSolveMetricSink.js';
10
10
  export { SupabaseAuditQuery } from './SupabaseAuditQuery.js';
11
- export { buildClientBundle } from './client.js';
12
- export type { ClientBundle, BuildClientOptions } from './client.js';
11
+ export { buildClientBundle, clientBundleFromEnv, DEFAULT_SCHEMA } from './client.js';
12
+ export type { ClientBundle, BuildClientOptions, ForRequestOptions, SchemaClient, SelvaSchemaClient } from './client.js';
13
+ export { mapPostgrestError } from './errors.js';
14
+ export { stampUpdate, stampSoftDelete } from './rowStamp.js';
15
+ export { notDeleted } from './query.js';
16
+ export { toRange, orderColumn, nextCursorFromRange, encodeCursor, decodeCursor } from './pagination.js';
17
+ export type { RangeSpec } from './pagination.js';
13
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrF,YAAY,EACX,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EACN,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
@@ -8,5 +8,9 @@ export { SupabaseDataProvider } from './SupabaseDataProvider.js';
8
8
  export { SupabaseEventSink } from './SupabaseEventSink.js';
9
9
  export { SupabaseSolveMetricSink } from './SupabaseSolveMetricSink.js';
10
10
  export { SupabaseAuditQuery } from './SupabaseAuditQuery.js';
11
- export { buildClientBundle } from './client.js';
11
+ export { buildClientBundle, clientBundleFromEnv, DEFAULT_SCHEMA } from './client.js';
12
+ export { mapPostgrestError } from './errors.js';
13
+ export { stampUpdate, stampSoftDelete } from './rowStamp.js';
14
+ export { notDeleted } from './query.js';
15
+ export { toRange, orderColumn, nextCursorFromRange, encodeCursor, decodeCursor } from './pagination.js';
12
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EACN,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Small query-builder helpers shared across Supabase stores.
3
+ */
4
+ /**
5
+ * The slice of a PostgREST filter builder these helpers touch: an `.is()`
6
+ * method that returns the same builder for chaining. Generic over the builder
7
+ * type so the caller's concrete builder (and its row generic) flows through.
8
+ */
9
+ interface DeletableQuery {
10
+ is(column: string, value: null): this;
11
+ }
12
+ /**
13
+ * Apply the soft-delete filter `deleted_at IS NULL`. The `.is('deleted_at', null)`
14
+ * idiom is repeated across every store that soft-deletes; this names it.
15
+ *
16
+ * let q = notDeleted(client.from('projects').select(COLS));
17
+ */
18
+ export declare function notDeleted<Q extends DeletableQuery>(query: Q): Q;
19
+ export {};
20
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,UAAU,cAAc;IACvB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEhE"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Small query-builder helpers shared across Supabase stores.
3
+ */
4
+ /**
5
+ * Apply the soft-delete filter `deleted_at IS NULL`. The `.is('deleted_at', null)`
6
+ * idiom is repeated across every store that soft-deletes; this names it.
7
+ *
8
+ * let q = notDeleted(client.from('projects').select(COLS));
9
+ */
10
+ export function notDeleted(query) {
11
+ return query.is('deleted_at', null);
12
+ }
13
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAA2B,KAAQ;IAC5D,OAAO,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}