dzql 0.9.0 → 0.10.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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * These are split out of `auth-jwt.ts` so the `delta init` CLI (which only
5
5
  * needs the file path) can run in projects that haven't installed jose.
6
- * Consumers of the `@blueshed/delta/auth-jwt` subpath still get these via
6
+ * Consumers of the `dzql/auth-jwt` subpath still get these via
7
7
  * re-export from `auth-jwt.ts`.
8
8
  */
9
9
  import type { Pool } from "pg";
@@ -2,7 +2,7 @@
2
2
  * Auth-JWT — reference `DeltaAuth` implementation using JWTs (jose) and
3
3
  * Postgres stored functions for credential verification.
4
4
  *
5
- * Ship this as an opt-in subpath: `@blueshed/delta/auth-jwt`. Users who want
5
+ * Ship this as an opt-in subpath: `dzql/auth-jwt`. Users who want
6
6
  * a different auth scheme (sessions, OAuth, magic links, none) implement
7
7
  * their own `DeltaAuth` instead.
8
8
  *
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * Both are overridable via the `loginSql` / `registerSql` options.
15
15
  *
16
- * import { jwtAuth } from "@blueshed/delta/auth-jwt";
16
+ * import { jwtAuth } from "dzql/auth-jwt";
17
17
  *
18
18
  * const auth = jwtAuth({
19
19
  * pool,
@@ -4,7 +4,7 @@
4
4
  * consumer's `init_db/` directory.
5
5
  *
6
6
  * import { Pool } from "pg";
7
- * import { applyFramework } from "@blueshed/delta/postgres";
7
+ * import { applyFramework } from "dzql/postgres";
8
8
  *
9
9
  * const pool = new Pool({ connectionString: process.env.PG_URL });
10
10
  * await applyFramework(pool);
@@ -23,7 +23,7 @@ const FRAMEWORK_SQL_DIR = join(import.meta.dir, "..", "sql");
23
23
 
24
24
  /** Absolute paths to the framework SQL files, in apply-order.
25
25
  * Filters to `001*-*.sql` — siblings like `auth-jwt.sql` are applied
26
- * separately (see `@blueshed/delta/auth-jwt` → `applyAuthJwtSchema`). */
26
+ * separately (see `dzql/auth-jwt` → `applyAuthJwtSchema`). */
27
27
  export function frameworkSqlFiles(): string[] {
28
28
  return readdirSync(FRAMEWORK_SQL_DIR)
29
29
  .filter((f) => /^001[a-z]?-.+\.sql$/.test(f))
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Drive it from your project's `types.ts`:
16
16
  *
17
- * import { generateSql } from "@blueshed/delta/postgres";
17
+ * import { generateSql } from "dzql/postgres";
18
18
  * import { schema, docs } from "./types";
19
19
  * await Bun.write("init_db/003-tables.sql", generateSql(schema, docs));
20
20
  *
@@ -27,6 +27,16 @@ import { lit, columnSqlType, sqlDefault, findCascadeOn } from "./sql";
27
27
  import { defaultForType, q } from "@blueshed/delta/backend";
28
28
  import { assertScopeKeys } from "@blueshed/delta/backend";
29
29
  import { compileOpens } from "../compile/opens";
30
+ import { compileHolds } from "../compile/holds";
31
+ import { compileInScope } from "../compile/scope";
32
+ import { compileHolders } from "../compile/holders";
33
+ import { compileResolve } from "../compile/resolve";
34
+ import { compileRootThere } from "../compile/root";
35
+ import { compileColumns } from "../compile/columns";
36
+ import { compileRows } from "../compile/rows";
37
+ import { compileCascades } from "../compile/cascades";
38
+ import { compileLedger } from "../compile/ledger";
39
+ import { compileTold } from "../compile/told";
30
40
 
31
41
  export interface GenerateSqlOptions {
32
42
  /** Header comment included at the top of the file. */
@@ -170,6 +180,6 @@ export function generateSql(
170
180
  lines.push("");
171
181
 
172
182
  // each document opened by SQL compiled for it (dzql), which docTypeFromDef calls
173
- lines.push("", compileOpens(schema, docs));
183
+ lines.push("", compileOpens(schema, docs), "", compileHolds(schema, docs), "", compileInScope(schema, docs), "", compileTold(schema, docs), "", compileHolders(schema, docs), "", compileResolve(schema, docs), "", compileRootThere(schema, docs), "", compileColumns(schema, docs), "", compileRows(schema), "", compileCascades(schema), "", compileLedger(schema, docs));
174
184
  return lines.join("\n");
175
185
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @blueshed/delta/postgres — Postgres backend for delta-doc.
2
+ * dzql/postgres — delta's Postgres backend, compiled from its definitions.
3
3
  *
4
4
  * Schema + stored functions + LISTEN dispatch. Tables are generated from
5
5
  * a TypeScript schema definition; all mutations go through stored functions
@@ -9,7 +9,7 @@
9
9
  * import {
10
10
  * defineSchema, defineDoc,
11
11
  * createDocListener, docTypeFromDef,
12
- * } from "@blueshed/delta/postgres";
12
+ * } from "dzql/postgres";
13
13
  *
14
14
  * The stored functions are `src/sql/001*.sql`: `applyFramework(pool)` applies
15
15
  * them, or `bunx @blueshed/delta init <dir>` copies them for a migration tool
@@ -4,7 +4,7 @@
4
4
  * The same stored functions, the same listener, the same documents as a
5
5
  * Postgres server, on PGlite (Postgres compiled to WASM): no database to run,
6
6
  * the data in a directory (or in memory). `openPglite()` answers the slice of
7
- * `pg`'s Pool delta uses, so everything in `@blueshed/delta/postgres` runs on
7
+ * `pg`'s Pool delta uses, so everything in `dzql/postgres` runs on
8
8
  * it unchanged:
9
9
  *
10
10
  * const pool = await openPglite("./data"); // or openPglite() in memory
@@ -136,7 +136,7 @@ $$;
136
136
  -- It used to be merged into the broadcast row, acked, and dropped (or cast) by
137
137
  -- jsonb_populate_record: stored otherwise than told. id, the parent key and
138
138
  -- the temporal columns are the row's own.
139
- CREATE OR REPLACE FUNCTION _delta_assert_fields(
139
+ CREATE OR REPLACE FUNCTION _delta_assert_fields_framework(
140
140
  p_collection TEXT, p_columns JSONB, p_parent_fk TEXT, p_value JSONB
141
141
  ) RETURNS void LANGUAGE plpgsql IMMUTABLE AS $$
142
142
  DECLARE
@@ -178,6 +178,13 @@ BEGIN
178
178
  END;
179
179
  $$;
180
180
 
181
+ -- The checks every write asks: the framework's, until dzql's compiler
182
+ -- (`compileColumns`) replaces this with one that checks each column set it
183
+ -- compiled by its own statements, and any other by the framework's.
184
+ CREATE OR REPLACE FUNCTION _delta_assert_fields(
185
+ p_collection TEXT, p_columns JSONB, p_parent_fk TEXT, p_value JSONB
186
+ ) RETURNS void LANGUAGE sql IMMUTABLE AS $$ SELECT _delta_assert_fields_framework(p_collection, p_columns, p_parent_fk, p_value); $$;
187
+
181
188
  -- Pick the source relation for a collection.
182
189
  -- temporal + no timestamp → current_<table> view
183
190
  -- temporal + timestamp → base <table> (caller filters with _delta_temporal_where)
@@ -51,7 +51,7 @@ $$ LANGUAGE sql STABLE;
51
51
  -- how the fan-out reads a name opened before a rule refused it (_delta_doc_holds).
52
52
  -- ---------------------------------------------------------------------------
53
53
 
54
- CREATE OR REPLACE FUNCTION _delta_resolve_scope(p_def _delta_docs, p_doc_name TEXT, p_refuse BOOLEAN)
54
+ CREATE OR REPLACE FUNCTION _delta_resolve_scope_framework(p_def _delta_docs, p_doc_name TEXT, p_refuse BOOLEAN)
55
55
  RETURNS JSONB AS $$
56
56
  DECLARE
57
57
  v_doc_id TEXT;
@@ -285,6 +285,12 @@ BEGIN
285
285
  END;
286
286
  $$ LANGUAGE plpgsql STABLE;
287
287
 
288
+ -- The resolver every caller asks: the framework's, until dzql's compiler
289
+ -- (`compileResolve`) replaces it with one sending each document it compiled
290
+ -- to its own.
291
+ CREATE OR REPLACE FUNCTION _delta_resolve_scope(p_def _delta_docs, p_doc_name TEXT, p_refuse BOOLEAN)
292
+ RETURNS JSONB LANGUAGE sql STABLE AS $$ SELECT _delta_resolve_scope_framework(p_def, p_doc_name, p_refuse); $$;
293
+
288
294
  -- The resolver as every caller but the fan-out asks it: a value refused is raised.
289
295
  CREATE OR REPLACE FUNCTION _delta_resolve_scope(p_def _delta_docs, p_doc_name TEXT)
290
296
  RETURNS JSONB LANGUAGE sql STABLE AS $$
@@ -323,7 +329,7 @@ $$;
323
329
  -- so the gate can't be used to probe for ids.
324
330
  -- ---------------------------------------------------------------------------
325
331
 
326
- CREATE OR REPLACE FUNCTION _delta_row_in_scope(
332
+ CREATE OR REPLACE FUNCTION _delta_row_in_scope_framework(
327
333
  p_def _delta_docs,
328
334
  p_doc_name TEXT,
329
335
  p_collection_key TEXT,
@@ -413,6 +419,12 @@ BEGIN
413
419
  END;
414
420
  $$ LANGUAGE plpgsql STABLE;
415
421
 
422
+ -- _delta_row_in_scope: as the framework reads a definition (above), until dzql's generateSql
423
+ -- compiles the app's documents: it then sends each to its own dzql_in_scope__<prefix>, and any
424
+ -- other (registered by hand, or by another generateSql) here.
425
+ CREATE OR REPLACE FUNCTION _delta_row_in_scope(p_def _delta_docs, p_doc_name TEXT, p_collection_key TEXT, p_id BIGINT)
426
+ RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT _delta_row_in_scope_framework(p_def, p_doc_name, p_collection_key, p_id); $$;
427
+
416
428
  -- ---------------------------------------------------------------------------
417
429
  -- Who holds a row: the fan-out (todo #28)
418
430
  --
@@ -459,7 +471,7 @@ $$ LANGUAGE plpgsql STABLE;
459
471
  -- _delta_doc_holds: does the document `p_doc_name` (of `p_def`) hold `p_row`
460
472
  -- of `p_coll`? Judged on the row's values, so a row as it was before a write
461
473
  -- is judged as well as one as it is after -- the same rule delta_open reads by.
462
- CREATE OR REPLACE FUNCTION _delta_doc_holds(p_def _delta_docs, p_doc_name TEXT, p_coll TEXT, p_row JSONB)
474
+ CREATE OR REPLACE FUNCTION _delta_doc_holds_framework(p_def _delta_docs, p_doc_name TEXT, p_coll TEXT, p_row JSONB)
463
475
  RETURNS BOOLEAN AS $$
464
476
  DECLARE
465
477
  v_scope JSONB;
@@ -503,6 +515,12 @@ BEGIN
503
515
  END;
504
516
  $$ LANGUAGE plpgsql STABLE;
505
517
 
518
+ -- _delta_doc_holds: as the framework reads a definition (above), until dzql's generateSql
519
+ -- compiles the app's documents: it then sends each to its own dzql_holds__<prefix>, and any
520
+ -- other (registered by hand, or by another generateSql) here.
521
+ CREATE OR REPLACE FUNCTION _delta_doc_holds(p_def _delta_docs, p_doc_name TEXT, p_coll TEXT, p_row JSONB)
522
+ RETURNS BOOLEAN LANGUAGE sql STABLE AS $$ SELECT _delta_doc_holds_framework(p_def, p_doc_name, p_coll, p_row); $$;
523
+
506
524
  -- _delta_holders: the documents that hold `p_row` of `p_coll` -- every one
507
525
  -- open now (a name in _delta_held, todo #73), and the writer's own `p_writer`.
508
526
  -- A single-mode document of an unscoped definition is found by its name,
@@ -527,7 +545,7 @@ $$ LANGUAGE plpgsql STABLE;
527
545
  -- that applied the framework, owning the tables without FORCE ROW LEVEL
528
546
  -- SECURITY, or with BYPASSRLS; applied as the app's own role, it sees what
529
547
  -- the writer sees, as before.
530
- CREATE OR REPLACE FUNCTION _delta_holders(p_coll TEXT, p_row JSONB, p_writer TEXT)
548
+ CREATE OR REPLACE FUNCTION _delta_holders_framework(p_coll TEXT, p_row JSONB, p_writer TEXT, p_skip TEXT[])
531
549
  RETURNS TEXT[] AS $$
532
550
  DECLARE
533
551
  v_def _delta_docs;
@@ -547,7 +565,7 @@ BEGIN
547
565
 
548
566
  FOR v_def IN
549
567
  SELECT * FROM _delta_docs
550
- WHERE root_collection = p_coll OR p_coll = ANY(include)
568
+ WHERE (root_collection = p_coll OR p_coll = ANY(include)) AND NOT (prefix = ANY(p_skip))
551
569
  LOOP
552
570
  v_all := v_def.scope <> '{}'::jsonb
553
571
  OR (p_coll IS DISTINCT FROM v_def.root_collection AND v_coll.parent_collection IS NULL);
@@ -572,6 +590,15 @@ END;
572
590
  $$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
573
591
  -- the search path it was applied with, and pg_temp last: a caller's temporary
574
592
  -- tables never stand in for delta's own (pg_temp is searched first unless named)
593
+ DO $$ BEGIN
594
+ EXECUTE format('ALTER FUNCTION _delta_holders_framework(TEXT, JSONB, TEXT, TEXT[]) SET search_path = %s, pg_temp', COALESCE(NULLIF(current_setting('search_path'), ''), 'public'));
595
+ END $$;
596
+
597
+ -- `p_skip`: the prefixes asked already, by the compiled `_delta_holders`
598
+ -- (dzql's `compileHolders`), which replaces this one: each document it
599
+ -- compiled asked by its own lookups, the rest by the framework's.
600
+ CREATE OR REPLACE FUNCTION _delta_holders(p_coll TEXT, p_row JSONB, p_writer TEXT)
601
+ RETURNS TEXT[] LANGUAGE sql VOLATILE SECURITY DEFINER AS $$ SELECT _delta_holders_framework(p_coll, p_row, p_writer, ARRAY[]::text[]); $$;
575
602
  DO $$ BEGIN
576
603
  EXECUTE format('ALTER FUNCTION _delta_holders(TEXT, JSONB, TEXT) SET search_path = %s, pg_temp', COALESCE(NULLIF(current_setting('search_path'), ''), 'public'));
577
604
  END $$;
@@ -212,8 +212,7 @@ BEGIN
212
212
  END IF;
213
213
  SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = p_def.root_collection;
214
214
  IF NOT FOUND THEN RETURN; END IF;
215
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
216
- INTO v_exists USING p_id;
215
+ v_exists := _delta_row_there(p_def.root_collection, p_id);
217
216
  IF v_exists THEN RETURN; END IF;
218
217
  -- Not there, as the writer reads it: made under the add rule (todo #71) -- an
219
218
  -- id the serial has handed out is taken, whether a row RLS hides holds it or
@@ -225,7 +224,7 @@ BEGIN
225
224
  END IF;
226
225
  v_row := _delta_implied_root(p_def, p_id);
227
226
  IF v_coll.temporal THEN v_row := v_row || jsonb_build_object('valid_from', p_ts, 'valid_to', NULL); END IF;
228
- EXECUTE _delta_insert_sql(v_coll.table_name, v_row) USING v_row;
227
+ PERFORM _delta_row_add(p_def.root_collection, v_coll.table_name, v_row);
229
228
  END;
230
229
  $$ LANGUAGE plpgsql;
231
230
 
@@ -235,17 +234,12 @@ DECLARE
235
234
  v_doc JSONB := COALESCE(_delta_open(p_doc_name, TRUE), '{}'::jsonb);
236
235
  v_def _delta_docs := _delta_find_doc(p_doc_name);
237
236
  v_scope JSONB;
238
- v_coll RECORD;
239
- v_exists BOOLEAN;
240
237
  BEGIN
241
238
  IF NOT COALESCE(v_def.implied, FALSE) THEN RETURN v_doc; END IF;
242
239
  v_scope := _delta_resolve_scope(v_def, p_doc_name);
243
240
  IF (v_scope->>'mode') <> 'single' THEN RETURN v_doc; END IF;
244
- SELECT * INTO v_coll FROM _delta_collections WHERE collection_key = v_def.root_collection;
245
- IF NOT FOUND THEN RETURN v_doc; END IF;
246
- EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I WHERE id = $1)', _delta_source_view(v_coll.table_name, v_coll.temporal))
247
- INTO v_exists USING (v_scope->'values'->>'id')::BIGINT;
248
- IF v_exists THEN RETURN v_doc; END IF;
241
+ -- (its root collection is there: _delta_open raised otherwise)
242
+ IF _delta_row_there(v_def.root_collection, (v_scope->'values'->>'id')::BIGINT) THEN RETURN v_doc; END IF;
249
243
  RETURN v_doc || jsonb_build_object(v_def.root_collection, 'null'::jsonb);
250
244
  END;
251
245
  $$ LANGUAGE plpgsql;
@@ -351,7 +345,7 @@ $$ LANGUAGE plpgsql STABLE;
351
345
  -- whole: what the ledger's inverse and a walk's guard read.
352
346
  -- ---------------------------------------------------------------------------
353
347
 
354
- CREATE OR REPLACE FUNCTION _delta_open(p_doc_name TEXT, p_whole BOOLEAN)
348
+ CREATE OR REPLACE FUNCTION _delta_open_framework(p_doc_name TEXT, p_whole BOOLEAN)
355
349
  RETURNS JSONB AS $$
356
350
  DECLARE
357
351
  v_def _delta_docs;
@@ -445,6 +439,11 @@ BEGIN
445
439
  END;
446
440
  $$ LANGUAGE plpgsql;
447
441
 
442
+ -- the framework's, until dzql's compiler (`compileLedger`) sends each document
443
+ -- it compiled to its own open, shown or whole
444
+ CREATE OR REPLACE FUNCTION _delta_open(p_doc_name TEXT, p_whole BOOLEAN)
445
+ RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_open_framework(p_doc_name, p_whole); $$;
446
+
448
447
  CREATE OR REPLACE FUNCTION delta_open(p_doc_name TEXT)
449
448
  RETURNS JSONB LANGUAGE sql AS $$ SELECT _delta_open(p_doc_name, FALSE); $$;
450
449