@xopcai/xopc 0.0.246 → 0.0.248

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/src/update-offset-store.js +17 -54
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/extensions/weixin/src/auth/accounts.js +3 -5
  5. package/dist/extensions/weixin/src/monitor/monitor.js +3 -5
  6. package/dist/extensions/weixin/src/storage/sync-buf.d.ts +3 -6
  7. package/dist/extensions/weixin/src/storage/sync-buf.js +9 -22
  8. package/dist/gateway/static/root/assets/{activity-store-BlxqErph.js → activity-store-etpmyV7S.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CJJzpYYS.js → agent-avatar-dicebear-BmKxSM1k.js} +1 -1
  10. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CUXApfcE.js +1 -0
  11. package/dist/gateway/static/root/assets/{agents-je3sRGmk.js → agents-DNq_GKuc.js} +1 -1
  12. package/dist/gateway/static/root/assets/{api-BbXzyQMj.js → api-B0-hLuZ1.js} +1 -1
  13. package/dist/gateway/static/root/assets/{app-management-settings-panel-Cnn3i00c.js → app-management-settings-panel-qfwAgyhQ.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-BToQaw_v.js → appearance-settings-DqQVUpCC.js} +1 -1
  15. package/dist/gateway/static/root/assets/apps-page-dwIqYmXk.js +1 -0
  16. package/dist/gateway/static/root/assets/{archive-plugin-DD2SweEH.js → archive-plugin-BnMSgZvR.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-BFtcib5t.js → attachment-load-CejsQMev.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-XS8wIl5o.js → automations-page-RC8EyoAf.js} +1 -1
  19. package/dist/gateway/static/root/assets/automations-workspace-SkdcSZ0i.js +6 -0
  20. package/dist/gateway/static/root/assets/{binary-plugins-Xs7pLHxR.js → binary-plugins-DGjppLSG.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-BPdSzceB.js → block-editor-CWdP4bUc.js} +44 -44
  22. package/dist/gateway/static/root/assets/browser-settings-page-CKCa6TvG.js +8 -0
  23. package/dist/gateway/static/root/assets/{browser-workflow-inputs-BAAc151l.js → browser-workflow-inputs-BQ1Ilw_o.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-workflows-page-DUPPvE30.js → browser-workflows-page-CJh4NyxT.js} +2 -2
  25. package/dist/gateway/static/root/assets/{capabilities-settings-panel-cl9kYilL.js → capabilities-settings-panel-DSp--zIZ.js} +5 -5
  26. package/dist/gateway/static/root/assets/channels-settings-CMd2dDrI.js +1 -0
  27. package/dist/gateway/static/root/assets/{channels-status-swr-CCBCtLDg.js → channels-status-swr-Ohhee4vm.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-DKW1M6aJ.js → chat-terminal-dock-BOtoKTK3.js} +2 -2
  29. package/dist/gateway/static/root/assets/connectors-page-udWs5Kdm.js +2 -0
  30. package/dist/gateway/static/root/assets/{cron-expression-Bijb0ZQu.js → cron-expression-DTj1V3Sw.js} +1 -1
  31. package/dist/gateway/static/root/assets/{date-picker-BZuHw-SY.js → date-picker-Yr6OCvGL.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-BsBD2JFY.js → dependency-picker-DpN3XZg9.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-Ceuw3QKb.js → desktop-pet-DbGzggPF.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-settings-DctOXexM.js → desktop-pet-settings-DinCToZh.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-BNlMPATK.js → extension-debug-page-DgTtk4KC.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-CK4Uvzge.js → extension-page-DtQeiP6f.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-CpnXXemw.js → extension-settings-page-DcguCgsH.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-config-api-C_mlpe-r.js → gateway-config-api-Br7lSf6H.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-config-swr-826dipyU.js → gateway-config-swr-DXuXtU1w.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-settings-DSSW17ed.js → gateway-settings-CdPoDMuj.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-startup-retry-DyvcAIMm.js → gateway-startup-retry-CzBzglzQ.js} +1 -1
  42. package/dist/gateway/static/root/assets/{heartbeat-settings-hbmIZTpg.js → heartbeat-settings-DHk1hawP.js} +1 -1
  43. package/dist/gateway/static/root/assets/home-page-YGipe_W6.js +1 -0
  44. package/dist/gateway/static/root/assets/{image-generation-api-D7cyfks6.js → image-generation-api-TIgsn04n.js} +1 -1
  45. package/dist/gateway/static/root/assets/{index-eZjksPvq.js → index-D5TQQpeb.js} +44 -44
  46. package/dist/gateway/static/root/assets/index-DReBaaTX.css +2 -0
  47. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BQrDwwgi.js → keyboard-shortcuts-settings-wsWuoa8p.js} +1 -1
  48. package/dist/gateway/static/root/assets/local-app-workbench-page-CpyvYyrb.js +2 -0
  49. package/dist/gateway/static/root/assets/{local-apps-page-D-YWD6LS.js → local-apps-page-CXrbrGo8.js} +1 -1
  50. package/dist/gateway/static/root/assets/local-context-graph-BjUf7fQ9.js +1 -0
  51. package/dist/gateway/static/root/assets/{locale-store-CBoCuIMx.js → locale-store-BJFLQY8O.js} +1 -1
  52. package/dist/gateway/static/root/assets/{logs-page-DvLc9l59.js → logs-page-CXKy6E_l.js} +2 -2
  53. package/dist/gateway/static/root/assets/{management-settings-DsULLUUv.js → management-settings-BdJZFR7U.js} +1 -1
  54. package/dist/gateway/static/root/assets/{markdown-split-CjqlP6IB.js → markdown-split-NK3ACVG2.js} +1 -1
  55. package/dist/gateway/static/root/assets/{markdown-view-BXsHghJd.js → markdown-view-DTF9v1gg.js} +1 -1
  56. package/dist/gateway/static/root/assets/{media-plugins-DQL029DU.js → media-plugins-F0jcUxyl.js} +1 -1
  57. package/dist/gateway/static/root/assets/messages-CHNYp8EQ.js +3 -0
  58. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-gauprAae.js → mobile-pairing-wizard-3NKCniSp.js} +1 -1
  59. package/dist/gateway/static/root/assets/models-hub-cache-nCfkBN8M.js +1 -0
  60. package/dist/gateway/static/root/assets/{notes-page-AsEfLZt9.js → notes-page-DuqLahi6.js} +1 -1
  61. package/dist/gateway/static/root/assets/notes-workbench-C7j9EdQk.js +5 -0
  62. package/dist/gateway/static/root/assets/{oauth-api-BjSUDIgq.js → oauth-api-8lP_qJpq.js} +1 -1
  63. package/dist/gateway/static/root/assets/{product-open-page-CAJeUXeU.js → product-open-page-CxGGlWA8.js} +1 -1
  64. package/dist/gateway/static/root/assets/project-detail-page-CfebYVX0.js +1 -0
  65. package/dist/gateway/static/root/assets/projects-page-48GvzSVa.js +1 -0
  66. package/dist/gateway/static/root/assets/{registry-api-BhGKnvLP.js → registry-api-CIsNlSBi.js} +1 -1
  67. package/dist/gateway/static/root/assets/remote-access-hub-64xj_s2m.js +1 -0
  68. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DH6EilMV.js → runtime-tools-settings-panel-YkjhiQfA.js} +2 -2
  69. package/dist/gateway/static/root/assets/{schema-form-DYHmEZeZ.js → schema-form-oFGf3G3s.js} +1 -1
  70. package/dist/gateway/static/root/assets/sessions-page-CcwcdRN8.js +1 -0
  71. package/dist/gateway/static/root/assets/settings-advanced-gate-BaN84CyW.js +1 -0
  72. package/dist/gateway/static/root/assets/{settings-page-BHwPd_M3.js → settings-page-C7VWs0IT.js} +1 -1
  73. package/dist/gateway/static/root/assets/setup-status-panel-DVVOCNbr.js +1 -0
  74. package/dist/gateway/static/root/assets/{share-preview-page-BjwrS03F.js → share-preview-page-t5oz18rh.js} +1 -1
  75. package/dist/gateway/static/root/assets/shares-settings-DUYbYLi7.js +1 -0
  76. package/dist/gateway/static/root/assets/skill-reload-api-BGyD0gB9.js +1 -0
  77. package/dist/gateway/static/root/assets/{skills-page-OHyK25op.js → skills-page-CjF8DTsN.js} +1 -1
  78. package/dist/gateway/static/root/assets/{skills-page.utils-DQx9ulmg.js → skills-page.utils-DEDTYs2W.js} +1 -1
  79. package/dist/gateway/static/root/assets/{system-settings-panel-wO7DWv4C.js → system-settings-panel-BSYuRgFI.js} +1 -1
  80. package/dist/gateway/static/root/assets/task-detail-page-YZYSTJH7.js +1 -0
  81. package/dist/gateway/static/root/assets/{theme-store-hybK3oC9.js → theme-store-O597U8q_.js} +1 -1
  82. package/dist/gateway/static/root/assets/{tunnel-api-CVC8BHZu.js → tunnel-api-FKUi9wDL.js} +1 -1
  83. package/dist/gateway/static/root/assets/{use-autosave-DMvRu2XZ.js → use-autosave-B5eUJ6Zq.js} +1 -1
  84. package/dist/gateway/static/root/assets/user-context-page-BCyiduKd.js +2 -0
  85. package/dist/gateway/static/root/assets/{voice-api-key-field-DuBK2NDG.js → voice-api-key-field-CMKSwz4C.js} +1 -1
  86. package/dist/gateway/static/root/assets/{voice-config-api-CBbiF1T3.js → voice-config-api-q7B6fOFB.js} +1 -1
  87. package/dist/gateway/static/root/assets/{work-discovery-overlay-B7VXWpDr.js → work-discovery-overlay-jhT2EzeH.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BokVAsWN.js → workflow-run-setup-panel-DZmvLZjb.js} +2 -2
  89. package/dist/gateway/static/root/assets/workflows-page-DQC0TpMa.js +5 -0
  90. package/dist/gateway/static/root/index.html +12 -12
  91. package/dist/package.js +1 -1
  92. package/dist/src/agent/commands/command-registry.d.ts +1 -0
  93. package/dist/src/agent/commands/command-registry.js +12 -22
  94. package/dist/src/agent/ipc/inbox.d.ts +8 -31
  95. package/dist/src/agent/ipc/inbox.js +64 -133
  96. package/dist/src/agent/service/process-direct-one-shot.js +15 -10
  97. package/dist/src/agent/workflow/catalog.d.ts +1 -8
  98. package/dist/src/agent/workflow/catalog.js +44 -102
  99. package/dist/src/agent/workflow/index.d.ts +1 -1
  100. package/dist/src/agent/workflow/index.js +2 -2
  101. package/dist/src/automations/domain/types.d.ts +1 -0
  102. package/dist/src/automations/service/action-executor.js +2 -2
  103. package/dist/src/automations/service/automation-service.js +2 -2
  104. package/dist/src/channels/manager.d.ts +1 -1
  105. package/dist/src/channels/manager.js +2 -2
  106. package/dist/src/channels/outbound/persist-store.d.ts +2 -8
  107. package/dist/src/channels/outbound/persist-store.js +11 -38
  108. package/dist/src/channels/outbound-sender.d.ts +1 -1
  109. package/dist/src/channels/outbound-sender.js +2 -2
  110. package/dist/src/cli/commands/connectors.js +1 -1
  111. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  112. package/dist/src/config/schema.js +2 -2
  113. package/dist/src/connectors/auth-provider-registry.js +1 -1
  114. package/dist/src/connectors/composio-triggers.d.ts +2 -2
  115. package/dist/src/connectors/composio-triggers.js +8 -15
  116. package/dist/src/connectors/connected-source-ingestion.js +1 -1
  117. package/dist/src/connectors/health.js +1 -1
  118. package/dist/src/gateway/automation-agent-session.js +3 -0
  119. package/dist/src/gateway/heartbeat/service.js +1 -1
  120. package/dist/src/gateway/hono/lib/extension-store.js +8 -25
  121. package/dist/src/gateway/hono/routes/capabilities.js +1 -1
  122. package/dist/src/gateway/hono/routes/connectors.js +3 -3
  123. package/dist/src/gateway/hono/routes/mcp.js +1 -1
  124. package/dist/src/gateway/hono/routes/projects.js +1 -1
  125. package/dist/src/gateway/hono/routes/you.js +20 -4
  126. package/dist/src/gateway/service.js +4 -5
  127. package/dist/src/heartbeat/index.js +1 -1
  128. package/dist/src/notes/index.d.ts +1 -1
  129. package/dist/src/notes/index.js +2 -2
  130. package/dist/src/notes/paths.d.ts +0 -3
  131. package/dist/src/notes/paths.js +1 -10
  132. package/dist/src/notes/service.d.ts +0 -1
  133. package/dist/src/notes/service.js +0 -3
  134. package/dist/src/notes/store.d.ts +0 -2
  135. package/dist/src/notes/store.js +33 -80
  136. package/dist/src/share/hosted-session-share.d.ts +1 -4
  137. package/dist/src/share/hosted-session-share.js +14 -47
  138. package/dist/src/share/share-auto.d.ts +1 -1
  139. package/dist/src/share/share-auto.js +2 -2
  140. package/dist/src/share/share-store.d.ts +2 -7
  141. package/dist/src/share/share-store.js +153 -195
  142. package/dist/src/share/share-types.d.ts +0 -4
  143. package/dist/src/share/site-share-store.d.ts +3 -8
  144. package/dist/src/share/site-share-store.js +94 -153
  145. package/dist/src/share/site-share-types.d.ts +0 -4
  146. package/dist/src/storage/sqlite/connection.d.ts +1 -2
  147. package/dist/src/storage/sqlite/connection.js +50 -47
  148. package/dist/src/storage/sqlite/durable-queue.d.ts +16 -0
  149. package/dist/src/storage/sqlite/durable-queue.js +52 -0
  150. package/dist/src/storage/sqlite/durable-state.d.ts +16 -0
  151. package/dist/src/storage/sqlite/durable-state.js +43 -0
  152. package/dist/src/storage/sqlite/migrations/146_understanding_focus_exclusions.sql +9 -0
  153. package/dist/src/storage/sqlite/migrations/147_durable_application_state.sql +39 -0
  154. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  155. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  156. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  157. package/dist/src/storage/sqlite/schema.js +1 -1
  158. package/dist/src/storage/sqlite/understanding-focus-exclusions.d.ts +2 -0
  159. package/dist/src/storage/sqlite/understanding-focus-exclusions.js +24 -0
  160. package/dist/src/tui/tui-scoped-models.js +9 -6
  161. package/dist/src/tui/tui-startup-resources.js +1 -1
  162. package/dist/src/voice/dashscope/streaming-tts-stream.js +49 -0
  163. package/dist/src/voice/realtime/agentEngine.js +63 -18
  164. package/dist/src/voice/realtime/conversationTurn.d.ts +20 -0
  165. package/dist/src/voice/realtime/conversationTurn.js +83 -0
  166. package/dist/src/voice/realtime/omniEngine.js +72 -18
  167. package/dist/src/workflows/authoring/workflow-draft-store.d.ts +1 -5
  168. package/dist/src/workflows/authoring/workflow-draft-store.js +23 -43
  169. package/dist/src/workflows/index.js +2 -2
  170. package/dist/src/workflows/store/event-store.d.ts +1 -2
  171. package/dist/src/workflows/store/event-store.js +19 -64
  172. package/dist/src/workflows/store/paths.d.ts +0 -2
  173. package/dist/src/workflows/store/paths.js +1 -7
  174. package/dist/src/workflows/store/run-store.d.ts +0 -1
  175. package/dist/src/workflows/store/run-store.js +1 -24
  176. package/package.json +3 -3
  177. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-0dRjnL2s.js +0 -1
  178. package/dist/gateway/static/root/assets/apps-page-EpUZsl4h.js +0 -1
  179. package/dist/gateway/static/root/assets/automations-workspace-BGQdHrip.js +0 -6
  180. package/dist/gateway/static/root/assets/browser-settings-page-DJGcm7rH.js +0 -8
  181. package/dist/gateway/static/root/assets/channels-settings-BcIr-_GM.js +0 -1
  182. package/dist/gateway/static/root/assets/connectors-page-DNHqaZfX.js +0 -2
  183. package/dist/gateway/static/root/assets/home-page-CaVvW77l.js +0 -1
  184. package/dist/gateway/static/root/assets/index-Dbh5SXyV.css +0 -2
  185. package/dist/gateway/static/root/assets/local-app-workbench-page-D3K1XmOM.js +0 -2
  186. package/dist/gateway/static/root/assets/messages-BbdwoxVo.js +0 -3
  187. package/dist/gateway/static/root/assets/models-hub-cache-88f4fzYS.js +0 -1
  188. package/dist/gateway/static/root/assets/notes-workbench-CI30cDh3.js +0 -4
  189. package/dist/gateway/static/root/assets/project-detail-page-jO_4I5zN.js +0 -1
  190. package/dist/gateway/static/root/assets/projects-page-BrTGFME0.js +0 -1
  191. package/dist/gateway/static/root/assets/remote-access-hub-CH70D5SH.js +0 -1
  192. package/dist/gateway/static/root/assets/sessions-page-hbfUNtNs.js +0 -1
  193. package/dist/gateway/static/root/assets/settings-advanced-gate-Vg3_jua6.js +0 -1
  194. package/dist/gateway/static/root/assets/setup-status-panel-DkcpWSss.js +0 -1
  195. package/dist/gateway/static/root/assets/shares-settings-BL4DEt6p.js +0 -1
  196. package/dist/gateway/static/root/assets/skill-reload-api-CafhNktn.js +0 -1
  197. package/dist/gateway/static/root/assets/task-detail-page-anyomXsu.js +0 -1
  198. package/dist/gateway/static/root/assets/user-context-page-BBk9YdBn.js +0 -1
  199. package/dist/gateway/static/root/assets/workflows-page-RUTv0yvR.js +0 -5
