sporades 0.9.29 โ†’ 0.9.30

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/CHANGES.md CHANGED
@@ -1,26 +1,17 @@
1
1
  # Changes
2
2
 
3
- ## Unreleased - 2026-09-23
3
+ ## Unreleased - 2026-09-24
4
4
 
5
- Changes since v0.9.28.
5
+ Changes since v0.9.29.
6
6
 
7
7
  ### ๐Ÿ› Bug Fixes
8
8
 
9
- - Evict only on an unanswered ping and document the heartbeat (66f964e0).
10
- - Ping idle WebSockets so proxy idle timeouts do not strand pages (7ee9c7f8).
11
- - Start no freshclam after retained-updater cleanup once shutdown began (0185fc01).
12
- - Close ClamAV refresh shutdown race and recover crashed clamd (6c23b686).
13
- - Start degraded on stale ClamAV signatures and allow 26h freshness (79dd3ece).
14
- - Own ClamAV signature refresh in the Capsule runtime (17193be4).
15
-
16
- ### ๐Ÿงช Tests
17
-
18
- - Buffer raw WebSocket frames across TCP chunks (e07288dd).
19
- - Describe the ClamAV freshness gate without a fixed age (b180ac1a).
9
+ - Refresh only live queries that read a written table (6942754d).
10
+ - Detect Promise combinators from call sites, not formatted stacks (a55a4fd3).
20
11
 
21
12
  ### ๐Ÿ“ฆ Packaging
22
13
 
23
- - Regenerate runtime artifacts for the WebSocket heartbeat (d0b1193b).
14
+ - Regenerate runtime artifacts for scoped live query refresh (14235222).
24
15
 
25
16
  ## v0.9.11 - 2026-09-02
26
17
 
@@ -123,3 +114,4 @@ generated artifacts, and documentation.
123
114
 
124
115
 
125
116
 
117
+
@@ -26881,6 +26881,17 @@ function logPayloadMaxBytes(config = {}) {
26881
26881
  return config.logs?.payloadMaxBytes ?? config.logging?.payloadMaxBytes ?? 4096;
26882
26882
  }
26883
26883
 
26884
+ // src/live-query-invalidation.ts
26885
+ var { AsyncLocalStorage } = process.getBuiltinModule("node:async_hooks");
26886
+ var liveQueryTablesTracked = Symbol.for("sporades.database.liveQueryTablesTracked");
26887
+ var liveQueryReads = new AsyncLocalStorage();
26888
+ var quotedIdentifier = String.raw`(?:\[([^\]]+)\]|"([^"]+)")`;
26889
+ var readTablePattern = new RegExp(String.raw`\b(?:FROM|JOIN)\s+${quotedIdentifier}`, "gi");
26890
+ var writeTablePattern = new RegExp(
26891
+ String.raw`^\s*(?:INSERT(?:\s+OR\s+\w+)?\s+INTO|REPLACE\s+INTO|UPDATE(?:\s+OR\s+\w+)?|DELETE\s+FROM)\s+${quotedIdentifier}`,
26892
+ "i"
26893
+ );
26894
+
26884
26895
  // src/auth-admission.ts
26885
26896
  var AUTH_REQUIREMENTS = Symbol.for("sporades.auth.requirements");
26886
26897
  var ACCESS_KEY_SCOPE_LIMIT = 1024;
@@ -42989,7 +43000,7 @@ var MutationExecutionStorage = process.getBuiltinModule("node:async_hooks").Asyn
42989
43000
  var mutationExecution = new MutationExecutionStorage();
42990
43001
 
42991
43002
  // src/cli/cli-version.ts
42992
- var CLI_VERSION = "0.9.29";
43003
+ var CLI_VERSION = "0.9.30";
42993
43004
 
42994
43005
  // src/cli/schedule-inspection-envelope.ts
42995
43006
  var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
package/bin/sporades.js CHANGED
@@ -91674,7 +91674,26 @@ var promiseHookStop;
91674
91674
  var compositionRootCandidates = [];
