@lunora/do 1.0.0-alpha.4 → 1.0.0-alpha.41

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 (52) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +5236 -3449
  4. package/dist/index.d.ts +5236 -3449
  5. package/dist/index.mjs +34 -25
  6. package/dist/packem_shared/{ADMIN_FUNCTIONS-Dzdqq5J2.mjs → ADMIN_FUNCTIONS-CnZkbXH_.mjs} +69 -8
  7. package/dist/packem_shared/{AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs → AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs} +5 -3
  8. package/dist/packem_shared/{CDC_LOG_TABLE-Ctdmxmrv.mjs → CDC_LOG_TABLE-uwOJxJJZ.mjs} +8 -4
  9. package/dist/packem_shared/{ConflictError-C0STs6bU.mjs → ConflictError-CLoq37xH.mjs} +4 -5
  10. package/dist/packem_shared/{CountRlsUnsupportedError-28ZvvwKS.mjs → CountRlsUnsupportedError-BGxj0pgS.mjs} +4 -4
  11. package/dist/packem_shared/{DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs → DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs} +11 -3
  12. package/dist/packem_shared/{DEFAULT_MAX_RELATION_KEYS-DyVYReuT.mjs → DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs} +58 -7
  13. package/dist/packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs +114 -0
  14. package/dist/packem_shared/{MAX_SQL_ROWS-dDcFE1YZ.mjs → MAX_SQL_ROWS-iFAA8FbD.mjs} +35 -3
  15. package/dist/packem_shared/NotFoundError-C70b9hLw.mjs +9 -0
  16. package/dist/packem_shared/{NotUniqueError-B6tzuDmM.mjs → NotUniqueError-DbtlWwcG.mjs} +400 -114
  17. package/dist/packem_shared/{rank-CrkEIpF4.mjs → RANK_TIEBREAK-CXhdcA1o.mjs} +2 -13
  18. package/dist/packem_shared/{RLS_UNWRAP_SYMBOL-u3UlnCH5.mjs → RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs} +22 -9
  19. package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-B4Cpm7Qv.mjs +7653 -0
  20. package/dist/packem_shared/{ReactiveCache-ByVzgH3d.mjs → ReactiveCache-DnSvbjil.mjs} +4 -30
  21. package/dist/packem_shared/{SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs → SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs} +16 -27
  22. package/dist/packem_shared/{SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs → SHARD_REGISTRY_DO_NAME-D99roc-r.mjs} +12 -14
  23. package/dist/packem_shared/{applyOnDelete-CMif2RKw.mjs → applyOnDelete-BXSq3S70.mjs} +23 -12
  24. package/dist/packem_shared/{buildSeekWhere-lVsNXSLy.mjs → applySelect-WQY8m62C.mjs} +21 -2
  25. package/dist/packem_shared/{armRestore-BJk53Ro8.mjs → armRestore-4Px61hHS.mjs} +4 -10
  26. package/dist/packem_shared/{backfillAggregateIndexes-BbVPvciS.mjs → backfillAggregateIndexes-BGTwynwh.mjs} +4 -3
  27. package/dist/packem_shared/{compileWhereSql-CXrhFA3G.mjs → compileWhereSql-DE6yfRcQ.mjs} +5 -3
  28. package/dist/packem_shared/constant-time-equal-BVRWZgES.mjs +12 -0
  29. package/dist/packem_shared/context-telemetry-CDpyil58.mjs +223 -0
  30. package/dist/packem_shared/createMetrics-C4I2Yanj.mjs +1 -0
  31. package/dist/packem_shared/{createSystemReader-8CzSZP9V.mjs → createSystemReader-D12eNH13.mjs} +4 -2
  32. package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
  33. package/dist/packem_shared/ctx-db-shapes-DTeFiHYS.mjs +53 -0
  34. package/dist/packem_shared/diffExternalSource-CovHfdyo.mjs +44 -0
  35. package/dist/packem_shared/{do-sql-BCHCWtrD.mjs → do-sql-CGAgiQUz.mjs} +2 -1
  36. package/dist/packem_shared/{exportShardRows-DZEhUeyI.mjs → exportShardRows-Dy3oFZ26.mjs} +4 -3
  37. package/dist/packem_shared/isSoftDeleted-CFJmhjFP.mjs +217 -0
  38. package/dist/packem_shared/json-response-BdbtpOhm.mjs +3 -0
  39. package/dist/packem_shared/materializeExternalRows-BtEGs1Fv.mjs +45 -0
  40. package/dist/packem_shared/{runShardMigrations-PabobOjF.mjs → runShardMigrations-BVax6rYu.mjs} +21 -4
  41. package/dist/packem_shared/selectExpiredIds-C5W29Upb.mjs +18 -0
  42. package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
  43. package/dist/packem_shared/{serveRelationFanout-CFBKWJ8Q.mjs → serveRelationFanout-k7HTh3CC.mjs} +4 -7
  44. package/dist/packem_shared/stableStringify-mC40mZts.mjs +40 -0
  45. package/dist/packem_shared/stableWireKey-DKuXO7T5.mjs +6 -0
  46. package/dist/packem_shared/subscriptionListDeltas-CT76bYny.mjs +126 -0
  47. package/dist/packem_shared/wire-codec-CzQc1pvf.mjs +225 -0
  48. package/package.json +5 -3
  49. package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +0 -10
  50. package/dist/packem_shared/RANK_TIEBREAK-C6blLR5K.mjs +0 -1
  51. package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-3lZ2yigq.mjs +0 -4003
  52. package/dist/packem_shared/ctx-db-idempotency-DkC9rP91.mjs +0 -35
