@lunora/do 1.0.0-alpha.2 → 1.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/__assets__/package-og.svg +1 -1
  2. package/dist/index.d.mts +1162 -114
  3. package/dist/index.d.ts +1162 -114
  4. package/dist/index.mjs +27 -22
  5. package/dist/packem_shared/{ADMIN_FUNCTION_PREFIX-Dzdqq5J2.mjs → ADMIN_FUNCTIONS-DSUQ5fX9.mjs} +49 -3
  6. package/dist/packem_shared/{applyCdcChanges-Ctdmxmrv.mjs → CDC_LOG_TABLE-DSycmnDf.mjs} +5 -1
  7. package/dist/packem_shared/{DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs → DATA_MIGRATION_STATE_TABLE-DfPxn8I0.mjs} +5 -1
  8. package/dist/packem_shared/{assertFlatPredicate-DyVYReuT.mjs → DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs} +51 -2
  9. package/dist/packem_shared/{assertValidClientId-CBZ1zC96.mjs → NotUniqueError-Do5h_jiW.mjs} +127 -37
  10. package/dist/packem_shared/{rank-CrkEIpF4.mjs → RANK_TIEBREAK-CXhdcA1o.mjs} +2 -13
  11. package/dist/packem_shared/{guardWriter-u3UlnCH5.mjs → RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs} +6 -2
  12. package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-DQkmGiCS.mjs → ROOT_DO_SIZE_WARN_BYTES-BfPNp3Jh.mjs} +2342 -200
  13. package/dist/packem_shared/{ReactiveCache-ByVzgH3d.mjs → ReactiveCache-BYlSGY0N.mjs} +1 -28
  14. package/dist/packem_shared/{applyOnDelete-CMif2RKw.mjs → applyOnDelete-BQ-8ZlZ1.mjs} +19 -9
  15. package/dist/packem_shared/{buildSeekWhere-lVsNXSLy.mjs → applySelect-BvZdFUBT.mjs} +18 -1
  16. package/dist/packem_shared/{backfillAggregateIndexes-BF5eL7kW.mjs → backfillAggregateIndexes-BZsOqDXP.mjs} +3 -2
  17. package/dist/packem_shared/{compileWhereSql-CXrhFA3G.mjs → compileWhereSql-MW_Lk8nJ.mjs} +3 -2
  18. package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
  19. package/dist/packem_shared/ctx-db-shapes-0RaIOy7J.mjs +53 -0
  20. package/dist/packem_shared/diffExternalSource-Cx9HUPJj.mjs +44 -0
  21. package/dist/packem_shared/isSourceDue-9mJRJ9Ld.mjs +40 -0
  22. package/dist/packem_shared/materializeExternalRows-Bj6EXy50.mjs +23 -0
  23. package/dist/packem_shared/{runShardMigrations-C3bn5r93.mjs → runShardMigrations-nIwoQeOK.mjs} +6 -4
  24. package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
  25. package/dist/packem_shared/{serveRelationFanout-Clr1a05L.mjs → serveRelationFanout-C5axmGDj.mjs} +1 -1
  26. package/dist/packem_shared/stableStringify-MydiuScU.mjs +40 -0
  27. package/dist/packem_shared/subscription-delivery-CK8qga-k.mjs +347 -0
  28. package/dist/packem_shared/subscriptionListDeltas-BxygQlXT.mjs +1 -0
  29. package/package.json +2 -2
  30. package/dist/packem_shared/ctx-db-idempotency-DkC9rP91.mjs +0 -35
  31. package/dist/packem_shared/encodePartitionKey-C6blLR5K.mjs +0 -1
  32. /package/dist/packem_shared/{matchesStaticWhere-CFk6adSu.mjs → AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs} +0 -0
  33. /package/dist/packem_shared/{AUTH_METRICS_BUCKET_MS-CiHHYeJi.mjs → AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs} +0 -0
  34. /package/dist/packem_shared/{ensureFunctionMetricsTables-UDNVD7FS.mjs → FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs} +0 -0
  35. /package/dist/packem_shared/{clearCapturedMail-CPpgl-dX.mjs → MAIL_RETENTION-CPpgl-dX.mjs} +0 -0
  36. /package/dist/packem_shared/{assertReadonly-dDcFE1YZ.mjs → MAX_SQL_ROWS-dDcFE1YZ.mjs} +0 -0
  37. /package/dist/packem_shared/{buildSecurityAudit-CCAvoFlr.mjs → MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs} +0 -0
  38. /package/dist/packem_shared/{ftsTableName-BLEMawrp.mjs → buildFtsMatch-BLEMawrp.mjs} +0 -0
  39. /package/dist/packem_shared/{runTriggers-5N6_Fx0A.mjs → hasTrigger-5N6_Fx0A.mjs} +0 -0
package/dist/index.mjs CHANGED
@@ -1,35 +1,40 @@
1
1
  export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow } from './packem_shared/exportShardRows-DZEhUeyI.mjs';
2
- export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/matchesStaticWhere-CFk6adSu.mjs';
2
+ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs';
3
3
  export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
4
4
  export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs';
5
- export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent } from './packem_shared/AUTH_METRICS_BUCKET_MS-CiHHYeJi.mjs';
6
- export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/assertValidClientId-CBZ1zC96.mjs';
7
- export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs';
5
+ export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent } from './packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
6
+ export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-Do5h_jiW.mjs';
7
+ export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-DfPxn8I0.mjs';
8
8
  export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
9
9
  export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
