litedbmodel 1.2.9 → 2.0.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 (88) hide show
  1. package/README.md +1 -1
  2. package/dist/DBModel.d.ts.map +1 -1
  3. package/dist/DBModel.js +6 -0
  4. package/dist/DBModel.js.map +1 -1
  5. package/dist/scp/authoring-sql.d.ts +56 -0
  6. package/dist/scp/authoring-sql.d.ts.map +1 -0
  7. package/dist/scp/authoring-sql.js +83 -0
  8. package/dist/scp/authoring-sql.js.map +1 -0
  9. package/dist/scp/authoring.d.ts +155 -0
  10. package/dist/scp/authoring.d.ts.map +1 -0
  11. package/dist/scp/authoring.js +162 -0
  12. package/dist/scp/authoring.js.map +1 -0
  13. package/dist/scp/bridge.d.ts +64 -0
  14. package/dist/scp/bridge.d.ts.map +1 -0
  15. package/dist/scp/bridge.js +329 -0
  16. package/dist/scp/bridge.js.map +1 -0
  17. package/dist/scp/catalog.d.ts +91 -0
  18. package/dist/scp/catalog.d.ts.map +1 -0
  19. package/dist/scp/catalog.js +203 -0
  20. package/dist/scp/catalog.js.map +1 -0
  21. package/dist/scp/codegen.d.ts +131 -0
  22. package/dist/scp/codegen.d.ts.map +1 -0
  23. package/dist/scp/codegen.js +137 -0
  24. package/dist/scp/codegen.js.map +1 -0
  25. package/dist/scp/compile-dialect.d.ts +39 -0
  26. package/dist/scp/compile-dialect.d.ts.map +1 -0
  27. package/dist/scp/compile-dialect.js +62 -0
  28. package/dist/scp/compile-dialect.js.map +1 -0
  29. package/dist/scp/compile-sqlite.d.ts +114 -0
  30. package/dist/scp/compile-sqlite.d.ts.map +1 -0
  31. package/dist/scp/compile-sqlite.js +149 -0
  32. package/dist/scp/compile-sqlite.js.map +1 -0
  33. package/dist/scp/dialect.d.ts +141 -0
  34. package/dist/scp/dialect.d.ts.map +1 -0
  35. package/dist/scp/dialect.js +194 -0
  36. package/dist/scp/dialect.js.map +1 -0
  37. package/dist/scp/errors.d.ts +36 -0
  38. package/dist/scp/errors.d.ts.map +1 -0
  39. package/dist/scp/errors.js +72 -0
  40. package/dist/scp/errors.js.map +1 -0
  41. package/dist/scp/guard.d.ts +47 -0
  42. package/dist/scp/guard.d.ts.map +1 -0
  43. package/dist/scp/guard.js +137 -0
  44. package/dist/scp/guard.js.map +1 -0
  45. package/dist/scp/index.cjs +4744 -0
  46. package/dist/scp/index.d.ts +49 -0
  47. package/dist/scp/index.d.ts.map +1 -0
  48. package/dist/scp/index.js +164 -0
  49. package/dist/scp/index.js.map +1 -0
  50. package/dist/scp/index.mjs +2000 -0
  51. package/dist/scp/ir.d.ts +92 -0
  52. package/dist/scp/ir.d.ts.map +1 -0
  53. package/dist/scp/ir.js +28 -0
  54. package/dist/scp/ir.js.map +1 -0
  55. package/dist/scp/relation.d.ts +136 -0
  56. package/dist/scp/relation.d.ts.map +1 -0
  57. package/dist/scp/relation.js +191 -0
  58. package/dist/scp/relation.js.map +1 -0
  59. package/dist/scp/render.d.ts +40 -0
  60. package/dist/scp/render.d.ts.map +1 -0
  61. package/dist/scp/render.js +170 -0
  62. package/dist/scp/render.js.map +1 -0
  63. package/dist/scp/runtime.d.ts +295 -0
  64. package/dist/scp/runtime.d.ts.map +1 -0
  65. package/dist/scp/runtime.js +665 -0
  66. package/dist/scp/runtime.js.map +1 -0
  67. package/dist/scp/typed-object.d.ts +88 -0
  68. package/dist/scp/typed-object.d.ts.map +1 -0
  69. package/dist/scp/typed-object.js +163 -0
  70. package/dist/scp/typed-object.js.map +1 -0
  71. package/dist/scp/write-plan.d.ts +161 -0
  72. package/dist/scp/write-plan.d.ts.map +1 -0
  73. package/dist/scp/write-plan.js +528 -0
  74. package/dist/scp/write-plan.js.map +1 -0
  75. package/dist/scp/write-runtime.d.ts +80 -0
  76. package/dist/scp/write-runtime.d.ts.map +1 -0
  77. package/dist/scp/write-runtime.js +167 -0
  78. package/dist/scp/write-runtime.js.map +1 -0
  79. package/dist/scp/writes.d.ts +251 -0
  80. package/dist/scp/writes.d.ts.map +1 -0
  81. package/dist/scp/writes.js +176 -0
  82. package/dist/scp/writes.js.map +1 -0
  83. package/docs/proposal/contracts-architecture-feasibility.md +328 -0
  84. package/docs/proposal/contracts-architecture.md +579 -0
  85. package/docs/proposal/litedbmodel-v2-scp-architecture.md +382 -0
  86. package/docs/proposal/sql-dynamic-expansion-spec.md +175 -0
  87. package/package.json +27 -5
  88. package/docs/README_REVIEW.md +0 -23
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — SQLite Backend Compile (WS1, #21).
4
+ *
5
+ * Lowers a structured CRUD operation description (a Component-graph Select/Insert/Update/
6
+ * Delete node with ports) into a {@link CompiledOperation}: SQLite dialect SQL text,
7
+ * a fragment tree (dynamic WHERE/SET with existence rules), and static param slots — the
8
+ * IR shape of spec §8. Placeholders are unified as `?` (the `?`→`$N` pass is PG-only and
9
+ * OUT OF SCOPE for WS1).
10
+ *
11
+ * The generated SQLite SQL is byte-identical to what litedbmodel v1's SqliteSqlBuilder /
12
+ * `DBModel._buildSelectSQL` produce for equivalent queries (the golden bar). In
13
+ * particular:
14
+ * - SELECT: `SELECT <cols> FROM <t>[ WHERE <c>][ GROUP BY <g>][ ORDER BY <o>][ LIMIT ?][ OFFSET ?]`
15
+ * - INSERT: `INSERT INTO <t> (<cols>) VALUES (<vals>)[ RETURNING <r>]`
16
+ * - UPDATE: `UPDATE <t> SET <a = ?, …> WHERE <c>[ RETURNING <r>]`
17
+ * - DELETE: `DELETE FROM <t> WHERE <c>[ RETURNING <r>]`
18
+ * - WHERE parts joined by ` AND ` (or ` OR ` for an OR group), equality `col = ?`,
19
+ * IN-list `col IN (?, …)`, empty array → `1 = 0`, NULL → `col IS NULL`.
20
+ *
21
+ * ## Hard rule: closed Expression IR only
22
+ *
23
+ * Every param slot this compiler emits is an Expression IR node from bc's closed set
24
+ * (`PORTABLE_EXPR_OPERATORS`). NO litedbmodel-local opcode is ever invented. An
25
+ * un-lowerable predicate is the Raw SQL escape hatch's concern (spec §13), not a fake
26
+ * opcode. {@link compileSelect} et al. only construct `ref` / `coalesce` / etc.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.compileSelect = compileSelect;
30
+ exports.compileInsert = compileInsert;
31
+ exports.compileUpdate = compileUpdate;
32
+ exports.compileDelete = compileDelete;
33
+ const ir_1 = require("./ir");
34
+ const dialect_1 = require("./dialect");
35
+ const compile_dialect_1 = require("./compile-dialect");
36
+ // ── WHERE fragment-tree lowering (spec §8) ────────────────────────────────────
37
+ /** Lower one condition to a fragment (leaf) or nested tree, without a leading connector. */
38
+ function lowerCondition(c) {
39
+ if (c.kind === 'group') {
40
+ const tree = {
41
+ connector: c.connector,
42
+ fragments: c.conditions.map(lowerCondition),
43
+ };
44
+ // A group's own SKIP guard is carried by wrapping it — but a FragmentTree has no
45
+ // `when`; represent a conditional group as a single-fragment tree is not expressible,
46
+ // so a guarded group is lowered as a leaf-less tree whose presence is the OR/AND of
47
+ // its members' presence (renderTree already drops it when empty). A group with its
48
+ // own skipWhen is not part of WS1's representative surface; reject loudly if used.
49
+ if (c.skipWhen !== undefined) {
50
+ throw new Error('a conditional (SKIP) group is not supported in WS1; guard the member conditions individually');
51
+ }
52
+ return tree;
53
+ }
54
+ let f;
55
+ switch (c.kind) {
56
+ case 'eq':
57
+ f = { always: true, sql: `${c.column} = ?`, params: [c.value] };
58
+ break;
59
+ case 'cmp':
60
+ f = { always: true, sql: `${c.column} ${c.op} ?`, params: [c.value] };
61
+ break;
62
+ case 'isNull':
63
+ f = { always: true, sql: `${c.column} IS NULL`, params: [] };
64
+ break;
65
+ case 'in':
66
+ // IN-list: single `(?)` slot expands at render time (spec §5). golden text:
67
+ // `col IN (?, ?, …)` with `, ` separators; empty array degenerates to `(NULL)`.
68
+ f = { always: true, sql: `${c.column} IN (?)`, params: [c.value], expand: 0 };
69
+ break;
70
+ }
71
+ if (c.skipWhen !== undefined) {
72
+ delete f.always;
73
+ f.when = c.skipWhen;
74
+ }
75
+ return f;
76
+ }
77
+ /** Build a top-level AND fragment tree from a condition list (null when empty). */
78
+ function lowerWhere(conditions) {
79
+ if (conditions === undefined || conditions.length === 0)
80
+ return null;
81
+ return { connector: 'AND', fragments: conditions.map(lowerCondition) };
82
+ }
83
+ // ── CRUD compilers ────────────────────────────────────────────────────────────
84
+ const assembly = (shape) => ({ shape });
85
+ /**
86
+ * Compile a SELECT. Static params (LIMIT / OFFSET) sit AFTER the `{where}` splice, so
87
+ * they are emitted after the fragment params (dynamic-expansion spec §6).
88
+ */
89
+ function compileSelect(desc) {
90
+ const cols = desc.select.length === 1 && desc.select[0] === '*' ? '*' : desc.select.join(', ');
91
+ let sql = `SELECT ${cols} FROM ${desc.table}`;
92
+ const where = lowerWhere(desc.where);
93
+ // The WHERE splice marker is emitted whenever a fragment tree exists (even if all
94
+ // fragments may skip — degeneration is a render-time decision, spec §3).
95
+ if (where !== null)
96
+ sql += ir_1.WHERE_SLOT;
97
+ if (desc.group !== undefined)
98
+ sql += ` GROUP BY ${desc.group}`;
99
+ if (desc.order !== undefined)
100
+ sql += ` ORDER BY ${desc.order}`;
101
+ const params = [];
102
+ if (desc.limit !== undefined) {
103
+ sql += ` LIMIT ?`;
104
+ params.push(desc.limit);
105
+ }
106
+ if (desc.offset !== undefined) {
107
+ sql += ` OFFSET ?`;
108
+ params.push(desc.offset);
109
+ }
110
+ return { component: 'Select', sql, where, params, assembly: assembly('items') };
111
+ }
112
+ /**
113
+ * Compile an INSERT (SQLite). All params are static (no WHERE); order = column order. The
114
+ * dialect-specific conflict/verb rendering is delegated to the SQLite {@link SQLITE} strategy
115
+ * ({@link compileInsertFor}) — the SAME dialect-parameterized compile the PG/MySQL Backend
116
+ * Compile uses (WS6), so SQLite is byte-for-byte unchanged and the other dialects diverge only
117
+ * where the v1 SqlBuilders diverge.
118
+ */
119
+ function compileInsert(desc) {
120
+ return (0, compile_dialect_1.compileInsertFor)(dialect_1.SQLITE, desc);
121
+ }
122
+ /** Compile an UPDATE. SET params (static) precede the `{where}` splice (spec §6). */
123
+ function compileUpdate(desc) {
124
+ const setCols = Object.keys(desc.set);
125
+ if (setCols.length === 0)
126
+ throw new Error('UPDATE requires at least one SET column');
127
+ const setClauses = setCols.map((c) => `${c} = ?`);
128
+ const params = setCols.map((c) => desc.set[c]);
129
+ const where = lowerWhere(desc.where);
130
+ if (where === null)
131
+ throw new Error('UPDATE requires conditions');
132
+ // The ` WHERE ` keyword is emitted by render() (it prepends ` WHERE ` when the fragment
133
+ // tree renders non-empty). Splice the marker directly after the SET clause.
134
+ let sql = `UPDATE ${desc.table} SET ${setClauses.join(', ')}${ir_1.WHERE_SLOT}`;
135
+ if (desc.returning !== undefined)
136
+ sql += ` RETURNING ${desc.returning.join(', ')}`;
137
+ return { component: 'Update', sql, where, params, assembly: assembly('items') };
138
+ }
139
+ /** Compile a DELETE. No static params before the WHERE (all params are conditions). */
140
+ function compileDelete(desc) {
141
+ const where = lowerWhere(desc.where);
142
+ if (where === null)
143
+ throw new Error('DELETE requires conditions');
144
+ let sql = `DELETE FROM ${desc.table}${ir_1.WHERE_SLOT}`;
145
+ if (desc.returning !== undefined)
146
+ sql += ` RETURNING ${desc.returning.join(', ')}`;
147
+ return { component: 'Delete', sql, where, params: [], assembly: assembly('items') };
148
+ }
149
+ //# sourceMappingURL=compile-sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-sqlite.js","sourceRoot":"","sources":["../../src/scp/compile-sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * litedbmodel v2 SCP — dialect strategy table (WS6, #26; spec §4/§5/§8/§10).
3
+ *
4
+ * The SINGLE SOURCE OF TRUTH for every SQL-dialect difference the Backend Compile + render
5
+ * pipeline needs. The dialect axis is compiled ONCE, TS-side (spec §10: "方言軸はコンパイル時
6
+ * に TS 側で1回検証"). This module encodes the differences as a closed strategy record — NO
7
+ * scattered `?:` in the engine code (the hard rule). A backend compile / renderer asks the
8
+ * {@link Dialect} for the dialect-specific text and never branches on the dialect name inline.
9
+ *
10
+ * ## What actually differs (verified against the v1 SqlBuilders — the golden bar)
11
+ *
12
+ * For the CRUD + write-tx + relation SQL the SCP Backend Compile emits (single-row VALUES,
13
+ * `?` placeholders, `col = ?` WHERE, `col IN (?, …)`), the dialect surface is small and
14
+ * closed:
15
+ *
16
+ * - **INSERT conflict clause**: SQLite `INSERT OR IGNORE … ` / `… ON CONFLICT (cols) DO
17
+ * UPDATE SET c = excluded.c`; Postgres `… ON CONFLICT (cols) DO NOTHING` / `… DO UPDATE
18
+ * SET c = EXCLUDED.c`; MySQL `INSERT IGNORE …` / `… ON DUPLICATE KEY UPDATE c = VALUES(c)`.
19
+ * These are byte-identical to `SqliteSqlBuilder` / `PostgresSqlBuilder` / `MysqlSqlBuilder`
20
+ * `buildInsert` for the single-record case (see the WS6 dialect-golden test).
21
+ * - **Bare `ON CONFLICT DO NOTHING`** (no column list — the gate-first idempotency/unique
22
+ * guard INSERTs of the write-tx plan, spec §6): SQLite/Postgres emit `ON CONFLICT DO
23
+ * NOTHING`; MySQL emits `INSERT IGNORE`.
24
+ * - **Placeholder style**: all dialects author with `?`; Postgres converts `?`→`$1,$2,…` in a
25
+ * final left-to-right one-pass AFTER the SQL text is fully assembled (spec §8 — the
26
+ * number-reassignment problem is designed out because the pass runs once over the final
27
+ * flat text). SQLite/MySQL keep `?`.
28
+ *
29
+ * Everything else (SELECT/UPDATE/DELETE skeletons, `col = ?`, `col IN (?, …)` expansion, the
30
+ * fragment tree, RETURNING for SQLite/Postgres) is identical text across dialects, so it is
31
+ * NOT part of the strategy — only the genuine divergences live here.
32
+ *
33
+ * ## DB behavior differences beyond SQL text (spec §13) — closeable-by-convention line
34
+ *
35
+ * SQL-text identity does not guarantee identical DB behavior (NULL ordering, collation,
36
+ * timezone). Per spec §13, those are closed "by dialect-compile-time convention" only within
37
+ * the closeable line. WS6 encodes the ONE convention that is mechanically closeable in the
38
+ * compiled SQL text: deterministic NULL ordering for an `ORDER BY … NULLS` requirement
39
+ * ({@link Dialect.orderByNulls}). Collation / timezone / float semantics are OUT OF SCOPE
40
+ * (documented, not silently defaulted).
41
+ */
42
+ /** The SQL dialects the Backend Compile targets (spec §4 breadth: PG/MySQL/SQLite). */
43
+ export type DialectName = 'sqlite' | 'postgres' | 'mysql';
44
+ /** An INSERT conflict action (dialect-neutral intent; the dialect renders the clause). */
45
+ export type ConflictAction = 'ignore' | {
46
+ readonly updateColumns: readonly string[];
47
+ };
48
+ /**
49
+ * A dialect strategy: the closed set of dialect-specific renderings the Backend Compile +
50
+ * render pipeline consumes. One frozen record per dialect (the SSoT). Every method is a pure
51
+ * text producer — no side effects, no hidden fallback.
52
+ */
53
+ export interface Dialect {
54
+ readonly name: DialectName;
55
+ /**
56
+ * Render an INSERT's optional conflict clause, appended after `… VALUES (…)`.
57
+ *
58
+ * @param conflictColumns the conflict-target columns (`ON CONFLICT (a, b)`), or an empty
59
+ * array for a bare conflict target (the write-tx guard INSERTs — spec §6).
60
+ * @param action `'ignore'` (do-nothing) or an upsert with the update-column list.
61
+ * @param insertColumns the INSERT's column list (needed for the `VALUES(col)` / `EXCLUDED.col`
62
+ * SET-clause rendering when `updateColumns` is `'all'` at the call site — the caller passes
63
+ * the resolved list, so this only needs the resolved update columns).
64
+ */
65
+ insertConflictClause(conflictColumns: readonly string[], action: ConflictAction): string;
66
+ /**
67
+ * For MySQL, an `ignore` conflict is expressed on the INSERT VERB (`INSERT IGNORE INTO`),
68
+ * not as a trailing clause; SQLite uses `INSERT OR IGNORE INTO`; Postgres uses a plain
69
+ * `INSERT INTO` + a trailing `ON CONFLICT … DO NOTHING`. This returns the INSERT verb+prefix
70
+ * up to and including `INTO` for the `ignore` case, and the plain `INSERT INTO` otherwise.
71
+ */
72
+ insertVerb(action: ConflictAction | undefined): string;
73
+ /**
74
+ * Whether this dialect's conflict `ignore` is a trailing clause (Postgres) rather than an
75
+ * INSERT-verb modifier (SQLite `OR IGNORE`, MySQL `IGNORE`). Drives whether
76
+ * {@link insertConflictClause} emits text for the `ignore` case.
77
+ */
78
+ readonly ignoreIsTrailingClause: boolean;
79
+ /**
80
+ * Render a bare do-nothing GUARD INSERT — the gate-first idempotency/unique guard of the
81
+ * write-tx plan (spec §6): `INSERT INTO <t> (<cols>) VALUES (<ph>)` + a do-nothing conflict
82
+ * on ANY unique constraint (no column target). SQLite/Postgres emit a trailing
83
+ * `… ON CONFLICT DO NOTHING`; MySQL emits `INSERT IGNORE INTO … VALUES (…)`.
84
+ *
85
+ * This is DISTINCT from the CRUD `ignore` path ({@link insertVerb} + {@link insertConflictClause}),
86
+ * which mirrors the v1 SqlBuilders (SQLite uses the `INSERT OR IGNORE` verb there). The guard
87
+ * form is SCP-internal and its SQLite golden is the trailing `ON CONFLICT DO NOTHING` (WS5),
88
+ * so the two contexts render differently ON SQLITE and are kept as separate strategy methods
89
+ * rather than one branch.
90
+ *
91
+ * @param table the guard table.
92
+ * @param columns the guard row's columns (in order).
93
+ * @param placeholders the pre-joined placeholder list (`"?, ?, ?"`).
94
+ */
95
+ guardInsert(table: string, columns: readonly string[], placeholders: string): string;
96
+ /**
97
+ * Convert the fully-assembled, param-flattened SQL text's `?` placeholders to this dialect's
98
+ * final placeholder style. SQLite/MySQL: identity (keep `?`). Postgres: a single left-to-right
99
+ * pass replacing the Nth `?` with `$N` (spec §8 final one-pass). This runs ONCE over the final
100
+ * text, so there is no number-reassignment problem.
101
+ */
102
+ finalizePlaceholders(sql: string): string;
103
+ /**
104
+ * Deterministic NULL ordering for an `ORDER BY <expr> <dir>` term when the model requires it
105
+ * (spec §13 "`ORDER BY … NULLS` 強制"). Postgres supports `NULLS FIRST/LAST` natively; SQLite
106
+ * (3.30+) also supports it; MySQL does NOT, so the convention is emulated with a leading
107
+ * `<expr> IS NULL` sort key. Returns the full ORDER BY term text.
108
+ */
109
+ orderByNulls(expr: string, dir: 'ASC' | 'DESC', nulls: 'FIRST' | 'LAST'): string;
110
+ }
111
+ /**
112
+ * Replace each `?` placeholder in a fully-assembled SQL string with `$1, $2, …` left-to-right
113
+ * (Postgres). This is the spec §8 "最終1パス" conversion: it runs ONCE over the final flat SQL
114
+ * text, so placeholder numbering is a plain running counter — the number-reassignment problem
115
+ * cannot reappear (there is no per-fragment renumbering). Every `?` is a bound param position
116
+ * (the render pipeline never emits a literal `?` inside a string literal — values are always
117
+ * parameterized), so a naive scan is correct for the compiled surface.
118
+ */
119
+ export declare function toDollarPlaceholders(sql: string): string;
120
+ /**
121
+ * SQLite. `ignore` is an INSERT-verb modifier (`INSERT OR IGNORE INTO`); upsert uses
122
+ * `ON CONFLICT (cols) DO UPDATE SET c = excluded.c`. Placeholders stay `?`. NULL ordering uses
123
+ * the native `NULLS FIRST/LAST` (SQLite 3.30+).
124
+ */
125
+ export declare const SQLITE: Dialect;
126
+ /**
127
+ * Postgres. `ignore` is a TRAILING clause (`… ON CONFLICT (cols) DO NOTHING`); upsert uses
128
+ * `ON CONFLICT (cols) DO UPDATE SET c = EXCLUDED.c`. Placeholders are converted `?`→`$N` in the
129
+ * final one-pass. NULL ordering uses native `NULLS FIRST/LAST`.
130
+ */
131
+ export declare const POSTGRES: Dialect;
132
+ /**
133
+ * MySQL. `ignore` is an INSERT-verb modifier (`INSERT IGNORE INTO`); upsert uses
134
+ * `… ON DUPLICATE KEY UPDATE c = VALUES(c)` (no conflict-column target — MySQL keys off any
135
+ * unique index). Placeholders stay `?`. NULL ordering is emulated with a leading
136
+ * `<expr> IS NULL` sort key (MySQL has no `NULLS FIRST/LAST`).
137
+ */
138
+ export declare const MYSQL: Dialect;
139
+ /** Look up a dialect strategy by name (throws on an unknown dialect — no silent default). */
140
+ export declare function dialectFor(name: DialectName): Dialect;
141
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../../src/scp/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,uFAAuF;AACvF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE1D,0FAA0F;AAC1F,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG;IAAE,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAEtF;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAE3B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC;IAEzF;;;;;OAKG;IACH,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAAC;IAEvD;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;IAEzC;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IAErF;;;;;OAKG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1C;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CAClF;AAID;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYxD;AAwCD;;;;GAIG;AACH,eAAO,MAAM,MAAM,EAAE,OAoBnB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,OAkBrB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,OA0BlB,CAAC;AASH,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAMrD"}
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — dialect strategy table (WS6, #26; spec §4/§5/§8/§10).
4
+ *
5
+ * The SINGLE SOURCE OF TRUTH for every SQL-dialect difference the Backend Compile + render
6
+ * pipeline needs. The dialect axis is compiled ONCE, TS-side (spec §10: "方言軸はコンパイル時
7
+ * に TS 側で1回検証"). This module encodes the differences as a closed strategy record — NO
8
+ * scattered `?:` in the engine code (the hard rule). A backend compile / renderer asks the
9
+ * {@link Dialect} for the dialect-specific text and never branches on the dialect name inline.
10
+ *
11
+ * ## What actually differs (verified against the v1 SqlBuilders — the golden bar)
12
+ *
13
+ * For the CRUD + write-tx + relation SQL the SCP Backend Compile emits (single-row VALUES,
14
+ * `?` placeholders, `col = ?` WHERE, `col IN (?, …)`), the dialect surface is small and
15
+ * closed:
16
+ *
17
+ * - **INSERT conflict clause**: SQLite `INSERT OR IGNORE … ` / `… ON CONFLICT (cols) DO
18
+ * UPDATE SET c = excluded.c`; Postgres `… ON CONFLICT (cols) DO NOTHING` / `… DO UPDATE
19
+ * SET c = EXCLUDED.c`; MySQL `INSERT IGNORE …` / `… ON DUPLICATE KEY UPDATE c = VALUES(c)`.
20
+ * These are byte-identical to `SqliteSqlBuilder` / `PostgresSqlBuilder` / `MysqlSqlBuilder`
21
+ * `buildInsert` for the single-record case (see the WS6 dialect-golden test).
22
+ * - **Bare `ON CONFLICT DO NOTHING`** (no column list — the gate-first idempotency/unique
23
+ * guard INSERTs of the write-tx plan, spec §6): SQLite/Postgres emit `ON CONFLICT DO
24
+ * NOTHING`; MySQL emits `INSERT IGNORE`.
25
+ * - **Placeholder style**: all dialects author with `?`; Postgres converts `?`→`$1,$2,…` in a
26
+ * final left-to-right one-pass AFTER the SQL text is fully assembled (spec §8 — the
27
+ * number-reassignment problem is designed out because the pass runs once over the final
28
+ * flat text). SQLite/MySQL keep `?`.
29
+ *
30
+ * Everything else (SELECT/UPDATE/DELETE skeletons, `col = ?`, `col IN (?, …)` expansion, the
31
+ * fragment tree, RETURNING for SQLite/Postgres) is identical text across dialects, so it is
32
+ * NOT part of the strategy — only the genuine divergences live here.
33
+ *
34
+ * ## DB behavior differences beyond SQL text (spec §13) — closeable-by-convention line
35
+ *
36
+ * SQL-text identity does not guarantee identical DB behavior (NULL ordering, collation,
37
+ * timezone). Per spec §13, those are closed "by dialect-compile-time convention" only within
38
+ * the closeable line. WS6 encodes the ONE convention that is mechanically closeable in the
39
+ * compiled SQL text: deterministic NULL ordering for an `ORDER BY … NULLS` requirement
40
+ * ({@link Dialect.orderByNulls}). Collation / timezone / float semantics are OUT OF SCOPE
41
+ * (documented, not silently defaulted).
42
+ */
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.MYSQL = exports.POSTGRES = exports.SQLITE = void 0;
45
+ exports.toDollarPlaceholders = toDollarPlaceholders;
46
+ exports.dialectFor = dialectFor;
47
+ // ── `?`→`$N` final one-pass (Postgres only) ───────────────────────────────────
48
+ /**
49
+ * Replace each `?` placeholder in a fully-assembled SQL string with `$1, $2, …` left-to-right
50
+ * (Postgres). This is the spec §8 "最終1パス" conversion: it runs ONCE over the final flat SQL
51
+ * text, so placeholder numbering is a plain running counter — the number-reassignment problem
52
+ * cannot reappear (there is no per-fragment renumbering). Every `?` is a bound param position
53
+ * (the render pipeline never emits a literal `?` inside a string literal — values are always
54
+ * parameterized), so a naive scan is correct for the compiled surface.
55
+ */
56
+ function toDollarPlaceholders(sql) {
57
+ let n = 0;
58
+ let out = '';
59
+ for (const ch of sql) {
60
+ if (ch === '?') {
61
+ n += 1;
62
+ out += `$${n}`;
63
+ }
64
+ else {
65
+ out += ch;
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ // ── Shared conflict-clause rendering (parameterized by the SET-assignment form) ─
71
+ /**
72
+ * Render the trailing conflict clause for the "has a column target" upsert/ignore forms shared
73
+ * by SQLite and Postgres (`ON CONFLICT (cols) DO NOTHING | DO UPDATE SET …`). MySQL overrides
74
+ * with its `ON DUPLICATE KEY UPDATE` form. `excludedRef` produces the per-column right-hand side
75
+ * (`excluded.c` for SQLite, `EXCLUDED.c` for Postgres).
76
+ */
77
+ function onConflictClause(conflictColumns, action, excludedRef) {
78
+ const target = conflictColumns.length > 0 ? ` (${conflictColumns.join(', ')})` : '';
79
+ if (action === 'ignore') {
80
+ return ` ON CONFLICT${target} DO NOTHING`;
81
+ }
82
+ const sets = action.updateColumns.map((c) => `${c} = ${excludedRef(c)}`);
83
+ return ` ON CONFLICT${target} DO UPDATE SET ${sets.join(', ')}`;
84
+ }
85
+ /**
86
+ * Render a bare do-nothing guard INSERT. `trailingDoNothing` dialects (SQLite/Postgres) emit
87
+ * `INSERT INTO … VALUES (…) ON CONFLICT DO NOTHING`; MySQL emits `INSERT IGNORE INTO … VALUES (…)`.
88
+ */
89
+ function guardInsertText(table, columns, placeholders, form) {
90
+ const head = form === 'insertIgnore' ? 'INSERT IGNORE INTO' : 'INSERT INTO';
91
+ const tail = form === 'trailingDoNothing' ? ' ON CONFLICT DO NOTHING' : '';
92
+ return `${head} ${table} (${columns.join(', ')}) VALUES (${placeholders})${tail}`;
93
+ }
94
+ // ── The three frozen dialect strategies (SSoT) ─────────────────────────────────
95
+ /**
96
+ * SQLite. `ignore` is an INSERT-verb modifier (`INSERT OR IGNORE INTO`); upsert uses
97
+ * `ON CONFLICT (cols) DO UPDATE SET c = excluded.c`. Placeholders stay `?`. NULL ordering uses
98
+ * the native `NULLS FIRST/LAST` (SQLite 3.30+).
99
+ */
100
+ exports.SQLITE = Object.freeze({
101
+ name: 'sqlite',
102
+ ignoreIsTrailingClause: false,
103
+ insertVerb(action) {
104
+ return action === 'ignore' ? 'INSERT OR IGNORE INTO' : 'INSERT INTO';
105
+ },
106
+ insertConflictClause(conflictColumns, action) {
107
+ // `ignore` is handled by the verb (`INSERT OR IGNORE`), so no trailing clause for it.
108
+ if (action === 'ignore')
109
+ return '';
110
+ return onConflictClause(conflictColumns, action, (c) => `excluded.${c}`);
111
+ },
112
+ guardInsert(table, columns, placeholders) {
113
+ return guardInsertText(table, columns, placeholders, 'trailingDoNothing');
114
+ },
115
+ finalizePlaceholders(sql) {
116
+ return sql;
117
+ },
118
+ orderByNulls(expr, dir, nulls) {
119
+ return `${expr} ${dir} NULLS ${nulls}`;
120
+ },
121
+ });
122
+ /**
123
+ * Postgres. `ignore` is a TRAILING clause (`… ON CONFLICT (cols) DO NOTHING`); upsert uses
124
+ * `ON CONFLICT (cols) DO UPDATE SET c = EXCLUDED.c`. Placeholders are converted `?`→`$N` in the
125
+ * final one-pass. NULL ordering uses native `NULLS FIRST/LAST`.
126
+ */
127
+ exports.POSTGRES = Object.freeze({
128
+ name: 'postgres',
129
+ ignoreIsTrailingClause: true,
130
+ insertVerb(_action) {
131
+ return 'INSERT INTO';
132
+ },
133
+ insertConflictClause(conflictColumns, action) {
134
+ return onConflictClause(conflictColumns, action, (c) => `EXCLUDED.${c}`);
135
+ },
136
+ guardInsert(table, columns, placeholders) {
137
+ return guardInsertText(table, columns, placeholders, 'trailingDoNothing');
138
+ },
139
+ finalizePlaceholders(sql) {
140
+ return toDollarPlaceholders(sql);
141
+ },
142
+ orderByNulls(expr, dir, nulls) {
143
+ return `${expr} ${dir} NULLS ${nulls}`;
144
+ },
145
+ });
146
+ /**
147
+ * MySQL. `ignore` is an INSERT-verb modifier (`INSERT IGNORE INTO`); upsert uses
148
+ * `… ON DUPLICATE KEY UPDATE c = VALUES(c)` (no conflict-column target — MySQL keys off any
149
+ * unique index). Placeholders stay `?`. NULL ordering is emulated with a leading
150
+ * `<expr> IS NULL` sort key (MySQL has no `NULLS FIRST/LAST`).
151
+ */
152
+ exports.MYSQL = Object.freeze({
153
+ name: 'mysql',
154
+ ignoreIsTrailingClause: false,
155
+ insertVerb(action) {
156
+ return action === 'ignore' ? 'INSERT IGNORE INTO' : 'INSERT INTO';
157
+ },
158
+ insertConflictClause(_conflictColumns, action) {
159
+ if (action === 'ignore')
160
+ return ''; // handled by the `INSERT IGNORE` verb
161
+ const sets = action.updateColumns.map((c) => `${c} = VALUES(${c})`);
162
+ return ` ON DUPLICATE KEY UPDATE ${sets.join(', ')}`;
163
+ },
164
+ guardInsert(table, columns, placeholders) {
165
+ return guardInsertText(table, columns, placeholders, 'insertIgnore');
166
+ },
167
+ finalizePlaceholders(sql) {
168
+ return sql;
169
+ },
170
+ orderByNulls(expr, dir, nulls) {
171
+ // MySQL: emulate NULLS FIRST/LAST with a leading `IS NULL` key.
172
+ // NULLS FIRST → `expr IS NULL DESC, expr <dir>` (nulls sort as 1, before 0... wait)
173
+ // In MySQL, NULL sorts LOWEST by default. `expr IS NULL` is 1 for null, 0 otherwise.
174
+ // NULLS FIRST: nulls must come first → order the IS-NULL flag DESC (1 before 0).
175
+ // NULLS LAST: nulls must come last → order the IS-NULL flag ASC (0 before 1).
176
+ const flagDir = nulls === 'FIRST' ? 'DESC' : 'ASC';
177
+ return `${expr} IS NULL ${flagDir}, ${expr} ${dir}`;
178
+ },
179
+ });
180
+ /** Resolve a {@link DialectName} to its frozen {@link Dialect} strategy (fail-closed). */
181
+ const DIALECTS = Object.freeze({
182
+ sqlite: exports.SQLITE,
183
+ postgres: exports.POSTGRES,
184
+ mysql: exports.MYSQL,
185
+ });
186
+ /** Look up a dialect strategy by name (throws on an unknown dialect — no silent default). */
187
+ function dialectFor(name) {
188
+ const d = DIALECTS[name];
189
+ if (d === undefined) {
190
+ throw new Error(`scp dialect: unknown dialect '${String(name)}' (known: ${Object.keys(DIALECTS).join(', ')})`);
191
+ }
192
+ return d;
193
+ }
194
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.js","sourceRoot":"","sources":["../../src/scp/dialect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * litedbmodel v2 SCP — Error Mapping (WS3, #23; spec §11 item 5).
3
+ *
4
+ * Maps a SQLite driver error (better-sqlite3 `SqliteError`, carrying a `SQLITE_*` `code`)
5
+ * to an SCP {@link SqlFailure}: a structured Failure with a stable `kind` and the bc
6
+ * Execution-Plan {@link PolicyKind} the runtime honors (fail / retry / continue). The
7
+ * handler returns `{ error }` to bc's `runBehavior`; a `fail`-policy node re-throws through
8
+ * `runPlan` (`OP_FAILED`), so the driver surfaces this structured failure at the boundary.
9
+ *
10
+ * The mapping is closed and explicit (no silent catch-all that hides a driver error): an
11
+ * unrecognized SQLite code maps to `kind: 'driver_error'` with `policy: 'fail'` — loud, and
12
+ * carrying the original code + message for diagnosis.
13
+ */
14
+ import type { PolicyKind } from 'behavior-contracts';
15
+ /** The SCP failure kinds litedbmodel maps SQL driver errors to (spec §11 item 5). */
16
+ export type SqlFailureKind = 'constraint_violation' | 'foreign_key_violation' | 'retryable' | 'driver_error';
17
+ /**
18
+ * A mapped SCP failure: the SCP `kind`, the honored bc `PolicyKind`, the original SQLite
19
+ * `code`, and a human message. Thrown at the runtime boundary (or carried as `{error}`
20
+ * into `runBehavior` for Policy-Kind interpretation by `runPlan`).
21
+ */
22
+ export declare class SqlFailure extends Error {
23
+ readonly kind: SqlFailureKind;
24
+ readonly policy: PolicyKind;
25
+ readonly sqliteCode: string | undefined;
26
+ constructor(kind: SqlFailureKind, policy: PolicyKind, sqliteCode: string | undefined, message: string);
27
+ }
28
+ /**
29
+ * Map a caught driver error to an {@link SqlFailure}. The `SQLITE_CONSTRAINT_*` family maps
30
+ * to constraint / FK kinds (Policy `fail` — a data conflict is not retryable); the
31
+ * `SQLITE_BUSY` / `SQLITE_LOCKED` family maps to `retryable` (Policy `retry`). Anything else
32
+ * (including a non-SQLite error) maps to `driver_error` (Policy `fail`), preserving the
33
+ * original message.
34
+ */
35
+ export declare function mapSqliteError(e: unknown): SqlFailure;
36
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/scp/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,qFAAqF;AACrF,MAAM,MAAM,cAAc,GACtB,sBAAsB,GACtB,uBAAuB,GACvB,WAAW,GACX,cAAc,CAAC;AAEnB;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAEjC,QAAQ,CAAC,IAAI,EAAE,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;gBAF9B,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,GAAG,SAAS,EACvC,OAAO,EAAE,MAAM;CAKlB;AAYD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,CAqBrD"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — Error Mapping (WS3, #23; spec §11 item 5).
4
+ *
5
+ * Maps a SQLite driver error (better-sqlite3 `SqliteError`, carrying a `SQLITE_*` `code`)
6
+ * to an SCP {@link SqlFailure}: a structured Failure with a stable `kind` and the bc
7
+ * Execution-Plan {@link PolicyKind} the runtime honors (fail / retry / continue). The
8
+ * handler returns `{ error }` to bc's `runBehavior`; a `fail`-policy node re-throws through
9
+ * `runPlan` (`OP_FAILED`), so the driver surfaces this structured failure at the boundary.
10
+ *
11
+ * The mapping is closed and explicit (no silent catch-all that hides a driver error): an
12
+ * unrecognized SQLite code maps to `kind: 'driver_error'` with `policy: 'fail'` — loud, and
13
+ * carrying the original code + message for diagnosis.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.SqlFailure = void 0;
17
+ exports.mapSqliteError = mapSqliteError;
18
+ /**
19
+ * A mapped SCP failure: the SCP `kind`, the honored bc `PolicyKind`, the original SQLite
20
+ * `code`, and a human message. Thrown at the runtime boundary (or carried as `{error}`
21
+ * into `runBehavior` for Policy-Kind interpretation by `runPlan`).
22
+ */
23
+ class SqlFailure extends Error {
24
+ kind;
25
+ policy;
26
+ sqliteCode;
27
+ constructor(kind, policy, sqliteCode, message) {
28
+ super(message);
29
+ this.kind = kind;
30
+ this.policy = policy;
31
+ this.sqliteCode = sqliteCode;
32
+ this.name = 'SqlFailure';
33
+ }
34
+ }
35
+ exports.SqlFailure = SqlFailure;
36
+ /** True if `e` looks like a better-sqlite3 `SqliteError` (has a `SQLITE_*` string `code`). */
37
+ function isSqliteError(e) {
38
+ return (typeof e === 'object' &&
39
+ e !== null &&
40
+ typeof e.code === 'string' &&
41
+ e.code.startsWith('SQLITE_'));
42
+ }
43
+ /**
44
+ * Map a caught driver error to an {@link SqlFailure}. The `SQLITE_CONSTRAINT_*` family maps
45
+ * to constraint / FK kinds (Policy `fail` — a data conflict is not retryable); the
46
+ * `SQLITE_BUSY` / `SQLITE_LOCKED` family maps to `retryable` (Policy `retry`). Anything else
47
+ * (including a non-SQLite error) maps to `driver_error` (Policy `fail`), preserving the
48
+ * original message.
49
+ */
50
+ function mapSqliteError(e) {
51
+ if (!isSqliteError(e)) {
52
+ const message = e instanceof Error ? e.message : String(e);
53
+ return new SqlFailure('driver_error', 'fail', undefined, `non-SQLite driver error: ${message}`);
54
+ }
55
+ const { code, message } = e;
56
+ // The message embeds the `SQLITE_*` code so it survives being wrapped by bc's `runPlan`
57
+ // (`OP_FAILED: <handler message>`); the runtime re-maps from it to re-surface the
58
+ // structured SqlFailure (kind / policy / code) at the boundary.
59
+ const tagged = `[${code}] ${message}`;
60
+ if (code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
61
+ return new SqlFailure('foreign_key_violation', 'fail', code, tagged);
62
+ }
63
+ if (code.startsWith('SQLITE_CONSTRAINT')) {
64
+ // UNIQUE / PRIMARYKEY / CHECK / NOTNULL / … — a durable constraint conflict.
65
+ return new SqlFailure('constraint_violation', 'fail', code, tagged);
66
+ }
67
+ if (code === 'SQLITE_BUSY' || code === 'SQLITE_LOCKED') {
68
+ return new SqlFailure('retryable', 'retry', code, tagged);
69
+ }
70
+ return new SqlFailure('driver_error', 'fail', code, tagged);
71
+ }
72
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/scp/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AA6CH,wCAqBC;AAvDD;;;;GAIG;AACH,MAAa,UAAW,SAAQ,KAAK;IAExB;IACA;IACA;IAHX,YACW,IAAoB,EACpB,MAAkB,EAClB,UAA8B,EACvC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QALN,SAAI,GAAJ,IAAI,CAAgB;QACpB,WAAM,GAAN,MAAM,CAAY;QAClB,eAAU,GAAV,UAAU,CAAoB;QAIvC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAVD,gCAUC;AAED,8FAA8F;AAC9F,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,CACL,OAAO,CAAC,KAAK,QAAQ;QACrB,CAAC,KAAK,IAAI;QACV,OAAQ,CAAwB,CAAC,IAAI,KAAK,QAAQ;QACjD,CAAsB,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CACnD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,CAAU;IACvC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO,IAAI,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,4BAA4B,OAAO,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC5B,wFAAwF;IACxF,kFAAkF;IAClF,gEAAgE;IAChE,MAAM,MAAM,GAAG,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;IACtC,IAAI,IAAI,KAAK,8BAA8B,EAAE,CAAC;QAC5C,OAAO,IAAI,UAAU,CAAC,uBAAuB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACzC,6EAA6E;QAC7E,OAAO,IAAI,UAAU,CAAC,sBAAsB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;QACvD,OAAO,IAAI,UAAU,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC"}