package/dist/index.mjs CHANGED
@@ -1,35 +1,44 @@
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/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs';
1
+ export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow } from './packem_shared/exportShardRows-Dy3oFZ26.mjs';
2
+ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs';
3
3
  export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
4
- export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs';
4
+ export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-BGxj0pgS.mjs';
5
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-B6tzuDmM.mjs';
7
- export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs';
6
+ export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-CDpyil58.mjs';
7
+ export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-DbtlWwcG.mjs';
8
+ export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
8
9
  export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
9
10
  export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
11
+ export { diffExternalSource } from './packem_shared/diffExternalSource-CovHfdyo.mjs';
12
+ export { materializeExternalRows, materializeExternalRowsIncremental, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-BtEGs1Fv.mjs';
13
+ export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick } from './packem_shared/isSoftDeleted-CFJmhjFP.mjs';
10
14
  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';
11
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-Dzdqq5J2.mjs';
15
+ export { GEO_DEFAULT_PRECISION, boundingBoxGeohashes, coveringGeohashes, encodeGeohash, haversineMeters, pointInBoundingBox } from './packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs';
16
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-CnZkbXH_.mjs';
12
17
  export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
13
18
  export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
14
- export { default as NotFoundError } from './packem_shared/NotFoundError-CMuMZt81.mjs';
15
- 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-CFBKWJ8Q.mjs';
20
- export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-DyVYReuT.mjs';
21
- export { applyOnDelete, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-CMif2RKw.mjs';
22
- export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-u3UlnCH5.mjs';
19
+ export { default as NotFoundError } from './packem_shared/NotFoundError-C70b9hLw.mjs';
20
+ export { armRestore, readBookmark } from './packem_shared/armRestore-4Px61hHS.mjs';
21
+ export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-WQY8m62C.mjs';
22
+ export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
23
+ export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-DnSvbjil.mjs';
24
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-k7HTh3CC.mjs';
25
+ export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs';
26
+ export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BXSq3S70.mjs';
27
+ export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs';
23
28
  export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
24
29
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
25
- 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-3lZ2yigq.mjs';
27
- 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/MAX_SQL_ROWS-dDcFE1YZ.mjs';
29
- export { createSystemReader } from './packem_shared/createSystemReader-8CzSZP9V.mjs';
30
- export { ConflictError } from './packem_shared/ConflictError-C0STs6bU.mjs';
30
+ export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs';
31
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-B4Cpm7Qv.mjs';
32
+ export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs';
33
+ export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs';
34
+ export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
35
+ export { ConflictError } from './packem_shared/ConflictError-CLoq37xH.mjs';
31
36
  export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
32
- export { compileWhereSql } from './packem_shared/compileWhereSql-CXrhFA3G.mjs';
33
- export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-Ctdmxmrv.mjs';
34
- export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BbVPvciS.mjs';
35
- export { runShardMigrations } from './packem_shared/runShardMigrations-PabobOjF.mjs';
37
+ export { selectExpiredIds } from './packem_shared/selectExpiredIds-C5W29Upb.mjs';
38
+ export { compileWhereSql } from './packem_shared/compileWhereSql-DE6yfRcQ.mjs';
39
+ export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs';
40
+ export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BGTwynwh.mjs';
41
+ export { runShardMigrations } from './packem_shared/runShardMigrations-BVax6rYu.mjs';
42
+ export { stableStringify } from './packem_shared/stableStringify-mC40mZts.mjs';
43
+ export { stableWireKey } from './packem_shared/stableWireKey-DKuXO7T5.mjs';
44
+ export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-CT76bYny.mjs';
@@ -1,9 +1,16 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
4
+
1
5
  const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2
6
  const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
7
+ const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
3
8
  const ADMIN_FUNCTIONS = {
4
9
  applyCdc: "__lunora_admin__:applyCdc",
10
+ assignIssue: "__lunora_admin__:assignIssue",
5
11
  cdcSync: "__lunora_admin__:cdcSync",
6
12
  clearCapturedMail: "__lunora_admin__:clearCapturedMail",
13
+ clearQueueMessages: "__lunora_admin__:clearQueueMessages",
7
14
  clearTable: "__lunora_admin__:clearTable",
8
15
  createWorkflowInstance: "__lunora_admin__:createWorkflowInstance",
9
16
  deleteRows: "__lunora_admin__:deleteRows",
@@ -15,18 +22,27 @@ const ADMIN_FUNCTIONS = {
15
22
  getAuditLog: "__lunora_admin__:getAuditLog",
16
23
  getAuthMetrics: "__lunora_admin__:getAuthMetrics",
17
24
  getCapturedMail: "__lunora_admin__:getCapturedMail",
25
+ getFanoutMetrics: "__lunora_admin__:getFanoutMetrics",
18
26
  getFunctionStats: "__lunora_admin__:getFunctionStats",
27
+ getIssues: "__lunora_admin__:getIssues",
28
+ getMetricHistory: "__lunora_admin__:getMetricHistory",
29
+ getMetricSeries: "__lunora_admin__:getMetricSeries",
19
30
  listSubscriptions: "__lunora_admin__:listSubscriptions",
20
31
  listTableIndexes: "__lunora_admin__:listTableIndexes",
21
32
  getLogs: "__lunora_admin__:getLogs",
22
33
  getMetrics: "__lunora_admin__:getMetrics",
23
34
  getPitrBookmark: "__lunora_admin__:getPitrBookmark",
35
+ getQueueMessages: "__lunora_admin__:getQueueMessages",
24
36
  getRequestLog: "__lunora_admin__:getRequestLog",
25
37
  getSecurityAudit: "__lunora_admin__:getSecurityAudit",
26
38
  getSettings: "__lunora_admin__:getSettings",
39
+ getTraces: "__lunora_admin__:getTraces",
27
40
  // eslint-disable-next-line no-secrets/no-secrets -- reserved admin RPC path constant, not a credential
28
41
  getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus",
42
+ ignoreIssue: "__lunora_admin__:ignoreIssue",
29
43
  importShard: "__lunora_admin__:importShard",
44
+ listFlags: "__lunora_admin__:listFlags",
45
+ listQueues: "__lunora_admin__:listQueues",
30
46
  listTables: "__lunora_admin__:listTables",
31
47
  listWorkflows: "__lunora_admin__:listWorkflows",
32
48
  maskPolicies: "__lunora_admin__:maskPolicies",
@@ -38,11 +54,16 @@ const ADMIN_FUNCTIONS = {
38
54
  recordAuthEvent: "__lunora_admin__:recordAuthEvent",
39
55
  recordContainerEvent: "__lunora_admin__:recordContainerEvent",
40
56
  recordMail: "__lunora_admin__:recordMail",
57
+ recordQueueMessage: "__lunora_admin__:recordQueueMessage",
58
+ replayQueueMessage: "__lunora_admin__:replayQueueMessage",
59
+ resolveIssue: "__lunora_admin__:resolveIssue",
41
60
  rlsPolicies: "__lunora_admin__:rlsPolicies",
42
61
  runAs: "__lunora_admin__:runAs",
43
62
  runMigration: "__lunora_admin__:runMigration",
44
63
  runSql: "__lunora_admin__:runSql",
64
+ sendQueueMessage: "__lunora_admin__:sendQueueMessage",
45
65
  sendTestMail: "__lunora_admin__:sendTestMail",
66
+ setIssueSeverity: "__lunora_admin__:setIssueSeverity",
46
67
  storageOrphans: "__lunora_admin__:storageOrphans",
47
68
  storageReferences: "__lunora_admin__:storageReferences",
48
69
  storageRules: "__lunora_admin__:storageRules",
@@ -91,7 +112,6 @@ const expandDocumentRows = (columns, rows) => {
91
112
  };
92
113
  const escapeLike = (value) => value.replaceAll(/[\\%_]/g, (character) => `\\${character}`);
93
114
  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
115
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
96
116
  const countRows = (sql, quotedTable) => {
97
117
  const row = sql.exec(`SELECT COUNT(*) AS c FROM ${quotedTable}`).one();
@@ -169,7 +189,7 @@ const buildOrderBy = (orderBy, physicalColumns) => {
169
189
  const readTablePage = (sql, options) => {
170
190
  const { table } = options;
171
191
  if (isInternalTable(table) || !tableExists(sql, table)) {
172
- throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
192
+ throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
173
193
  }
174
194
  const limit = clamp(Math.trunc(options.limit ?? DEFAULT_PAGE_SIZE), 1, MAX_PAGE_SIZE);
175
195
  const offset = Math.max(0, Math.trunc(options.offset ?? 0));
@@ -195,14 +215,17 @@ const readTablePage = (sql, options) => {
195
215
  const orderSql = order === void 0 ? "" : ` ORDER BY ${order.sql}`;
196
216
  const whereParams = predicate?.parameters ?? [];
197
217
  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);
218
+ let total;
219
+ if (!options.skipCount) {
220
+ total = predicate === void 0 ? countRows(sql, quoted) : Number(sql.exec(`SELECT COUNT(*) AS c FROM ${quoted}${whereSql}`, ...whereParams).one().c);
221
+ }
199
222
  const rawRows = sql.exec(`SELECT * FROM ${quoted}${whereSql}${orderSql} LIMIT ? OFFSET ?`, ...whereParams, ...orderParams, limit, offset).toArray();
200
223
  return withReferences({ ...expandDocumentRows(columns, rawRows), total });
201
224
  };
202
225
  const selectMatchingIds = (sql, options) => {
203
226
  const { table } = options;
204
227
  if (isInternalTable(table) || !tableExists(sql, table)) {
205
- throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
228
+ throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
206
229
  }
207
230
  const limit = clamp(Math.trunc(options.limit ?? MAX_PAGE_SIZE), 1, MAX_PAGE_SIZE);
208
231
  const quoted = quoteIdentifier(table);
@@ -233,16 +256,16 @@ const knownDisplayColumns = (sql, quotedTable, physicalColumns) => {
233
256
  const facetColumn = (sql, options) => {
234
257
  const { column, table } = options;
235
258
  if (isInternalTable(table) || !tableExists(sql, table)) {
236
- throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
259
+ throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
237
260
  }
238
261
  const quoted = quoteIdentifier(table);
239
262
  const physicalColumns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((info) => info.name);
240
263
  if (!knownDisplayColumns(sql, quoted, physicalColumns).has(column)) {
241
- throw Object.assign(new Error(`unknown column: ${column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
264
+ throw new LunoraError("UNKNOWN_COLUMN", `unknown column: ${column}`, { status: 404 });
242
265
  }
243
266
  const resolved = resolveColumnExpression(column, physicalColumns);
244
267
  if (resolved === void 0) {
245
- throw Object.assign(new Error(`unknown column: ${column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
268
+ throw new LunoraError("UNKNOWN_COLUMN", `unknown column: ${column}`, { status: 404 });
246
269
  }
247
270
  const limit = clamp(Math.trunc(options.limit ?? DEFAULT_FACET_LIMIT), 1, MAX_FACET_LIMIT);
248
271
  const needle = options.search?.trim() ?? "";
@@ -309,5 +332,43 @@ const summarizeSubscriptions = (attachments) => {
309
332
  const totalSubscriptions = connections.reduce((sum, connection) => sum + connection.subscriptions.length, 0);
310
333
  return { connections, totalConnections: connections.length, totalSubscriptions };
311
334
  };
335
+ const DEFAULT_FANOUT_TOPIC_LIMIT = 20;
336
+ const createFanoutCounters = () => {
337
+ return { maxMs: 0, passes: 0, peakSocketsIterated: 0, socketsDelivered: 0, socketsIterated: 0, totalMs: 0 };
338
+ };
339
+ const recordFanoutPass = (counters, iterated, delivered, ms) => {
340
+ return {
341
+ maxMs: Math.max(counters.maxMs, ms),
342
+ passes: counters.passes + 1,
343
+ peakSocketsIterated: Math.max(counters.peakSocketsIterated, iterated),
344
+ socketsDelivered: counters.socketsDelivered + delivered,
345
+ socketsIterated: counters.socketsIterated + iterated,
346
+ totalMs: counters.totalMs + ms
347
+ };
348
+ };
349
+ const summarizeFanoutTopics = (attachments, limit = DEFAULT_FANOUT_TOPIC_LIMIT) => {
350
+ const shapeCounts = /* @__PURE__ */ new Map();
351
+ const whisperCounts = /* @__PURE__ */ new Map();
352
+ for (const attachment of attachments) {
353
+ for (const shape of Object.values(attachment.shapes ?? {})) {
354
+ const key = shape.name ?? "(unknown shape)";
355
+ shapeCounts.set(key, (shapeCounts.get(key) ?? 0) + 1);
356
+ }
357
+ for (const topic of attachment.whispers ?? []) {
358
+ whisperCounts.set(topic, (whisperCounts.get(topic) ?? 0) + 1);
359
+ }
360
+ }
361
+ const topics = [
362
+ ...[...shapeCounts].map(([topic, subscribers]) => {
363
+ return { kind: "shape", subscribers, topic };
364
+ }),
365
+ ...[...whisperCounts].map(([topic, subscribers]) => {
366
+ return { kind: "whisper", subscribers, topic };
367
+ })
368
+ ];
369
+ topics.sort((a, b) => b.subscribers - a.subscribers || a.topic.localeCompare(b.topic));
370
+ const peakSubscribers = topics[0]?.subscribers ?? 0;
371
+ return { peakSubscribers, topics: topics.slice(0, limit), totalConnections: attachments.length };
372
+ };
312
373
 
313
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
374
+ 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 };
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const COUNT_OPTION_KEYS = /* @__PURE__ */ new Set(["baseWhere", "relationBaseWhere", "restrictsCounts", "where"]);
2
4
  const matchesStaticWhere = (document, predicate) => {
3
5
  for (const [field, expected] of Object.entries(predicate)) {
@@ -38,16 +40,16 @@ const AGGREGATE_SQL_FUNCTION = { avg: "AVG", count: "COUNT", max: "MAX", min: "M
38
40
  const aggregateSqlFunction = (op) => {
39
41
  const sqlFunction = AGGREGATE_SQL_FUNCTION[op];
40
42
  if (sqlFunction === void 0) {
41
- throw new Error(`unknown aggregate op "${op}": expected one of ${Object.keys(AGGREGATE_SQL_FUNCTION).join(", ")}`);
43
+ throw new LunoraError("INTERNAL", `unknown aggregate op "${op}": expected one of ${Object.keys(AGGREGATE_SQL_FUNCTION).join(", ")}`);
42
44
  }
43
45
  return sqlFunction;
44
46
  };
45
47
  const throwingScheduler = {
46
48
  runAfter: () => {
47
- throw new Error("ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
49
+ throw new LunoraError("INTERNAL", "ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
48
50
  },
49
51
  runAt: () => {
50
- throw new Error("ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
52
+ throw new LunoraError("INTERNAL", "ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
51
53
  }
52
54
  };
53
55
 
@@ -1,6 +1,6 @@
1
1
  import { sql } from 'drizzle-orm';
2
2
  import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
3
- import { ConflictError } from './ConflictError-C0STs6bU.mjs';
3
+ import { ConflictError } from './ConflictError-CLoq37xH.mjs';
4
4
 
5
5
  const CDC_LOG_TABLE = "__cdc_log";
6
6
  const migrateCdcLog = (sql$1) => {
@@ -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 };
@@ -75,7 +79,7 @@ const bumpCdcEpoch = (sql$1) => {
75
79
  };
76
80
  const applyCdcChange = async (writer, change) => {
77
81
  if (change.op === "delete") {
78
- await writer.delete(change.id);
82
+ await writer.delete(change.id, change.table);
79
83
  return;
80
84
  }
81
85
  const document = change.doc ?? {};
@@ -91,7 +95,7 @@ const applyCdcChange = async (writer, change) => {
91
95
  fields[key] = value;
92
96
  }
93
97
  }
94
- await writer.replace(change.id, fields);
98
+ await writer.replace(change.id, fields, void 0, { allowExplicitId: true });
95
99
  }
96
100
  };
97
101
  const applyCdcChanges = async (writer, changes) => {
@@ -1,11 +1,10 @@
1
- class ConflictError extends Error {
2
- code = "CONFLICT";
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ class ConflictError extends LunoraError {
3
4
  /** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
4
5
  kind;
5
- status = 409;
6
6
  constructor(message = "Optimistic concurrency conflict", kind = "conflict") {
7
- super(message);
8
- this.name = "ConflictError";
7
+ super("CONFLICT", message, { name: "ConflictError" });
9
8
  this.kind = kind;
10
9
  }
11
10
  }
@@ -1,9 +1,9 @@
1
- class CountRlsUnsupportedError extends Error {
2
- code = "COUNT_RLS_UNSUPPORTED";
3
- name = "LunoraError";
4
- status = 422;
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ class CountRlsUnsupportedError extends LunoraError {
5
4
  constructor(table) {
6
5
  super(
6
+ "COUNT_RLS_UNSUPPORTED",
7
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
8
  );
9
9
  }
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const DATA_MIGRATION_STATE_TABLE = "__lunora_migrations";
2
4
  const DEFAULT_BATCH_SIZE = 100;
3
5
  const STALE_CLAIM_TIMEOUT_MS = 3e4;
@@ -128,7 +130,7 @@ const runDataMigration = async (options) => {
128
130
  const batchSize = options.batchSize ?? migration.batchSize ?? DEFAULT_BATCH_SIZE;
129
131
  const transform = direction === "up" ? migration.up : migration.down;
130
132
  if (!transform) {
131
- throw new Error(`data migration "${migration.id}" has no \`${direction}\` transform`);
133
+ throw new LunoraError("INTERNAL", `data migration "${migration.id}" has no \`${direction}\` transform`);
132
134
  }
133
135
  let cursor = null;
134
136
  let processed = 0;
@@ -176,7 +178,9 @@ const runDataMigration = async (options) => {
176
178
  if (next !== void 0) {
177
179
  changed += 1;
178
180
  if (!dryRun) {
179
- await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] });
181
+ await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] }, void 0, {
182
+ allowExplicitId: true
183
+ });
180
184
  }
181
185
  }
182
186
  if (!dryRun) {
@@ -229,7 +233,11 @@ const runDataMigration = async (options) => {
229
233
  throw error;
230
234
  }
231
235
  if (!dryRun && !isDone) {
232
- releaseClaim(sql, migration.id);
236
+ try {
237
+ releaseClaim(sql, migration.id);
238
+ } catch (error) {
239
+ console.warn(`data migration "${migration.id}": releaseClaim failed`, error);
240
+ }
233
241
  }
234
242
  return { changed, cursor: isDone ? null : cursor, direction, dryRun, id: migration.id, processed, status: isDone ? "completed" : "in_progress" };
235
243
  };
@@ -1,4 +1,5 @@
1
- import { distinctValues } from './applyOnDelete-CMif2RKw.mjs';
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { distinctValues } from './applyOnDelete-BXSq3S70.mjs';
2
3
 
3
4
  const RELATION_EXISTS_KEY = "__relationExists";
4
5
 
@@ -43,7 +44,7 @@ const containsRelationPredicate = (where, schema, tableName) => {
43
44
  };
44
45
  const assertFlatPredicate = (where, schema, tableName, op) => {
45
46
  if (where && containsRelationPredicate(where, schema, tableName)) {
46
- throw new Error(`relation-crossing predicates are not supported in ${op}() — use them in findMany/findFirst or an RLS read policy`);
47
+ throw new LunoraError("INTERNAL", `relation-crossing predicates are not supported in ${op}() — use them in findMany/findFirst or an RLS read policy`);
47
48
  }
48
49
  };
49
50
  const projectChildKeys = async (relation, childWhere, projectField, context, escalatable) => {
@@ -58,7 +59,8 @@ const projectChildKeys = async (relation, childWhere, projectField, context, esc
58
59
  if (escalatable) {
59
60
  return KEY_OVERFLOW;
60
61
  }
61
- throw new Error(
62
+ throw new LunoraError(
63
+ "INTERNAL",
62
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)`
63
65
  );
64
66
  }
@@ -67,7 +69,7 @@ const projectChildKeys = async (relation, childWhere, projectField, context, esc
67
69
  const compileOperator = async (operator, relation, childWhere, context, escalatable) => {
68
70
  const meta = RELATION_OPERATOR_META[operator];
69
71
  if (!meta) {
70
- throw new Error(`unknown relation operator "${operator}"`);
72
+ throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
71
73
  }
72
74
  const { clause, project } = joinColumns(relation);
73
75
  const keys = await projectChildKeys(relation, meta.negateChild ? { NOT: childWhere } : childWhere, project, context, escalatable);
@@ -85,7 +87,7 @@ const compileOperator = async (operator, relation, childWhere, context, escalata
85
87
  const buildExistsMarker = async (operator, relation, childWhere, parentTable, context) => {
86
88
  const meta = RELATION_OPERATOR_META[operator];
87
89
  if (!meta) {
88
- throw new Error(`unknown relation operator "${operator}"`);
90
+ throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
89
91
  }
90
92
  const base = context.relationBaseWhere?.(relation.table);
91
93
  const predicatePart = meta.negateChild ? { NOT: childWhere } : childWhere;
@@ -97,7 +99,7 @@ const buildExistsMarker = async (operator, relation, childWhere, parentTable, co
97
99
  const assertCardinality = (operator, name, relation) => {
98
100
  const meta = RELATION_OPERATOR_META[operator];
99
101
  if (meta && meta.kind !== relation.kind) {
100
- throw new Error(`relation operator "${operator}" requires a to-${meta.kind} relation, but "${name}" is to-${relation.kind}`);
102
+ throw new LunoraError("INTERNAL", `relation operator "${operator}" requires a to-${meta.kind} relation, but "${name}" is to-${relation.kind}`);
101
103
  }
102
104
  };
103
105
  const resolveRelationNode = async (name, relation, predicate, parentTable, context) => {
@@ -156,5 +158,54 @@ const resolveRelationPredicates = async (where, options) => {
156
158
  schema: options.schema
157
159
  });
158
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
+ };
159
210
 
160
- export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
211
+ export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
@@ -0,0 +1,114 @@
1
+ const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
2
+ const GEO_DEFAULT_PRECISION = 9;
3
+ const EARTH_RADIUS_METERS = 63710088e-1;
4
+ const CELL_WIDTH_METERS = [Number.POSITIVE_INFINITY, 5009400, 1252300, 156500, 39100, 4900, 1200, 152.9, 38.2, 4.77, 1.19, 0.149, 0.037];
5
+ const clampPrecision = (precision) => Math.min(Math.max(Math.trunc(precision), 1), 12);
6
+ const encodeGeohash = (point, precision) => {
7
+ const chars = clampPrecision(precision);
8
+ let latMin = -90;
9
+ let latMax = 90;
10
+ let lngMin = -180;
11
+ let lngMax = 180;
12
+ let hash = "";
13
+ let bit = 0;
14
+ let index = 0;
15
+ let even = true;
16
+ while (hash.length < chars) {
17
+ if (even) {
18
+ const mid = (lngMin + lngMax) / 2;
19
+ if (point.lng >= mid) {
20
+ index = index * 2 + 1;
21
+ lngMin = mid;
22
+ } else {
23
+ index *= 2;
24
+ lngMax = mid;
25
+ }
26
+ } else {
27
+ const mid = (latMin + latMax) / 2;
28
+ if (point.lat >= mid) {
29
+ index = index * 2 + 1;
30
+ latMin = mid;
31
+ } else {
32
+ index *= 2;
33
+ latMax = mid;
34
+ }
35
+ }
36
+ even = !even;
37
+ if (bit < 4) {
38
+ bit += 1;
39
+ } else {
40
+ hash += BASE32[index] ?? "0";
41
+ bit = 0;
42
+ index = 0;
43
+ }
44
+ }
45
+ return hash;
46
+ };
47
+ const haversineMeters = (a, b) => {
48
+ const toRadians = (degrees) => degrees * Math.PI / 180;
49
+ const dLat = toRadians(b.lat - a.lat);
50
+ const dLng = toRadians(b.lng - a.lng);
51
+ const lat1 = toRadians(a.lat);
52
+ const lat2 = toRadians(b.lat);
53
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
54
+ return 2 * EARTH_RADIUS_METERS * Math.asin(Math.min(1, Math.sqrt(h)));
55
+ };
56
+ const precisionForRadius = (radiusMeters) => {
57
+ for (let length = CELL_WIDTH_METERS.length - 1; length >= 1; length -= 1) {
58
+ const width = CELL_WIDTH_METERS[length];
59
+ if (width !== void 0 && width >= radiusMeters) {
60
+ return length;
61
+ }
62
+ }
63
+ return 1;
64
+ };
65
+ const NEIGHBOURS = {
66
+ east: ["bc01fg45238967deuvhjyznpkmstqrwx", "bc01fg45238967deuvhjyznpkmstqrwx"],
67
+ north: ["p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx"],
68
+ south: ["14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp"],
69
+ west: ["238967debc01fg45kmstqrwxuvhjyznp", "238967debc01fg45kmstqrwxuvhjyznp"]
70
+ };
71
+ const BORDERS = {
72
+ east: ["bcfguvyz", "prxz"],
73
+ north: ["prxz", "bcfguvyz"],
74
+ south: ["028b", "0145hjnp"],
75
+ west: ["0145hjnp", "028b"]
76
+ };
77
+ const adjacent = (hash, direction) => {
78
+ const lower = hash.toLowerCase();
79
+ const lastChar = lower.at(-1) ?? "";
80
+ let base = lower.slice(0, -1);
81
+ const type = lower.length % 2 === 0 ? 0 : 1;
82
+ if (BORDERS[direction][type].includes(lastChar) && base !== "") {
83
+ base = adjacent(base, direction);
84
+ }
85
+ return base + (BASE32[NEIGHBOURS[direction][type].indexOf(lastChar)] ?? "");
86
+ };
87
+ const coveringGeohashes = (center, radiusMeters) => {
88
+ const precision = precisionForRadius(Math.max(radiusMeters, 1));
89
+ const origin = encodeGeohash(center, precision);
90
+ const north = adjacent(origin, "north");
91
+ const south = adjacent(origin, "south");
92
+ const cells = [
93
+ origin,
94
+ north,
95
+ south,
96
+ adjacent(origin, "east"),
97
+ adjacent(origin, "west"),
98
+ adjacent(north, "east"),
99
+ adjacent(north, "west"),
100
+ adjacent(south, "east"),
101
+ adjacent(south, "west")
102
+ ];
103
+ return [...new Set(cells)];
104
+ };
105
+ const pointInBoundingBox = (point, box) => point.lat >= box.sw.lat && point.lat <= box.ne.lat && point.lng >= box.sw.lng && point.lng <= box.ne.lng;
106
+ const boundingBoxCenter = (box) => {
107
+ return { lat: (box.sw.lat + box.ne.lat) / 2, lng: (box.sw.lng + box.ne.lng) / 2 };
108
+ };
109
+ const boundingBoxGeohashes = (box) => {
110
+ const center = boundingBoxCenter(box);
111
+ return coveringGeohashes(center, Math.max(haversineMeters(center, box.ne), 1));
112
+ };
113
+
114
+ export { GEO_DEFAULT_PRECISION, boundingBoxCenter, boundingBoxGeohashes, coveringGeohashes, encodeGeohash, haversineMeters, pointInBoundingBox };
@@ -1,10 +1,42 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const MAX_SQL_ROWS = 1e3;
2
4
  const READONLY_LEAD = /^(?:explain\s+(?:query\s+plan\s+)?)?(?:select|with)\b/iu;
3
5
  const FORBIDDEN_KEYWORD = /\b(?:alter|attach|create|delete|detach|drop|insert|pragma|reindex|replace|truncate|update|vacuum)\b/iu;
4
- const LEADING_NOISE = /^(?:\s|--[^\n]*\n?|\/\*[\s\S]*?\*\/)+/u;
5
6
  const TRAILING_SEMICOLON = /;\s*$/u;
6
- const stripLeading = (sql) => sql.replace(LEADING_NOISE, "");
7
- const sqlError = (message, code) => Object.assign(new Error(message), { code, name: "LunoraError", status: 400 });
7
+ const WHITESPACE = /\s/u;
8
+ const skipLineComment = (sql, from) => {
9
+ let index = from + 2;
10
+ while (index < sql.length && sql[index] !== "\n") {
11
+ index += 1;
12
+ }
13
+ return index;
14
+ };
15
+ const skipBlockComment = (sql, from) => {
16
+ const close = sql.indexOf("*/", from + 2);
17
+ return close === -1 ? -1 : close + 2;
18
+ };
19
+ const stripLeading = (sql) => {
20
+ let index = 0;
21
+ while (index < sql.length) {
22
+ const char = sql[index];
23
+ if (char !== void 0 && WHITESPACE.test(char)) {
24
+ index += 1;
25
+ } else if (char === "-" && sql[index + 1] === "-") {
26
+ index = skipLineComment(sql, index);
27
+ } else if (char === "/" && sql[index + 1] === "*") {
28
+ const next = skipBlockComment(sql, index);
29
+ if (next === -1) {
30
+ break;
31
+ }
32
+ index = next;
33
+ } else {
34
+ break;
35
+ }
36
+ }
37
+ return sql.slice(index);
38
+ };
39
+ const sqlError = (message, code) => new LunoraError(code, message, { status: 400 });
8
40
  const assertReadonly = (query) => {
9
41
  const trimmed = stripLeading(query).trim();
10
42
  if (trimmed === "") {