@lunora/do 1.0.0-alpha.44 → 1.0.0-alpha.45

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 (112) hide show
  1. package/dist/index.d.mts +55 -1
  2. package/dist/index.d.ts +55 -1
  3. package/dist/index.mjs +1 -44
  4. package/dist/packem_shared/ADMIN_FUNCTIONS-PxZ8Lr9e.mjs +1 -0
  5. package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-QYaiuty4.mjs +1 -0
  6. package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-D0wNaez7.mjs +22 -0
  7. package/dist/packem_shared/CDC_LOG_TABLE-E_J5LPoK.mjs +8 -0
  8. package/dist/packem_shared/ConflictError-C8GtJmjS.mjs +1 -0
  9. package/dist/packem_shared/CountRlsUnsupportedError-Cl8XpYDL.mjs +1 -0
  10. package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-CaO6L0Ee.mjs +31 -0
  11. package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-BRdmX7WW.mjs +1 -0
  12. package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-BPPeqM11.mjs +56 -0
  13. package/dist/packem_shared/GEO_DEFAULT_PRECISION-CWAm_oYY.mjs +1 -0
  14. package/dist/packem_shared/LogBuffer-bIvCelI-.mjs +1 -0
  15. package/dist/packem_shared/MAIL_RETENTION-KmozO2NQ.mjs +18 -0
  16. package/dist/packem_shared/MAX_SQL_ROWS-Bdu25ASB.mjs +2 -0
  17. package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-Ch6OW6_3.mjs +1 -0
  18. package/dist/packem_shared/NotFoundError-J3tjf4Uo.mjs +1 -0
  19. package/dist/packem_shared/NotUniqueError-BcP6GOem.mjs +1 -0
  20. package/dist/packem_shared/RANK_TIEBREAK-DtX8zQyc.mjs +1 -0
  21. package/dist/packem_shared/RLS_UNWRAP_SYMBOL-C6_WX3dG.mjs +1 -0
  22. package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-CP5wvJOW.mjs +101 -0
  23. package/dist/packem_shared/ReactiveCache-1_9Rs7J_.mjs +1 -0
  24. package/dist/packem_shared/SCAN_DEP-D_yR9EeV.mjs +1 -0
  25. package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-GvBy_DBz.mjs +1 -0
  26. package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-Caa0fR4N.mjs +1 -0
  27. package/dist/packem_shared/aggregateTableName-G-eXyjcz.mjs +1 -0
  28. package/dist/packem_shared/applyOnDelete-BvQN7pDL.mjs +1 -0
  29. package/dist/packem_shared/applySelect-Bq2KOrkL.mjs +1 -0
  30. package/dist/packem_shared/armRestore-BNzdvQ_o.mjs +1 -0
  31. package/dist/packem_shared/backfillAggregateIndexes-BAQ3Fwwh.mjs +1 -0
  32. package/dist/packem_shared/buildFtsMatch-CV0Z7PWv.mjs +1 -0
  33. package/dist/packem_shared/compileWhereSql-BLcfs4QW.mjs +1 -0
  34. package/dist/packem_shared/constant-time-equal-BVG05Guz.mjs +1 -0
  35. package/dist/packem_shared/context-telemetry-8eigmiNQ.mjs +1 -0
  36. package/dist/packem_shared/createMetrics-CyjLFXtA.mjs +1 -0
  37. package/dist/packem_shared/createSystemReader-DcDcrtM3.mjs +1 -0
  38. package/dist/packem_shared/ctx-db-idempotency-wiVoGnpQ.mjs +19 -0
  39. package/dist/packem_shared/ctx-db-shapes-DzX_H5q8.mjs +1 -0
  40. package/dist/packem_shared/diffExternalSource-DMpkJta1.mjs +1 -0
  41. package/dist/packem_shared/do-exec-BLe9lLrN.mjs +1 -0
  42. package/dist/packem_shared/do-sql-BYIQTG3z.mjs +1 -0
  43. package/dist/packem_shared/exportShardRows-kt42wijd.mjs +1 -0
  44. package/dist/packem_shared/hasTrigger-_rexbWMO.mjs +1 -0
  45. package/dist/packem_shared/isSoftDeleted-BvhQov04.mjs +9 -0
  46. package/dist/packem_shared/json-response-wrh9TBPw.mjs +1 -0
  47. package/dist/packem_shared/materializeExternalRows-BFmT9gsw.mjs +1 -0
  48. package/dist/packem_shared/renderSql-B5lF5Jd9.mjs +1 -0
  49. package/dist/packem_shared/runShardMigrations-bxOHpfID.mjs +5 -0
  50. package/dist/packem_shared/security-audit-BKUOgE0x.mjs +1 -0
  51. package/dist/packem_shared/selectExpiredIds-BGVP3d8-.mjs +1 -0
  52. package/dist/packem_shared/serialize-sql-DiRzL7A4.mjs +1 -0
  53. package/dist/packem_shared/serveRelationFanout-Ct5D2Tbk.mjs +1 -0
  54. package/dist/packem_shared/stableStringify-BjLh4gvA.mjs +1 -0
  55. package/dist/packem_shared/stableWireKey-YEHLaX6X.mjs +1 -0
  56. package/dist/packem_shared/subscriptionListDeltas-Bs69JbA8.mjs +1 -0
  57. package/dist/packem_shared/wire-codec-Ctnni0h6.mjs +1 -0
  58. package/package.json +1 -1
  59. package/dist/packem_shared/ADMIN_FUNCTIONS-CnZkbXH_.mjs +0 -374
  60. package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs +0 -56
  61. package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs +0 -84
  62. package/dist/packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs +0 -107
  63. package/dist/packem_shared/ConflictError-CLoq37xH.mjs +0 -12
  64. package/dist/packem_shared/CountRlsUnsupportedError-BGxj0pgS.mjs +0 -133
  65. package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs +0 -245
  66. package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs +0 -211
  67. package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs +0 -248
  68. package/dist/packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs +0 -114
  69. package/dist/packem_shared/LogBuffer-B_Ezju_N.mjs +0 -37
  70. package/dist/packem_shared/MAIL_RETENTION-CPpgl-dX.mjs +0 -104
  71. package/dist/packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs +0 -61
  72. package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs +0 -1
  73. package/dist/packem_shared/NotFoundError-C70b9hLw.mjs +0 -9
  74. package/dist/packem_shared/NotUniqueError-DbtlWwcG.mjs +0 -2031
  75. package/dist/packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs +0 -91
  76. package/dist/packem_shared/RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs +0 -141
  77. package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-B_zt4RQE.mjs +0 -7676
  78. package/dist/packem_shared/ReactiveCache-DnSvbjil.mjs +0 -233
  79. package/dist/packem_shared/SCAN_DEP-DLJF8dsj.mjs +0 -19
  80. package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs +0 -169
  81. package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs +0 -144
  82. package/dist/packem_shared/aggregateTableName-CxNqY1Sl.mjs +0 -64
  83. package/dist/packem_shared/applyOnDelete-BXSq3S70.mjs +0 -176
  84. package/dist/packem_shared/applySelect-WQY8m62C.mjs +0 -103
  85. package/dist/packem_shared/armRestore-4Px61hHS.mjs +0 -49
  86. package/dist/packem_shared/backfillAggregateIndexes-BGTwynwh.mjs +0 -81
  87. package/dist/packem_shared/buildFtsMatch-BLEMawrp.mjs +0 -38
  88. package/dist/packem_shared/compileWhereSql-DE6yfRcQ.mjs +0 -129
  89. package/dist/packem_shared/constant-time-equal-BVRWZgES.mjs +0 -12
  90. package/dist/packem_shared/context-telemetry-CDpyil58.mjs +0 -223
  91. package/dist/packem_shared/createMetrics-C4I2Yanj.mjs +0 -1
  92. package/dist/packem_shared/createSystemReader-D12eNH13.mjs +0 -82
  93. package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +0 -108
  94. package/dist/packem_shared/ctx-db-shapes-DTeFiHYS.mjs +0 -53
  95. package/dist/packem_shared/diffExternalSource-CovHfdyo.mjs +0 -44
  96. package/dist/packem_shared/do-exec-5eQy5cEi.mjs +0 -12
  97. package/dist/packem_shared/do-sql-CGAgiQUz.mjs +0 -88
  98. package/dist/packem_shared/exportShardRows-Dy3oFZ26.mjs +0 -157
  99. package/dist/packem_shared/hasTrigger-5N6_Fx0A.mjs +0 -20
  100. package/dist/packem_shared/isSoftDeleted-CFJmhjFP.mjs +0 -217
  101. package/dist/packem_shared/json-response-BdbtpOhm.mjs +0 -3
  102. package/dist/packem_shared/materializeExternalRows-BtEGs1Fv.mjs +0 -45
  103. package/dist/packem_shared/renderSql-D6eUcn2N.mjs +0 -16
  104. package/dist/packem_shared/runShardMigrations-BVax6rYu.mjs +0 -120
  105. package/dist/packem_shared/security-audit-CucgBice.mjs +0 -158
  106. package/dist/packem_shared/selectExpiredIds-C5W29Upb.mjs +0 -18
  107. package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +0 -14
  108. package/dist/packem_shared/serveRelationFanout-k7HTh3CC.mjs +0 -21
  109. package/dist/packem_shared/stableStringify-mC40mZts.mjs +0 -40
  110. package/dist/packem_shared/stableWireKey-DKuXO7T5.mjs +0 -6
  111. package/dist/packem_shared/subscriptionListDeltas-CT76bYny.mjs +0 -126
  112. package/dist/packem_shared/wire-codec-CzQc1pvf.mjs +0 -225
