@lunora/do 1.0.0-alpha.22 → 1.0.0-alpha.23

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 CHANGED
@@ -2460,6 +2460,7 @@ declare const ADMIN_FUNCTIONS: {
2460
2460
  readonly applyCdc: "__lunora_admin__:applyCdc";
2461
2461
  readonly cdcSync: "__lunora_admin__:cdcSync";
2462
2462
  readonly clearCapturedMail: "__lunora_admin__:clearCapturedMail";
2463
+ readonly clearQueueMessages: "__lunora_admin__:clearQueueMessages";
2463
2464
  readonly clearTable: "__lunora_admin__:clearTable";
2464
2465
  readonly createWorkflowInstance: "__lunora_admin__:createWorkflowInstance";
2465
2466
  readonly deleteRows: "__lunora_admin__:deleteRows";
@@ -2478,6 +2479,7 @@ declare const ADMIN_FUNCTIONS: {
2478
2479
  readonly getLogs: "__lunora_admin__:getLogs";
2479
2480
  readonly getMetrics: "__lunora_admin__:getMetrics";
2480
2481
  readonly getPitrBookmark: "__lunora_admin__:getPitrBookmark";
2482
+ readonly getQueueMessages: "__lunora_admin__:getQueueMessages";
2481
2483
  readonly getRequestLog: "__lunora_admin__:getRequestLog";
2482
2484
  readonly getSecurityAudit: "__lunora_admin__:getSecurityAudit";
2483
2485
  readonly getSettings: "__lunora_admin__:getSettings";
@@ -2496,10 +2498,13 @@ declare const ADMIN_FUNCTIONS: {
2496
2498
  readonly recordAuthEvent: "__lunora_admin__:recordAuthEvent";
2497
2499
  readonly recordContainerEvent: "__lunora_admin__:recordContainerEvent";
2498
2500
  readonly recordMail: "__lunora_admin__:recordMail";
2501
+ readonly recordQueueMessage: "__lunora_admin__:recordQueueMessage";
2502
+ readonly replayQueueMessage: "__lunora_admin__:replayQueueMessage";
2499
2503
  readonly rlsPolicies: "__lunora_admin__:rlsPolicies";
2500
2504
  readonly runAs: "__lunora_admin__:runAs";
2501
2505
  readonly runMigration: "__lunora_admin__:runMigration";
2502
2506
  readonly runSql: "__lunora_admin__:runSql";
2507
+ readonly sendQueueMessage: "__lunora_admin__:sendQueueMessage";
2503
2508
  readonly sendTestMail: "__lunora_admin__:sendTestMail";
2504
2509
  readonly storageOrphans: "__lunora_admin__:storageOrphans";
2505
2510
  readonly storageReferences: "__lunora_admin__:storageReferences";
@@ -5655,6 +5660,54 @@ declare abstract class ShardDO {
5655
5660
  */
5656
5661
  private handleSendTestMail;
5657
5662
  /**
5663
+ * Serve `__lunora_admin__:recordQueueMessage` — the capture sink the generated
5664
+ * worker `queue()` handler (via `@lunora/queue`'s `dispatchQueueBatch`) posts
5665
+ * every consumed message batch to. Records it into the reserved
5666
+ * `__lunora_queue_messages` table (bounded, auto-trimmed) so the studio Queues
5667
+ * panel shows one unified consumed-message log across every push consumer. Like
5668
+ * the mail catcher, the gate is the admin token alone — a token holder can
5669
+ * already mutate the shard, so a token-gated capture insert adds no privilege.
5670
+ */
5671
+ private handleRecordQueueMessage;
5672
+ /** Empty the dev queue consumed-message log (studio "clear log" action). Admin-gated by the caller. */
5673
+ private handleClearQueueMessages;
5674
+ /**
5675
+ * Serve `__lunora_admin__:sendQueueMessage` — the studio's "Send test message"
5676
+ * button. Resolves the declared queue's `QUEUE_*` producer binding and calls
5677
+ * `.send(body, { delaySeconds?, contentType? })`, or `.sendBatch(...)` when a
5678
+ * `batch` array is supplied. No SQLite write happens here (the message is only
5679
+ * captured once a consumer processes it), so this only records an audit entry.
5680
+ * Admin-gated by `handleAdminRpc`'s caller.
5681
+ */
5682
+ private handleSendQueueMessage;
5683
+ /**
5684
+ * Serve `__lunora_admin__:replayQueueMessage` — the studio's one-click replay /
5685
+ * DLQ redrive. Looks the captured row up by id, resolves the destination export
5686
+ * (explicit `target` → the parent queue when the message was captured off a
5687
+ * dead-letter queue → the queue it was consumed from), and re-enqueues the
5688
+ * stored body onto that producer. Records an audit entry; no SQLite write beyond
5689
+ * that (the replayed message is re-captured when a consumer processes it).
5690
+ * Admin-gated by `handleAdminRpc`'s caller.
5691
+ */
5692
+ private handleReplayQueueMessage;
5693
+ /**
5694
+ * Resolve a declared queue's runtime producer binding from this shard's `env`.
5695
+ * Looks the `exportName` up in {@link queuesMetadata} (the codegen subclass's
5696
+ * statically-discovered list) to find its generated `QUEUE_*` binding, then
5697
+ * reads `env[binding]` and validates it carries `send`/`sendBatch`. A bad export
5698
+ * name or a missing/malformed binding throws a 400 `LunoraError` so the studio
5699
+ * surfaces an actionable message. Mirrors {@link resolveWorkflowBinding}.
5700
+ */
5701
+ private resolveQueueBinding;
5702
+ /**
5703
+ * Pick the replay destination export for a captured message's origin queue.
5704
+ * When the message was consumed off a queue that is another queue's dead-letter
5705
+ * queue, prefer that PARENT queue's producer (a DLQ usually has no producer of
5706
+ * its own) so replay redrives onto the original; otherwise re-enqueue onto the
5707
+ * queue the message came from. Returns `undefined` when neither is declared.
5708
+ */
5709
+ private resolveReplayTarget;
5710
+ /**
5658
5711
  * Append one durable audit entry for a state-changing admin op that just
5659
5712
  * succeeded, folding the acting user (from `getCurrentUserId`) into `detail`.
5660
5713
  * Called only on the success path, so a rejected/validated op leaves no
@@ -5860,6 +5913,17 @@ declare abstract class ShardDO {
5860
5913
  * JSON memo still suppresses byte-identical pushes).
5861
5914
  */
5862
5915
  private readAdminCapturedMail;
5916
+ /**
5917
+ * Resolve a `getQueueMessages` admin read — the dev queue catcher's consumed
5918
+ * message log (`queue-catcher.ts`), newest-first, optionally filtered to one
5919
+ * queue. Best-effort: a SQL failure returns an empty log rather than throwing.
5920
+ * Reported against the {@link QUEUE_TABLE} so this read participates in
5921
+ * table-scoped subscription invalidation, but new captures arrive via the
5922
+ * worker→root-shard `recordQueueMessage` write, which (like the mail catcher)
5923
+ * inserts directly without a `flushChangedTables` — so the panel refreshes on
5924
+ * its poll (`useAutoRefresh`) rather than a live push.
5925
+ */
5926
+ private readAdminQueueMessages;
5863
5927
  /** Resolve a `readTablePage` admin read, parsing the loosely-typed args into the reader's options. */
5864
5928
  private readAdminTablePage;
5865
5929
  /**
package/dist/index.d.ts CHANGED
@@ -2460,6 +2460,7 @@ declare const ADMIN_FUNCTIONS: {
2460
2460
  readonly applyCdc: "__lunora_admin__:applyCdc";
2461
2461
  readonly cdcSync: "__lunora_admin__:cdcSync";
2462
2462
  readonly clearCapturedMail: "__lunora_admin__:clearCapturedMail";
2463
+ readonly clearQueueMessages: "__lunora_admin__:clearQueueMessages";
2463
2464
  readonly clearTable: "__lunora_admin__:clearTable";
2464
2465
  readonly createWorkflowInstance: "__lunora_admin__:createWorkflowInstance";
2465
2466
  readonly deleteRows: "__lunora_admin__:deleteRows";
@@ -2478,6 +2479,7 @@ declare const ADMIN_FUNCTIONS: {
2478
2479
  readonly getLogs: "__lunora_admin__:getLogs";
2479
2480
  readonly getMetrics: "__lunora_admin__:getMetrics";
2480
2481
  readonly getPitrBookmark: "__lunora_admin__:getPitrBookmark";
2482
+ readonly getQueueMessages: "__lunora_admin__:getQueueMessages";
2481
2483
  readonly getRequestLog: "__lunora_admin__:getRequestLog";
2482
2484
  readonly getSecurityAudit: "__lunora_admin__:getSecurityAudit";
2483
2485
  readonly getSettings: "__lunora_admin__:getSettings";
@@ -2496,10 +2498,13 @@ declare const ADMIN_FUNCTIONS: {
2496
2498
  readonly recordAuthEvent: "__lunora_admin__:recordAuthEvent";
2497
2499
  readonly recordContainerEvent: "__lunora_admin__:recordContainerEvent";
2498
2500
  readonly recordMail: "__lunora_admin__:recordMail";
2501
+ readonly recordQueueMessage: "__lunora_admin__:recordQueueMessage";
2502
+ readonly replayQueueMessage: "__lunora_admin__:replayQueueMessage";
2499
2503
  readonly rlsPolicies: "__lunora_admin__:rlsPolicies";
2500
2504
  readonly runAs: "__lunora_admin__:runAs";
2501
2505
  readonly runMigration: "__lunora_admin__:runMigration";
2502
2506
  readonly runSql: "__lunora_admin__:runSql";
2507
+ readonly sendQueueMessage: "__lunora_admin__:sendQueueMessage";
2503
2508
  readonly sendTestMail: "__lunora_admin__:sendTestMail";
2504
2509
  readonly storageOrphans: "__lunora_admin__:storageOrphans";
2505
2510
  readonly storageReferences: "__lunora_admin__:storageReferences";
@@ -5655,6 +5660,54 @@ declare abstract class ShardDO {
5655
5660
  */
5656
5661
  private handleSendTestMail;
5657
5662
  /**
5663
+ * Serve `__lunora_admin__:recordQueueMessage` — the capture sink the generated
5664
+ * worker `queue()` handler (via `@lunora/queue`'s `dispatchQueueBatch`) posts
5665
+ * every consumed message batch to. Records it into the reserved
5666
+ * `__lunora_queue_messages` table (bounded, auto-trimmed) so the studio Queues
5667
+ * panel shows one unified consumed-message log across every push consumer. Like
5668
+ * the mail catcher, the gate is the admin token alone — a token holder can
5669
+ * already mutate the shard, so a token-gated capture insert adds no privilege.
5670
+ */
5671
+ private handleRecordQueueMessage;
5672
+ /** Empty the dev queue consumed-message log (studio "clear log" action). Admin-gated by the caller. */
5673
+ private handleClearQueueMessages;
5674
+ /**
5675
+ * Serve `__lunora_admin__:sendQueueMessage` — the studio's "Send test message"
5676
+ * button. Resolves the declared queue's `QUEUE_*` producer binding and calls
5677
+ * `.send(body, { delaySeconds?, contentType? })`, or `.sendBatch(...)` when a
5678
+ * `batch` array is supplied. No SQLite write happens here (the message is only
5679
+ * captured once a consumer processes it), so this only records an audit entry.
5680
+ * Admin-gated by `handleAdminRpc`'s caller.
5681
+ */
5682
+ private handleSendQueueMessage;
5683
+ /**
5684
+ * Serve `__lunora_admin__:replayQueueMessage` — the studio's one-click replay /
5685
+ * DLQ redrive. Looks the captured row up by id, resolves the destination export
5686
+ * (explicit `target` → the parent queue when the message was captured off a
5687
+ * dead-letter queue → the queue it was consumed from), and re-enqueues the
5688
+ * stored body onto that producer. Records an audit entry; no SQLite write beyond
5689
+ * that (the replayed message is re-captured when a consumer processes it).
5690
+ * Admin-gated by `handleAdminRpc`'s caller.
5691
+ */
5692
+ private handleReplayQueueMessage;
5693
+ /**
5694
+ * Resolve a declared queue's runtime producer binding from this shard's `env`.
5695
+ * Looks the `exportName` up in {@link queuesMetadata} (the codegen subclass's
5696
+ * statically-discovered list) to find its generated `QUEUE_*` binding, then
5697
+ * reads `env[binding]` and validates it carries `send`/`sendBatch`. A bad export
5698
+ * name or a missing/malformed binding throws a 400 `LunoraError` so the studio
5699
+ * surfaces an actionable message. Mirrors {@link resolveWorkflowBinding}.
5700
+ */
5701
+ private resolveQueueBinding;
5702
+ /**
5703
+ * Pick the replay destination export for a captured message's origin queue.
5704
+ * When the message was consumed off a queue that is another queue's dead-letter
5705
+ * queue, prefer that PARENT queue's producer (a DLQ usually has no producer of
5706
+ * its own) so replay redrives onto the original; otherwise re-enqueue onto the
5707
+ * queue the message came from. Returns `undefined` when neither is declared.
5708
+ */
5709
+ private resolveReplayTarget;
5710
+ /**
5658
5711
  * Append one durable audit entry for a state-changing admin op that just
5659
5712
  * succeeded, folding the acting user (from `getCurrentUserId`) into `detail`.
5660
5713
  * Called only on the success path, so a rejected/validated op leaves no
@@ -5860,6 +5913,17 @@ declare abstract class ShardDO {
5860
5913
  * JSON memo still suppresses byte-identical pushes).
5861
5914
  */
5862
5915
  private readAdminCapturedMail;
5916
+ /**
5917
+ * Resolve a `getQueueMessages` admin read — the dev queue catcher's consumed
5918
+ * message log (`queue-catcher.ts`), newest-first, optionally filtered to one
5919
+ * queue. Best-effort: a SQL failure returns an empty log rather than throwing.
5920
+ * Reported against the {@link QUEUE_TABLE} so this read participates in
5921
+ * table-scoped subscription invalidation, but new captures arrive via the
5922
+ * worker→root-shard `recordQueueMessage` write, which (like the mail catcher)
5923
+ * inserts directly without a `flushChangedTables` — so the panel refreshes on
5924
+ * its poll (`useAutoRefresh`) rather than a live push.
5925
+ */
5926
+ private readAdminQueueMessages;
5863
5927
  /** Resolve a `readTablePage` admin read, parsing the loosely-typed args into the reader's options. */
5864
5928
  private readAdminTablePage;
5865
5929
  /**
package/dist/index.mjs CHANGED
@@ -11,7 +11,7 @@ export { diffExternalSource } from './packem_shared/diffExternalSource-Cx9HUPJj.
11
11
  export { materializeExternalRows, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-DNcvoLRT.mjs';
12
12
  export { isSourceDue, liftSourceId, pullExternalSourceTick } from './packem_shared/isSourceDue-BptUN8CR.mjs';
13
13
  export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
14
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-DSUQ5fX9.mjs';
14
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-BbQdj8h1.mjs';
15
15
  export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
16
16
  export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
17
17
  export { default as NotFoundError } from './packem_shared/NotFoundError-C70b9hLw.mjs';
@@ -19,14 +19,14 @@ export { armRestore, readBookmark } from './packem_shared/armRestore-BJk53Ro8.mj
19
19
  export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-BvZdFUBT.mjs';
20
20
  export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
21
21
  export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-BYlSGY0N.mjs';
22
- export { serveRelationFanout } from './packem_shared/serveRelationFanout-C5axmGDj.mjs';
22
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-BhZF9AuB.mjs';
23
23
  export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-CjM9Y1_G.mjs';
24
24
  export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-CAwZfp-5.mjs';
25
25
  export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-DnjkqVgY.mjs';
26
26
  export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
27
27
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
28
28
  export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs';
29
- export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-BQoX61l-.mjs';
29
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-BM4TOOvf.mjs';
30
30
  export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs';
31
31
  export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs';
32
32
  export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
@@ -7,6 +7,7 @@ const ADMIN_FUNCTIONS = {
7
7
  applyCdc: "__lunora_admin__:applyCdc",
8
8
  cdcSync: "__lunora_admin__:cdcSync",
9
9
  clearCapturedMail: "__lunora_admin__:clearCapturedMail",
10
+ clearQueueMessages: "__lunora_admin__:clearQueueMessages",
10
11
  clearTable: "__lunora_admin__:clearTable",
11
12
  createWorkflowInstance: "__lunora_admin__:createWorkflowInstance",
12
13
  deleteRows: "__lunora_admin__:deleteRows",
@@ -25,6 +26,7 @@ const ADMIN_FUNCTIONS = {
25
26
  getLogs: "__lunora_admin__:getLogs",
26
27
  getMetrics: "__lunora_admin__:getMetrics",
27
28
  getPitrBookmark: "__lunora_admin__:getPitrBookmark",
29
+ getQueueMessages: "__lunora_admin__:getQueueMessages",
28
30
  getRequestLog: "__lunora_admin__:getRequestLog",
29
31
  getSecurityAudit: "__lunora_admin__:getSecurityAudit",
30
32
  getSettings: "__lunora_admin__:getSettings",
@@ -44,10 +46,13 @@ const ADMIN_FUNCTIONS = {
44
46
  recordAuthEvent: "__lunora_admin__:recordAuthEvent",
45
47
  recordContainerEvent: "__lunora_admin__:recordContainerEvent",
46
48
  recordMail: "__lunora_admin__:recordMail",
49
+ recordQueueMessage: "__lunora_admin__:recordQueueMessage",
50
+ replayQueueMessage: "__lunora_admin__:replayQueueMessage",
47
51
  rlsPolicies: "__lunora_admin__:rlsPolicies",
48
52
  runAs: "__lunora_admin__:runAs",
49
53
  runMigration: "__lunora_admin__:runMigration",
50
54
  runSql: "__lunora_admin__:runSql",
55
+ sendQueueMessage: "__lunora_admin__:sendQueueMessage",
51
56
  sendTestMail: "__lunora_admin__:sendTestMail",
52
57
  storageOrphans: "__lunora_admin__:storageOrphans",
53
58
  storageReferences: "__lunora_admin__:storageReferences",
@@ -6,7 +6,7 @@ import { recordAuthEvent, readAuthMetrics } from './AUTH_METRICS_BUCKETS_TABLE-C
6
6
  import { DATA_MIGRATION_STATE_TABLE, readMigrationStatus } from './DATA_MIGRATION_STATE_TABLE-DB3IYUR3.mjs';
7
7
  import { SCAN_DEP, createDependencyTracker, tableFromDepKey } from './SCAN_DEP-DLJF8dsj.mjs';
8
8
  import { readFunctionMetricsTotals, readFunctionMetricIndexHits, recordFunctionMetric, mergeScanAttribution, readFunctionMetrics, readFunctionMetricBuckets } from './FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
9
- 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-DSUQ5fX9.mjs';
9
+ 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-BbQdj8h1.mjs';
10
10
  import { LogBuffer } from './LogBuffer-B_Ezju_N.mjs';
11
11
  import { recordCapturedMail, clearCapturedMail, readCapturedMail, MAIL_TABLE } from './MAIL_RETENTION-CPpgl-dX.mjs';
12
12
  import { readBookmark, armRestore } from './armRestore-BJk53Ro8.mjs';
@@ -24,12 +24,12 @@ const MAX_BATCH_ENTRIES = 500;
24
24
 
25
25
  const AUDIT_LOG_TABLE = "__lunora_audit__";
26
26
  const AUDIT_LOG_RETENTION = 1e3;
27
- const runSql$2 = (sql, query, ...params) => {
27
+ const runSql$3 = (sql, query, ...params) => {
28
28
  const runner = sql.exec;
29
29
  return runner.call(sql, query, ...params);
30
30
  };
31
31
  const ensureAuditTable = (sql) => {
32
- runSql$2(
32
+ runSql$3(
33
33
  sql,
34
34
  `CREATE TABLE IF NOT EXISTS "${AUDIT_LOG_TABLE}" (
35
35
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -43,7 +43,7 @@ const ensureAuditTable = (sql) => {
43
43
  };
44
44
  const appendAuditEntry = (sql, entry) => {
45
45
  ensureAuditTable(sql);
46
- runSql$2(
46
+ runSql$3(
47
47
  sql,
48
48
  `INSERT INTO "${AUDIT_LOG_TABLE}" (ts, op, "table", id, detail) VALUES (?, ?, ?, ?, ?)`,
49
49
  entry.ts,
@@ -55,13 +55,13 @@ const appendAuditEntry = (sql, entry) => {
55
55
  // eslint-disable-next-line unicorn/no-null -- SQL NULL is the correct value for an op with no associated table/id/detail.
56
56
  entry.detail === void 0 ? null : JSON.stringify(entry.detail)
57
57
  );
58
- runSql$2(sql, `DELETE FROM "${AUDIT_LOG_TABLE}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${AUDIT_LOG_TABLE}")`, AUDIT_LOG_RETENTION);
58
+ runSql$3(sql, `DELETE FROM "${AUDIT_LOG_TABLE}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${AUDIT_LOG_TABLE}")`, AUDIT_LOG_RETENTION);
59
59
  };
60
60
  const readAuditLog = (sql, options = {}) => {
61
61
  ensureAuditTable(sql);
62
62
  const sinceSeq = options.sinceSeq ?? 0;
63
63
  const limit = Math.max(1, Math.min(options.limit ?? AUDIT_LOG_RETENTION, 1e4));
64
- const rows = runSql$2(
64
+ const rows = runSql$3(
65
65
  sql,
66
66
  `SELECT seq, ts, op, "table", id, detail FROM "${AUDIT_LOG_TABLE}" WHERE seq > ? ORDER BY seq DESC LIMIT ?`,
67
67
  sinceSeq,
@@ -117,7 +117,7 @@ const buildBatchEntryRequest = (batchRequest, entry) => {
117
117
  const QUERY_METRICS_TABLE = "__lunora_metrics_queries";
118
118
  const QUERY_METRICS_MAX_SQL_LEN = 512;
119
119
  const QUERY_METRICS_MAX_STATEMENTS = 500;
120
- const runSql$1 = (sql, query, ...params) => {
120
+ const runSql$2 = (sql, query, ...params) => {
121
121
  const runner = sql.exec;
122
122
  return runner.call(sql, query, ...params);
123
123
  };
@@ -129,7 +129,7 @@ const normalizeSql = (sql) => {
129
129
  return normalized;
130
130
  };
131
131
  const ensureQueryMetricsTable = (sql) => {
132
- runSql$1(
132
+ runSql$2(
133
133
  sql,
134
134
  `CREATE TABLE IF NOT EXISTS "${QUERY_METRICS_TABLE}" (
135
135
  normalized_sql TEXT PRIMARY KEY,
@@ -146,10 +146,10 @@ const recordQueryMetric = (sql, rawSql, durationMs, rowsRead, rowsWritten) => {
146
146
  return;
147
147
  }
148
148
  ensureQueryMetricsTable(sql);
149
- const countRow = runSql$1(sql, `SELECT COUNT(*) AS n FROM "${QUERY_METRICS_TABLE}"`).one();
149
+ const countRow = runSql$2(sql, `SELECT COUNT(*) AS n FROM "${QUERY_METRICS_TABLE}"`).one();
150
150
  const count = countRow.n;
151
151
  if (count >= QUERY_METRICS_MAX_STATEMENTS) {
152
- const existing = runSql$1(sql, `SELECT COUNT(*) AS c FROM "${QUERY_METRICS_TABLE}" WHERE normalized_sql = ?`, normalized).one();
152
+ const existing = runSql$2(sql, `SELECT COUNT(*) AS c FROM "${QUERY_METRICS_TABLE}" WHERE normalized_sql = ?`, normalized).one();
153
153
  if (existing.c === 0) {
154
154
  return;
155
155
  }
@@ -161,11 +161,11 @@ const recordQueryMetric = (sql, rawSql, durationMs, rowsRead, rowsWritten) => {
161
161
  total_duration_ms = total_duration_ms + excluded.total_duration_ms,
162
162
  rows_read = rows_read + excluded.rows_read,
163
163
  rows_written = rows_written + excluded.rows_written`;
164
- runSql$1(sql, upsertSql, normalized, durationMs, rowsRead, rowsWritten);
164
+ runSql$2(sql, upsertSql, normalized, durationMs, rowsRead, rowsWritten);
165
165
  };
166
166
  const readQueryMetrics = (sql) => {
167
167
  ensureQueryMetricsTable(sql);
168
- const rows = runSql$1(
168
+ const rows = runSql$2(
169
169
  sql,
170
170
  `SELECT normalized_sql, exec_count, total_duration_ms, rows_read, rows_written FROM "${QUERY_METRICS_TABLE}" ORDER BY total_duration_ms DESC`
171
171
  ).toArray();
@@ -180,6 +180,128 @@ const readQueryMetrics = (sql) => {
180
180
  });
181
181
  };
182
182
 
183
+ const QUEUE_TABLE = "__lunora_queue_messages";
184
+ const QUEUE_RETENTION = 500;
185
+ const MAX_BODY_CHARS = 128 * 1024;
186
+ const runSql$1 = (sql, query, ...params) => {
187
+ const runner = sql.exec;
188
+ return runner.call(sql, query, ...params);
189
+ };
190
+ const orNull = (value) => (
191
+ // eslint-disable-next-line unicorn/no-null -- SQL NULL is the correct value for an absent column.
192
+ value ?? null
193
+ );
194
+ const TRUNCATION_SUFFIX = "… [truncated by the dev queue catcher]";
195
+ const UNSERIALIZABLE_MARKER = "[unserializable message body]";
196
+ const encodeBody = (value) => {
197
+ if (value === void 0) {
198
+ return "null";
199
+ }
200
+ try {
201
+ const encoded = JSON.stringify(value);
202
+ if (encoded.length > MAX_BODY_CHARS) {
203
+ return JSON.stringify(`${encoded.slice(0, MAX_BODY_CHARS)}${TRUNCATION_SUFFIX}`);
204
+ }
205
+ return encoded;
206
+ } catch {
207
+ return JSON.stringify(UNSERIALIZABLE_MARKER);
208
+ }
209
+ };
210
+ const isLossyBody = (body) => typeof body === "string" && (body === UNSERIALIZABLE_MARKER || body.endsWith(TRUNCATION_SUFFIX));
211
+ const decodeBody = (value) => {
212
+ if (value === null || value === void 0 || value === "") {
213
+ return void 0;
214
+ }
215
+ try {
216
+ return JSON.parse(value);
217
+ } catch {
218
+ return void 0;
219
+ }
220
+ };
221
+ const ensureQueueTable = (sql) => {
222
+ runSql$1(
223
+ sql,
224
+ `CREATE TABLE IF NOT EXISTS "${QUEUE_TABLE}" (
225
+ id TEXT PRIMARY KEY,
226
+ captured_at INTEGER NOT NULL,
227
+ message_id TEXT NOT NULL,
228
+ queue TEXT NOT NULL,
229
+ export_name TEXT,
230
+ body TEXT NOT NULL,
231
+ attempts INTEGER NOT NULL,
232
+ outcome TEXT NOT NULL,
233
+ error TEXT,
234
+ dead_lettered INTEGER NOT NULL,
235
+ message_ts INTEGER NOT NULL
236
+ )`
237
+ );
238
+ };
239
+ const recordQueueMessages = (sql, inputs, capturedAt) => {
240
+ ensureQueueTable(sql);
241
+ for (const input of inputs) {
242
+ runSql$1(
243
+ sql,
244
+ `INSERT INTO "${QUEUE_TABLE}" (id, captured_at, message_id, queue, export_name, body, attempts, outcome, error, dead_lettered, message_ts)
245
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
246
+ crypto.randomUUID(),
247
+ capturedAt,
248
+ input.messageId,
249
+ input.queue,
250
+ orNull(input.exportName),
251
+ encodeBody(input.body),
252
+ input.attempts,
253
+ input.outcome,
254
+ orNull(input.error),
255
+ input.deadLettered === true ? 1 : 0,
256
+ input.timestamp
257
+ );
258
+ }
259
+ runSql$1(
260
+ sql,
261
+ `DELETE FROM "${QUEUE_TABLE}"
262
+ WHERE id NOT IN (
263
+ SELECT id FROM "${QUEUE_TABLE}" ORDER BY captured_at DESC, id DESC LIMIT ?
264
+ )`,
265
+ QUEUE_RETENTION
266
+ );
267
+ return { recorded: inputs.length };
268
+ };
269
+ const rowToEntry = (row) => {
270
+ return {
271
+ attempts: row.attempts,
272
+ body: decodeBody(row.body),
273
+ capturedAt: row.captured_at,
274
+ deadLettered: row.dead_lettered === 1,
275
+ error: row.error ?? void 0,
276
+ exportName: row.export_name ?? void 0,
277
+ id: row.id,
278
+ messageId: row.message_id,
279
+ outcome: row.outcome,
280
+ queue: row.queue,
281
+ timestamp: row.message_ts
282
+ };
283
+ };
284
+ const readQueueMessages = (sql, options = {}) => {
285
+ ensureQueueTable(sql);
286
+ const limit = Math.min(Math.max(options.limit ?? 100, 1), QUEUE_RETENTION);
287
+ const filterQueue = typeof options.queue === "string" && options.queue.length > 0 ? options.queue : void 0;
288
+ const where = filterQueue === void 0 ? "" : `WHERE queue = ?`;
289
+ const params = filterQueue === void 0 ? [limit] : [filterQueue, limit];
290
+ const rows = runSql$1(sql, `SELECT * FROM "${QUEUE_TABLE}" ${where} ORDER BY captured_at DESC, id DESC LIMIT ?`, ...params).toArray();
291
+ return { entries: rows.map((row) => rowToEntry(row)) };
292
+ };
293
+ const readQueueMessageById = (sql, id) => {
294
+ ensureQueueTable(sql);
295
+ const rows = runSql$1(sql, `SELECT * FROM "${QUEUE_TABLE}" WHERE id = ? LIMIT 1`, id).toArray();
296
+ const row = rows[0];
297
+ return row === void 0 ? void 0 : rowToEntry(row);
298
+ };
299
+ const clearQueueMessages = (sql) => {
300
+ ensureQueueTable(sql);
301
+ runSql$1(sql, `DELETE FROM "${QUEUE_TABLE}"`);
302
+ return { cleared: true };
303
+ };
304
+
183
305
  const RELAY_NAME_INFIX = "::relay::";
184
306
  const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
185
307
  const parseRelayName = (name) => {
@@ -1512,6 +1634,84 @@ Verify your email: ${link}`,
1512
1634
  to: recipient
1513
1635
  };
1514
1636
  };
1637
+ const parseRecordQueueMessageArgs = (args) => {
1638
+ const bad = (message) => {
1639
+ throw Object.assign(new Error(`recordQueueMessage: ${message}`), { code: "BAD_REQUEST", name: "LunoraError", status: 400 });
1640
+ };
1641
+ const raw = args["messages"];
1642
+ if (!Array.isArray(raw)) {
1643
+ bad("`messages` must be an array");
1644
+ }
1645
+ const outcomes = /* @__PURE__ */ new Set(["ack", "error", "retry"]);
1646
+ return raw.map((entry, index) => {
1647
+ if (typeof entry !== "object" || entry === null) {
1648
+ bad(`\`messages[${String(index)}]\` must be an object`);
1649
+ }
1650
+ const record = entry;
1651
+ const messageId = typeof record["messageId"] === "string" ? record["messageId"] : "";
1652
+ const queue = typeof record["queue"] === "string" ? record["queue"] : "";
1653
+ const outcome = typeof record["outcome"] === "string" ? record["outcome"] : "";
1654
+ if (messageId === "") {
1655
+ bad(`\`messages[${String(index)}].messageId\` is required`);
1656
+ }
1657
+ if (queue === "") {
1658
+ bad(`\`messages[${String(index)}].queue\` is required`);
1659
+ }
1660
+ if (!outcomes.has(outcome)) {
1661
+ bad(`\`messages[${String(index)}].outcome\` must be one of ack | error | retry`);
1662
+ }
1663
+ const { attempts, timestamp } = record;
1664
+ return {
1665
+ attempts: typeof attempts === "number" && Number.isFinite(attempts) ? attempts : 1,
1666
+ body: record["body"],
1667
+ deadLettered: record["deadLettered"] === true,
1668
+ error: typeof record["error"] === "string" ? record["error"] : void 0,
1669
+ exportName: typeof record["exportName"] === "string" ? record["exportName"] : void 0,
1670
+ messageId,
1671
+ outcome,
1672
+ queue,
1673
+ timestamp: typeof timestamp === "number" && Number.isFinite(timestamp) ? timestamp : 0
1674
+ };
1675
+ });
1676
+ };
1677
+ const MAX_QUEUE_SEND_BATCH = 100;
1678
+ const parseSendQueueMessageArgs = (args) => {
1679
+ const exportName = typeof args["exportName"] === "string" ? args["exportName"].trim() : "";
1680
+ if (exportName === "") {
1681
+ throw Object.assign(new Error("sendQueueMessage: `exportName` is required"), { code: "BAD_REQUEST", name: "LunoraError", status: 400 });
1682
+ }
1683
+ const delayRaw = args["delaySeconds"];
1684
+ if (delayRaw !== void 0 && (typeof delayRaw !== "number" || !Number.isFinite(delayRaw) || delayRaw < 0)) {
1685
+ throw Object.assign(new Error("sendQueueMessage: `delaySeconds` must be a non-negative number"), {
1686
+ code: "BAD_REQUEST",
1687
+ name: "LunoraError",
1688
+ status: 400
1689
+ });
1690
+ }
1691
+ const batch = Array.isArray(args["batch"]) ? args["batch"] : void 0;
1692
+ if (batch !== void 0 && (batch.length === 0 || batch.length > MAX_QUEUE_SEND_BATCH)) {
1693
+ throw Object.assign(new Error(`sendQueueMessage: \`batch\` must contain between 1 and ${String(MAX_QUEUE_SEND_BATCH)} messages`), {
1694
+ code: "BAD_REQUEST",
1695
+ name: "LunoraError",
1696
+ status: 400
1697
+ });
1698
+ }
1699
+ return {
1700
+ batch,
1701
+ body: args["body"],
1702
+ contentType: typeof args["contentType"] === "string" ? args["contentType"] : void 0,
1703
+ delaySeconds: delayRaw,
1704
+ exportName
1705
+ };
1706
+ };
1707
+ const parseReplayQueueMessageArgs = (args) => {
1708
+ const id = typeof args["id"] === "string" ? args["id"].trim() : "";
1709
+ if (id === "") {
1710
+ throw Object.assign(new Error("replayQueueMessage: `id` is required"), { code: "BAD_REQUEST", name: "LunoraError", status: 400 });
1711
+ }
1712
+ const target = typeof args["target"] === "string" && args["target"].trim() !== "" ? args["target"].trim() : void 0;
1713
+ return { id, target };
1714
+ };
1515
1715
  const parseRankBeforeArgs = (args) => {
1516
1716
  const table = typeof args["table"] === "string" ? args["table"] : "";
1517
1717
  const index = typeof args["index"] === "string" ? args["index"] : "";
@@ -4186,6 +4386,18 @@ class ShardDO {
4186
4386
  if (functionPath === ADMIN_FUNCTIONS.sendTestMail) {
4187
4387
  return this.handleSendTestMail(args);
4188
4388
  }
4389
+ if (functionPath === ADMIN_FUNCTIONS.recordQueueMessage) {
4390
+ return this.handleRecordQueueMessage(args);
4391
+ }
4392
+ if (functionPath === ADMIN_FUNCTIONS.clearQueueMessages) {
4393
+ return this.handleClearQueueMessages();
4394
+ }
4395
+ if (functionPath === ADMIN_FUNCTIONS.sendQueueMessage) {
4396
+ return this.handleSendQueueMessage(args);
4397
+ }
4398
+ if (functionPath === ADMIN_FUNCTIONS.replayQueueMessage) {
4399
+ return this.handleReplayQueueMessage(args);
4400
+ }
4189
4401
  if (functionPath === ADMIN_FUNCTIONS.createWorkflowInstance) {
4190
4402
  return this.handleCreateWorkflowInstance(args);
4191
4403
  }
@@ -4395,6 +4607,127 @@ class ShardDO {
4395
4607
  const result = recordCapturedMail(this.state.storage.sql, input, Date.now());
4396
4608
  return jsonResponse({ result }, 200);
4397
4609
  }
4610
+ /**
4611
+ * Serve `__lunora_admin__:recordQueueMessage` — the capture sink the generated
4612
+ * worker `queue()` handler (via `@lunora/queue`'s `dispatchQueueBatch`) posts
4613
+ * every consumed message batch to. Records it into the reserved
4614
+ * `__lunora_queue_messages` table (bounded, auto-trimmed) so the studio Queues
4615
+ * panel shows one unified consumed-message log across every push consumer. Like
4616
+ * the mail catcher, the gate is the admin token alone — a token holder can
4617
+ * already mutate the shard, so a token-gated capture insert adds no privilege.
4618
+ */
4619
+ handleRecordQueueMessage(args) {
4620
+ const messages = parseRecordQueueMessageArgs(args);
4621
+ const result = recordQueueMessages(this.state.storage.sql, messages, Date.now());
4622
+ return jsonResponse({ result }, 200);
4623
+ }
4624
+ /** Empty the dev queue consumed-message log (studio "clear log" action). Admin-gated by the caller. */
4625
+ handleClearQueueMessages() {
4626
+ const result = clearQueueMessages(this.state.storage.sql);
4627
+ return jsonResponse({ result }, 200);
4628
+ }
4629
+ /**
4630
+ * Serve `__lunora_admin__:sendQueueMessage` — the studio's "Send test message"
4631
+ * button. Resolves the declared queue's `QUEUE_*` producer binding and calls
4632
+ * `.send(body, { delaySeconds?, contentType? })`, or `.sendBatch(...)` when a
4633
+ * `batch` array is supplied. No SQLite write happens here (the message is only
4634
+ * captured once a consumer processes it), so this only records an audit entry.
4635
+ * Admin-gated by `handleAdminRpc`'s caller.
4636
+ */
4637
+ async handleSendQueueMessage(args) {
4638
+ const parsed = parseSendQueueMessageArgs(args);
4639
+ const { binding } = this.resolveQueueBinding(parsed.exportName);
4640
+ let sent;
4641
+ if (parsed.batch === void 0) {
4642
+ await binding.send(parsed.body, { contentType: parsed.contentType, delaySeconds: parsed.delaySeconds });
4643
+ sent = 1;
4644
+ } else {
4645
+ await binding.sendBatch(
4646
+ parsed.batch.map((body) => {
4647
+ return { body, contentType: parsed.contentType, delaySeconds: parsed.delaySeconds };
4648
+ })
4649
+ );
4650
+ sent = parsed.batch.length;
4651
+ }
4652
+ this.recordAudit("sendQueueMessage", { detail: { count: sent, exportName: parsed.exportName } });
4653
+ return jsonResponse({ result: { sent } }, 200);
4654
+ }
4655
+ /**
4656
+ * Serve `__lunora_admin__:replayQueueMessage` — the studio's one-click replay /
4657
+ * DLQ redrive. Looks the captured row up by id, resolves the destination export
4658
+ * (explicit `target` → the parent queue when the message was captured off a
4659
+ * dead-letter queue → the queue it was consumed from), and re-enqueues the
4660
+ * stored body onto that producer. Records an audit entry; no SQLite write beyond
4661
+ * that (the replayed message is re-captured when a consumer processes it).
4662
+ * Admin-gated by `handleAdminRpc`'s caller.
4663
+ */
4664
+ async handleReplayQueueMessage(args) {
4665
+ const parsed = parseReplayQueueMessageArgs(args);
4666
+ const row = readQueueMessageById(this.state.storage.sql, parsed.id);
4667
+ if (row === void 0) {
4668
+ throw Object.assign(new Error(`replayQueueMessage: captured message "${parsed.id}" was not found`), {
4669
+ code: "BAD_REQUEST",
4670
+ name: "LunoraError",
4671
+ status: 404
4672
+ });
4673
+ }
4674
+ if (isLossyBody(row.body)) {
4675
+ throw Object.assign(
4676
+ new Error(`replayQueueMessage: captured message "${parsed.id}" has a truncated or unserializable body and can't be replayed faithfully`),
4677
+ { code: "BAD_REQUEST", name: "LunoraError", status: 422 }
4678
+ );
4679
+ }
4680
+ const target = parsed.target ?? this.resolveReplayTarget(row.queue) ?? row.exportName;
4681
+ if (typeof target !== "string" || target === "") {
4682
+ throw Object.assign(new Error(`replayQueueMessage: captured message "${parsed.id}" has no declared producer to replay onto (pass \`target\`)`), {
4683
+ code: "BAD_REQUEST",
4684
+ name: "LunoraError",
4685
+ status: 400
4686
+ });
4687
+ }
4688
+ const { binding } = this.resolveQueueBinding(target);
4689
+ await binding.send(row.body);
4690
+ this.recordAudit("replayQueueMessage", { detail: { messageId: row.messageId, target }, id: parsed.id });
4691
+ return jsonResponse({ result: { sent: 1, target } }, 200);
4692
+ }
4693
+ /**
4694
+ * Resolve a declared queue's runtime producer binding from this shard's `env`.
4695
+ * Looks the `exportName` up in {@link queuesMetadata} (the codegen subclass's
4696
+ * statically-discovered list) to find its generated `QUEUE_*` binding, then
4697
+ * reads `env[binding]` and validates it carries `send`/`sendBatch`. A bad export
4698
+ * name or a missing/malformed binding throws a 400 `LunoraError` so the studio
4699
+ * surfaces an actionable message. Mirrors {@link resolveWorkflowBinding}.
4700
+ */
4701
+ resolveQueueBinding(exportName) {
4702
+ const metadata = this.queuesMetadata().queues.find((queue) => queue.exportName === exportName);
4703
+ if (!metadata) {
4704
+ throw Object.assign(new Error(`queue "${exportName}" is not declared`), { code: "BAD_REQUEST", name: "LunoraError", status: 400 });
4705
+ }
4706
+ const binding = this.env?.[metadata.binding];
4707
+ if (typeof binding !== "object" || binding === null || typeof binding.send !== "function") {
4708
+ throw Object.assign(new Error(`queue binding "${metadata.binding}" is not available on this deployment`), {
4709
+ code: "BAD_REQUEST",
4710
+ name: "LunoraError",
4711
+ status: 400
4712
+ });
4713
+ }
4714
+ return { binding, metadata };
4715
+ }
4716
+ /**
4717
+ * Pick the replay destination export for a captured message's origin queue.
4718
+ * When the message was consumed off a queue that is another queue's dead-letter
4719
+ * queue, prefer that PARENT queue's producer (a DLQ usually has no producer of
4720
+ * its own) so replay redrives onto the original; otherwise re-enqueue onto the
4721
+ * queue the message came from. Returns `undefined` when neither is declared.
4722
+ */
4723
+ resolveReplayTarget(queueName) {
4724
+ const { queues } = this.queuesMetadata();
4725
+ const parent = queues.find((queue) => queue.deadLetterQueue === queueName);
4726
+ if (parent !== void 0) {
4727
+ return parent.exportName;
4728
+ }
4729
+ return queues.find((queue) => queue.name === queueName)?.exportName;
4730
+ }
4398
4731
  /**
4399
4732
  * Append one durable audit entry for a state-changing admin op that just
4400
4733
  * succeeded, folding the acting user (from `getCurrentUserId`) into `detail`.
@@ -4816,6 +5149,9 @@ class ShardDO {
4816
5149
  if (functionPath === ADMIN_FUNCTIONS.getCapturedMail) {
4817
5150
  return this.readAdminCapturedMail(sql, args);
4818
5151
  }
5152
+ if (functionPath === ADMIN_FUNCTIONS.getQueueMessages) {
5153
+ return this.readAdminQueueMessages(sql, args);
5154
+ }
4819
5155
  return void 0;
4820
5156
  }
4821
5157
  // eslint-disable-next-line class-methods-use-this -- kept an instance method for symmetry with the other `readAdmin*` resolvers
@@ -4846,6 +5182,28 @@ class ShardDO {
4846
5182
  }
4847
5183
  return { result, tables: /* @__PURE__ */ new Set([MAIL_TABLE]) };
4848
5184
  }
5185
+ /**
5186
+ * Resolve a `getQueueMessages` admin read — the dev queue catcher's consumed
5187
+ * message log (`queue-catcher.ts`), newest-first, optionally filtered to one
5188
+ * queue. Best-effort: a SQL failure returns an empty log rather than throwing.
5189
+ * Reported against the {@link QUEUE_TABLE} so this read participates in
5190
+ * table-scoped subscription invalidation, but new captures arrive via the
5191
+ * worker→root-shard `recordQueueMessage` write, which (like the mail catcher)
5192
+ * inserts directly without a `flushChangedTables` — so the panel refreshes on
5193
+ * its poll (`useAutoRefresh`) rather than a live push.
5194
+ */
5195
+ // eslint-disable-next-line class-methods-use-this -- kept an instance method for symmetry with the other `readAdmin*` resolvers
5196
+ readAdminQueueMessages(sql, args) {
5197
+ const limit = typeof args["limit"] === "number" ? args["limit"] : void 0;
5198
+ const queue = typeof args["queue"] === "string" ? args["queue"] : void 0;
5199
+ let result;
5200
+ try {
5201
+ result = readQueueMessages(sql, { limit, queue });
5202
+ } catch {
5203
+ result = { entries: [] };
5204
+ }
5205
+ return { result, tables: /* @__PURE__ */ new Set([QUEUE_TABLE]) };
5206
+ }
4849
5207
  /** Resolve a `readTablePage` admin read, parsing the loosely-typed args into the reader's options. */
4850
5208
  readAdminTablePage(sql, args) {
4851
5209
  const table = typeof args["table"] === "string" ? args["table"] : "";
@@ -1,4 +1,4 @@
1
- import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-DSUQ5fX9.mjs';
1
+ import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-BbQdj8h1.mjs';
2
2
 
3
3
  const serveRelationFanout = async (schema, database, functionPath, args) => {
4
4
  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.22",
3
+ "version": "1.0.0-alpha.23",
4
4
  "description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
5
5
  "keywords": [
6
6
  "cloudflare",