@@ -1,33 +1,22 @@
1
- import { resolveStateDir } from "../config/paths-state.js";
2
1
  import { createLogger } from "../utils/logger/index.js";
3
2
  import { init_logger } from "../utils/logger.js";
4
- import { init_paths } from "../config/paths.js";
3
+ import { requireXopcDatabase } from "../storage/sqlite/connection.js";
4
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
5
+ import { DurableState } from "../storage/sqlite/durable-state.js";
5
6
  import { SITE_SHARE_CONFIG_DEFAULTS } from "./site-share-types.js";
6
7
  import { isPathUnderWorkspace } from "../gateway/workspace-editor-path.js";
7
- import { join, relative, resolve } from "node:path";
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { relative, resolve } from "node:path";
9
9
  import { randomBytes, randomUUID } from "node:crypto";
10
10
  import { lstat, readdir, realpath, stat } from "node:fs/promises";
11
11
  //#region src/share/site-share-store.ts
12
- init_paths();
13
12
  init_logger();
14
13
  const log = createLogger("SiteShareStore");
15
- const SITE_SHARES_FILE = "site-shares.json";
16
14
  const CLEANUP_INTERVAL_MS = 10 * 6e4;
17
15
  const EXPIRED_RETENTION_MS = 1440 * 6e4;