10
- export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/ensureFunctionMetricsTables-UDNVD7FS.mjs';
11
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTION_PREFIX-Dzdqq5J2.mjs';
10
+ export { diffExternalSource } from './packem_shared/diffExternalSource-Cx9HUPJj.mjs';
11
+ export { materializeExternalRows, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-Bj6EXy50.mjs';
12
+ export { isSourceDue, liftSourceId, pullExternalSourceTick } from './packem_shared/isSourceDue-9mJRJ9Ld.mjs';
13
+ export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
14
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-DSUQ5fX9.mjs';
12
15
  export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
13
- export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/clearCapturedMail-CPpgl-dX.mjs';
16
+ export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
14
17
  export { default as NotFoundError } from './packem_shared/NotFoundError-CMuMZt81.mjs';
15
18
  export { armRestore, readBookmark } from './packem_shared/armRestore-BJk53Ro8.mjs';
16
- export { buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys } from './packem_shared/buildSeekWhere-lVsNXSLy.mjs';
17
- export { R as RANK_TIEBREAK, e as encodePartitionKey, m as matchesRankStaticWhere, r as rankTableName, a as resolveRankPartition, s as sortColumnName } from './packem_shared/rank-CrkEIpF4.mjs';
18
- export { ReactiveCache, reactiveCacheKey, stableStringify } from './packem_shared/ReactiveCache-ByVzgH3d.mjs';
19
- export { serveRelationFanout } from './packem_shared/serveRelationFanout-Clr1a05L.mjs';
20
- export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/assertFlatPredicate-DyVYReuT.mjs';
21
- export { applyOnDelete, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-CMif2RKw.mjs';
22
- export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/guardWriter-u3UlnCH5.mjs';
23
- export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/ftsTableName-BLEMawrp.mjs';
19
+ export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-BvZdFUBT.mjs';
20
+ export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
21
+ export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-BYlSGY0N.mjs';
22
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-C5axmGDj.mjs';
23
+ export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs';
24
+ export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BQ-8ZlZ1.mjs';
25
+ export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
26
+ export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
24
27
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
25
28
  export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs';
26
- export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO, subscriptionListDeltas } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-DQkmGiCS.mjs';
29
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-BfPNp3Jh.mjs';
27
30
  export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs';
28
- export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/assertReadonly-dDcFE1YZ.mjs';
31
+ export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs';
29
32
  export { createSystemReader } from './packem_shared/createSystemReader-8CzSZP9V.mjs';
30
33
  export { ConflictError } from './packem_shared/ConflictError-C0STs6bU.mjs';
31
- export { hasTrigger, runTriggers } from './packem_shared/runTriggers-5N6_Fx0A.mjs';
32
- export { compileWhereSql } from './packem_shared/compileWhereSql-CXrhFA3G.mjs';
33
- export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/applyCdcChanges-Ctdmxmrv.mjs';
34
- export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BF5eL7kW.mjs';
35
- export { runShardMigrations } from './packem_shared/runShardMigrations-C3bn5r93.mjs';
34
+ export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
35
+ export { compileWhereSql } from './packem_shared/compileWhereSql-MW_Lk8nJ.mjs';
36
+ export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-DSycmnDf.mjs';
37
+ export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BZsOqDXP.mjs';
38
+ export { runShardMigrations } from './packem_shared/runShardMigrations-nIwoQeOK.mjs';
39
+ export { stableStringify } from './packem_shared/stableStringify-MydiuScU.mjs';
40
+ export { s as subscriptionListDeltas } from './packem_shared/subscription-delivery-CK8qga-k.mjs';
@@ -1,5 +1,8 @@
1
+ const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2
+
1
3
  const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2
4
  const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
5
+ const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
3
6
  const ADMIN_FUNCTIONS = {
4
7
  applyCdc: "__lunora_admin__:applyCdc",
5
8
  cdcSync: "__lunora_admin__:cdcSync",
@@ -15,6 +18,7 @@ const ADMIN_FUNCTIONS = {
15
18
  getAuditLog: "__lunora_admin__:getAuditLog",
16
19
  getAuthMetrics: "__lunora_admin__:getAuthMetrics",
17
20
  getCapturedMail: "__lunora_admin__:getCapturedMail",
21
+ getFanoutMetrics: "__lunora_admin__:getFanoutMetrics",
18
22
  getFunctionStats: "__lunora_admin__:getFunctionStats",
19
23
  listSubscriptions: "__lunora_admin__:listSubscriptions",
20
24
  listTableIndexes: "__lunora_admin__:listTableIndexes",
@@ -27,6 +31,8 @@ const ADMIN_FUNCTIONS = {
27
31
  // eslint-disable-next-line no-secrets/no-secrets -- reserved admin RPC path constant, not a credential
28
32
  getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus",
29
33
  importShard: "__lunora_admin__:importShard",
34
+ listFlags: "__lunora_admin__:listFlags",
35
+ listQueues: "__lunora_admin__:listQueues",
30
36
  listTables: "__lunora_admin__:listTables",
31
37
  listWorkflows: "__lunora_admin__:listWorkflows",
32
38
  maskPolicies: "__lunora_admin__:maskPolicies",
@@ -91,7 +97,6 @@ const expandDocumentRows = (columns, rows) => {
91
97
  };
92
98
  const escapeLike = (value) => value.replaceAll(/[\\%_]/g, (character) => `\\${character}`);
93
99
  const isInternalTable = (name) => name.startsWith("sqlite_") || name.startsWith("_cf_") || name.startsWith("__miniflare") || name.startsWith("__lunora") || name.includes("__fts_");
94
- const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
95
100
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
96
101
  const countRows = (sql, quotedTable) => {
97
102
  const row = sql.exec(`SELECT COUNT(*) AS c FROM ${quotedTable}`).one();
@@ -195,7 +200,10 @@ const readTablePage = (sql, options) => {
195
200
  const orderSql = order === void 0 ? "" : ` ORDER BY ${order.sql}`;
196
201
  const whereParams = predicate?.parameters ?? [];
197
202
  const orderParams = order?.params ?? [];
198
- const total = predicate === void 0 ? countRows(sql, quoted) : Number(sql.exec(`SELECT COUNT(*) AS c FROM ${quoted}${whereSql}`, ...whereParams).one().c);
203
+ let total;
204
+ if (!options.skipCount) {
205
+ total = predicate === void 0 ? countRows(sql, quoted) : Number(sql.exec(`SELECT COUNT(*) AS c FROM ${quoted}${whereSql}`, ...whereParams).one().c);
206
+ }
199
207
  const rawRows = sql.exec(`SELECT * FROM ${quoted}${whereSql}${orderSql} LIMIT ? OFFSET ?`, ...whereParams, ...orderParams, limit, offset).toArray();
200
208
  return withReferences({ ...expandDocumentRows(columns, rawRows), total });
201
209
  };
@@ -309,5 +317,43 @@ const summarizeSubscriptions = (attachments) => {
309
317
  const totalSubscriptions = connections.reduce((sum, connection) => sum + connection.subscriptions.length, 0);
310
318
  return { connections, totalConnections: connections.length, totalSubscriptions };
311
319
  };
320
+ const DEFAULT_FANOUT_TOPIC_LIMIT = 20;
321
+ const createFanoutCounters = () => {
322
+ return { maxMs: 0, passes: 0, peakSocketsIterated: 0, socketsDelivered: 0, socketsIterated: 0, totalMs: 0 };
323
+ };
324
+ const recordFanoutPass = (counters, iterated, delivered, ms) => {
325
+ return {
326
+ maxMs: Math.max(counters.maxMs, ms),
327
+ passes: counters.passes + 1,
328
+ peakSocketsIterated: Math.max(counters.peakSocketsIterated, iterated),
329
+ socketsDelivered: counters.socketsDelivered + delivered,
330
+ socketsIterated: counters.socketsIterated + iterated,
331
+ totalMs: counters.totalMs + ms
332
+ };
333
+ };
334
+ const summarizeFanoutTopics = (attachments, limit = DEFAULT_FANOUT_TOPIC_LIMIT) => {
335
+ const shapeCounts = /* @__PURE__ */ new Map();
336
+ const whisperCounts = /* @__PURE__ */ new Map();
337
+ for (const attachment of attachments) {
338
+ for (const shape of Object.values(attachment.shapes ?? {})) {
339
+ const key = shape.name ?? "(unknown shape)";
340
+ shapeCounts.set(key, (shapeCounts.get(key) ?? 0) + 1);
341
+ }
342
+ for (const topic of attachment.whispers ?? []) {
343
+ whisperCounts.set(topic, (whisperCounts.get(topic) ?? 0) + 1);
344
+ }
345
+ }
346
+ const topics = [
347
+ ...[...shapeCounts].map(([topic, subscribers]) => {
348
+ return { kind: "shape", subscribers, topic };
349
+ }),
350
+ ...[...whisperCounts].map(([topic, subscribers]) => {
351
+ return { kind: "whisper", subscribers, topic };
352
+ })
353
+ ];
354
+ topics.sort((a, b) => b.subscribers - a.subscribers || a.topic.localeCompare(b.topic));
355
+ const peakSubscribers = topics[0]?.subscribers ?? 0;
356
+ return { peakSubscribers, topics: topics.slice(0, limit), totalConnections: attachments.length };
357
+ };
312
358
 
313
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
359
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, DEFAULT_FANOUT_TOPIC_LIMIT, FLAGS_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, createFanoutCounters, facetColumn, findStorageReferences, listTables, readTablePage, recordFanoutPass, selectMatchingIds, summarizeFanoutTopics, summarizeSubscriptions };
@@ -26,9 +26,13 @@ const appendCdcChange = (sql$1, ts, table, id, op, doc) => {
26
26
  const readCdcChanges = (sql$1, options = {}) => {
27
27
  const sinceSeq = options.sinceSeq ?? 0;
28
28
  const limit = Math.max(1, Math.min(options.limit ?? 1e3, 1e4));
29
+ const tableFilter = options.tables && options.tables.size > 0 ? sql` AND ${sql.identifier("table")} IN (${sql.join(
30
+ [...options.tables].map((table) => sql`${table}`),
31
+ sql`, `
32
+ )})` : sql``;
29
33
  const rows = runDrizzle(
30
34
  sql$1,
31
- sql`SELECT seq, ts, ${sql.identifier("table")}, id, op, doc FROM ${sql.identifier(CDC_LOG_TABLE)} WHERE seq > ${sinceSeq} ORDER BY seq ASC LIMIT ${limit}`
35
+ sql`SELECT seq, ts, ${sql.identifier("table")}, id, op, doc FROM ${sql.identifier(CDC_LOG_TABLE)} WHERE seq > ${sinceSeq}${tableFilter} ORDER BY seq ASC LIMIT ${limit}`
32
36
  ).toArray();
33
37
  const changes = rows.map((row) => {
34
38
  const base = { id: row.id, op: row.op, seq: row.seq, table: row.table, ts: row.ts };
@@ -229,7 +229,11 @@ const runDataMigration = async (options) => {
229
229
  throw error;
230
230
  }
231
231
  if (!dryRun && !isDone) {
232
- releaseClaim(sql, migration.id);
232
+ try {
233
+ releaseClaim(sql, migration.id);
234
+ } catch (error) {
235
+ console.warn(`data migration "${migration.id}": releaseClaim failed`, error);
236
+ }
233
237
  }
234
238
  return { changed, cursor: isDone ? null : cursor, direction, dryRun, id: migration.id, processed, status: isDone ? "completed" : "in_progress" };
235
239
  };
@@ -1,4 +1,4 @@
1
- import { distinctValues } from './applyOnDelete-CMif2RKw.mjs';
1
+ import { distinctValues } from './applyOnDelete-BQ-8ZlZ1.mjs';
2
2
 
3
3
  const RELATION_EXISTS_KEY = "__relationExists";
4
4
 
@@ -156,5 +156,54 @@ const resolveRelationPredicates = async (where, options) => {
156
156
  schema: options.schema
157
157
  });
158
158
  };
159
+ const firstShardedHit = (branches, schema, tableName) => {
160
+ for (const branch of branches) {
161
+ const hit = findShardedRelationTarget(branch, schema, tableName);
162
+ if (hit) {
163
+ return hit;
164
+ }
165
+ }
166
+ return void 0;
167
+ };
168
+ const inspectKey = (key, value, schema, tableName) => {
169
+ if (key === "AND" || key === "OR") {
170
+ return firstShardedHit(branchesOf(value), schema, tableName);
171
+ }
172
+ if (key === "NOT") {
173
+ return firstShardedHit([value ?? {}], schema, tableName);
174
+ }
175
+ const relation = schema.tables[tableName]?.relationMap?.[key];
176
+ if (!relation || !isRelationPredicate(value)) {
177
+ return void 0;
178
+ }
179
+ if (schema.tables[relation.table]?.shardMode?.kind === "shardBy") {
180
+ return { relation: key, target: relation.table };
181
+ }
182
+ return firstShardedHit(Object.values(value), schema, relation.table);
183
+ };
184
+ const findShardedRelationTarget = (where, schema, tableName) => {
185
+ for (const key of Object.keys(where)) {
186
+ const hit = inspectKey(key, where[key], schema, tableName);
187
+ if (hit) {
188
+ return hit;
189
+ }
190
+ }
191
+ return void 0;
192
+ };
193
+ const assertShapeShardable = (effectiveWhere, schema, table) => {
194
+ if (!effectiveWhere) {
195
+ return;
196
+ }
197
+ const offending = findShardedRelationTarget(effectiveWhere, schema, table);
198
+ if (!offending) {
199
+ return;
200
+ }
201
+ throw Object.assign(
202
+ new Error(
203
+ `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.`
204
+ ),
205
+ { code: "SHAPE_CROSS_SHARD_JOIN", name: "LunoraError", status: 400 }
206
+ );
207
+ };
159
208
 
160
- export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
209
+ export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
@@ -1,27 +1,29 @@
1
1
  import { sql } from 'drizzle-orm';
2
- import { matchesStaticWhere, aggregateSqlFunction, normalizeCountArgument, throwingScheduler } from './matchesStaticWhere-CFk6adSu.mjs';
2
+ import { matchesStaticWhere, aggregateSqlFunction, normalizeCountArgument, throwingScheduler } from './AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs';
3
3
  import { encodeAggregateKey, foldAggregateTally, aggregateTableName, coerceAggregateNumber, readAggregateValue } from './aggregateTableName-CxNqY1Sl.mjs';
4
4
  import { mergeWhere, CountRlsUnsupportedError, selectIndexForGroupBy, selectIndexForCount, selectIndexForAggregate } from './CountRlsUnsupportedError-28ZvvwKS.mjs';
5
- import { appendCdcChange } from './applyCdcChanges-Ctdmxmrv.mjs';
6
- export { CDC_LOG_TABLE, CDC_META_TABLE, applyCdcChanges, bumpCdcEpoch, migrateCdcLog, migrateCdcMeta, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges } from './applyCdcChanges-Ctdmxmrv.mjs';
5
+ import { appendCdcChange } from './CDC_LOG_TABLE-DSycmnDf.mjs';
6
+ export { CDC_LOG_TABLE, applyCdcChanges, bumpCdcEpoch, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges } from './CDC_LOG_TABLE-DSycmnDf.mjs';
7
7
  import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
8
8
  import { i as isFtsAvailable, D as DOC_COLUMN$1, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT, d as aggUpsertSql, j as jsonPathSql, q as quoteIdentifier, t as tableColumns, e as qualifiedJsonPathSql } from './do-sql-BCHCWtrD.mjs';
9
9
  import { param } from './renderSql-D6eUcn2N.mjs';
10
- import { s as sortColumnName, m as matchesRankStaticWhere, e as encodePartitionKey, b as serializeSqlValue, r as rankTableName, a as resolveRankPartition, R as RANK_TIEBREAK } from './rank-CrkEIpF4.mjs';
11
- import { stringifySearchText, ftsTableName, tokenizeSearch, buildFtsMatch, scoreDocument } from './ftsTableName-BLEMawrp.mjs';
10
+ import { sortColumnName, matchesRankStaticWhere, encodePartitionKey, rankTableName, resolveRankPartition, RANK_TIEBREAK } from './RANK_TIEBREAK-CXhdcA1o.mjs';
11
+ import { stringifySearchText, ftsTableName, tokenizeSearch, buildFtsMatch, scoreDocument } from './buildFtsMatch-BLEMawrp.mjs';
12
+ import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
12
13
  import { SCAN_DEP } from './SCAN_DEP-DLJF8dsj.mjs';
13
- import { decodeCursor, normalizeOrderKeys, buildSeekWhere, encodeCursor, buildSeekBeforeWhere } from './buildSeekWhere-lVsNXSLy.mjs';
14
+ import { decodeCursor, normalizeOrderKeys, buildSeekWhere, applySelect, encodeCursor, softDeleteScope, buildSeekBeforeWhere } from './applySelect-BvZdFUBT.mjs';
14
15
  import NotFoundError from './NotFoundError-CMuMZt81.mjs';
15
- import { assertFlatPredicate, resolveRelationPredicates } from './assertFlatPredicate-DyVYReuT.mjs';
16
- import { runRowValidators, resolveWith, applyOnDelete } from './applyOnDelete-CMif2RKw.mjs';
17
- import { guardWriter } from './guardWriter-u3UlnCH5.mjs';
16
+ import { assertFlatPredicate, resolveRelationPredicates } from './DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs';
17
+ import { runRowValidators, resolveWith, applyOnDelete, fanOutScalarCounts } from './applyOnDelete-BQ-8ZlZ1.mjs';
18
+ import { guardWriter } from './RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
18
19
  import { createSystemReader } from './createSystemReader-8CzSZP9V.mjs';
19
20
  import { ConflictError } from './ConflictError-C0STs6bU.mjs';
20
- import { runTriggers } from './runTriggers-5N6_Fx0A.mjs';
21
- import { compileWhereSql } from './compileWhereSql-CXrhFA3G.mjs';
22
- export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-BF5eL7kW.mjs';
23
- export { I as IDEMPOTENCY_TABLE, m as migrateIdempotency, r as readIdempotent, t as trimIdempotent, w as writeIdempotent } from './ctx-db-idempotency-DkC9rP91.mjs';
24
- export { runShardMigrations } from './runShardMigrations-C3bn5r93.mjs';
21
+ import { runTriggers } from './hasTrigger-5N6_Fx0A.mjs';
22
+ import { compileWhereSql } from './compileWhereSql-MW_Lk8nJ.mjs';
23
+ export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-BZsOqDXP.mjs';
24
+ export { C as CLIENT_WATERMARK_TABLE, G as GLOBAL_SHAPE_SNAPSHOT_TABLE, I as IDEMPOTENCY_TABLE, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, e as deleteGlobalShapeSnapshotsForConnection, m as migrateClientWatermark, b as migrateGlobalShapeSnapshot, r as readClientWatermark, f as readGlobalShapeSnapshot, g as readIdempotent, t as trimIdempotent, w as writeGlobalShapeSnapshot, h as writeIdempotent } from './ctx-db-idempotency-BdcNpvY4.mjs';
25
+ export { runShardMigrations } from './runShardMigrations-nIwoQeOK.mjs';
26
+ export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-0RaIOy7J.mjs';
25
27
 
26
28
  const rankIndexFieldsUnchanged = (index, previous, next) => {
27
29
  const fields = [...index.partitionBy ?? [], ...index.sortBy.map((key) => key.field), ...index.where ? Object.keys(index.where) : []];
@@ -534,7 +536,7 @@ const createSearchBuilder = (search, tableName) => {
534
536
  };
535
537
  return builder;
536
538
  };
537
- const searchViaFts = (sql$1, tableName, search, limit) => {
539
+ const searchViaFts = (sql$1, tableName, search, limit, scopeCondition) => {
538
540
  const tokens = tokenizeSearch(search.query);
539
541
  if (tokens.length === 0) {
540
542
  return [];
@@ -544,6 +546,9 @@ const searchViaFts = (sql$1, tableName, search, limit) => {
544
546
  for (const filter of search.filters) {
545
547
  whereClauses.push(sql`${jsonPathSql(filter.field)} = ${serializeSqlValue(filter.value)}`);
546
548
  }
549
+ if (scopeCondition) {
550
+ whereClauses.push(scopeCondition);
551
+ }
547
552
  let query = sql`SELECT m.id, m._creationTime, m.${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(ftName)} f JOIN ${sql.identifier(tableName)} m ON m.id = f.${sql.identifier("__id__")} WHERE ${sql.join(whereClauses, sql` AND `)} ORDER BY f.rank, m._creationTime DESC`;
548
553
  if (typeof limit === "number") {
549
554
  query = sql`${query} LIMIT ${sql.raw(String(Math.max(0, Math.floor(limit))))}`;
@@ -558,7 +563,7 @@ const searchViaFts = (sql$1, tableName, search, limit) => {
558
563
  }
559
564
  return docs;
560
565
  };
561
- const searchViaScan = (sql$1, tableName, search, limit) => {
566
+ const searchViaScan = (sql$1, tableName, search, limit, scopeCondition) => {
562
567
  const tokens = tokenizeSearch(search.query);
563
568
  if (tokens.length === 0) {
564
569
  return [];
@@ -567,6 +572,9 @@ const searchViaScan = (sql$1, tableName, search, limit) => {
567
572
  for (const filter of search.filters) {
568
573
  whereClauses.push(sql`${jsonPathSql(filter.field)} = ${serializeSqlValue(filter.value)}`);
569
574
  }
575
+ if (scopeCondition) {
576
+ whereClauses.push(scopeCondition);
577
+ }
570
578
  let query = sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(tableName)}`;
571
579
  if (whereClauses.length > 0) {
572
580
  query = sql`${query} WHERE ${sql.join(whereClauses, sql` AND `)}`;
@@ -660,11 +668,12 @@ const scanDocs = (rows, filters, cap) => {
660
668
  }
661
669
  return docs;
662
670
  };
663
- const paginateStage = (sql$1, tableName, stage, options) => {
671
+ const paginateStage = (sql$1, tableName, stage, options, scopeCondition) => {
664
672
  const numberItems = Math.max(0, Math.floor(options.numItems));
665
673
  const orderKeys = paginateOrderKeys(stage);
666
674
  const bounded = typeof options.endCursor === "string";
667
- const whereCondition = compileWhereSql(paginateWhere(stage, orderKeys, options.cursor, options.endCursor), doWhereSqlStrategy);
675
+ const pageWhere = compileWhereSql(paginateWhere(stage, orderKeys, options.cursor, options.endCursor), doWhereSqlStrategy);
676
+ const whereCondition = scopeCondition && pageWhere ? sql`${pageWhere} AND ${scopeCondition}` : scopeCondition ?? pageWhere;
668
677
  let query = sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(tableName)}`;
669
678
  if (whereCondition) {
670
679
  query = sql`${query} WHERE ${whereCondition}`;
@@ -721,6 +730,8 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
721
730
  if (!tableDefinition) {
722
731
  throw new Error(`unknown table: ${tableName}`);
723
732
  }
733
+ const scopeWhere = softDeleteScope(tableDefinition.softDeleteMode, void 0);
734
+ const scopeCondition = scopeWhere ? compileWhereSql(scopeWhere, doWhereSqlStrategy) : void 0;
724
735
  const stage = {
725
736
  indexFields: [],
726
737
  indexName: void 0,
@@ -735,7 +746,7 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
735
746
  }
736
747
  const filtered = stage.inMemoryFilters.length > 0;
737
748
  const engineLimit = filtered ? void 0 : limit;
738
- const docs = isFtsAvailable(sql$1) ? searchViaFts(sql$1, tableName, search, engineLimit) : searchViaScan(sql$1, tableName, search, engineLimit);
749
+ const docs = isFtsAvailable(sql$1) ? searchViaFts(sql$1, tableName, search, engineLimit, scopeCondition) : searchViaScan(sql$1, tableName, search, engineLimit, scopeCondition);
739
750
  if (!filtered) {
740
751
  return docs;
741
752
  }
@@ -766,6 +777,9 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
766
777
  for (const condition of stage.sqlConditions) {
767
778
  whereClauses.push(sql`${jsonPathSql(condition.field)} ${sql.raw(condition.comparator)} ${serializeSqlValue(condition.value)}`);
768
779
  }
780
+ if (scopeCondition) {
781
+ whereClauses.push(scopeCondition);
782
+ }
769
783
  let query = sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(tableName)}`;
770
784
  if (whereClauses.length > 0) {
771
785
  query = sql`${query} WHERE ${sql.join(whereClauses, sql` AND `)}`;
@@ -813,7 +827,7 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
813
827
  if (stage.search) {
814
828
  throw new Error("pagination is not supported on search queries; use .take(n) or .collect()");
815
829
  }
816
- return paginateStage(sql$1, tableName, stage, options);
830
+ return paginateStage(sql$1, tableName, stage, options, scopeCondition);
817
831
  },
818
832
  // eslint-disable-next-line @typescript-eslint/require-await -- TableReaderLike returns Promises (the D1 twin awaits real I/O); the DO impl is synchronous over local SQLite
819
833
  async take(limit) {
@@ -997,7 +1011,6 @@ const createShardCtxDb = (options) => {
997
1011
  };
998
1012
  const globalFallback = () => globalDb;
999
1013
  const relationFetcher = (relationTable, relationArgs) => routeBackend(relationTable, "relation load").findMany(relationTable, relationArgs);
1000
- const relationCounter = (relationTable, relationWhere) => routeBackend(relationTable, "relation load").count(relationTable, relationWhere);
1001
1014
  const relationPredicateFetcher = (relationTable, relationArgs) => {
1002
1015
  if (isGlobalTable(relationTable)) {
1003
1016
  onRead(relationTable, SCAN_DEP);
@@ -1015,6 +1028,31 @@ const createShardCtxDb = (options) => {
1015
1028
  schema,
1016
1029
  tableName: predicateTable
1017
1030
  });
1031
+ const relationGroupedCounter = async (relationTable, whereField, values, policyWhere) => {
1032
+ const globalWriter = globalWriterFor(relationTable, "relation grouped count");
1033
+ if (globalWriter) {
1034
+ onRead(relationTable, SCAN_DEP);
1035
+ return fanOutScalarCounts((t, w) => globalWriter.count(t, w), relationTable, whereField, values, policyWhere);
1036
+ }
1037
+ const definition = schema.tables[relationTable];
1038
+ if (!definition) {
1039
+ throw new Error(`unknown table: ${relationTable}`);
1040
+ }
1041
+ onRead(relationTable, SCAN_DEP);
1042
+ const softScope = softDeleteScope(definition.softDeleteMode, void 0);
1043
+ const inFilter = { [whereField]: { in: values } };
1044
+ const combined = mergeWhere(mergeWhere(inFilter, policyWhere), softScope);
1045
+ const resolvedCombined = await resolveAggregateRelations(combined, relationTable, void 0);
1046
+ const whereCondition = compileWhereSql(resolvedCombined, doWhereSqlStrategy);
1047
+ const fieldSql = jsonPathSql(whereField);
1048
+ let query = sql`SELECT ${fieldSql} AS __fk__, COUNT(*) AS count FROM ${sql.identifier(relationTable)}`;
1049
+ if (whereCondition) {
1050
+ query = sql`${query} WHERE ${whereCondition}`;
1051
+ }
1052
+ query = sql`${query} GROUP BY ${fieldSql}`;
1053
+ const rows = runDrizzle(sql$1, query).toArray();
1054
+ return new Map(rows.map((row) => [row["__fk__"], row.count]));
1055
+ };
1018
1056
  let triggerDepth = 0;
1019
1057
  const triggerMatchers = /* @__PURE__ */ new Set();
1020
1058
  for (const [tableName, definition] of Object.entries(schema.tables)) {
@@ -1130,10 +1168,11 @@ const createShardCtxDb = (options) => {
1130
1168
  throw new Error(`aggregate(${tableName}, { op: "${aggOptions.op}" }): "field" is required for non-count reducers`);
1131
1169
  }
1132
1170
  onRead(tableName, SCAN_DEP);
1133
- const effective = mergeWhere(aggOptions.baseWhere, aggOptions.where);
1171
+ const aggScope = softDeleteScope(definition.softDeleteMode, void 0);
1172
+ const effective = mergeWhere(mergeWhere(aggOptions.baseWhere, aggOptions.where), aggScope);
1134
1173
  const resolved = await resolveAggregateRelations(effective, tableName, aggOptions.relationBaseWhere);
1135
1174
  const hasRelation = resolved !== effective;
1136
- if (definition.aggregateIndexes && !aggOptions.baseWhere && !hasRelation) {
1175
+ if (definition.aggregateIndexes && !aggOptions.baseWhere && !hasRelation && !aggScope) {
1137
1176
  const planned = selectIndexForAggregate(definition.aggregateIndexes, aggOptions.op, aggOptions.field, aggOptions.where);
1138
1177
  if (planned) {
1139
1178
  ensureBackfilled(tableName, planned.index);
@@ -1175,10 +1214,11 @@ const createShardCtxDb = (options) => {
1175
1214
  throw new CountRlsUnsupportedError(tableName);
1176
1215
  }
1177
1216
  onRead(tableName, SCAN_DEP);
1178
- const effective = mergeWhere(countOptions.baseWhere, countOptions.where);
1217
+ const countScope = softDeleteScope(definition.softDeleteMode, void 0);
1218
+ const effective = mergeWhere(mergeWhere(countOptions.baseWhere, countOptions.where), countScope);
1179
1219
  const resolved = await resolveAggregateRelations(effective, tableName, countOptions.relationBaseWhere);
1180
1220
  const hasRelation = resolved !== effective;
1181
- if (definition.aggregateIndexes && !countOptions.baseWhere && !hasRelation) {
1221
+ if (definition.aggregateIndexes && !countOptions.baseWhere && !hasRelation && !countScope) {
1182
1222
  const planned = selectIndexForCount(definition.aggregateIndexes, countOptions.where);
1183
1223
  if (planned) {
1184
1224
  ensureBackfilled(tableName, planned.index);
@@ -1199,16 +1239,22 @@ const createShardCtxDb = (options) => {
1199
1239
  const row = runDrizzle(sql$1, query).one();
1200
1240
  return row.count;
1201
1241
  },
1202
- async delete(id, expectedTable) {
1242
+ async delete(id, expectedTable, deleteOptions) {
1203
1243
  const located = locateRowById(id, expectedTable);
1204
1244
  if (!located) {
1205
1245
  const global = expectedTable === void 0 ? globalFallback() : void 0;
1206
1246
  if (global) {
1207
- await global.delete(id);
1247
+ await global.delete(id, void 0, deleteOptions);
1208
1248
  }
1209
1249
  return;
1210
1250
  }
1211
1251
  const { docJson: existingJson, row: existing, tableName } = located;
1252
+ const definition = schema.tables[tableName];
1253
+ const hard = deleteOptions?.hard === true;
1254
+ const softField = !hard && definition?.softDeleteMode ? definition.softDeleteMode.field : void 0;
1255
+ if (softField && existing[softField] !== null && existing[softField] !== void 0) {
1256
+ return;
1257
+ }
1212
1258
  if (hasMatchingTrigger(tableName, "before", "delete")) {
1213
1259
  await fireTriggers("before", "delete", { id, op: "delete", previous: existing, table: tableName });
1214
1260
  }
@@ -1216,10 +1262,10 @@ const createShardCtxDb = (options) => {
1216
1262
  deletedId: id,
1217
1263
  deletedReference: (references) => existing[references],
1218
1264
  findHolders: async (holderTable, field, value) => {
1219
- const holders = await routeForHolder(holderTable).findMany(holderTable, { where: { [field]: value } });
1265
+ const holders = await routeForHolder(holderTable).findMany(holderTable, { includeDeleted: hard, where: { [field]: value } });
1220
1266
  return holders.page;
1221
1267
  },
1222
- onCascade: (holderTable, holderId) => routeForHolder(holderTable).delete(holderId),
1268
+ onCascade: (holderTable, holderId) => routeForHolder(holderTable).delete(holderId, void 0, deleteOptions),
1223
1269
  onRestrict: (message) => {
1224
1270
  throw new ConflictError(message, "restrict");
1225
1271
  },
@@ -1230,6 +1276,25 @@ const createShardCtxDb = (options) => {
1230
1276
  });
1231
1277
  ensureBackfilledForTable(tableName);
1232
1278
  ensureRankBackfilledForTable(tableName);
1279
+ if (softField) {
1280
+ const merged = { ...existing, [softField]: clock(), _id: id };
1281
+ runGuardedWrite(
1282
+ sql$1,
1283
+ tableName,
1284
+ sql`UPDATE ${sql.identifier(tableName)} SET ${sql.identifier(DOC_COLUMN$1)} = ${JSON.stringify(merged)} WHERE id = ${id} AND ${sql.identifier(DOC_COLUMN$1)} = ${existingJson}`
1285
+ );
1286
+ syncSearch(tableName, id, merged);
1287
+ syncAggregates(tableName, existing, merged);
1288
+ syncRanks(tableName, id, existing, void 0);
1289
+ cache?.invalidate(tableName, id);
1290
+ recordCdc(tableName, id, "update", merged);
1291
+ broadcast({ key: id, op: "update", row: merged, table: tableName });
1292
+ if (hasMatchingTrigger(tableName, "after", "delete")) {
1293
+ await fireTriggers("after", "delete", { id, op: "delete", previous: existing, table: tableName });
1294
+ }
1295
+ await onWrite({ id, op: "delete", table: tableName });
1296
+ return;
1297
+ }
1233
1298
  runGuardedWrite(
1234
1299
  sql$1,
1235
1300
  tableName,
@@ -1271,7 +1336,8 @@ const createShardCtxDb = (options) => {
1271
1336
  onRead(tableName, SCAN_DEP);
1272
1337
  return global.findMany(tableName, args);
1273
1338
  }
1274
- if (!schema.tables[tableName]) {
1339
+ const findManyDefinition = schema.tables[tableName];
1340
+ if (!findManyDefinition) {
1275
1341
  throw new Error(`unknown table: ${tableName}`);
1276
1342
  }
1277
1343
  const isFullScan = !args.where && !args.baseWhere;
@@ -1283,6 +1349,7 @@ const createShardCtxDb = (options) => {
1283
1349
  const orderKeys = normalizeOrderKeys(args.orderBy);
1284
1350
  const seek = args.cursor ? buildSeekWhere(orderKeys, decodeCursor(args.cursor)) : void 0;
1285
1351
  let predicate = mergeWhere(args.baseWhere, args.where);
1352
+ predicate = mergeWhere(predicate, softDeleteScope(findManyDefinition.softDeleteMode, args.includeDeleted));
1286
1353
  predicate = await resolveRelationPredicates(predicate, {
1287
1354
  canPushExists: relationExistsPushDownEnabled ? canPushRelationExists : void 0,
1288
1355
  existsPushMode: relationExistsPushDown === "always" ? "always" : "auto",
@@ -1320,7 +1387,7 @@ const createShardCtxDb = (options) => {
1320
1387
  if (limit === void 0) {
1321
1388
  if (args.with) {
1322
1389
  await resolveWith({
1323
- counter: relationCounter,
1390
+ groupedCounter: relationGroupedCounter,
1324
1391
  fetcher: relationFetcher,
1325
1392
  parents: docs,
1326
1393
  relationBaseWhere: args.relationBaseWhere,
@@ -1329,15 +1396,15 @@ const createShardCtxDb = (options) => {
1329
1396
  with: args.with
1330
1397
  });
1331
1398
  }
1332
- return { continueCursor: null, isDone: true, page: docs };
1399
+ return { continueCursor: null, isDone: true, page: applySelect(docs, args.select, args.with) };
1333
1400
  }
1334
1401
  const hasMore = docs.length > limit;
1335
1402
  const page = hasMore ? docs.slice(0, limit) : docs;
1336
1403
  const last = page.at(-1);
1337
1404
  if (args.with) {
1338
1405
  await resolveWith({
1339
- counter: relationCounter,
1340
1406
  fetcher: relationFetcher,
1407
+ groupedCounter: relationGroupedCounter,
1341
1408
  parents: page,
1342
1409
  relationBaseWhere: args.relationBaseWhere,
1343
1410
  schema,
@@ -1346,10 +1413,12 @@ const createShardCtxDb = (options) => {
1346
1413
  });
1347
1414
  }
1348
1415
  return {
1416
+ // The cursor is encoded from `last` (the full, unprojected row) above,
1417
+ // so `applySelect` only trims the returned payload — paging is intact.
1349
1418
  // eslint-disable-next-line unicorn/no-null -- QueryPage.continueCursor is `null | string`: null is the documented "no further page" cursor on the wire
1350
1419
  continueCursor: hasMore && last ? encodeCursor(last, orderKeys) : null,
1351
1420
  isDone: !hasMore,
1352
- page
1421
+ page: applySelect(page, args.select, args.with)
1353
1422
  };
1354
1423
  },
1355
1424
  async get(id, expectedTable) {
@@ -1390,10 +1459,11 @@ const createShardCtxDb = (options) => {
1390
1459
  if (agg.op !== "count" && !agg.field) {
1391
1460
  throw new Error(`groupBy(${tableName}, { agg: { op: "${agg.op}" } }): "field" is required for non-count reducers`);
1392
1461
  }
1393
- const effective = mergeWhere(groupOptions.baseWhere, groupOptions.where);
1462
+ const groupScope = softDeleteScope(definition.softDeleteMode, void 0);
1463
+ const effective = mergeWhere(mergeWhere(groupOptions.baseWhere, groupOptions.where), groupScope);
1394
1464
  const resolved = await resolveAggregateRelations(effective, tableName, groupOptions.relationBaseWhere);
1395
1465
  const hasRelation = resolved !== effective;
1396
- if (definition.aggregateIndexes && !groupOptions.baseWhere && !hasRelation) {
1466
+ if (definition.aggregateIndexes && !groupOptions.baseWhere && !hasRelation && !groupScope) {
1397
1467
  const planned = selectIndexForGroupBy(definition.aggregateIndexes, agg.op, agg.field, groupOptions.by, groupOptions.where);
1398
1468
  if (planned) {
1399
1469
  ensureBackfilled(tableName, planned.index);
@@ -1696,6 +1766,26 @@ const createShardCtxDb = (options) => {
1696
1766
  const { directions, hasMore, rows } = computeRankPage(rankPageDeps, tableName, indexName, rankPageOptions);
1697
1767
  return { directions, hasMore, rows };
1698
1768
  },
1769
+ async restore(id, expectedTable) {
1770
+ const located = locateRowById(id, expectedTable);
1771
+ if (!located) {
1772
+ const global = expectedTable === void 0 ? globalFallback() : void 0;
1773
+ if (global?.restore) {
1774
+ await global.restore(id);
1775
+ return;
1776
+ }
1777
+ throw new Error(`document not found: ${id}`);
1778
+ }
1779
+ const field = schema.tables[located.tableName]?.softDeleteMode?.field;
1780
+ if (!field) {
1781
+ throw new Error(`ctx.db.restore: table "${located.tableName}" is not a .softDelete() table`);
1782
+ }
1783
+ const wasDeleted = located.row[field] !== null && located.row[field] !== void 0;
1784
+ await writer.patch(id, { [field]: null }, expectedTable);
1785
+ if (wasDeleted) {
1786
+ syncRanks(located.tableName, id, void 0, located.row);
1787
+ }
1788
+ },
1699
1789
  async replace(id, document, expectedTable) {
1700
1790
  const located = locateRowById(id, expectedTable);
1701
1791
  if (!located) {
@@ -1742,4 +1832,4 @@ const createShardCtxDb = (options) => {
1742
1832
  return options.enforceRls === true ? guardWriter(writer, schema, (id, expectedTable) => locateRowById(id, expectedTable)?.tableName) : writer;
1743
1833
  };
1744
1834
 
1745
- export { NotUniqueError, appendCdcChange, assertValidClientId, createShardCtxDb, normalizeIdStructurally };
1835
+ export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally };
@@ -1,15 +1,4 @@
1
- const serializeSqlValue = (value) => {
2
- if (typeof value === "boolean") {
3
- return value ? 1 : 0;
4
- }
5
- if (value === null || typeof value === "string" || typeof value === "number") {
6
- return value;
7
- }
8
- if (typeof value === "bigint") {
9
- return value.toString();
10
- }
11
- return JSON.stringify(value);
12
- };
1
+ import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
13
2
 
14
3
  const compareStrings = (a, b) => {
15
4
  if (a < b) {
@@ -99,4 +88,4 @@ const rankKeyFromDocument = (index, document_) => {
99
88
  };
100
89
  };
101
90
 
102
- export { RANK_TIEBREAK as R, resolveRankPartition as a, serializeSqlValue as b, rankKeyFromDocument as c, encodePartitionKey as e, matchesRankStaticWhere as m, rankTableName as r, sortColumnName as s };
91
+ export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankKeyFromDocument as rankKeyFromDoc, rankTableName, resolveRankPartition, sortColumnName };