91675
91675
  var thenableWrapperRoots = /* @__PURE__ */ new WeakSet();
91676
91676
  var promiseCombinatorKinds = /* @__PURE__ */ new WeakMap();
91677
+ var promiseCombinatorNames = /* @__PURE__ */ new Set(["all", "allSettled", "any", "race"]);
91677
91678
  var compositionRootClearQueued = false;
91679
+ function promiseCombinatorOnStack() {
91680
+ const prepareStackTrace = Error.prepareStackTrace;
91681
+ let callSites;
91682
+ Error.prepareStackTrace = (_error, sites) => sites;
91683
+ try {
91684
+ callSites = new Error().stack;
91685
+ } finally {
91686
+ Error.prepareStackTrace = prepareStackTrace;
91687
+ }
91688
+ if (!Array.isArray(callSites)) return void 0;
91689
+ for (const site of callSites) {
91690
+ if (site.isAsync?.()) continue;
91691
+ const name2 = site.getFunctionName();
91692
+ const type = site.getTypeName();
91693
+ if (name2 && promiseCombinatorNames.has(name2) && (type === "Promise" || type === "Function")) return name2;
91694
+ }
91695
+ return void 0;
91696
+ }
91678
91697
  function retainCompositionRootCandidate(promise) {
91679
91698
  compositionRootCandidates.push(promise);
91680
91699
  if (compositionRootClearQueued) return;
@@ -91690,11 +91709,11 @@ function installPromiseHook() {
91690
91709
  init(promise, parent) {
91691
91710
  if (parent) promiseParents.set(promise, parent);
91692
91711
  else retainCompositionRootCandidate(promise);
91693
- const match = parent && (new Error().stack ?? "").match(/at (?:Promise|Function)\.(all|allSettled|any|race)\b/);
91694
- if (parent && match) {
91712
+ const combinator = parent ? promiseCombinatorOnStack() : void 0;
91713
+ if (parent && combinator) {
91695
91714
  const root = [...compositionRootCandidates].reverse().find((candidate) => !thenableWrapperRoots.has(candidate));
91696
91715
  if (root) {
91697
- promiseCombinatorKinds.set(root, match[1]);
91716
+ promiseCombinatorKinds.set(root, combinator);
91698
91717
  const inputs = promiseCombinatorInputs.get(root) ?? /* @__PURE__ */ new Set();
91699
91718
  inputs.add(parent);
91700
91719
  promiseCombinatorInputs.set(root, inputs);
@@ -91769,25 +91788,23 @@ function promiseCompositionRootCandidate() {
91769
91788
  const wrapper = compositionRootCandidates.at(-1);
91770
91789
  if (!wrapper) return void 0;
91771
91790
  thenableWrapperRoots.add(wrapper);
91772
- const stack = new Error().stack ?? "";
91773
- const match = stack.match(/at (?:Promise|Function)\.(all|allSettled|any|race)\b/);
91774
- if (!match) return void 0;
91791
+ const combinator = promiseCombinatorOnStack();
91792
+ if (!combinator) return void 0;
91775
91793
  for (let index = compositionRootCandidates.length - 2; index >= 0; index -= 1) {
91776
91794
  const candidate = compositionRootCandidates[index];
91777
91795
  if (!thenableWrapperRoots.has(candidate)) {
91778
- promiseCombinatorKinds.set(candidate, match[1]);
91796
+ promiseCombinatorKinds.set(candidate, combinator);
91779
91797
  return candidate;
91780
91798
  }
91781
91799
  }
91782
- promiseCombinatorKinds.set(wrapper, match[1]);
91800
+ promiseCombinatorKinds.set(wrapper, combinator);
91783
91801
  return wrapper;
91784
91802
  }
91785
91803
  function promiseCombinatorKind(promise) {
91786
91804
  return promiseCombinatorKinds.get(promise);
91787
91805
  }
91788
91806
  function enclosingPromiseCombinatorRoot() {
91789
- const stack = new Error().stack ?? "";
91790
- if (!/at (?:Promise|Function)\.(?:all|allSettled|any|race)\b/.test(stack)) return void 0;
91807
+ if (!promiseCombinatorOnStack()) return void 0;
91791
91808
  for (let index = compositionRootCandidates.length - 1; index >= 0; index -= 1) {
91792
91809
  const candidate = compositionRootCandidates[index];
91793
91810
  if (!settledPromises.has(candidate)) return candidate;
@@ -114132,6 +114149,53 @@ function createEmailEventEndpoints(mailConfig, serverEnv, subscription) {
114132
114149
  return endpoints;
114133
114150
  }
114134
114151
 
114152
+ // src/live-query-invalidation.ts
114153
+ var { AsyncLocalStorage } = process.getBuiltinModule("node:async_hooks");
114154
+ var liveQueryTablesTracked = Symbol.for("sporades.database.liveQueryTablesTracked");
114155
+ var LIVE_QUERY_ANY_TABLE = "*";
114156
+ var liveQueryReads = new AsyncLocalStorage();
114157
+ var dirtyTables = /* @__PURE__ */ new Set();
114158
+ var quotedIdentifier = String.raw`(?:\[([^\]]+)\]|"([^"]+)")`;
114159
+ var readTablePattern = new RegExp(String.raw`\b(?:FROM|JOIN)\s+${quotedIdentifier}`, "gi");
114160
+ var writeTablePattern = new RegExp(
114161
+ String.raw`^\s*(?:INSERT(?:\s+OR\s+\w+)?\s+INTO|REPLACE\s+INTO|UPDATE(?:\s+OR\s+\w+)?|DELETE\s+FROM)\s+${quotedIdentifier}`,
114162
+ "i"
114163
+ );
114164
+ var nonWritingStatementPattern = /^\s*(?:BEGIN|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE|PRAGMA|SELECT)\b/i;
114165
+ function trackLiveQueryReads(tables, run2) {
114166
+ return liveQueryReads.run(tables, run2);
114167
+ }
114168
+ function recordLiveQueryStatementRead(sql2) {
114169
+ const tables = liveQueryReads.getStore();
114170
+ if (!tables) return;
114171
+ let identified = false;
114172
+ for (const match of String(sql2).matchAll(readTablePattern)) {
114173
+ tables.add(match[1] ?? match[2]);
114174
+ identified = true;
114175
+ }
114176
+ if (!identified) tables.add(LIVE_QUERY_ANY_TABLE);
114177
+ }
114178
+ function recordLiveQueryTableRead(table) {
114179
+ liveQueryReads.getStore()?.add(table);
114180
+ }
114181
+ function recordLiveQueryStatementWrite(sql2, result) {
114182
+ if (result && typeof result === "object" && "changes" in result && Number(result.changes) === 0) return;
114183
+ const text2 = String(sql2);
114184
+ if (nonWritingStatementPattern.test(text2)) return;
114185
+ const match = writeTablePattern.exec(text2);
114186
+ dirtyTables.add(match ? match[1] ?? match[2] : LIVE_QUERY_ANY_TABLE);
114187
+ }
114188
+ function takeLiveQueryDirtyTables() {
114189
+ const taken = dirtyTables;
114190
+ dirtyTables = /* @__PURE__ */ new Set();
114191
+ return taken;
114192
+ }
114193
+ function liveQueryNeedsRefresh(readTables, dirty) {
114194
+ if (!readTables || dirty.has(LIVE_QUERY_ANY_TABLE)) return true;
114195
+ for (const table of readTables) if (dirty.has(table)) return true;
114196
+ return false;
114197
+ }
114198
+
114135
114199
  // src/service-users-runtime.ts
114136
114200
  var SERVICE_USER_DISPLAY_NAME_BYTES = 160;
114137
114201
  function serviceUserError(code, message, hint) {
@@ -115097,8 +115161,8 @@ function chainSchemaOperation(previous, operation) {
115097
115161
  var nodeCryptoModule4 = process.getBuiltinModule("node:crypto");
115098
115162
  var nodeFsModule = process.getBuiltinModule("node:fs");
115099
115163
  function createConnectionTransactionGate() {
115100
- const AsyncLocalStorage = process.getBuiltinModule("node:async_hooks").AsyncLocalStorage;
115101
- const transactionOwnership = new AsyncLocalStorage();
115164
+ const AsyncLocalStorage2 = process.getBuiltinModule("node:async_hooks").AsyncLocalStorage;
115165
+ const transactionOwnership = new AsyncLocalStorage2();
115102
115166
  const transactionOwner = Object.freeze({});
115103
115167
  let transactionTail = Promise.resolve();
115104
115168
  let transactionActive = false;
@@ -116449,18 +116513,28 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
116449
116513
  };
116450
116514
  return {
116451
116515
  exec(sql2) {
116452
- return run2(() => useConnection(() => connection.exec(sql2)));
116516
+ return run2(() => useConnection(() => {
116517
+ const result = connection.exec(sql2);
116518
+ recordLiveQueryStatementWrite(sql2);
116519
+ return result;
116520
+ }));
116453
116521
  },
116454
116522
  prepare(sql2) {
116455
116523
  return {
116456
116524
  all(...params) {
116525
+ recordLiveQueryStatementRead(sql2);
116457
116526
  return run2(() => useConnection(() => connection.prepare(sql2).all(...params)));
116458
116527
  },
116459
116528
  get(...params) {
116529
+ recordLiveQueryStatementRead(sql2);
116460
116530
  return run2(() => useConnection(() => connection.prepare(sql2).get(...params)));
116461
116531
  },
116462
116532
  run(...params) {
116463
- return run2(() => useConnection(() => connection.prepare(sql2).run(...params)));
116533
+ return run2(() => useConnection(() => {
116534
+ const result = connection.prepare(sql2).run(...params);
116535
+ recordLiveQueryStatementWrite(sql2, result);
116536
+ return result;
116537
+ }));
116464
116538
  },
116465
116539
  columns() {
116466
116540
  return run2(() => useConnection(() => connection.prepare(sql2).columns()));
@@ -116473,6 +116547,9 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
116473
116547
  ...createSharedDatabaseAdapterMethods(dialect),
116474
116548
  ...createOperations(connectionGate.runOperation),
116475
116549
  engine: "sqlite",
116550
+ // Every statement on the shared and dedicated connections reports its tables, so live
116551
+ // query refreshes can be scoped to the tables a write changed.
116552
+ [liveQueryTablesTracked]: true,
116476
116553
  // Outer resources must be able to open one independent durable SQLite
116477
116554
  // connection. This runtime-owned marker propagates through transaction
116478
116555
  // adapters; callers cannot opt an in-memory or read-only adapter in.
@@ -116492,8 +116569,30 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
116492
116569
  let begun = false;
116493
116570
  let commitIssued = false;
116494
116571
  const operations = {
116495
- exec: (sql2) => dedicated.exec(sql2),
116496
- prepare: (sql2) => dedicated.prepare(sql2)
116572
+ exec: (sql2) => {
116573
+ const result = dedicated.exec(sql2);
116574
+ recordLiveQueryStatementWrite(sql2);
116575
+ return result;
116576
+ },
116577
+ prepare: (sql2) => {
116578
+ const statement = dedicated.prepare(sql2);
116579
+ return Object.assign(Object.create(statement), {
116580
+ all: (...params) => {
116581
+ recordLiveQueryStatementRead(sql2);
116582
+ return statement.all(...params);
116583
+ },
116584
+ get: (...params) => {
116585
+ recordLiveQueryStatementRead(sql2);
116586
+ return statement.get(...params);
116587
+ },
116588
+ run: (...params) => {
116589
+ const result = statement.run(...params);
116590
+ recordLiveQueryStatementWrite(sql2, result);
116591
+ return result;
116592
+ },
116593
+ columns: () => statement.columns()
116594
+ });
116595
+ }
116497
116596
  };
116498
116597
  const transaction = createTransactionScopedAdapter(adapter, operations, adapter, "transaction");
116499
116598
  try {
@@ -123214,9 +123313,11 @@ function createWebSocketHub(getDatabase, trustedRefresh = null, options = {}) {
123214
123313
  subscription.generation = generation;
123215
123314
  try {
123216
123315
  const database = getDatabase();
123217
- const result = await runQuery(database, client.session.auth, subscription.name, subscription.args, {
123316
+ const readTables = /* @__PURE__ */ new Set();
123317
+ const result = await trackLiveQueryReads(readTables, () => runQuery(database, client.session.auth, subscription.name, subscription.args, {
123218
123318
  sessionToken: client.session.token
123219
- });
123319
+ }));
123320
+ if (subscription.generation === generation) subscription.readTables = result?.error || readTables.has(LIVE_QUERY_ANY_TABLE) ? null : readTables;
123220
123321
  const data2 = subscription.style === "direct" ? result.data ?? result.rows : { rows: result.data ?? result.rows };
123221
123322
  if (client.subscriptions.get(subscription.id) !== subscription || subscription.generation !== generation) return;
123222
123323
  sendJson(client, {
@@ -123228,6 +123329,7 @@ function createWebSocketHub(getDatabase, trustedRefresh = null, options = {}) {
123228
123329
  });
123229
123330
  } catch (error) {
123230
123331
  if (client.subscriptions.get(subscription.id) !== subscription || subscription.generation !== generation) return;
123332
+ subscription.readTables = null;
123231
123333
  try {
123232
123334
  onError(error);
123233
123335
  } catch {
@@ -123235,8 +123337,11 @@ function createWebSocketHub(getDatabase, trustedRefresh = null, options = {}) {
123235
123337
  }
123236
123338
  }
123237
123339
  function refreshQueries() {
123340
+ const dirty = takeLiveQueryDirtyTables();
123341
+ const scoped = getDatabase()?.adapter?.[liveQueryTablesTracked] === true;
123238
123342
  for (const subscribedClient of clients) {
123239
123343
  for (const subscription of subscribedClient.subscriptions.values()) {
123344
+ if (scoped && !liveQueryNeedsRefresh(subscription.readTables, dirty)) continue;
123240
123345
  void sendQueryResult(
123241
123346
  subscribedClient,
123242
123347
  subscription,
@@ -123515,6 +123620,7 @@ async function runQuery(database, auth, queryName, rawArgs = [], options = {}) {
123515
123620
  }
123516
123621
  if (args.length > 0) return { rows: null, data: null, error: invalidQueryArgumentsError() };
123517
123622
  const cacheKey = `${table.name}:${context.auth.userId}`;
123623
+ recordLiveQueryTableRead(table.name);
123518
123624
  if (!database.rowCache.has(cacheKey)) {
123519
123625
  const columns = ["id", "createdAt", "updatedAt", ...table.fields.map((field) => field.name)];
123520
123626
  const ownerScoped = table.fields.some((field) => field.name === "ownerId");
@@ -131249,7 +131355,7 @@ jobs:
131249
131355
  }
131250
131356
 
131251
131357
  // src/cli/cli-version.ts
131252
- var CLI_VERSION = "0.9.29";
131358
+ var CLI_VERSION = "0.9.30";
131253
131359
 
131254
131360
  // src/cli/sporades.ts
131255
131361
  var SUPPORTED_TEMPLATES = new Set(CLIENT_TEMPLATES);
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.9.29";
1
+ export declare const CLI_VERSION = "0.9.30";
2
2
  //# sourceMappingURL=cli-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Generated by `npm run build` from package.json. Do not edit by hand.
2
- export const CLI_VERSION = "0.9.29";
2
+ export const CLI_VERSION = "0.9.30";
3
3
  //# sourceMappingURL=cli-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database-runtime.d.ts","sourceRoot":"","sources":["../src/database-runtime.ts"],"names":[],"mappings":"AAmKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAyBxC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,KAAK,aAAa,GAAG,WAAW,CAAC;AACjC,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAgGrD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,WAOvE;AA+DD,wBAAsB,4BAA4B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAqBlJ;AAED,wBAAsB,8BAA8B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS3J;AAkBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAyBpE;AA2BD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAa1E;AAMD,wBAAgB,sBAAsB,gBAMrC;AAED,wBAAgB,wBAAwB,gBAgBvC;AAED,wBAAgB,sBAAsB,gBASrC;AAGD,wBAAgB,qBAAqB,gBAoCpC;AAED,wBAAgB,uBAAuB,gBAmDtC;AAaD,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAunCpF;AAED,wBAAsB,2BAA2B,CAAC,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,wBA4KlG;AAED,wBAAsB,6BAA6B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;CAAE,wBA2YzE;AAID,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW;;eAsG9D,MAAM;;;;;;GAwIpB;AA8MD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAE,GAAG,EAAO,UA2E/D;AAMD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,CAAC;CAAE;;;;wBAiF1D,CAAC,kBAAkB,EAAE,WAAW,KAAK,GAAG,YAAW;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE;6BA8BtE,CAAC,OAAO,EAAE,WAAW,KAAK,GAAG;;cAtFlD,MAAM;iBAUH,MAAM;uBAGA,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE;uBAQtB,SAAS,EAAE;uBAGX,MAAM,EAAE;;;GAuG9B;AA4VD,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,MAAa,OAM7F;AAyED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAEhF;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAE1E;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,EAAE,GAAG,EAAE,GAAG,gBAExF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,YAuE1C;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,GAAG,UAE9C"}
1
+ {"version":3,"file":"database-runtime.d.ts","sourceRoot":"","sources":["../src/database-runtime.ts"],"names":[],"mappings":"AAoKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAyBxC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,KAAK,aAAa,GAAG,WAAW,CAAC;AACjC,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAgGrD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,WAOvE;AA+DD,wBAAsB,4BAA4B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAqBlJ;AAED,wBAAsB,8BAA8B,CAAC,YAAY,EAAE,GAAG,EAAE,SAAS,GAAE,UAAe,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS3J;AAkBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAyBpE;AA2BD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAa1E;AAMD,wBAAgB,sBAAsB,gBAMrC;AAED,wBAAgB,wBAAwB,gBAgBvC;AAED,wBAAgB,sBAAsB,gBASrC;AAGD,wBAAgB,qBAAqB,gBAoCpC;AAED,wBAAgB,uBAAuB,gBAmDtC;AAaD,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAunCpF;AAED,wBAAsB,2BAA2B,CAAC,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,wBAyMlG;AAED,wBAAsB,6BAA6B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;CAAE,wBA2YzE;AAID,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW;;eAsG9D,MAAM;;;;;;GAwIpB;AA8MD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAE,GAAG,EAAO,UA2E/D;AAMD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,CAAC;CAAE;;;;wBAiF1D,CAAC,kBAAkB,EAAE,WAAW,KAAK,GAAG,YAAW;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE;6BA8BtE,CAAC,OAAO,EAAE,WAAW,KAAK,GAAG;;cAtFlD,MAAM;iBAUH,MAAM;uBAGA,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE;uBAQtB,SAAS,EAAE;uBAGX,MAAM,EAAE;;;GAuG9B;AA4VD,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,MAAa,OAM7F;AAyED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAEhF;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,gBAE1E;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;CAAE,EAAE,GAAG,EAAE,GAAG,gBAExF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,YAuE1C;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,GAAG,UAE9C"}
@@ -1,5 +1,6 @@
1
1
  import { acquirePostgresResourceBootstrapLock, resourceError } from "./resource-runtime.js";
2
2
  import { notificationIntentSchemas } from "./notification-intent-runtime.js";
3
+ import { liveQueryTablesTracked, recordLiveQueryStatementRead, recordLiveQueryStatementWrite } from "./live-query-invalidation.js";
3
4
  // The Capsule runtime's Database adapters and dialect: the three engines, the seam they answer, the
4
5
  // one shared method set every behavioural call goes through, and the app-schema DDL that method set
5
6
  // emits. Batch 9 of the migration ADR-0041 records, and the last domain to leave
@@ -1382,18 +1383,28 @@ export async function createSqliteDatabaseAdapter(databasePath, options = {}) {
1382
1383
  };
1383
1384
  return {
1384
1385
  exec(sql) {
1385
- return run(() => useConnection(() => connection.exec(sql)));
1386
+ return run(() => useConnection(() => {
1387
+ const result = connection.exec(sql);
1388
+ recordLiveQueryStatementWrite(sql);
1389
+ return result;
1390
+ }));
1386
1391
  },
1387
1392
  prepare(sql) {
1388
1393
  return {
1389
1394
  all(...params) {
1395
+ recordLiveQueryStatementRead(sql);
1390
1396
  return run(() => useConnection(() => connection.prepare(sql).all(...params)));
1391
1397
  },
1392
1398
  get(...params) {
1399
+ recordLiveQueryStatementRead(sql);
1393
1400
  return run(() => useConnection(() => connection.prepare(sql).get(...params)));
1394
1401
  },
1395
1402
  run(...params) {
1396
- return run(() => useConnection(() => connection.prepare(sql).run(...params)));
1403
+ return run(() => useConnection(() => {
1404
+ const result = connection.prepare(sql).run(...params);
1405
+ recordLiveQueryStatementWrite(sql, result);
1406
+ return result;
1407
+ }));
1397
1408
  },
1398
1409
  columns() {
1399
1410
  return run(() => useConnection(() => connection.prepare(sql).columns()));
@@ -1408,6 +1419,9 @@ export async function createSqliteDatabaseAdapter(databasePath, options = {}) {
1408
1419
  ...createSharedDatabaseAdapterMethods(dialect),
1409
1420
  ...createOperations(connectionGate.runOperation),
1410
1421
  engine: "sqlite",
1422
+ // Every statement on the shared and dedicated connections reports its tables, so live
1423
+ // query refreshes can be scoped to the tables a write changed.
1424
+ [liveQueryTablesTracked]: true,
1411
1425
  // Outer resources must be able to open one independent durable SQLite
1412
1426
  // connection. This runtime-owned marker propagates through transaction
1413
1427
  // adapters; callers cannot opt an in-memory or read-only adapter in.
@@ -1432,8 +1446,24 @@ export async function createSqliteDatabaseAdapter(databasePath, options = {}) {
1432
1446
  let begun = false;
1433
1447
  let commitIssued = false;
1434
1448
  const operations = {
1435
- exec: (sql) => dedicated.exec(sql),
1436
- prepare: (sql) => dedicated.prepare(sql),
1449
+ exec: (sql) => {
1450
+ const result = dedicated.exec(sql);
1451
+ recordLiveQueryStatementWrite(sql);
1452
+ return result;
1453
+ },
1454
+ prepare: (sql) => {
1455
+ const statement = dedicated.prepare(sql);
1456
+ return Object.assign(Object.create(statement), {
1457
+ all: (...params) => { recordLiveQueryStatementRead(sql); return statement.all(...params); },
1458
+ get: (...params) => { recordLiveQueryStatementRead(sql); return statement.get(...params); },
1459
+ run: (...params) => {
1460
+ const result = statement.run(...params);
1461
+ recordLiveQueryStatementWrite(sql, result);
1462
+ return result;
1463
+ },
1464
+ columns: () => statement.columns(),
1465
+ });
1466
+ },
1437
1467
  };
1438
1468
  const transaction = createTransactionScopedAdapter(adapter, operations, adapter, "transaction");
1439
1469
  try {