18
- const MAX_STORED = 200;
19
- const REQUEST_COUNT_DEBOUNCE_MS = 5e3;
20
16
  const SUBDOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;
21
- function resolveStorePath() {
22
- return join(resolveStateDir(), SITE_SHARES_FILE);
23
- }
24
17
  var SiteShareStore = class {
25
- shares = /* @__PURE__ */ new Map();
26
- byToken = /* @__PURE__ */ new Map();
27
- bySubdomain = /* @__PURE__ */ new Map();
18
+ shares = new DurableState("site-shares");
28
19
  config;
29
- dirty = false;
30
- debounceTimer = null;
31
20
  cleanupTimer = null;
32
21
  /** Optional cleanup hook invoked when a record is dropped (e.g. delete staging dir). */
33
22
  onCleanup = null;
@@ -36,9 +25,15 @@ var SiteShareStore = class {
36
25
  ...SITE_SHARE_CONFIG_DEFAULTS,
37
26
  ...config
38
27
  };
39
- this.load();
40
28
  this.startCleanupTimer();
41
29
  }
30
+ saveNew(record) {
31
+ requireXopcDatabase();
32
+ runSqliteWriteTransaction(() => {
33
+ if (this.getActiveShares().length >= this.config.maxActiveSites) throw new Error("Maximum active shares reached");
34
+ this.shares.set(record.id, record);
35
+ });
36
+ }
42
37
  updateConfig(config) {
43
38
  this.config = {
44
39
  ...this.config,
@@ -75,10 +70,7 @@ var SiteShareStore = class {
75
70
  uniqueClientCount: 0,
76
71
  maxRequests: params.maxRequests ?? null
77
72
  };
78
- this.shares.set(id, record);
79
- this.byToken.set(token, id);
80
- if (subdomain) this.bySubdomain.set(subdomain, id);
81
- this.persistSync();
73
+ this.saveNew(record);
82
74
  log.info({
83
75
  id,
84
76
  tokenPrefix: token.slice(0, 8),
@@ -132,19 +124,17 @@ var SiteShareStore = class {
132
124
  if (!preferred) return null;
133
125
  const lower = preferred.trim().toLowerCase();
134
126
  if (!SUBDOMAIN_PATTERN.test(lower)) throw new Error("Subdomain must match /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/");
135
- if (this.bySubdomain.has(lower)) throw new Error(`Subdomain '${lower}' already in use`);
127
+ if (this.hasSubdomain(lower)) throw new Error(`Subdomain '${lower}' already in use`);
136
128
  return lower;
137
129
  }
138
130
  getById(id) {
139
131
  return this.shares.get(id) ?? null;
140
132
  }
141
133
  getByToken(token) {
142
- const id = this.byToken.get(token);
143
- return id ? this.shares.get(id) ?? null : null;
134
+ return this.shares.values().find((record) => record.token === token) ?? null;
144
135
  }
145
136
  getBySubdomain(subdomain) {
146
- const id = this.bySubdomain.get(subdomain.toLowerCase());
147
- return id ? this.shares.get(id) ?? null : null;
137
+ return this.shares.values().find((record) => record.subdomain === subdomain.toLowerCase()) ?? null;
148
138
  }
149
139
  /**
150
140
  * Look up a record by token first, then by subdomain. Subdomain label may
@@ -175,134 +165,98 @@ var SiteShareStore = class {
175
165
  };
176
166
  return { valid: true };
177
167
  }
178
- /** Increment counters when a request lands. Debounced persist. */
168
+ /** Increment counters when a request lands. Persisted before returning. */
179
169
  recordRequest(id, clientIp) {
180
- const record = this.shares.get(id);
181
- if (!record) return;
182
- record.requestCount++;
183
- const recent = record.recentClientIps ??= [];
184
- if (!recent.includes(clientIp)) {
185
- recent.unshift(clientIp);
186
- if (recent.length > 200) recent.length = 200;
187
- record.uniqueClientCount = recent.length;
188
- }
189
- this.scheduleDebouncedPersist();
170
+ this.shares.update(id, (record) => {
171
+ if (record) {
172
+ record.requestCount++;
173
+ const recent = record.recentClientIps ??= [];
174
+ if (!recent.includes(clientIp)) {
175
+ recent.unshift(clientIp);
176
+ if (recent.length > 200) recent.length = 200;
177
+ record.uniqueClientCount = recent.length;
178
+ }
179
+ }
180
+ return {
181
+ value: record,
182
+ result: void 0
183
+ };
184
+ });
190
185
  }
191
186
  setThumbnailStatus(id, status) {
192
- const record = this.shares.get(id);
193
- if (!record) return;
194
- record.thumbnailStatus = status;
195
- if (status === "ready") record.thumbnailGeneratedAt = (/* @__PURE__ */ new Date()).toISOString();
196
- if (status === "failed") record.thumbnailFailedAt = (/* @__PURE__ */ new Date()).toISOString();
197
- this.persistSync();
187
+ requireXopcDatabase();
188
+ return runSqliteWriteTransaction(() => {
189
+ const record = this.shares.get(id);
190
+ if (!record) return;
191
+ record.thumbnailStatus = status;
192
+ if (status === "ready") record.thumbnailGeneratedAt = (/* @__PURE__ */ new Date()).toISOString();
193
+ if (status === "failed") record.thumbnailFailedAt = (/* @__PURE__ */ new Date()).toISOString();
194
+ this.shares.set(record.id, record);
195
+ });
198
196
  }
199
197
  revoke(id) {
200
- const record = this.shares.get(id);
201
- if (!record) return false;
202
- record.revoked = true;
203
- this.persistSync();
204
- if (this.onCleanup) try {
205
- this.onCleanup(record);
206
- } catch (err) {
207
- log.warn({
208
- err,
209
- id
210
- }, "Site share cleanup hook threw on revoke");
211
- }
212
- log.info({
213
- id,
214
- tokenPrefix: record.token.slice(0, 8)
215
- }, "Site share revoked");
216
- return true;
198
+ requireXopcDatabase();
199
+ return runSqliteWriteTransaction(() => {
200
+ const record = this.shares.get(id);
201
+ if (!record) return false;
202
+ record.revoked = true;
203
+ this.shares.set(record.id, record);
204
+ if (this.onCleanup) try {
205
+ this.onCleanup(record);
206
+ } catch (err) {
207
+ log.warn({
208
+ err,
209
+ id
210
+ }, "Site share cleanup hook threw on revoke");
211
+ }
212
+ log.info({
213
+ id,
214
+ tokenPrefix: record.token.slice(0, 8)
215
+ }, "Site share revoked");
216
+ return true;
217
+ });
217
218
  }
218
219
  revokeMany(ids) {
219
- let count = 0;
220
- for (const id of ids) {
221
- const record = this.shares.get(id);
222
- if (record && !record.revoked) {
223
- record.revoked = true;
224
- count++;
220
+ requireXopcDatabase();
221
+ return runSqliteWriteTransaction(() => {
222
+ let count = 0;
223
+ for (const id of ids) {
224
+ const record = this.shares.get(id);
225
+ if (record && !record.revoked) {
226
+ record.revoked = true;
227
+ this.shares.set(record.id, record);
228
+ count++;
229
+ }
225
230
  }
226
- }
227
- if (count > 0) this.persistSync();
228
- return count;
231
+ return count;
232
+ });
229
233
  }
230
234
  revokeExpired() {
231
- const now = Date.now();
232
- let count = 0;
233
- for (const record of this.shares.values()) if (!record.revoked && now >= new Date(record.expiresAt).getTime()) {
234
- record.revoked = true;
235
- count++;
236
- }
237
- if (count > 0) this.persistSync();
238
- return count;
239
- }
240
- update(id, patch) {
241
- const record = this.shares.get(id);
242
- if (!record) return null;
243
- if (patch.extendTtlMs !== void 0) record.expiresAt = new Date(Date.now() + patch.extendTtlMs).toISOString();
244
- if (patch.maxRequests !== void 0) record.maxRequests = patch.maxRequests;
245
- this.persistSync();
246
- return record;
247
- }
248
- load() {
249
- const path = resolveStorePath();
250
- if (!existsSync(path)) return;
251
- try {
252
- const raw = readFileSync(path, "utf8");
253
- const data = JSON.parse(raw);
254
- if (data.version !== 1 || !Array.isArray(data.shares)) return;
235
+ requireXopcDatabase();
236
+ return runSqliteWriteTransaction(() => {
255
237
  const now = Date.now();
256
- let cleaned = 0;
257
- for (const record of data.shares) {
258
- if (now - new Date(record.expiresAt).getTime() > EXPIRED_RETENTION_MS) {
259
- cleaned++;
260
- continue;
261
- }
238
+ let count = 0;
239
+ for (const record of this.shares.values()) if (!record.revoked && now >= new Date(record.expiresAt).getTime()) {
240
+ record.revoked = true;
262
241
  this.shares.set(record.id, record);
263
- this.byToken.set(record.token, record.id);
264
- if (record.subdomain) this.bySubdomain.set(record.subdomain, record.id);
265
- }
266
- if (cleaned > 0) {
267
- log.info({ cleaned }, `Cleaned ${cleaned} expired site-share records on load`);
268
- this.persistSync();
242
+ count++;
269
243
  }
270
- } catch (err) {
271
- log.warn({ err }, "Failed to load site-shares.json");
272
- }
244
+ return count;
245
+ });
273
246
  }
274
- persistSync() {
275
- const path = resolveStorePath();
276
- mkdirSync(resolveStateDir(), { recursive: true });
277
- let records = [...this.shares.values()];
278
- if (records.length > MAX_STORED) {
279
- records.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
280
- records = records.slice(0, MAX_STORED);
281
- this.shares.clear();
282
- this.byToken.clear();
283
- this.bySubdomain.clear();
284
- for (const r of records) {
285
- this.shares.set(r.id, r);
286
- this.byToken.set(r.token, r.id);
287
- if (r.subdomain) this.bySubdomain.set(r.subdomain, r.id);
288
- }
289
- }
290
- writeFileSync(path, `${JSON.stringify({
291
- version: 1,
292
- shares: records
293
- }, null, 2)}\n`, "utf8");
247
+ update(id, patch) {
248
+ requireXopcDatabase();
249
+ return runSqliteWriteTransaction(() => {
250
+ const record = this.shares.get(id);
251
+ if (!record) return null;
252
+ if (patch.extendTtlMs !== void 0) record.expiresAt = new Date(Date.now() + patch.extendTtlMs).toISOString();
253
+ if (patch.maxRequests !== void 0) record.maxRequests = patch.maxRequests;
254
+ this.shares.set(record.id, record);
255
+ return record;
256
+ });
294
257
  }
295
- scheduleDebouncedPersist() {
296
- this.dirty = true;
297
- if (this.debounceTimer) return;
298
- this.debounceTimer = setTimeout(() => {
299
- this.debounceTimer = null;
300
- if (this.dirty) {
301
- this.dirty = false;
302
- this.persistSync();
303
- }
304
- }, REQUEST_COUNT_DEBOUNCE_MS);
305
- this.debounceTimer.unref?.();
258
+ hasSubdomain(subdomain) {
259
+ return this.getBySubdomain(subdomain) !== null;
306
260
  }
307
261
  startCleanupTimer() {
308
262
  this.cleanupTimer = setInterval(() => {
@@ -310,8 +264,6 @@ var SiteShareStore = class {
310
264
  let removed = 0;
311
265
  for (const [id, record] of this.shares) if (now - new Date(record.expiresAt).getTime() > EXPIRED_RETENTION_MS) {
312
266
  this.shares.delete(id);
313
- this.byToken.delete(record.token);
314
- if (record.subdomain) this.bySubdomain.delete(record.subdomain);
315
267
  if (this.onCleanup) try {
316
268
  this.onCleanup(record);
317
269
  } catch (err) {
@@ -322,10 +274,7 @@ var SiteShareStore = class {
322
274
  }
323
275
  removed++;
324
276
  }
325
- if (removed > 0) {
326
- this.persistSync();
327
- log.debug({ removed }, `Cleaned ${removed} expired site shares`);
328
- }
277
+ if (removed > 0) log.debug({ removed }, `Cleaned ${removed} expired site shares`);
329
278
  }, CLEANUP_INTERVAL_MS);
330
279
  this.cleanupTimer.unref?.();
331
280
  }
@@ -334,14 +283,6 @@ var SiteShareStore = class {
334
283
  clearInterval(this.cleanupTimer);
335
284
  this.cleanupTimer = null;
336
285
  }
337
- if (this.debounceTimer) {
338
- clearTimeout(this.debounceTimer);
339
- this.debounceTimer = null;
340
- }
341
- if (this.dirty) {
342
- this.dirty = false;
343
- this.persistSync();
344
- }
345
286
  }
346
287
  };
347
288
  async function scanStatic(root, maxFileCount, maxRootDirSize) {
@@ -49,10 +49,6 @@ export interface SiteShareRecord {
49
49
  thumbnailGeneratedAt?: string;
50
50
  thumbnailFailedAt?: string;
51
51
  }
52
- export interface SiteShareStoreData {
53
- version: 1;
54
- shares: SiteShareRecord[];
55
- }
56
52
  export interface CreateSiteShareParams {
57
53
  /** 'static' uses workspace path; 'proxy' uses upstreamUrl. */
58
54
  kind: SiteSourceKind;
@@ -10,8 +10,7 @@ export type SqliteConnectionPragmaOptions = {
10
10
  checkpointMode?: SqliteWalCheckpointMode;
11
11
  busyTimeoutMs?: number;
12
12
  foreignKeys?: boolean;
13
- synchronous?: 'NORMAL';
14
- databasePath?: string;
13
+ synchronous?: 'NORMAL' | 'FULL';
15
14
  onCheckpointError?: (error: unknown) => void;
16
15
  };
17
16
  export type XopcDatabase = {
@@ -88,44 +88,38 @@ function isPathWithinMount(targetPath, mountPoint) {
88
88
  const resolvedMountPoint = path.resolve(mountPoint);
89
89
  return resolvedTarget === resolvedMountPoint || resolvedMountPoint === path.parse(resolvedMountPoint).root || resolvedTarget.startsWith(`${resolvedMountPoint}${path.sep}`);
90
90
  }
91
- function isNfsMountType(fsType) {
92
- return fsType.toLowerCase().startsWith("nfs");
91
+ function isNetworkMountType(fsType) {
92
+ const type = fsType.toLowerCase();
93
+ return type.startsWith("nfs") || [
94
+ "cifs",
95
+ "smbfs",
96
+ "smb3",
97
+ "fuse.sshfs"
98
+ ].includes(type);
93
99
  }
94
- function isNfsMountEntryPath(targetPath) {
100
+ function isNetworkMountEntryPath(targetPath) {
95
101
  const mountEntry = readMountEntries().filter((entry) => isPathWithinMount(targetPath, entry.mountPoint)).toSorted((a, b) => b.mountPoint.length - a.mountPoint.length)[0];
96
- return mountEntry ? isNfsMountType(mountEntry.fsType) : false;
102
+ return mountEntry ? isNetworkMountType(mountEntry.fsType) : false;
97
103
  }
98
- function isNfsBackedPath(targetPath) {
99
- if (typeof fs.statfsSync !== "function") return isNfsMountEntryPath(targetPath);
104
+ function isNetworkBackedPath(targetPath) {
105
+ if (typeof fs.statfsSync !== "function") return isNetworkMountEntryPath(targetPath);
100
106
  const checkedPath = findExistingVolumePath(targetPath);
101
107
  if (!checkedPath) return false;
102
108
  try {
103
- if (fs.statfsSync(checkedPath).type === LINUX_NFS_SUPER_MAGIC) return true;
109
+ if ([
110
+ LINUX_NFS_SUPER_MAGIC,
111
+ 4283649346,
112
+ 4266872130,
113
+ 20859
114
+ ].includes(fs.statfsSync(checkedPath).type >>> 0)) return true;
104
115
  } catch {
105
- return isNfsMountEntryPath(checkedPath);
116
+ return isNetworkMountEntryPath(checkedPath);
106
117
  }
107
- return isNfsMountEntryPath(checkedPath);
108
- }
109
- function readJournalModeResult(row) {
110
- if (!row || typeof row !== "object") return null;
111
- const record = row;
112
- const value = record.journal_mode ?? Object.values(record)[0];
113
- return typeof value === "string" ? value.toLowerCase() : null;
114
- }
115
- function requireRollbackJournalMode(db, dbPath) {
116
- const mode = readJournalModeResult(db.prepare("PRAGMA journal_mode = DELETE;").get());
117
- if (mode !== "delete") throw new Error(`xopc.db at ${dbPath} is on an NFS-backed volume but SQLite kept journal_mode=${mode ?? "unknown"}. WAL mode is unsafe on NFS. Move ~/.xopc to a local filesystem, or set XOPC_STATE_DIR to a local path.`);
118
+ return isNetworkMountEntryPath(checkedPath);
118
119
  }
119
120
  function configureSqliteConnectionPragmas(db, options = {}) {
120
121
  const busyTimeoutMs = options.busyTimeoutMs ?? XOPC_SQLITE_BUSY_TIMEOUT_MS;
121
122
  db.exec(`PRAGMA busy_timeout = ${normalizeNonNegativeInteger(busyTimeoutMs, "busyTimeoutMs")};`);
122
- if (options.databasePath && isNfsBackedPath(options.databasePath)) {
123
- requireRollbackJournalMode(db, options.databasePath);
124
- return {
125
- checkpoint: () => true,
126
- close: () => true
127
- };
128
- }
129
123
  db.exec("PRAGMA journal_mode = WAL;");
130
124
  const autoCheckpointPages = normalizeNonNegativeInteger(options.autoCheckpointPages ?? 1e3, "autoCheckpointPages");
131
125
  db.exec(`PRAGMA wal_autocheckpoint = ${autoCheckpointPages};`);
@@ -136,8 +130,7 @@ function configureSqliteConnectionPragmas(db, options = {}) {
136
130
  const checkpointIntervalMs = normalizeNonNegativeInteger(options.checkpointIntervalMs ?? 1800 * 1e3, "checkpointIntervalMs");
137
131
  const runCheckpoint = (mode) => {
138
132
  try {
139
- db.exec(`PRAGMA wal_checkpoint(${mode});`);
140
- return true;
133
+ return db.prepare(`PRAGMA wal_checkpoint(${mode});`).get().busy === 0;
141
134
  } catch (error) {
142
135
  options.onCheckpointError?.(error);
143
136
  return false;
@@ -174,29 +167,39 @@ function ensureDatabasePermissions(pathname) {
174
167
  }
175
168
  function openDatabaseAtPath(pathname) {
176
169
  installSqliteTransientRejectionHandler();
170
+ if (pathname !== ":memory:" && isNetworkBackedPath(pathname)) throw new Error("SQLite requires a local filesystem; run the Gateway on the NAS instead of opening a network-mounted database.");
177
171
  ensureDatabasePermissions(pathname);
178
172
  const { DatabaseSync } = requireNodeSqlite();
179
173
  const db = new DatabaseSync(pathname);
180
- const walMaintenance = configureSqliteConnectionPragmas(db, {
181
- databasePath: pathname,
182
- synchronous: "NORMAL",
183
- foreignKeys: true,
184
- onCheckpointError: (error) => {
185
- const em = error instanceof Error ? error.message : String(error);
186
- log.warn({
187
- err: error instanceof Error ? error : void 0,
188
- errorMessage: em
189
- }, `SQLite WAL checkpoint failed: ${em}`);
174
+ let walMaintenance;
175
+ try {
176
+ walMaintenance = configureSqliteConnectionPragmas(db, {
177
+ synchronous: "FULL",
178
+ foreignKeys: true,
179
+ onCheckpointError: (error) => {
180
+ const em = error instanceof Error ? error.message : String(error);
181
+ log.warn({
182
+ err: error instanceof Error ? error : void 0,
183
+ errorMessage: em
184
+ }, `SQLite WAL checkpoint failed: ${em}`);
185
+ }
186
+ });
187
+ ensureXopcDatabaseSchema(db, { databasePath: pathname });
188
+ ensureDatabasePermissions(pathname);
189
+ log.info({ path: pathname }, "Opened xopc SQLite database");
190
+ return {
191
+ db,
192
+ path: pathname,
193
+ walMaintenance
194
+ };
195
+ } catch (error) {
196
+ try {
197
+ walMaintenance?.close();
198
+ } finally {
199
+ db.close();
190
200
  }
191
- });
192
- ensureXopcDatabaseSchema(db, { databasePath: pathname });
193
- ensureDatabasePermissions(pathname);
194
- log.info({ path: pathname }, "Opened xopc SQLite database");
195
- return {
196
- db,
197
- path: pathname,
198
- walMaintenance
199
- };
201
+ throw error;
202
+ }
200
203
  }
201
204
  function openXopcDatabase(options = {}) {
202
205
  const env = options.env ?? process.env;
@@ -0,0 +1,16 @@
1
+ export declare class DurableQueue<T> {
2
+ readonly queue: string;
3
+ readonly scope: string;
4
+ constructor(queue: string, scope: string);
5
+ enqueue(id: string, payload: T, now?: number): void;
6
+ pending(limit?: number): T[];
7
+ delete(id: string): void;
8
+ claim(leaseMs: number): {
9
+ id: string;
10
+ payload: T;
11
+ token: string;
12
+ } | null;
13
+ renew(id: string, token: string, leaseMs: number): boolean;
14
+ finish(id: string, token: string, success: boolean): void;
15
+ clearProcessed(olderThanMs?: number): number;
16
+ }
@@ -0,0 +1,52 @@
1
+ import { requireXopcDatabase } from "./connection.js";
2
+ import { runSqliteWriteTransaction } from "./transaction.js";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/storage/sqlite/durable-queue.ts
5
+ var DurableQueue = class {
6
+ constructor(queue, scope) {
7
+ this.queue = queue;
8
+ this.scope = scope;
9
+ }
10
+ enqueue(id, payload, now = Date.now()) {
11
+ requireXopcDatabase().db.prepare(`INSERT INTO durable_messages(queue, scope, id, payload, enqueued_at)
12
+ VALUES (?, ?, ?, ?, ?) ON CONFLICT(queue, scope, id) DO NOTHING`).run(this.queue, this.scope, id, JSON.stringify(payload), now);
13
+ }
14
+ pending(limit = 2147483647) {
15
+ return requireXopcDatabase().db.prepare(`SELECT payload FROM durable_messages
16
+ WHERE queue = ? AND scope = ? AND processed_at IS NULL ORDER BY sequence LIMIT ?`).all(this.queue, this.scope, Math.max(0, Math.floor(limit))).map((row) => JSON.parse(row.payload));
17
+ }
18
+ delete(id) {
19
+ requireXopcDatabase().db.prepare("DELETE FROM durable_messages WHERE queue = ? AND scope = ? AND id = ?").run(this.queue, this.scope, id);
20
+ }
21
+ claim(leaseMs) {
22
+ requireXopcDatabase();
23
+ return runSqliteWriteTransaction((db) => {
24
+ const now = Date.now();
25
+ const row = db.prepare(`SELECT id, payload FROM durable_messages
26
+ WHERE queue = ? AND scope = ? AND processed_at IS NULL AND (lease_until IS NULL OR lease_until <= ?)
27
+ ORDER BY sequence LIMIT 1`).get(this.queue, this.scope, now);
28
+ if (!row) return null;
29
+ const token = randomUUID();
30
+ db.prepare("UPDATE durable_messages SET lease_token = ?, lease_until = ? WHERE queue = ? AND scope = ? AND id = ?").run(token, now + leaseMs, this.queue, this.scope, row.id);
31
+ return {
32
+ id: row.id,
33
+ payload: JSON.parse(row.payload),
34
+ token
35
+ };
36
+ });
37
+ }
38
+ renew(id, token, leaseMs) {
39
+ return requireXopcDatabase().db.prepare(`UPDATE durable_messages SET lease_until = ?
40
+ WHERE queue = ? AND scope = ? AND id = ? AND lease_token = ? AND processed_at IS NULL`).run(Date.now() + leaseMs, this.queue, this.scope, id, token).changes > 0;
41
+ }
42
+ finish(id, token, success) {
43
+ requireXopcDatabase().db.prepare(`UPDATE durable_messages SET processed_at = ?, lease_token = NULL, lease_until = NULL
44
+ WHERE queue = ? AND scope = ? AND id = ? AND lease_token = ?`).run(success ? Date.now() : null, this.queue, this.scope, id, token);
45
+ }
46
+ clearProcessed(olderThanMs) {
47
+ return Number(requireXopcDatabase().db.prepare(`DELETE FROM durable_messages
48
+ WHERE queue = ? AND scope = ? AND processed_at IS NOT NULL AND processed_at <= ?`).run(this.queue, this.scope, Date.now() - (olderThanMs ?? 0)).changes);
49
+ }
50
+ };
51
+ //#endregion
52
+ export { DurableQueue };
@@ -0,0 +1,16 @@
1
+ /** Small namespaced extension/runtime documents. Domain histories use dedicated tables. */
2
+ export declare class DurableState<T> {
3
+ readonly namespace: string;
4
+ readonly scope: string;
5
+ constructor(namespace: string, scope?: string);
6
+ get(key: string): T | undefined;
7
+ set(key: string, value: T): void;
8
+ delete(key: string): boolean;
9
+ entries(): Array<[string, T]>;
10
+ [Symbol.iterator](): Iterator<[string, T]>;
11
+ values(limit?: number, newestFirst?: boolean): T[];
12
+ update<R>(key: string, operation: (value: T | undefined) => {
13
+ value: T | undefined;
14
+ result: R;
15
+ }): R;
16
+ }
@@ -0,0 +1,43 @@
1
+ import { requireXopcDatabase } from "./connection.js";
2
+ import { runSqliteWriteTransaction } from "./transaction.js";
3
+ //#region src/storage/sqlite/durable-state.ts
4
+ /** Small namespaced extension/runtime documents. Domain histories use dedicated tables. */
5
+ var DurableState = class {
6
+ constructor(namespace, scope = "") {
7
+ this.namespace = namespace;
8
+ this.scope = scope;
9
+ }
10
+ get(key) {
11
+ const row = requireXopcDatabase().db.prepare("SELECT payload FROM durable_state WHERE namespace = ? AND scope = ? AND key = ?").get(this.namespace, this.scope, key);
12
+ return row ? JSON.parse(row.payload) : void 0;
13
+ }
14
+ set(key, value) {
15
+ requireXopcDatabase().db.prepare(`INSERT INTO durable_state(namespace, scope, key, payload)
16
+ VALUES (?, ?, ?, ?) ON CONFLICT(namespace, scope, key) DO UPDATE SET payload = excluded.payload`).run(this.namespace, this.scope, key, JSON.stringify(value));
17
+ }
18
+ delete(key) {
19
+ return requireXopcDatabase().db.prepare("DELETE FROM durable_state WHERE namespace = ? AND scope = ? AND key = ?").run(this.namespace, this.scope, key).changes > 0;
20
+ }
21
+ entries() {
22
+ return requireXopcDatabase().db.prepare("SELECT key, payload FROM durable_state WHERE namespace = ? AND scope = ? ORDER BY rowid").all(this.namespace, this.scope).map((row) => [row.key, JSON.parse(row.payload)]);
23
+ }
24
+ [Symbol.iterator]() {
25
+ return this.entries()[Symbol.iterator]();
26
+ }
27
+ values(limit, newestFirst = false) {
28
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 0)) throw new Error("Invalid record limit");
29
+ return requireXopcDatabase().db.prepare(`SELECT payload FROM durable_state
30
+ WHERE namespace = ? AND scope = ? ORDER BY rowid ${newestFirst ? "DESC" : "ASC"} LIMIT ?`).all(this.namespace, this.scope, limit ?? -1).map((row) => JSON.parse(row.payload));
31
+ }
32
+ update(key, operation) {
33
+ requireXopcDatabase();
34
+ return runSqliteWriteTransaction(() => {
35
+ const next = operation(this.get(key));
36
+ if (next.value === void 0) this.delete(key);
37
+ else this.set(key, next.value);
38
+ return next.result;
39
+ });
40
+ }
41
+ };
42
+ //#endregion
43
+ export { DurableState };
@@ -0,0 +1,9 @@
1
+ CREATE TABLE understanding_focus_exclusions (
2
+ understanding_id TEXT NOT NULL REFERENCES user_understandings(understanding_id) ON DELETE CASCADE,
3
+ focus_id TEXT NOT NULL REFERENCES user_focuses(focus_id) ON DELETE CASCADE,
4
+ created_at INTEGER NOT NULL,
5
+ PRIMARY KEY (understanding_id, focus_id)
6
+ );
7
+
8
+ CREATE INDEX idx_understanding_focus_exclusions_focus
9
+ ON understanding_focus_exclusions(focus_id);