@@ -1,12 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- class ConflictError extends LunoraError {
4
- /** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
5
- kind;
6
- constructor(message = "Optimistic concurrency conflict", kind = "conflict") {
7
- super("CONFLICT", message, { name: "ConflictError" });
8
- this.kind = kind;
9
- }
10
- }
11
-
12
- export { ConflictError };
@@ -1,133 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- class CountRlsUnsupportedError extends LunoraError {
4
- constructor(table) {
5
- super(
6
- "COUNT_RLS_UNSUPPORTED",
7
- table === void 0 ? "count() is not supported in an RLS-restricted context" : `count() is not supported on table "${table}" inside an RLS-restricted context`
8
- );
9
- }
10
- }
11
- const mergeWhere = (left, right) => {
12
- if (!left) {
13
- return right;
14
- }
15
- if (!right) {
16
- return left;
17
- }
18
- return { AND: [left, right] };
19
- };
20
- const BOOLEAN_COMBINATORS = /* @__PURE__ */ new Set(["AND", "NOT", "OR"]);
21
- const NOT_EQ = /* @__PURE__ */ Symbol("not-eq");
22
- const resolveEqValue = (value) => {
23
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
24
- const operatorKeys = Object.keys(value);
25
- if (operatorKeys.length === 1 && operatorKeys[0] === "eq") {
26
- return value.eq;
27
- }
28
- return NOT_EQ;
29
- }
30
- return value;
31
- };
32
- const parseRequestedEqKeys = (requested, accept) => {
33
- const resolved = {};
34
- for (const [key, raw] of Object.entries(requested)) {
35
- if (BOOLEAN_COMBINATORS.has(key) || !accept(key)) {
36
- return void 0;
37
- }
38
- const value = resolveEqValue(raw);
39
- if (value === NOT_EQ) {
40
- return void 0;
41
- }
42
- resolved[key] = value;
43
- }
44
- return resolved;
45
- };
46
- const reconcileStaticWhere = (staticWhere, resolved, requested, crossCheckRequested) => {
47
- const merged = { ...resolved };
48
- if (!staticWhere) {
49
- return merged;
50
- }
51
- for (const [key, value] of Object.entries(staticWhere)) {
52
- if (key in merged) {
53
- if (merged[key] !== value) {
54
- return void 0;
55
- }
56
- } else if (crossCheckRequested && key in requested) {
57
- if (requested[key] !== value) {
58
- return void 0;
59
- }
60
- } else {
61
- merged[key] = value;
62
- }
63
- }
64
- return merged;
65
- };
66
- const planAggregateLookup = (index, requestedWhere) => {
67
- const by = index.by ?? [];
68
- const requested = requestedWhere ?? {};
69
- const resolved = parseRequestedEqKeys(requested, (key) => by.includes(key));
70
- if (resolved === void 0) {
71
- return void 0;
72
- }
73
- for (const key of by) {
74
- if (!(key in resolved)) {
75
- return void 0;
76
- }
77
- }
78
- return reconcileStaticWhere(index.where, resolved, requested, true);
79
- };
80
- const collectPartialKey = (index, requestedWhere, byFields) => {
81
- const partial = parseRequestedEqKeys(requestedWhere ?? {}, (key) => byFields.has(key));
82
- if (partial === void 0) {
83
- return void 0;
84
- }
85
- return reconcileStaticWhere(index.where, partial, {}, false);
86
- };
87
- const selectIndexForReducer = (indexes, op, field, requestedWhere) => {
88
- let best;
89
- for (const index of indexes) {
90
- if (index.op !== op) {
91
- continue;
92
- }
93
- if (op !== "count" && index.field !== field) {
94
- continue;
95
- }
96
- const key = planAggregateLookup(index, requestedWhere);
97
- if (!key) {
98
- continue;
99
- }
100
- if (!best || (index.by?.length ?? 0) > (best.index.by?.length ?? 0)) {
101
- best = { index, key };
102
- }
103
- }
104
- return best;
105
- };
106
- const selectIndexForCount = (indexes, requestedWhere) => selectIndexForReducer(indexes, "count", void 0, requestedWhere);
107
- const selectIndexForAggregate = (indexes, op, field, requestedWhere) => selectIndexForReducer(indexes, op, field, requestedWhere);
108
- const selectIndexForGroupBy = (indexes, op, field, by, requestedWhere) => {
109
- const requestedFields = new Set(by);
110
- for (const index of indexes) {
111
- if (index.op !== op) {
112
- continue;
113
- }
114
- if (op !== "count" && index.field !== field) {
115
- continue;
116
- }
117
- const indexBy = index.by ?? [];
118
- if (indexBy.length !== requestedFields.size) {
119
- continue;
120
- }
121
- if (!indexBy.every((key) => requestedFields.has(key))) {
122
- continue;
123
- }
124
- const partial = collectPartialKey(index, requestedWhere, requestedFields);
125
- if (partial === void 0) {
126
- continue;
127
- }
128
- return { index, partial };
129
- }
130
- return void 0;
131
- };
132
-
133
- export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy };
@@ -1,245 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const DATA_MIGRATION_STATE_TABLE = "__lunora_migrations";
4
- const DEFAULT_BATCH_SIZE = 100;
5
- const STALE_CLAIM_TIMEOUT_MS = 3e4;
6
- const CLAIM_HEARTBEAT_INTERVAL_MS = 1e4;
7
- const runSql = (sql, query, ...params) => {
8
- const runner = sql.exec;
9
- return runner.call(sql, query, ...params);
10
- };
11
- const ensureStateTable = (sql) => {
12
- runSql(
13
- sql,
14
- `CREATE TABLE IF NOT EXISTS "${DATA_MIGRATION_STATE_TABLE}" (
15
- id TEXT PRIMARY KEY,
16
- direction TEXT NOT NULL,
17
- status TEXT NOT NULL,
18
- cursor TEXT,
19
- processed INTEGER NOT NULL DEFAULT 0,
20
- changed INTEGER NOT NULL DEFAULT 0,
21
- started_at REAL,
22
- updated_at REAL,
23
- error TEXT
24
- )`
25
- );
26
- };
27
- const readState = (sql, id) => {
28
- const rows = runSql(sql, `SELECT * FROM "${DATA_MIGRATION_STATE_TABLE}" WHERE id = ?`, id).toArray();
29
- const row = rows[0];
30
- if (!row) {
31
- return void 0;
32
- }
33
- return {
34
- changed: row.changed,
35
- // eslint-disable-next-line unicorn/no-null -- mirrors the SQLite `cursor` column: a missing cursor is NULL, not undefined
36
- cursor: typeof row.cursor === "string" ? row.cursor : null,
37
- direction: row.direction === "down" ? "down" : "up",
38
- processed: row.processed,
39
- startedAt: typeof row.started_at === "number" ? row.started_at : void 0,
40
- status: row.status === "completed" || row.status === "failed" ? row.status : "in_progress"
41
- };
42
- };
43
- const deleteState = (sql, id) => {
44
- runSql(sql, `DELETE FROM "${DATA_MIGRATION_STATE_TABLE}" WHERE id = ?`, id);
45
- };
46
- const persistState = (sql, state) => {
47
- runSql(
48
- sql,
49
- `INSERT INTO "${DATA_MIGRATION_STATE_TABLE}"
50
- (id, direction, status, cursor, processed, changed, started_at, updated_at, error)
51
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
52
- ON CONFLICT(id) DO UPDATE SET
53
- direction = excluded.direction,
54
- status = excluded.status,
55
- cursor = excluded.cursor,
56
- processed = excluded.processed,
57
- changed = excluded.changed,
58
- updated_at = excluded.updated_at,
59
- error = excluded.error`,
60
- state.id,
61
- state.direction,
62
- state.status,
63
- state.cursor,
64
- state.processed,
65
- state.changed,
66
- state.startedAt,
67
- state.updatedAt,
68
- state.error
69
- );
70
- };
71
- const claimMigration = (sql, id, direction, now) => {
72
- runSql(
73
- sql,
74
- `INSERT INTO "${DATA_MIGRATION_STATE_TABLE}"
75
- (id, direction, status, cursor, processed, changed, started_at, updated_at, error)
76
- VALUES (?, ?, 'in_progress', NULL, 0, 0, ?, ?, NULL)
77
- ON CONFLICT(id) DO UPDATE SET
78
- status = 'in_progress',
79
- updated_at = excluded.updated_at
80
- WHERE
81
- "${DATA_MIGRATION_STATE_TABLE}".direction <> excluded.direction
82
- OR "${DATA_MIGRATION_STATE_TABLE}".status <> 'in_progress'
83
- OR "${DATA_MIGRATION_STATE_TABLE}".updated_at IS NULL
84
- OR "${DATA_MIGRATION_STATE_TABLE}".updated_at <= excluded.updated_at - ${String(STALE_CLAIM_TIMEOUT_MS)}`,
85
- id,
86
- direction,
87
- now,
88
- now
89
- );
90
- return runSql(sql, `SELECT changes() AS changed`).one().changed > 0;
91
- };
92
- const releaseClaim = (sql, id) => {
93
- runSql(sql, `UPDATE "${DATA_MIGRATION_STATE_TABLE}" SET updated_at = 0 WHERE id = ? AND status = 'in_progress'`, id);
94
- };
95
- const touchClaim = (sql, id, now) => {
96
- runSql(sql, `UPDATE "${DATA_MIGRATION_STATE_TABLE}" SET updated_at = ? WHERE id = ? AND status = 'in_progress'`, now, id);
97
- };
98
- const readMigrationStatus = (sql, id) => {
99
- const exists = runSql(
100
- sql,
101
- `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
102
- DATA_MIGRATION_STATE_TABLE
103
- ).toArray();
104
- if (exists.length === 0) {
105
- return [];
106
- }
107
- const filter = id === void 0 ? " ORDER BY id" : " WHERE id = ?";
108
- const params = id === void 0 ? [] : [id];
109
- const rows = runSql(sql, `SELECT * FROM "${DATA_MIGRATION_STATE_TABLE}"${filter}`, ...params).toArray();
110
- return rows.map((row) => {
111
- return {
112
- changed: row.changed,
113
- cursor: typeof row.cursor === "string" ? row.cursor : null,
114
- direction: row.direction === "down" ? "down" : "up",
115
- error: typeof row.error === "string" ? row.error : null,
116
- id: row.id,
117
- processed: row.processed,
118
- startedAt: typeof row.started_at === "number" ? row.started_at : null,
119
- status: row.status === "completed" || row.status === "failed" ? row.status : "in_progress",
120
- updatedAt: typeof row.updated_at === "number" ? row.updated_at : null
121
- };
122
- });
123
- };
124
- const runDataMigration = async (options) => {
125
- const { migration, sql, writer } = options;
126
- const direction = options.direction ?? "up";
127
- const dryRun = options.dryRun ?? false;
128
- const clock = options.clock ?? (() => Date.now());
129
- const maxBatches = options.maxBatches ?? Number.POSITIVE_INFINITY;
130
- const batchSize = options.batchSize ?? migration.batchSize ?? DEFAULT_BATCH_SIZE;
131
- const transform = direction === "up" ? migration.up : migration.down;
132
- if (!transform) {
133
- throw new LunoraError("INTERNAL", `data migration "${migration.id}" has no \`${direction}\` transform`);
134
- }
135
- let cursor = null;
136
- let processed = 0;
137
- let changed = 0;
138
- let startedAt = clock();
139
- if (!dryRun) {
140
- ensureStateTable(sql);
141
- const existing = readState(sql, migration.id);
142
- if (existing?.direction === direction && existing.status === "completed") {
143
- return { changed: existing.changed, cursor: null, direction, dryRun, id: migration.id, processed: existing.processed, status: "completed" };
144
- }
145
- if (existing && existing.direction !== direction) {
146
- deleteState(sql, migration.id);
147
- }
148
- const claimed = claimMigration(sql, migration.id, direction, clock());
149
- if (!claimed) {
150
- const active = readState(sql, migration.id);
151
- return {
152
- changed: active?.changed ?? 0,
153
- cursor: active?.cursor ?? cursor,
154
- direction,
155
- dryRun,
156
- id: migration.id,
157
- processed: active?.processed ?? 0,
158
- status: active?.status ?? "in_progress"
159
- };
160
- }
161
- const resume = existing?.direction === direction ? existing : void 0;
162
- if (resume) {
163
- cursor = resume.cursor;
164
- processed = resume.processed;
165
- changed = resume.changed;
166
- startedAt = resume.startedAt ?? startedAt;
167
- }
168
- }
169
- let isDone = false;
170
- let batches = 0;
171
- let lastHeartbeatAt = startedAt;
172
- try {
173
- while (!isDone && batches < maxBatches) {
174
- const batch = await writer.findMany(migration.table, { cursor, limit: batchSize });
175
- for (const document of batch.page) {
176
- processed += 1;
177
- const next = transform(document);
178
- if (next !== void 0) {
179
- changed += 1;
180
- if (!dryRun) {
181
- await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] }, void 0, {
182
- allowExplicitId: true
183
- });
184
- }
185
- }
186
- if (!dryRun) {
187
- const now = clock();
188
- if (now - lastHeartbeatAt >= CLAIM_HEARTBEAT_INTERVAL_MS) {
189
- touchClaim(sql, migration.id, now);
190
- lastHeartbeatAt = now;
191
- }
192
- }
193
- }
194
- cursor = batch.continueCursor;
195
- isDone = batch.isDone;
196
- batches += 1;
197
- if (!dryRun) {
198
- const batchEndedAt = clock();
199
- persistState(sql, {
200
- changed,
201
- // eslint-disable-next-line unicorn/no-null -- bound to the SQLite cursor column: a finished run stores NULL
202
- cursor: isDone ? null : cursor,
203
- direction,
204
- // eslint-disable-next-line unicorn/no-null -- bound to the SQLite error column: a clean batch stores NULL
205
- error: null,
206
- id: migration.id,
207
- processed,
208
- startedAt,
209
- status: isDone ? "completed" : "in_progress",
210
- updatedAt: batchEndedAt
211
- });
212
- lastHeartbeatAt = batchEndedAt;
213
- try {
214
- await options.onBatch?.({ batches, changed, processed });
215
- } catch {
216
- }
217
- }
218
- }
219
- } catch (error) {
220
- if (!dryRun) {
221
- persistState(sql, {
222
- changed,
223
- cursor,
224
- direction,
225
- error: error instanceof Error ? error.message : String(error),
226
- id: migration.id,
227
- processed,
228
- startedAt,
229
- status: "failed",
230
- updatedAt: clock()
231
- });
232
- }
233
- throw error;
234
- }
235
- if (!dryRun && !isDone) {
236
- try {
237
- releaseClaim(sql, migration.id);
238
- } catch (error) {
239
- console.warn(`data migration "${migration.id}": releaseClaim failed`, error);
240
- }
241
- }
242
- return { changed, cursor: isDone ? null : cursor, direction, dryRun, id: migration.id, processed, status: isDone ? "completed" : "in_progress" };
243
- };
244
-
245
- export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration };
@@ -1,211 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
- import { distinctValues } from './applyOnDelete-BXSq3S70.mjs';
3
-
4
- const RELATION_EXISTS_KEY = "__relationExists";
5
-
6
- const RELATION_OPERATOR_META = {
7
- every: { kind: "many", negateChild: true, negated: true },
8
- is: { kind: "one", negated: false },
9
- isNot: { kind: "one", negated: true, nullDisjunct: true },
10
- none: { kind: "many", negated: true },
11
- some: { kind: "many", negated: false }
12
- };
13
- const RELATION_OPERATORS = new Set(Object.keys(RELATION_OPERATOR_META));
14
- const joinColumns = (relation) => relation.kind === "one" ? { clause: relation.field, project: relation.references } : { clause: relation.references, project: relation.field };
15
- const DEFAULT_MAX_RELATION_KEYS = 5e3;
16
- const KEY_OVERFLOW = /* @__PURE__ */ Symbol("relation-key-overflow");
17
- const branchesOf = (value) => Array.isArray(value) ? value.map((branch) => branch ?? {}) : [];
18
- const combineAnd = (clauses) => {
19
- if (clauses.length === 1) {
20
- const [only] = clauses;
21
- return only ?? {};
22
- }
23
- return clauses.length === 0 ? {} : { AND: clauses };
24
- };
25
- const isRelationPredicate = (value) => {
26
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
27
- return false;
28
- }
29
- const keys = Object.keys(value);
30
- return keys.length > 0 && keys.every((key) => RELATION_OPERATORS.has(key));
31
- };
32
- const containsRelationPredicate = (where, schema, tableName) => {
33
- const relationMap = schema.tables[tableName]?.relationMap ?? {};
34
- return Object.keys(where).some((key) => {
35
- const value = where[key];
36
- if (key === "AND" || key === "OR") {
37
- return branchesOf(value).some((branch) => containsRelationPredicate(branch, schema, tableName));
38
- }
39
- if (key === "NOT") {
40
- return containsRelationPredicate(value ?? {}, schema, tableName);
41
- }
42
- return Boolean(relationMap[key]) && isRelationPredicate(value);
43
- });
44
- };
45
- const assertFlatPredicate = (where, schema, tableName, op) => {
46
- if (where && containsRelationPredicate(where, schema, tableName)) {
47
- throw new LunoraError("INTERNAL", `relation-crossing predicates are not supported in ${op}() — use them in findMany/findFirst or an RLS read policy`);
48
- }
49
- };
50
- const projectChildKeys = async (relation, childWhere, projectField, context, escalatable) => {
51
- const resolvedChildWhere = await resolveForTable(childWhere, relation.table, context);
52
- const { page } = await context.fetcher(relation.table, {
53
- baseWhere: context.relationBaseWhere?.(relation.table),
54
- relationBaseWhere: context.relationBaseWhere,
55
- where: resolvedChildWhere
56
- });
57
- const keys = distinctValues(page, projectField);
58
- if (keys.length > context.maxRelationKeys) {
59
- if (escalatable) {
60
- return KEY_OVERFLOW;
61
- }
62
- throw new LunoraError(
63
- "INTERNAL",
64
- `relation predicate on "${relation.table}" matched ${String(keys.length)} rows, exceeding the ${String(context.maxRelationKeys)}-key limit; narrow the predicate (a same-shard EXISTS push-down lifts this cap)`
65
- );
66
- }
67
- return keys;
68
- };
69
- const compileOperator = async (operator, relation, childWhere, context, escalatable) => {
70
- const meta = RELATION_OPERATOR_META[operator];
71
- if (!meta) {
72
- throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
73
- }
74
- const { clause, project } = joinColumns(relation);
75
- const keys = await projectChildKeys(relation, meta.negateChild ? { NOT: childWhere } : childWhere, project, context, escalatable);
76
- if (keys === KEY_OVERFLOW) {
77
- return KEY_OVERFLOW;
78
- }
79
- if (!meta.negated) {
80
- return { [clause]: { in: keys } };
81
- }
82
- if (meta.nullDisjunct) {
83
- return { OR: [{ [clause]: { notIn: keys } }, { [clause]: { isNull: true } }] };
84
- }
85
- return { [clause]: { notIn: keys } };
86
- };
87
- const buildExistsMarker = async (operator, relation, childWhere, parentTable, context) => {
88
- const meta = RELATION_OPERATOR_META[operator];
89
- if (!meta) {
90
- throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
91
- }
92
- const base = context.relationBaseWhere?.(relation.table);
93
- const predicatePart = meta.negateChild ? { NOT: childWhere } : childWhere;
94
- const merged = base ? { AND: [base, predicatePart] } : predicatePart;
95
- const resolvedChild = await resolveForTable(merged, relation.table, context);
96
- const marker = { childWhere: resolvedChild, negated: meta.negated, parentTable, relation };
97
- return { [RELATION_EXISTS_KEY]: marker };
98
- };
99
- const assertCardinality = (operator, name, relation) => {
100
- const meta = RELATION_OPERATOR_META[operator];
101
- if (meta && meta.kind !== relation.kind) {
102
- throw new LunoraError("INTERNAL", `relation operator "${operator}" requires a to-${meta.kind} relation, but "${name}" is to-${relation.kind}`);
103
- }
104
- };
105
- const resolveRelationNode = async (name, relation, predicate, parentTable, context) => {
106
- const clauses = [];
107
- for (const operator of Object.keys(predicate)) {
108
- assertCardinality(operator, name, relation);
109
- const childWhere = predicate[operator] ?? {};
110
- const pushable = context.canPushExists?.(relation) ?? false;
111
- if (pushable && context.existsPushMode === "always") {
112
- clauses.push(await buildExistsMarker(operator, relation, childWhere, parentTable, context));
113
- continue;
114
- }
115
- const semijoin = await compileOperator(operator, relation, childWhere, context, pushable);
116
- if (semijoin === KEY_OVERFLOW) {
117
- clauses.push(await buildExistsMarker(operator, relation, childWhere, parentTable, context));
118
- } else {
119
- clauses.push(semijoin);
120
- }
121
- }
122
- return combineAnd(clauses);
123
- };
124
- const resolveKey = async (key, value, tableName, context) => {
125
- if (key === "AND" || key === "OR") {
126
- const resolved = [];
127
- for (const branch of branchesOf(value)) {
128
- resolved.push(await resolveForTable(branch, tableName, context));
129
- }
130
- return { [key]: resolved };
131
- }
132
- if (key === "NOT") {
133
- return { NOT: await resolveForTable(value ?? {}, tableName, context) };
134
- }
135
- const relation = context.schema.tables[tableName]?.relationMap?.[key];
136
- if (relation && isRelationPredicate(value)) {
137
- return resolveRelationNode(key, relation, value, tableName, context);
138
- }
139
- return { [key]: value };
140
- };
141
- const resolveForTable = async (where, tableName, context) => {
142
- const clauses = [];
143
- for (const key of Object.keys(where)) {
144
- clauses.push(await resolveKey(key, where[key], tableName, context));
145
- }
146
- return combineAnd(clauses);
147
- };
148
- const resolveRelationPredicates = async (where, options) => {
149
- if (!where || !containsRelationPredicate(where, options.schema, options.tableName)) {
150
- return where;
151
- }
152
- return resolveForTable(where, options.tableName, {
153
- canPushExists: options.canPushExists,
154
- existsPushMode: options.existsPushMode ?? "auto",
155
- fetcher: options.fetcher,
156
- maxRelationKeys: options.maxRelationKeys ?? DEFAULT_MAX_RELATION_KEYS,
157
- relationBaseWhere: options.relationBaseWhere,
158
- schema: options.schema
159
- });
160
- };
161
- const firstShardedHit = (branches, schema, tableName) => {
162
- for (const branch of branches) {
163
- const hit = findShardedRelationTarget(branch, schema, tableName);
164
- if (hit) {
165
- return hit;
166
- }
167
- }
168
- return void 0;
169
- };
170
- const inspectKey = (key, value, schema, tableName) => {
171
- if (key === "AND" || key === "OR") {
172
- return firstShardedHit(branchesOf(value), schema, tableName);
173
- }
174
- if (key === "NOT") {
175
- return firstShardedHit([value ?? {}], schema, tableName);
176
- }
177
- const relation = schema.tables[tableName]?.relationMap?.[key];
178
- if (!relation || !isRelationPredicate(value)) {
179
- return void 0;
180
- }
181
- if (schema.tables[relation.table]?.shardMode?.kind === "shardBy") {
182
- return { relation: key, target: relation.table };
183
- }
184
- return firstShardedHit(Object.values(value), schema, relation.table);
185
- };
186
- const findShardedRelationTarget = (where, schema, tableName) => {
187
- for (const key of Object.keys(where)) {
188
- const hit = inspectKey(key, where[key], schema, tableName);
189
- if (hit) {
190
- return hit;
191
- }
192
- }
193
- return void 0;
194
- };
195
- const assertShapeShardable = (effectiveWhere, schema, table) => {
196
- if (!effectiveWhere) {
197
- return;
198
- }
199
- const offending = findShardedRelationTarget(effectiveWhere, schema, table);
200
- if (!offending) {
201
- return;
202
- }
203
- throw Object.assign(
204
- new Error(
205
- `shape on "${table}" joins the sharded table "${offending.target}" via relation "${offending.relation}" — a live shape cannot replicate rows that live in another shard's Durable Object. Fix it by (a) denormalizing the joined columns into "${table}", or (b) moving "${offending.target}" to .global() so it is served through the latency-tiered D1 shape tier.`
206
- ),
207
- { code: "SHAPE_CROSS_SHARD_JOIN", name: "LunoraError", status: 400 }
208
- );
209
- };
210
-
211
- export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };