@lunora/do 1.0.0-alpha.37 → 1.0.0-alpha.39
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.
- package/dist/index.d.mts +160 -5
- package/dist/index.d.ts +160 -5
- package/dist/index.mjs +11 -9
- package/dist/packem_shared/{ADMIN_FUNCTIONS-DVk02KpP.mjs → ADMIN_FUNCTIONS-CnZkbXH_.mjs} +1 -0
- package/dist/packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs +114 -0
- package/dist/packem_shared/{NotUniqueError-Ca21uDuU.mjs → NotUniqueError-DbtlWwcG.mjs} +168 -33
- package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-CBlw_ir3.mjs → ROOT_DO_SIZE_WARN_BYTES-CJGwzfoT.mjs} +244 -27
- package/dist/packem_shared/{backfillAggregateIndexes-DDoT-UUI.mjs → backfillAggregateIndexes-BGTwynwh.mjs} +1 -1
- package/dist/packem_shared/{context-telemetry-DWfYDxCS.mjs → context-telemetry-CqcOObBl.mjs} +12 -2
- package/dist/packem_shared/{createMetrics-BIL8Cl8X.mjs → createMetrics-Dk4eQ2aJ.mjs} +1 -1
- package/dist/packem_shared/{ctx-db-shapes-Cz9dHyh1.mjs → ctx-db-shapes-DTeFiHYS.mjs} +1 -1
- package/dist/packem_shared/{do-sql-BCHCWtrD.mjs → do-sql-CGAgiQUz.mjs} +2 -1
- package/dist/packem_shared/{isSoftDeleted-YLKR6JYw.mjs → isSoftDeleted-CFJmhjFP.mjs} +1 -1
- package/dist/packem_shared/{materializeExternalRows-DlQWMlw_.mjs → materializeExternalRows-BtEGs1Fv.mjs} +1 -1
- package/dist/packem_shared/{runShardMigrations-DFzx6Qld.mjs → runShardMigrations-BVax6rYu.mjs} +16 -1
- package/dist/packem_shared/selectExpiredIds-C5W29Upb.mjs +18 -0
- package/dist/packem_shared/{serveRelationFanout-FdrPflv1.mjs → serveRelationFanout-k7HTh3CC.mjs} +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -3,16 +3,17 @@ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, norma
|
|
|
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-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 { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-
|
|
7
|
-
export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-
|
|
6
|
+
export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-CqcOObBl.mjs';
|
|
7
|
+
export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-DbtlWwcG.mjs';
|
|
8
8
|
export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
|
|
9
9
|
export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
|
|
10
10
|
export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
|
|
11
11
|
export { diffExternalSource } from './packem_shared/diffExternalSource-CovHfdyo.mjs';
|
|
12
|
-
export { materializeExternalRows, materializeExternalRowsIncremental, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-
|
|
13
|
-
export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick } from './packem_shared/isSoftDeleted-
|
|
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';
|
|
14
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';
|
|
15
|
-
export {
|
|
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';
|
|
16
17
|
export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
|
|
17
18
|
export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
|
|
18
19
|
export { default as NotFoundError } from './packem_shared/NotFoundError-C70b9hLw.mjs';
|
|
@@ -20,23 +21,24 @@ export { armRestore, readBookmark } from './packem_shared/armRestore-4Px61hHS.mj
|
|
|
20
21
|
export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-WQY8m62C.mjs';
|
|
21
22
|
export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
|
|
22
23
|
export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-DnSvbjil.mjs';
|
|
23
|
-
export { serveRelationFanout } from './packem_shared/serveRelationFanout-
|
|
24
|
+
export { serveRelationFanout } from './packem_shared/serveRelationFanout-k7HTh3CC.mjs';
|
|
24
25
|
export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs';
|
|
25
26
|
export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BXSq3S70.mjs';
|
|
26
27
|
export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs';
|
|
27
28
|
export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
|
|
28
29
|
export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
|
|
29
30
|
export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs';
|
|
30
|
-
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-
|
|
31
|
+
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-CJGwzfoT.mjs';
|
|
31
32
|
export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs';
|
|
32
33
|
export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs';
|
|
33
34
|
export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
|
|
34
35
|
export { ConflictError } from './packem_shared/ConflictError-CLoq37xH.mjs';
|
|
35
36
|
export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
|
|
37
|
+
export { selectExpiredIds } from './packem_shared/selectExpiredIds-C5W29Upb.mjs';
|
|
36
38
|
export { compileWhereSql } from './packem_shared/compileWhereSql-DE6yfRcQ.mjs';
|
|
37
39
|
export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
38
|
-
export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-
|
|
39
|
-
export { runShardMigrations } from './packem_shared/runShardMigrations-
|
|
40
|
+
export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BGTwynwh.mjs';
|
|
41
|
+
export { runShardMigrations } from './packem_shared/runShardMigrations-BVax6rYu.mjs';
|
|
40
42
|
export { stableStringify } from './packem_shared/stableStringify-mC40mZts.mjs';
|
|
41
43
|
export { stableWireKey } from './packem_shared/stableWireKey-DKuXO7T5.mjs';
|
|
42
44
|
export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-CT76bYny.mjs';
|
|
@@ -25,6 +25,7 @@ const ADMIN_FUNCTIONS = {
|
|
|
25
25
|
getFanoutMetrics: "__lunora_admin__:getFanoutMetrics",
|
|
26
26
|
getFunctionStats: "__lunora_admin__:getFunctionStats",
|
|
27
27
|
getIssues: "__lunora_admin__:getIssues",
|
|
28
|
+
getMetricHistory: "__lunora_admin__:getMetricHistory",
|
|
28
29
|
getMetricSeries: "__lunora_admin__:getMetricSeries",
|
|
29
30
|
listSubscriptions: "__lunora_admin__:listSubscriptions",
|
|
30
31
|
listTableIndexes: "__lunora_admin__:listTableIndexes",
|
|
@@ -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 };
|
|
@@ -6,8 +6,9 @@ import { mergeWhere, CountRlsUnsupportedError, selectIndexForGroupBy, selectInde
|
|
|
6
6
|
import { appendCdcChange } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
7
7
|
export { CDC_LOG_TABLE, applyCdcChanges, bumpCdcEpoch, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
8
8
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
9
|
-
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-
|
|
9
|
+
import { i as isFtsAvailable, D as DOC_COLUMN$1, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT, g as geoTableName, d as aggUpsertSql, j as jsonPathSql, q as quoteIdentifier, t as tableColumns, e as qualifiedJsonPathSql } from './do-sql-CGAgiQUz.mjs';
|
|
10
10
|
import { param } from './renderSql-D6eUcn2N.mjs';
|
|
11
|
+
import { encodeGeohash, GEO_DEFAULT_PRECISION, coveringGeohashes, boundingBoxGeohashes, pointInBoundingBox, haversineMeters } from './GEO_DEFAULT_PRECISION-BWnsNmpP.mjs';
|
|
11
12
|
import { sortColumnName, matchesRankStaticWhere, encodePartitionKey, rankTableName, resolveRankPartition, RANK_TIEBREAK } from './RANK_TIEBREAK-CXhdcA1o.mjs';
|
|
12
13
|
import { stringifySearchText, ftsTableName, tokenizeSearch, buildFtsMatch, scoreDocument } from './buildFtsMatch-BLEMawrp.mjs';
|
|
13
14
|
import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
|
|
@@ -21,10 +22,10 @@ import { createSystemReader } from './createSystemReader-D12eNH13.mjs';
|
|
|
21
22
|
import { ConflictError } from './ConflictError-CLoq37xH.mjs';
|
|
22
23
|
import { runTriggers } from './hasTrigger-5N6_Fx0A.mjs';
|
|
23
24
|
import { compileWhereSql } from './compileWhereSql-DE6yfRcQ.mjs';
|
|
24
|
-
export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-
|
|
25
|
+
export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-BGTwynwh.mjs';
|
|
25
26
|
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';
|
|
26
|
-
export { runShardMigrations } from './runShardMigrations-
|
|
27
|
-
export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-
|
|
27
|
+
export { runShardMigrations } from './runShardMigrations-BVax6rYu.mjs';
|
|
28
|
+
export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-DTeFiHYS.mjs';
|
|
28
29
|
|
|
29
30
|
const rankIndexFieldsUnchanged = (index, previous, next) => {
|
|
30
31
|
const fields = [...index.partitionBy ?? [], ...index.sortBy.map((key) => key.field), ...index.where ? Object.keys(index.where) : []];
|
|
@@ -314,8 +315,28 @@ const createCompanionSync = (deps) => {
|
|
|
314
315
|
}
|
|
315
316
|
}
|
|
316
317
|
};
|
|
318
|
+
const syncGeo = (tableName, id, document) => {
|
|
319
|
+
const indexes = schema.tables[tableName]?.geoIndexes;
|
|
320
|
+
if (!indexes || indexes.length === 0) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
for (const index of indexes) {
|
|
324
|
+
const geoTable = geoTableName(tableName, index.name);
|
|
325
|
+
runDrizzle(sql$1, sql`DELETE FROM ${sql.identifier(geoTable)} WHERE ${sql.identifier("__id__")} = ${id}`);
|
|
326
|
+
const point = document?.[index.field];
|
|
327
|
+
if (point !== null && typeof point === "object" && typeof point.lat === "number" && typeof point.lng === "number") {
|
|
328
|
+
const { lat, lng } = point;
|
|
329
|
+
const hash = encodeGeohash({ lat, lng }, index.precision ?? GEO_DEFAULT_PRECISION);
|
|
330
|
+
runDrizzle(
|
|
331
|
+
sql$1,
|
|
332
|
+
sql`INSERT INTO ${sql.identifier(geoTable)} (${sql.identifier("__id__")}, ${sql.identifier("__geohash__")}, ${sql.identifier("__lat__")}, ${sql.identifier("__lng__")}) VALUES (${id}, ${hash}, ${lat}, ${lng})`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
317
337
|
const syncCompanionsForInsert = (tableName, id, document) => {
|
|
318
338
|
syncSearch(tableName, id, document);
|
|
339
|
+
syncGeo(tableName, id, document);
|
|
319
340
|
syncAggregates(tableName, void 0, document);
|
|
320
341
|
syncRanks(tableName, id, void 0, document);
|
|
321
342
|
invalidateCache(tableName, id);
|
|
@@ -329,6 +350,7 @@ const createCompanionSync = (deps) => {
|
|
|
329
350
|
ensureRankBackfilledForTable,
|
|
330
351
|
syncAggregates,
|
|
331
352
|
syncCompanionsForInsert,
|
|
353
|
+
syncGeo,
|
|
332
354
|
syncRanks,
|
|
333
355
|
syncSearch
|
|
334
356
|
};
|
|
@@ -599,6 +621,123 @@ const searchViaScan = (sql$1, tableName, search, limit, scopeCondition) => {
|
|
|
599
621
|
const docs = scored.map((entry) => entry.doc);
|
|
600
622
|
return typeof limit === "number" ? docs.slice(0, Math.max(0, Math.floor(limit))) : docs;
|
|
601
623
|
};
|
|
624
|
+
const createGeoBuilder = (geo, tableName) => {
|
|
625
|
+
const staged = geo;
|
|
626
|
+
const builder = {
|
|
627
|
+
near: (point, radiusMeters) => {
|
|
628
|
+
if (staged.within) {
|
|
629
|
+
throw new LunoraError("INTERNAL", `geo index "${staged.indexName}" on table "${tableName}": call .near() or .within(), not both`);
|
|
630
|
+
}
|
|
631
|
+
staged.near = { point: { lat: point.lat, lng: point.lng }, radiusMeters };
|
|
632
|
+
return builder;
|
|
633
|
+
},
|
|
634
|
+
within: (box) => {
|
|
635
|
+
if (staged.near) {
|
|
636
|
+
throw new LunoraError("INTERNAL", `geo index "${staged.indexName}" on table "${tableName}": call .near() or .within(), not both`);
|
|
637
|
+
}
|
|
638
|
+
staged.within = { ne: { lat: box.ne.lat, lng: box.ne.lng }, sw: { lat: box.sw.lat, lng: box.sw.lng } };
|
|
639
|
+
return builder;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
return builder;
|
|
643
|
+
};
|
|
644
|
+
const readGeoPoint = (document, field) => {
|
|
645
|
+
const value = document[field];
|
|
646
|
+
if (value === null || typeof value !== "object") {
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
const { lat, lng } = value;
|
|
650
|
+
return typeof lat === "number" && typeof lng === "number" ? { lat, lng } : void 0;
|
|
651
|
+
};
|
|
652
|
+
const scoreGeoRow = (record, geo) => {
|
|
653
|
+
const point = readGeoPoint(record, geo.definition.field);
|
|
654
|
+
if (!point) {
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
657
|
+
const creationTime = typeof record["_creationTime"] === "number" ? record["_creationTime"] : 0;
|
|
658
|
+
if (geo.near) {
|
|
659
|
+
const distance = haversineMeters(geo.near.point, point);
|
|
660
|
+
return distance <= geo.near.radiusMeters ? { creationTime, distance } : void 0;
|
|
661
|
+
}
|
|
662
|
+
return pointInBoundingBox(point, geo.within) ? { creationTime, distance: 0 } : void 0;
|
|
663
|
+
};
|
|
664
|
+
const runGeoFetch = (sql$1, tableName, geo, limit, scopeCondition) => {
|
|
665
|
+
if (!geo.near && !geo.within) {
|
|
666
|
+
throw new LunoraError("INTERNAL", `geo index "${geo.indexName}" on table "${tableName}": call .near(point, radius) or .within(box)`);
|
|
667
|
+
}
|
|
668
|
+
const prefixes = geo.near ? coveringGeohashes(geo.near.point, geo.near.radiusMeters) : boundingBoxGeohashes(geo.within);
|
|
669
|
+
const geoTable = geoTableName(tableName, geo.indexName);
|
|
670
|
+
const prefixClauses = prefixes.map(
|
|
671
|
+
(prefix) => sql`(g.${sql.identifier("__geohash__")} >= ${prefix} AND g.${sql.identifier("__geohash__")} < ${`${prefix}{`})`
|
|
672
|
+
);
|
|
673
|
+
const whereClauses = [sql`(${sql.join(prefixClauses, sql` OR `)})`];
|
|
674
|
+
if (scopeCondition) {
|
|
675
|
+
whereClauses.push(scopeCondition);
|
|
676
|
+
}
|
|
677
|
+
const query = sql`SELECT m.id, m._creationTime, m.${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(geoTable)} g JOIN ${sql.identifier(tableName)} m ON m.id = g.${sql.identifier("__id__")} WHERE ${sql.join(whereClauses, sql` AND `)}`;
|
|
678
|
+
const rows = runDrizzle(sql$1, query).toArray();
|
|
679
|
+
const scored = [];
|
|
680
|
+
for (const row of rows) {
|
|
681
|
+
const record = rowToDocument(row);
|
|
682
|
+
const score = record ? scoreGeoRow(record, geo) : void 0;
|
|
683
|
+
if (record && score) {
|
|
684
|
+
scored.push({ creationTime: score.creationTime, distance: score.distance, doc: record });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
scored.sort((a, b) => a.distance - b.distance || b.creationTime - a.creationTime);
|
|
688
|
+
const docs = scored.map((entry) => entry.doc);
|
|
689
|
+
return typeof limit === "number" ? docs.slice(0, Math.max(0, Math.floor(limit))) : docs;
|
|
690
|
+
};
|
|
691
|
+
const runGeoTerminal = (sql, tableName, stage, scopeCondition, limit) => {
|
|
692
|
+
const { geo } = stage;
|
|
693
|
+
if (!geo) {
|
|
694
|
+
throw new LunoraError("INTERNAL", "runGeoTerminal called without a staged geo query");
|
|
695
|
+
}
|
|
696
|
+
const filtered = stage.inMemoryFilters.length > 0;
|
|
697
|
+
const docs = runGeoFetch(sql, tableName, geo, filtered ? void 0 : limit, scopeCondition);
|
|
698
|
+
if (!filtered) {
|
|
699
|
+
return docs;
|
|
700
|
+
}
|
|
701
|
+
const result = [];
|
|
702
|
+
for (const record of docs) {
|
|
703
|
+
if (stage.inMemoryFilters.every((predicate) => predicate(record))) {
|
|
704
|
+
result.push(record);
|
|
705
|
+
if (typeof limit === "number" && result.length >= limit) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
};
|
|
712
|
+
const runPlainFetch = (sql$1, tableName, stage, scopeCondition, orderClause, limit) => {
|
|
713
|
+
const whereClauses = [];
|
|
714
|
+
for (const condition of stage.sqlConditions) {
|
|
715
|
+
whereClauses.push(sql`${jsonPathSql(condition.field)} ${sql.raw(condition.comparator)} ${serializeSqlValue(condition.value)}`);
|
|
716
|
+
}
|
|
717
|
+
if (scopeCondition) {
|
|
718
|
+
whereClauses.push(scopeCondition);
|
|
719
|
+
}
|
|
720
|
+
let query = sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN$1)} FROM ${sql.identifier(tableName)}`;
|
|
721
|
+
if (whereClauses.length > 0) {
|
|
722
|
+
query = sql`${query} WHERE ${sql.join(whereClauses, sql` AND `)}`;
|
|
723
|
+
}
|
|
724
|
+
query = sql`${query} ORDER BY ${orderClause}`;
|
|
725
|
+
if (typeof limit === "number" && stage.inMemoryFilters.length === 0) {
|
|
726
|
+
query = sql`${query} LIMIT ${sql.raw(String(Math.max(0, Math.floor(limit))))}`;
|
|
727
|
+
}
|
|
728
|
+
const rows = runDrizzle(sql$1, query).toArray();
|
|
729
|
+
const docs = [];
|
|
730
|
+
for (const row of rows) {
|
|
731
|
+
const record = rowToDocument(row);
|
|
732
|
+
if (record && stage.inMemoryFilters.every((predicate) => predicate(record))) {
|
|
733
|
+
docs.push(record);
|
|
734
|
+
if (typeof limit === "number" && docs.length >= limit) {
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return docs;
|
|
740
|
+
};
|
|
602
741
|
const doWhereSqlStrategy = { fieldRef: jsonPathSql, serialize: serializeSqlValue };
|
|
603
742
|
const makeRelationExistsSqlStrategy = (onRead) => {
|
|
604
743
|
let aliasCounter = 0;
|
|
@@ -774,36 +913,10 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
|
|
|
774
913
|
if (stage.search) {
|
|
775
914
|
return runSearchFetch(limit);
|
|
776
915
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
whereClauses.push(sql`${jsonPathSql(condition.field)} ${sql.raw(condition.comparator)} ${serializeSqlValue(condition.value)}`);
|
|
780
|
-
}
|
|
781
|
-
if (scopeCondition) {
|
|
782
|
-
whereClauses.push(scopeCondition);
|
|
916
|
+
if (stage.geo) {
|
|
917
|
+
return runGeoTerminal(sql$1, tableName, stage, scopeCondition, limit);
|
|
783
918
|
}
|
|
784
|
-
|
|
785
|
-
if (whereClauses.length > 0) {
|
|
786
|
-
query = sql`${query} WHERE ${sql.join(whereClauses, sql` AND `)}`;
|
|
787
|
-
}
|
|
788
|
-
query = sql`${query} ORDER BY ${buildOrderClause()}`;
|
|
789
|
-
if (typeof limit === "number" && stage.inMemoryFilters.length === 0) {
|
|
790
|
-
query = sql`${query} LIMIT ${sql.raw(String(Math.max(0, Math.floor(limit))))}`;
|
|
791
|
-
}
|
|
792
|
-
const rows = runDrizzle(sql$1, query).toArray();
|
|
793
|
-
const docs = [];
|
|
794
|
-
for (const row of rows) {
|
|
795
|
-
const record = rowToDocument(row);
|
|
796
|
-
if (!record) {
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
if (stage.inMemoryFilters.every((predicate) => predicate(record))) {
|
|
800
|
-
docs.push(record);
|
|
801
|
-
if (typeof limit === "number" && docs.length >= limit) {
|
|
802
|
-
break;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
return docs;
|
|
919
|
+
return runPlainFetch(sql$1, tableName, stage, scopeCondition, buildOrderClause(), limit);
|
|
807
920
|
};
|
|
808
921
|
const reader = {
|
|
809
922
|
// 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
|
|
@@ -828,6 +941,9 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
|
|
|
828
941
|
if (stage.search) {
|
|
829
942
|
throw new LunoraError("INTERNAL", "pagination is not supported on search queries; use .take(n) or .collect()");
|
|
830
943
|
}
|
|
944
|
+
if (stage.geo) {
|
|
945
|
+
throw new LunoraError("INTERNAL", "pagination is not supported on geo queries; use .take(n) or .collect()");
|
|
946
|
+
}
|
|
831
947
|
return paginateStage(sql$1, tableName, stage, options, scopeCondition);
|
|
832
948
|
},
|
|
833
949
|
// 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
|
|
@@ -842,6 +958,20 @@ const buildReader = (sql$1, schema, tableName, onIndexUse = () => void 0) => {
|
|
|
842
958
|
}
|
|
843
959
|
return rows[0] ?? null;
|
|
844
960
|
},
|
|
961
|
+
withGeoIndex(indexName, build) {
|
|
962
|
+
const definition = (tableDefinition.geoIndexes ?? []).find((index) => index.name === indexName);
|
|
963
|
+
if (!definition) {
|
|
964
|
+
throw new LunoraError("INTERNAL", `unknown geo index "${indexName}" on table "${tableName}"`);
|
|
965
|
+
}
|
|
966
|
+
onIndexUse(tableName, indexName, "geo");
|
|
967
|
+
const geoStage = { definition, indexName };
|
|
968
|
+
stage.geo = geoStage;
|
|
969
|
+
build(createGeoBuilder(geoStage, tableName));
|
|
970
|
+
if (!geoStage.near && !geoStage.within) {
|
|
971
|
+
throw new LunoraError("INTERNAL", `geo index "${indexName}" on table "${tableName}" requires a .near(point, radius) or .within(box) call`);
|
|
972
|
+
}
|
|
973
|
+
return reader;
|
|
974
|
+
},
|
|
845
975
|
withIndex(indexName, range) {
|
|
846
976
|
const definition = tableDefinition.indexes.find((index) => index.name === indexName);
|
|
847
977
|
if (!definition) {
|
|
@@ -1090,6 +1220,7 @@ const createShardCtxDb = (options) => {
|
|
|
1090
1220
|
ensureRankBackfilledForTable,
|
|
1091
1221
|
syncAggregates,
|
|
1092
1222
|
syncCompanionsForInsert,
|
|
1223
|
+
syncGeo,
|
|
1093
1224
|
syncRanks,
|
|
1094
1225
|
syncSearch
|
|
1095
1226
|
} = createCompanionSync({
|
|
@@ -1291,6 +1422,7 @@ const createShardCtxDb = (options) => {
|
|
|
1291
1422
|
sql`UPDATE ${sql.identifier(tableName)} SET ${sql.identifier(DOC_COLUMN$1)} = ${JSON.stringify(merged)} WHERE id = ${id} AND ${sql.identifier(DOC_COLUMN$1)} = ${existingJson}`
|
|
1292
1423
|
);
|
|
1293
1424
|
syncSearch(tableName, id, merged);
|
|
1425
|
+
syncGeo(tableName, id, void 0);
|
|
1294
1426
|
syncAggregates(tableName, existing, merged);
|
|
1295
1427
|
syncRanks(tableName, id, existing, void 0);
|
|
1296
1428
|
cache?.invalidate(tableName, id);
|
|
@@ -1308,6 +1440,7 @@ const createShardCtxDb = (options) => {
|
|
|
1308
1440
|
sql`DELETE FROM ${sql.identifier(tableName)} WHERE id = ${id} AND ${sql.identifier(DOC_COLUMN$1)} = ${existingJson}`
|
|
1309
1441
|
);
|
|
1310
1442
|
syncSearch(tableName, id, void 0);
|
|
1443
|
+
syncGeo(tableName, id, void 0);
|
|
1311
1444
|
syncAggregates(tableName, existing, void 0);
|
|
1312
1445
|
syncRanks(tableName, id, existing, void 0);
|
|
1313
1446
|
cache?.invalidate(tableName, id);
|
|
@@ -1682,6 +1815,7 @@ const createShardCtxDb = (options) => {
|
|
|
1682
1815
|
sql`UPDATE ${sql.identifier(tableName)} SET ${sql.identifier(DOC_COLUMN$1)} = ${JSON.stringify(merged)} WHERE id = ${id} AND ${sql.identifier(DOC_COLUMN$1)} = ${existingJson}`
|
|
1683
1816
|
);
|
|
1684
1817
|
syncSearch(tableName, id, merged);
|
|
1818
|
+
syncGeo(tableName, id, merged);
|
|
1685
1819
|
syncAggregates(tableName, existing, merged);
|
|
1686
1820
|
syncRanks(tableName, id, existing, merged);
|
|
1687
1821
|
cache?.invalidate(tableName, id);
|
|
@@ -1878,6 +2012,7 @@ const createShardCtxDb = (options) => {
|
|
|
1878
2012
|
sql`UPDATE ${sql.identifier(tableName)} SET _creationTime = ${creationTime}, ${sql.identifier(DOC_COLUMN$1)} = ${JSON.stringify(replaced)} WHERE id = ${id} AND ${sql.identifier(DOC_COLUMN$1)} = ${existingJson}`
|
|
1879
2013
|
);
|
|
1880
2014
|
syncSearch(tableName, id, replaced);
|
|
2015
|
+
syncGeo(tableName, id, replaced);
|
|
1881
2016
|
syncAggregates(tableName, previous, replaced);
|
|
1882
2017
|
syncRanks(tableName, id, previous, replaced);
|
|
1883
2018
|
cache?.invalidate(tableName, id);
|