@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
|
@@ -5,11 +5,11 @@ import { j as jsonResponse } from './json-response-BdbtpOhm.mjs';
|
|
|
5
5
|
import { e as encodeWire, d as decodeWire } from './wire-codec-CzQc1pvf.mjs';
|
|
6
6
|
import { parseExportShardArgs, parseImportShardArgs } from './exportShardRows-Dy3oFZ26.mjs';
|
|
7
7
|
import { recordAuthEvent, readAuthMetrics } from './AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
|
|
8
|
-
import { n as normalizeLogFields, r as resolveTraceAnchor, a as createTracer, c as createMetrics, d as dispatchRootSpan } from './context-telemetry-
|
|
8
|
+
import { n as normalizeLogFields, r as resolveTraceAnchor, a as createTracer, c as createMetrics, d as dispatchRootSpan } from './context-telemetry-CqcOObBl.mjs';
|
|
9
9
|
import { DATA_MIGRATION_STATE_TABLE, readMigrationStatus } from './DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
|
|
10
10
|
import { SCAN_DEP, createDependencyTracker, tableFromDepKey } from './SCAN_DEP-DLJF8dsj.mjs';
|
|
11
11
|
import { readFunctionMetricsTotals, readFunctionMetricIndexHits, recordFunctionMetric, mergeScanAttribution, readFunctionMetrics, readFunctionMetricBuckets } from './FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
|
|
12
|
-
import { createFanoutCounters, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, summarizeFanoutTopics, readTablePage, facetColumn, FLAGS_FUNCTION_PREFIX, recordFanoutPass, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-
|
|
12
|
+
import { createFanoutCounters, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, summarizeFanoutTopics, readTablePage, facetColumn, FLAGS_FUNCTION_PREFIX, recordFanoutPass, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-CnZkbXH_.mjs';
|
|
13
13
|
import { LogBuffer } from './LogBuffer-B_Ezju_N.mjs';
|
|
14
14
|
import { recordCapturedMail, clearCapturedMail, readCapturedMail, MAIL_TABLE } from './MAIL_RETENTION-CPpgl-dX.mjs';
|
|
15
15
|
import { stableStringify } from './stableStringify-mC40mZts.mjs';
|
|
@@ -22,9 +22,10 @@ import { redact, standardRules } from '@visulima/redact';
|
|
|
22
22
|
import { i as isDevEnvironment, c as buildSettings, b as buildSecurityAudit } from './security-audit-CucgBice.mjs';
|
|
23
23
|
import { runReadonlySql } from './MAX_SQL_ROWS-iFAA8FbD.mjs';
|
|
24
24
|
import { ConflictError } from './ConflictError-CLoq37xH.mjs';
|
|
25
|
+
import { selectExpiredIds } from './selectExpiredIds-C5W29Upb.mjs';
|
|
25
26
|
import { e as deleteGlobalShapeSnapshotsForConnection, g as readIdempotent, h as writeIdempotent, t as trimIdempotent, r as readClientWatermark, m as migrateClientWatermark, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, f as readGlobalShapeSnapshot, w as writeGlobalShapeSnapshot } from './ctx-db-idempotency-BdcNpvY4.mjs';
|
|
26
27
|
import { CDC_LOG_TABLE, readCdcChanges, readCdcCursor, readCdcEpoch, minCdcSeq, bumpCdcEpoch } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
27
|
-
import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-
|
|
28
|
+
import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-DTeFiHYS.mjs';
|
|
28
29
|
|
|
29
30
|
const MAX_BATCH_ENTRIES = 500;
|
|
30
31
|
|
|
@@ -93,12 +94,12 @@ const verifyWsAdminToken = async (secret, token, now = Date.now()) => {
|
|
|
93
94
|
|
|
94
95
|
const AUDIT_LOG_TABLE = "__lunora_audit__";
|
|
95
96
|
const AUDIT_LOG_RETENTION = 1e3;
|
|
96
|
-
const runSql$
|
|
97
|
+
const runSql$5 = (sql, query, ...params) => {
|
|
97
98
|
const runner = sql.exec;
|
|
98
99
|
return runner.call(sql, query, ...params);
|
|
99
100
|
};
|
|
100
101
|
const ensureAuditTable = (sql) => {
|
|
101
|
-
runSql$
|
|
102
|
+
runSql$5(
|
|
102
103
|
sql,
|
|
103
104
|
`CREATE TABLE IF NOT EXISTS "${AUDIT_LOG_TABLE}" (
|
|
104
105
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -112,7 +113,7 @@ const ensureAuditTable = (sql) => {
|
|
|
112
113
|
};
|
|
113
114
|
const appendAuditEntry = (sql, entry) => {
|
|
114
115
|
ensureAuditTable(sql);
|
|
115
|
-
runSql$
|
|
116
|
+
runSql$5(
|
|
116
117
|
sql,
|
|
117
118
|
`INSERT INTO "${AUDIT_LOG_TABLE}" (ts, op, "table", id, detail) VALUES (?, ?, ?, ?, ?)`,
|
|
118
119
|
entry.ts,
|
|
@@ -124,13 +125,13 @@ const appendAuditEntry = (sql, entry) => {
|
|
|
124
125
|
// eslint-disable-next-line unicorn/no-null -- SQL NULL is the correct value for an op with no associated table/id/detail.
|
|
125
126
|
entry.detail === void 0 ? null : JSON.stringify(entry.detail)
|
|
126
127
|
);
|
|
127
|
-
runSql$
|
|
128
|
+
runSql$5(sql, `DELETE FROM "${AUDIT_LOG_TABLE}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${AUDIT_LOG_TABLE}")`, AUDIT_LOG_RETENTION);
|
|
128
129
|
};
|
|
129
130
|
const readAuditLog = (sql, options = {}) => {
|
|
130
131
|
ensureAuditTable(sql);
|
|
131
132
|
const sinceSeq = options.sinceSeq ?? 0;
|
|
132
133
|
const limit = Math.max(1, Math.min(options.limit ?? AUDIT_LOG_RETENTION, 1e4));
|
|
133
|
-
const rows = runSql$
|
|
134
|
+
const rows = runSql$5(
|
|
134
135
|
sql,
|
|
135
136
|
`SELECT seq, ts, op, "table", id, detail FROM "${AUDIT_LOG_TABLE}" WHERE seq > ? ORDER BY seq DESC LIMIT ?`,
|
|
136
137
|
sinceSeq,
|
|
@@ -187,14 +188,14 @@ const ISSUE_STATE_TABLE = "__lunora_issue_state__";
|
|
|
187
188
|
const HASH_QUERY_BATCH = 100;
|
|
188
189
|
const ISSUE_STATUSES = ["ignored", "open", "resolved"];
|
|
189
190
|
const ISSUE_SEVERITIES = ["critical", "high", "low", "medium"];
|
|
190
|
-
const runSql$
|
|
191
|
+
const runSql$4 = (sql, query, ...parameters) => {
|
|
191
192
|
const runner = sql.exec;
|
|
192
193
|
return runner.call(sql, query, ...parameters);
|
|
193
194
|
};
|
|
194
195
|
const SQL_NULL = null;
|
|
195
196
|
const orNull$1 = (value) => value ?? SQL_NULL;
|
|
196
197
|
const ensureIssueStateTable = (sql) => {
|
|
197
|
-
runSql$
|
|
198
|
+
runSql$4(
|
|
198
199
|
sql,
|
|
199
200
|
`CREATE TABLE IF NOT EXISTS "${ISSUE_STATE_TABLE}" (
|
|
200
201
|
hash TEXT PRIMARY KEY,
|
|
@@ -225,7 +226,7 @@ const readIssueStates = (sql, hashes) => {
|
|
|
225
226
|
for (let start = 0; start < hashes.length; start += HASH_QUERY_BATCH) {
|
|
226
227
|
const batch = hashes.slice(start, start + HASH_QUERY_BATCH);
|
|
227
228
|
const placeholders = batch.map(() => "?").join(", ");
|
|
228
|
-
const rows = runSql$
|
|
229
|
+
const rows = runSql$4(
|
|
229
230
|
sql,
|
|
230
231
|
`SELECT hash, status, assignee, severity, updated_at, updated_by FROM "${ISSUE_STATE_TABLE}" WHERE hash IN (${placeholders})`,
|
|
231
232
|
...batch
|
|
@@ -244,7 +245,7 @@ const upsertIssueState = (sql, hash, patch, updatedAt, updatedBy) => {
|
|
|
244
245
|
const severity = orNull$1(patch.severity);
|
|
245
246
|
const clearSeverity = patch.severity === null ? 1 : 0;
|
|
246
247
|
const actor = orNull$1(updatedBy);
|
|
247
|
-
runSql$
|
|
248
|
+
runSql$4(
|
|
248
249
|
sql,
|
|
249
250
|
`INSERT INTO "${ISSUE_STATE_TABLE}" (hash, status, assignee, severity, updated_at, updated_by)
|
|
250
251
|
VALUES (?, COALESCE(?, 'open'), ?, ?, ?, ?)
|
|
@@ -268,7 +269,7 @@ const upsertIssueState = (sql, hash, patch, updatedAt, updatedBy) => {
|
|
|
268
269
|
updatedAt,
|
|
269
270
|
actor
|
|
270
271
|
);
|
|
271
|
-
const [row] = runSql$
|
|
272
|
+
const [row] = runSql$4(
|
|
272
273
|
sql,
|
|
273
274
|
`SELECT hash, status, assignee, severity, updated_at, updated_by FROM "${ISSUE_STATE_TABLE}" WHERE hash = ?`,
|
|
274
275
|
hash
|
|
@@ -277,7 +278,7 @@ const upsertIssueState = (sql, hash, patch, updatedAt, updatedBy) => {
|
|
|
277
278
|
};
|
|
278
279
|
|
|
279
280
|
const DEFAULT_CAPACITY$1 = 256;
|
|
280
|
-
const
|
|
281
|
+
const metricSeriesKey = (event) => `${event.kind}${event.name}${stableStringify(event.attributes ?? {})}`;
|
|
281
282
|
class MetricBuffer {
|
|
282
283
|
capacity;
|
|
283
284
|
series = /* @__PURE__ */ new Map();
|
|
@@ -304,7 +305,7 @@ class MetricBuffer {
|
|
|
304
305
|
}
|
|
305
306
|
/** Fold one measurement into its series, creating or updating the aggregate. */
|
|
306
307
|
push(event) {
|
|
307
|
-
const key =
|
|
308
|
+
const key = metricSeriesKey(event);
|
|
308
309
|
const existing = this.series.get(key);
|
|
309
310
|
if (existing === void 0) {
|
|
310
311
|
if (this.series.size >= this.capacity) {
|
|
@@ -316,6 +317,7 @@ class MetricBuffer {
|
|
|
316
317
|
this.series.set(key, {
|
|
317
318
|
...event.attributes === void 0 ? {} : { attributes: event.attributes },
|
|
318
319
|
count: 1,
|
|
320
|
+
...event.traceId === void 0 ? {} : { exemplarTraceId: event.traceId },
|
|
319
321
|
firstTs: event.ts,
|
|
320
322
|
functionPath: event.functionPath,
|
|
321
323
|
kind: event.kind,
|
|
@@ -337,10 +339,152 @@ class MetricBuffer {
|
|
|
337
339
|
existing.last = event.value;
|
|
338
340
|
existing.lastTs = event.ts;
|
|
339
341
|
existing.functionPath = event.functionPath;
|
|
342
|
+
if (event.traceId !== void 0) {
|
|
343
|
+
existing.exemplarTraceId = event.traceId;
|
|
344
|
+
}
|
|
340
345
|
this.series.set(key, existing);
|
|
341
346
|
}
|
|
342
347
|
}
|
|
343
348
|
|
|
349
|
+
const METRIC_HISTORY_TABLE = "__lunora_metric_history";
|
|
350
|
+
const METRIC_HISTORY_BUCKET_MS = 6e4;
|
|
351
|
+
const METRIC_HISTORY_BUCKET_RETENTION = 1440;
|
|
352
|
+
const METRIC_HISTORY_MAX_SERIES = 1e3;
|
|
353
|
+
const METRIC_HISTORY_READ_LIMIT = 5e3;
|
|
354
|
+
const runSql$3 = (sql, query, ...parameters) => {
|
|
355
|
+
const runner = sql.exec;
|
|
356
|
+
return runner.call(sql, query, ...parameters);
|
|
357
|
+
};
|
|
358
|
+
const bucketFloor = (ts) => Math.floor(ts / METRIC_HISTORY_BUCKET_MS) * METRIC_HISTORY_BUCKET_MS;
|
|
359
|
+
const ensureMetricHistoryTable = (sql) => {
|
|
360
|
+
runSql$3(
|
|
361
|
+
sql,
|
|
362
|
+
`CREATE TABLE IF NOT EXISTS "${METRIC_HISTORY_TABLE}" (
|
|
363
|
+
series_key TEXT NOT NULL,
|
|
364
|
+
bucket_ms INTEGER NOT NULL,
|
|
365
|
+
name TEXT NOT NULL,
|
|
366
|
+
kind TEXT NOT NULL,
|
|
367
|
+
attrs TEXT NOT NULL DEFAULT '{}',
|
|
368
|
+
function_path TEXT NOT NULL DEFAULT '',
|
|
369
|
+
shard_key TEXT,
|
|
370
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
371
|
+
sum REAL NOT NULL DEFAULT 0,
|
|
372
|
+
min REAL NOT NULL DEFAULT 0,
|
|
373
|
+
max REAL NOT NULL DEFAULT 0,
|
|
374
|
+
last REAL NOT NULL DEFAULT 0,
|
|
375
|
+
last_ts REAL NOT NULL DEFAULT 0,
|
|
376
|
+
exemplar_trace TEXT,
|
|
377
|
+
PRIMARY KEY (series_key, bucket_ms)
|
|
378
|
+
)`
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
const recordMetricHistory = (sql, event, exemplarTraceId) => {
|
|
382
|
+
ensureMetricHistoryTable(sql);
|
|
383
|
+
const key = metricSeriesKey(event);
|
|
384
|
+
const bucket = bucketFloor(event.ts);
|
|
385
|
+
const bucketExists = runSql$3(sql, `SELECT 1 AS c FROM "${METRIC_HISTORY_TABLE}" WHERE series_key = ? AND bucket_ms = ? LIMIT 1`, key, bucket).toArray().length > 0;
|
|
386
|
+
if (!bucketExists) {
|
|
387
|
+
const seriesTracked = runSql$3(sql, `SELECT 1 AS c FROM "${METRIC_HISTORY_TABLE}" WHERE series_key = ? LIMIT 1`, key).toArray().length > 0;
|
|
388
|
+
if (!seriesTracked) {
|
|
389
|
+
const seriesCountRow = runSql$3(sql, `SELECT COUNT(DISTINCT series_key) AS n FROM "${METRIC_HISTORY_TABLE}"`).one();
|
|
390
|
+
if (seriesCountRow.n >= METRIC_HISTORY_MAX_SERIES) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const exemplar = exemplarTraceId ?? null;
|
|
396
|
+
runSql$3(
|
|
397
|
+
sql,
|
|
398
|
+
`INSERT INTO "${METRIC_HISTORY_TABLE}"
|
|
399
|
+
(series_key, bucket_ms, name, kind, attrs, function_path, shard_key, count, sum, min, max, last, last_ts, exemplar_trace)
|
|
400
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
|
|
401
|
+
ON CONFLICT(series_key, bucket_ms) DO UPDATE SET
|
|
402
|
+
count = count + 1,
|
|
403
|
+
sum = sum + excluded.sum,
|
|
404
|
+
min = MIN(min, excluded.min),
|
|
405
|
+
max = MAX(max, excluded.max),
|
|
406
|
+
last = excluded.last,
|
|
407
|
+
last_ts = excluded.last_ts,
|
|
408
|
+
exemplar_trace = CASE WHEN excluded.exemplar_trace IS NULL THEN exemplar_trace ELSE excluded.exemplar_trace END`,
|
|
409
|
+
key,
|
|
410
|
+
bucket,
|
|
411
|
+
event.name,
|
|
412
|
+
event.kind,
|
|
413
|
+
stableStringify(event.attributes ?? {}),
|
|
414
|
+
event.functionPath,
|
|
415
|
+
// eslint-disable-next-line unicorn/no-null -- SQL NULL for the unnamed root DO's absent shard key.
|
|
416
|
+
event.shardKey ?? null,
|
|
417
|
+
event.value,
|
|
418
|
+
event.value,
|
|
419
|
+
event.value,
|
|
420
|
+
event.value,
|
|
421
|
+
event.ts,
|
|
422
|
+
exemplar
|
|
423
|
+
);
|
|
424
|
+
if (!bucketExists) {
|
|
425
|
+
runSql$3(
|
|
426
|
+
sql,
|
|
427
|
+
`DELETE FROM "${METRIC_HISTORY_TABLE}"
|
|
428
|
+
WHERE series_key = ?
|
|
429
|
+
AND bucket_ms <= (
|
|
430
|
+
SELECT MAX(bucket_ms) - ? FROM "${METRIC_HISTORY_TABLE}" WHERE series_key = ?
|
|
431
|
+
)`,
|
|
432
|
+
key,
|
|
433
|
+
METRIC_HISTORY_BUCKET_RETENTION * METRIC_HISTORY_BUCKET_MS,
|
|
434
|
+
key
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const parseAttributes = (raw) => {
|
|
439
|
+
if (raw === "" || raw === "{}") {
|
|
440
|
+
return void 0;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const parsed = JSON.parse(raw);
|
|
444
|
+
return parsed !== null && typeof parsed === "object" ? parsed : void 0;
|
|
445
|
+
} catch {
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
const readMetricHistory = (sql, options = {}) => {
|
|
450
|
+
ensureMetricHistoryTable(sql);
|
|
451
|
+
const rows = options.sinceMs === void 0 ? runSql$3(sql, `SELECT * FROM "${METRIC_HISTORY_TABLE}" ORDER BY bucket_ms DESC LIMIT ?`, METRIC_HISTORY_READ_LIMIT).toArray() : runSql$3(
|
|
452
|
+
sql,
|
|
453
|
+
`SELECT * FROM "${METRIC_HISTORY_TABLE}" WHERE bucket_ms >= ? ORDER BY bucket_ms DESC LIMIT ?`,
|
|
454
|
+
options.sinceMs,
|
|
455
|
+
METRIC_HISTORY_READ_LIMIT
|
|
456
|
+
).toArray();
|
|
457
|
+
const bySeries = /* @__PURE__ */ new Map();
|
|
458
|
+
for (const row of rows) {
|
|
459
|
+
let series = bySeries.get(row.series_key);
|
|
460
|
+
if (series === void 0) {
|
|
461
|
+
const attributes = parseAttributes(row.attrs);
|
|
462
|
+
series = {
|
|
463
|
+
...attributes === void 0 ? {} : { attributes },
|
|
464
|
+
functionPath: row.function_path,
|
|
465
|
+
kind: row.kind,
|
|
466
|
+
name: row.name,
|
|
467
|
+
points: [],
|
|
468
|
+
...row.shard_key === null ? {} : { shardKey: row.shard_key }
|
|
469
|
+
};
|
|
470
|
+
bySeries.set(row.series_key, series);
|
|
471
|
+
}
|
|
472
|
+
series.points.push({
|
|
473
|
+
bucketMs: row.bucket_ms,
|
|
474
|
+
count: row.count,
|
|
475
|
+
...row.exemplar_trace === null ? {} : { exemplarTraceId: row.exemplar_trace },
|
|
476
|
+
last: row.last,
|
|
477
|
+
max: row.max,
|
|
478
|
+
min: row.min,
|
|
479
|
+
sum: row.sum
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
for (const series of bySeries.values()) {
|
|
483
|
+
series.points.sort((a, b) => a.bucketMs - b.bucketMs);
|
|
484
|
+
}
|
|
485
|
+
return { series: [...bySeries.values()] };
|
|
486
|
+
};
|
|
487
|
+
|
|
344
488
|
const QUERY_METRICS_TABLE = "__lunora_metrics_queries";
|
|
345
489
|
const QUERY_METRICS_MAX_SQL_LEN = 512;
|
|
346
490
|
const QUERY_METRICS_MAX_STATEMENTS = 500;
|
|
@@ -1855,6 +1999,9 @@ const parseRunMigrationArgs = (args) => {
|
|
|
1855
1999
|
};
|
|
1856
2000
|
};
|
|
1857
2001
|
const SHARD_BULK_DELETE_CAP = MAX_PAGE_SIZE;
|
|
2002
|
+
const TTL_SWEEP_BATCH = 200;
|
|
2003
|
+
const TTL_SWEEP_MAX_BATCHES = 20;
|
|
2004
|
+
const TTL_SWEEP_INTERVAL_MS = 3e4;
|
|
1858
2005
|
const parseWriteRowArgs = (args) => {
|
|
1859
2006
|
const { op } = args;
|
|
1860
2007
|
const table = typeof args["table"] === "string" ? args["table"] : "";
|
|
@@ -2461,12 +2608,10 @@ class ShardDO {
|
|
|
2461
2608
|
* reason; otherwise the earlier of the two candidate times (never later than
|
|
2462
2609
|
* `nowMs`, so a source that's already due arms essentially immediately).
|
|
2463
2610
|
*/
|
|
2464
|
-
static nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, nowMs) {
|
|
2611
|
+
static nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, nextTtlDueAt, nowMs) {
|
|
2465
2612
|
const globalTarget = globalShapesRemaining > 0 ? nowMs + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS : void 0;
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
}
|
|
2469
|
-
return nextSourceDueAt === void 0 ? globalTarget : Math.min(globalTarget, nextSourceDueAt);
|
|
2613
|
+
const candidates = [globalTarget, nextSourceDueAt, nextTtlDueAt].filter((value) => value !== void 0).map((value) => Math.max(value, nowMs));
|
|
2614
|
+
return candidates.length > 0 ? Math.min(...candidates) : void 0;
|
|
2470
2615
|
}
|
|
2471
2616
|
state;
|
|
2472
2617
|
env;
|
|
@@ -3147,8 +3292,15 @@ class ShardDO {
|
|
|
3147
3292
|
this.recordShapeError("source:poll", error);
|
|
3148
3293
|
nextSourceDueAt = Date.now() + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS;
|
|
3149
3294
|
}
|
|
3295
|
+
let nextTtlDueAt;
|
|
3296
|
+
try {
|
|
3297
|
+
nextTtlDueAt = await this.pollTtlSweeps();
|
|
3298
|
+
} catch (error) {
|
|
3299
|
+
this.recordShapeError("ttl:sweep", error);
|
|
3300
|
+
nextTtlDueAt = Date.now() + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS;
|
|
3301
|
+
}
|
|
3150
3302
|
await this.flushChangedTables();
|
|
3151
|
-
const nextAlarmAt = ShardDO.nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, Date.now());
|
|
3303
|
+
const nextAlarmAt = ShardDO.nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, nextTtlDueAt, Date.now());
|
|
3152
3304
|
if (nextAlarmAt !== void 0) {
|
|
3153
3305
|
await this.scheduleGlobalPoll(nextAlarmAt);
|
|
3154
3306
|
}
|
|
@@ -4337,6 +4489,60 @@ class ShardDO {
|
|
|
4337
4489
|
scheduleSourcePoll() {
|
|
4338
4490
|
return this.scheduleGlobalPoll();
|
|
4339
4491
|
}
|
|
4492
|
+
/**
|
|
4493
|
+
* The resolved TTL policies (`.ttl(field, { after })`) for this DO's schema —
|
|
4494
|
+
* one {@link TtlSweepSpec} per table that declares a TTL. The base `ShardDO`
|
|
4495
|
+
* has no schema, so it returns `[]` and the TTL tier stays dormant. The
|
|
4496
|
+
* codegen subclass overrides it to read each table's `ttlPolicy` (+ its
|
|
4497
|
+
* `.softDelete()` marker) off the imported schema.
|
|
4498
|
+
*/
|
|
4499
|
+
// eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass reads the imported schema
|
|
4500
|
+
ttlSweeps() {
|
|
4501
|
+
return [];
|
|
4502
|
+
}
|
|
4503
|
+
/**
|
|
4504
|
+
* Sweep every `.ttl()` table once: page the rows past their expiry and remove
|
|
4505
|
+
* each THROUGH the schema-aware writer (`deleteRowThroughWriter`) so companions
|
|
4506
|
+
* / CDC / live subscriptions stay correct and a `.softDelete()` table soft-deletes
|
|
4507
|
+
* instead of physically removing the row. Work is bounded per tick
|
|
4508
|
+
* ({@link TTL_SWEEP_BATCH} × {@link TTL_SWEEP_MAX_BATCHES}) so a large backlog
|
|
4509
|
+
* drains across several alarms without stalling the shard.
|
|
4510
|
+
*
|
|
4511
|
+
* Returns the next-due timestamp (a coarse {@link TTL_SWEEP_INTERVAL_MS}
|
|
4512
|
+
* cadence, so freshly-written rows expire within a bounded window) while any
|
|
4513
|
+
* TTL table exists, or `undefined` when there are none — so a DO with no TTL
|
|
4514
|
+
* table never arms this tier.
|
|
4515
|
+
*/
|
|
4516
|
+
async pollTtlSweeps() {
|
|
4517
|
+
const specs = this.ttlSweeps();
|
|
4518
|
+
if (specs.length === 0) {
|
|
4519
|
+
return void 0;
|
|
4520
|
+
}
|
|
4521
|
+
const sql = this.sql;
|
|
4522
|
+
const now = Date.now();
|
|
4523
|
+
for (const spec of specs) {
|
|
4524
|
+
let batches = 0;
|
|
4525
|
+
let hasMore = true;
|
|
4526
|
+
while (hasMore && batches < TTL_SWEEP_MAX_BATCHES) {
|
|
4527
|
+
const page = selectExpiredIds(sql, spec, now, TTL_SWEEP_BATCH);
|
|
4528
|
+
for (const id of page.ids) {
|
|
4529
|
+
await this.deleteRowThroughWriter(spec.table, id);
|
|
4530
|
+
}
|
|
4531
|
+
hasMore = page.hasMore;
|
|
4532
|
+
batches += 1;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
return now + TTL_SWEEP_INTERVAL_MS;
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* Arm the shared poll alarm for the TTL sweep. Mirrors {@link scheduleSourcePoll};
|
|
4539
|
+
* the codegen subclass calls it once on construction when the schema declares a
|
|
4540
|
+
* `.ttl()` table so the sweep loop starts, after which {@link ShardDO.alarm}
|
|
4541
|
+
* re-arms itself. Idempotent; a no-op when the runtime exposes no `setAlarm`.
|
|
4542
|
+
*/
|
|
4543
|
+
scheduleTtlSweep() {
|
|
4544
|
+
return this.scheduleGlobalPoll();
|
|
4545
|
+
}
|
|
4340
4546
|
/** This DO's shard key (its DO name), or `__root__` for the single-DO default. The `tenantBy` mapper binds it into the source query. */
|
|
4341
4547
|
currentShardKey() {
|
|
4342
4548
|
return this.state.id?.name ?? ROOT_SHARD_NAME;
|
|
@@ -4595,15 +4801,23 @@ class ShardDO {
|
|
|
4595
4801
|
* cross-instance aggregation is still the sink's job.
|
|
4596
4802
|
*/
|
|
4597
4803
|
recordMetric(event, sink) {
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
}
|
|
4602
|
-
if (sink?.onMetric) {
|
|
4804
|
+
const exemplarTraceId = this.currentRequestTrace?.traceId;
|
|
4805
|
+
const stamped = exemplarTraceId === void 0 ? event : { ...event, traceId: exemplarTraceId };
|
|
4806
|
+
const bestEffort = (run) => {
|
|
4603
4807
|
try {
|
|
4604
|
-
|
|
4808
|
+
run();
|
|
4605
4809
|
} catch {
|
|
4606
4810
|
}
|
|
4811
|
+
};
|
|
4812
|
+
bestEffort(() => {
|
|
4813
|
+
this.metricSeries.push(stamped);
|
|
4814
|
+
});
|
|
4815
|
+
const rawSql = this.state.storage.sql;
|
|
4816
|
+
bestEffort(() => {
|
|
4817
|
+
recordMetricHistory(rawSql, stamped, exemplarTraceId);
|
|
4818
|
+
});
|
|
4819
|
+
if (sink?.onMetric) {
|
|
4820
|
+
bestEffort(() => sink.onMetric?.(stamped, { waitUntil: this.state.waitUntil?.bind(this.state) }));
|
|
4607
4821
|
}
|
|
4608
4822
|
}
|
|
4609
4823
|
/**
|
|
@@ -5809,6 +6023,9 @@ class ShardDO {
|
|
|
5809
6023
|
if (functionPath === ADMIN_FUNCTIONS.getMetricSeries) {
|
|
5810
6024
|
return { series: this.metricSeries.entries() };
|
|
5811
6025
|
}
|
|
6026
|
+
if (functionPath === ADMIN_FUNCTIONS.getMetricHistory) {
|
|
6027
|
+
return readMetricHistory(this.sql);
|
|
6028
|
+
}
|
|
5812
6029
|
if (functionPath === ADMIN_FUNCTIONS.getSettings) {
|
|
5813
6030
|
return buildSettings(this.env);
|
|
5814
6031
|
}
|
|
@@ -2,7 +2,7 @@ import { sql } from 'drizzle-orm';
|
|
|
2
2
|
import { matchesStaticWhere } from './AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs';
|
|
3
3
|
import { encodeAggregateKey, foldAggregateTally, aggregateTableName } from './aggregateTableName-CxNqY1Sl.mjs';
|
|
4
4
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
5
|
-
import { D as DOC_COLUMN, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-
|
|
5
|
+
import { D as DOC_COLUMN, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-CGAgiQUz.mjs';
|
|
6
6
|
import { param } from './renderSql-D6eUcn2N.mjs';
|
|
7
7
|
import { sortColumnName, matchesRankStaticWhere, encodePartitionKey, rankTableName } from './RANK_TIEBREAK-CXhdcA1o.mjs';
|
|
8
8
|
import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
|
package/dist/packem_shared/{context-telemetry-DWfYDxCS.mjs → context-telemetry-CqcOObBl.mjs}
RENAMED
|
@@ -69,10 +69,19 @@ const createTracer = (deps) => {
|
|
|
69
69
|
const spanId = otlpRandomHex(8);
|
|
70
70
|
const startTs = Date.now();
|
|
71
71
|
const normalized = normalizeLogFields(attributes);
|
|
72
|
+
const collected = {};
|
|
73
|
+
const spanHandle = {
|
|
74
|
+
setAttribute: (key, value) => {
|
|
75
|
+
Object.assign(collected, normalizeLogFields({ [key]: value }));
|
|
76
|
+
},
|
|
77
|
+
setAttributes: (fields) => {
|
|
78
|
+
Object.assign(collected, normalizeLogFields(fields));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
72
81
|
let ok = true;
|
|
73
82
|
let error;
|
|
74
83
|
try {
|
|
75
|
-
return await function_(tracerFor(spanId));
|
|
84
|
+
return await function_(tracerFor(spanId), spanHandle);
|
|
76
85
|
} catch (error_) {
|
|
77
86
|
ok = false;
|
|
78
87
|
error = {
|
|
@@ -84,8 +93,9 @@ const createTracer = (deps) => {
|
|
|
84
93
|
throw error_;
|
|
85
94
|
} finally {
|
|
86
95
|
try {
|
|
96
|
+
const merged = { ...normalized, ...collected };
|
|
87
97
|
record({
|
|
88
|
-
...
|
|
98
|
+
...Object.keys(merged).length === 0 ? {} : { attributes: merged },
|
|
89
99
|
durationMs: Date.now() - startTs,
|
|
90
100
|
...error === void 0 ? {} : { error },
|
|
91
101
|
functionPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './context-telemetry-
|
|
1
|
+
export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './context-telemetry-CqcOObBl.mjs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
2
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
|
-
import { D as DOC_COLUMN, r as rowToDocument, j as jsonPathSql } from './do-sql-
|
|
3
|
+
import { D as DOC_COLUMN, r as rowToDocument, j as jsonPathSql } from './do-sql-CGAgiQUz.mjs';
|
|
4
4
|
import { compileWhereSql } from './compileWhereSql-DE6yfRcQ.mjs';
|
|
5
5
|
import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
|
|
6
6
|
|
|
@@ -2,6 +2,7 @@ import { sql } from 'drizzle-orm';
|
|
|
2
2
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
3
|
|
|
4
4
|
const DOC_COLUMN = "__doc__";
|
|
5
|
+
const geoTableName = (table, indexName) => `${table}__geo_${indexName}`;
|
|
5
6
|
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
6
7
|
const jsonPath = (field) => {
|
|
7
8
|
if (field === "_id" || field === "id") {
|
|
@@ -84,4 +85,4 @@ const isFtsAvailable = (sql$1) => {
|
|
|
84
85
|
return available;
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
export { AGG_KEY as A, DOC_COLUMN as D, AGG_VALUE as a, AGG_COUNT as b, createIndexSql as c, aggUpsertSql as d, qualifiedJsonPathSql as e, isFtsAvailable as i, jsonPathSql as j, quoteIdentifier as q, rowToDocument as r, tableColumns as t };
|
|
88
|
+
export { AGG_KEY as A, DOC_COLUMN as D, AGG_VALUE as a, AGG_COUNT as b, createIndexSql as c, aggUpsertSql as d, qualifiedJsonPathSql as e, geoTableName as g, isFtsAvailable as i, jsonPathSql as j, quoteIdentifier as q, rowToDocument as r, tableColumns as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
import { sql } from 'drizzle-orm';
|
|
3
3
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
4
|
-
import { runExternalSourceTick, materializeExternalRowsIncremental } from './materializeExternalRows-
|
|
4
|
+
import { runExternalSourceTick, materializeExternalRowsIncremental } from './materializeExternalRows-BtEGs1Fv.mjs';
|
|
5
5
|
|
|
6
6
|
const SOURCE_CURSOR_TABLE = "__lunora_source_cursor";
|
|
7
7
|
const serializeCursor = (value) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyCdcChanges } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
2
|
-
import { s as selectShapeRows } from './ctx-db-shapes-
|
|
2
|
+
import { s as selectShapeRows } from './ctx-db-shapes-DTeFiHYS.mjs';
|
|
3
3
|
import { diffExternalSource, projectExternalSourceRow } from './diffExternalSource-CovHfdyo.mjs';
|
|
4
4
|
import { stableStringify } from './stableStringify-mC40mZts.mjs';
|
|
5
5
|
|
package/dist/packem_shared/{runShardMigrations-DFzx6Qld.mjs → runShardMigrations-BVax6rYu.mjs}
RENAMED
|
@@ -3,7 +3,7 @@ import { aggregateTableName } from './aggregateTableName-CxNqY1Sl.mjs';
|
|
|
3
3
|
import { migrateCdcLog, migrateCdcMeta } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
4
4
|
import { m as migrateClientWatermark, a as migrateIdempotency, b as migrateGlobalShapeSnapshot } from './ctx-db-idempotency-BdcNpvY4.mjs';
|
|
5
5
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
6
|
-
import { D as DOC_COLUMN, j as jsonPathSql, c as createIndexSql, t as tableColumns, i as isFtsAvailable, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-
|
|
6
|
+
import { D as DOC_COLUMN, j as jsonPathSql, c as createIndexSql, t as tableColumns, i as isFtsAvailable, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT, g as geoTableName } from './do-sql-CGAgiQUz.mjs';
|
|
7
7
|
import { sortColumnName, rankTableName } from './RANK_TIEBREAK-CXhdcA1o.mjs';
|
|
8
8
|
import { ftsTableName } from './buildFtsMatch-BLEMawrp.mjs';
|
|
9
9
|
|
|
@@ -36,6 +36,20 @@ const migrateSearchIndexes = (sql$1, tableName, definition) => {
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
const migrateGeoIndexes = (sql$1, tableName, definition) => {
|
|
40
|
+
if (!definition.geoIndexes) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
for (const index of definition.geoIndexes) {
|
|
44
|
+
const geoTable = geoTableName(tableName, index.name);
|
|
45
|
+
runDrizzle(
|
|
46
|
+
sql$1,
|
|
47
|
+
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(geoTable)} (${sql.identifier("__id__")} TEXT PRIMARY KEY, ${sql.identifier("__geohash__")} TEXT NOT NULL, ${sql.identifier("__lat__")} REAL NOT NULL, ${sql.identifier("__lng__")} REAL NOT NULL)`
|
|
48
|
+
);
|
|
49
|
+
const btreeName = `${tableName}__geo_${index.name}__btree`;
|
|
50
|
+
runDrizzle(sql$1, createIndexSql(btreeName, geoTable, sql`${sql.identifier("__geohash__")} ASC, ${sql.identifier("__id__")} ASC`, false));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
39
53
|
const migrateAggregateIndexes = (sql$1, tableName, definition) => {
|
|
40
54
|
if (!definition.aggregateIndexes) {
|
|
41
55
|
return;
|
|
@@ -90,6 +104,7 @@ const runShardMigrations = (sql$1, schema, options = {}) => {
|
|
|
90
104
|
);
|
|
91
105
|
migrateSecondaryIndexes(sql$1, tableName, definition);
|
|
92
106
|
migrateSearchIndexes(sql$1, tableName, definition);
|
|
107
|
+
migrateGeoIndexes(sql$1, tableName, definition);
|
|
93
108
|
migrateAggregateIndexes(sql$1, tableName, definition);
|
|
94
109
|
migrateRankIndexes(sql$1, tableName, definition);
|
|
95
110
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
|
+
import { j as jsonPathSql } from './do-sql-CGAgiQUz.mjs';
|
|
4
|
+
|
|
5
|
+
const selectExpiredIds = (sql$1, spec, now, limit) => {
|
|
6
|
+
const cutoff = now - (spec.after ?? 0);
|
|
7
|
+
const conditions = [sql`${jsonPathSql(spec.field)} IS NOT NULL`, sql`${jsonPathSql(spec.field)} < ${cutoff}`];
|
|
8
|
+
if (spec.softDeleteField !== void 0) {
|
|
9
|
+
conditions.push(sql`${jsonPathSql(spec.softDeleteField)} IS NULL`);
|
|
10
|
+
}
|
|
11
|
+
const query = sql`SELECT id FROM ${sql.identifier(spec.table)} WHERE ${sql.join(conditions, sql` AND `)} LIMIT ${sql.raw(String(Math.max(0, Math.floor(limit)) + 1))}`;
|
|
12
|
+
const rows = runDrizzle(sql$1, query).toArray();
|
|
13
|
+
const hasMore = rows.length > limit;
|
|
14
|
+
const ids = rows.slice(0, limit).map((row) => row.id);
|
|
15
|
+
return { hasMore, ids };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { selectExpiredIds };
|
package/dist/packem_shared/{serveRelationFanout-FdrPflv1.mjs → serveRelationFanout-k7HTh3CC.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-
|
|
2
|
+
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-CnZkbXH_.mjs';
|
|
3
3
|
|
|
4
4
|
const serveRelationFanout = async (schema, database, functionPath, args) => {
|
|
5
5
|
const table = typeof args["table"] === "string" ? args["table"] : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/do",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.39",
|
|
4
4
|
"description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.7",
|
|
50
50
|
"@lunora/fingerprint": "1.0.0-alpha.3",
|
|
51
51
|
"@visulima/redact": "3.0.0",
|
|
52
52
|
"drizzle-orm": "^0.45.2"
|