@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/src/update-offset-store.js +17 -54
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js +3 -5
- package/dist/extensions/weixin/src/monitor/monitor.js +3 -5
- package/dist/extensions/weixin/src/storage/sync-buf.d.ts +3 -6
- package/dist/extensions/weixin/src/storage/sync-buf.js +9 -22
- package/dist/gateway/static/root/assets/{activity-store-BlxqErph.js → activity-store-etpmyV7S.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CJJzpYYS.js → agent-avatar-dicebear-BmKxSM1k.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CUXApfcE.js +1 -0
- package/dist/gateway/static/root/assets/{agents-je3sRGmk.js → agents-DNq_GKuc.js} +1 -1
- package/dist/gateway/static/root/assets/{api-BbXzyQMj.js → api-B0-hLuZ1.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-Cnn3i00c.js → app-management-settings-panel-qfwAgyhQ.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BToQaw_v.js → appearance-settings-DqQVUpCC.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-dwIqYmXk.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-DD2SweEH.js → archive-plugin-BnMSgZvR.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-BFtcib5t.js → attachment-load-CejsQMev.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-XS8wIl5o.js → automations-page-RC8EyoAf.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-SkdcSZ0i.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-Xs7pLHxR.js → binary-plugins-DGjppLSG.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BPdSzceB.js → block-editor-CWdP4bUc.js} +44 -44
- package/dist/gateway/static/root/assets/browser-settings-page-CKCa6TvG.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflow-inputs-BAAc151l.js → browser-workflow-inputs-BQ1Ilw_o.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-workflows-page-DUPPvE30.js → browser-workflows-page-CJh4NyxT.js} +2 -2
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-cl9kYilL.js → capabilities-settings-panel-DSp--zIZ.js} +5 -5
- package/dist/gateway/static/root/assets/channels-settings-CMd2dDrI.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CCBCtLDg.js → channels-status-swr-Ohhee4vm.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DKW1M6aJ.js → chat-terminal-dock-BOtoKTK3.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-udWs5Kdm.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-Bijb0ZQu.js → cron-expression-DTj1V3Sw.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-BZuHw-SY.js → date-picker-Yr6OCvGL.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-BsBD2JFY.js → dependency-picker-DpN3XZg9.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Ceuw3QKb.js → desktop-pet-DbGzggPF.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DctOXexM.js → desktop-pet-settings-DinCToZh.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BNlMPATK.js → extension-debug-page-DgTtk4KC.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CK4Uvzge.js → extension-page-DtQeiP6f.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CpnXXemw.js → extension-settings-page-DcguCgsH.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-C_mlpe-r.js → gateway-config-api-Br7lSf6H.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-826dipyU.js → gateway-config-swr-DXuXtU1w.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-DSSW17ed.js → gateway-settings-CdPoDMuj.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DyvcAIMm.js → gateway-startup-retry-CzBzglzQ.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-hbmIZTpg.js → heartbeat-settings-DHk1hawP.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-YGipe_W6.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-D7cyfks6.js → image-generation-api-TIgsn04n.js} +1 -1
- package/dist/gateway/static/root/assets/{index-eZjksPvq.js → index-D5TQQpeb.js} +44 -44
- package/dist/gateway/static/root/assets/index-DReBaaTX.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BQrDwwgi.js → keyboard-shortcuts-settings-wsWuoa8p.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CpyvYyrb.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-D-YWD6LS.js → local-apps-page-CXrbrGo8.js} +1 -1
- package/dist/gateway/static/root/assets/local-context-graph-BjUf7fQ9.js +1 -0
- package/dist/gateway/static/root/assets/{locale-store-CBoCuIMx.js → locale-store-BJFLQY8O.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-DvLc9l59.js → logs-page-CXKy6E_l.js} +2 -2
- package/dist/gateway/static/root/assets/{management-settings-DsULLUUv.js → management-settings-BdJZFR7U.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-CjqlP6IB.js → markdown-split-NK3ACVG2.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-BXsHghJd.js → markdown-view-DTF9v1gg.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-DQL029DU.js → media-plugins-F0jcUxyl.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CHNYp8EQ.js +3 -0
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-gauprAae.js → mobile-pairing-wizard-3NKCniSp.js} +1 -1
- package/dist/gateway/static/root/assets/models-hub-cache-nCfkBN8M.js +1 -0
- package/dist/gateway/static/root/assets/{notes-page-AsEfLZt9.js → notes-page-DuqLahi6.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-C7j9EdQk.js +5 -0
- package/dist/gateway/static/root/assets/{oauth-api-BjSUDIgq.js → oauth-api-8lP_qJpq.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-CAJeUXeU.js → product-open-page-CxGGlWA8.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CfebYVX0.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-48GvzSVa.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-BhGKnvLP.js → registry-api-CIsNlSBi.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-64xj_s2m.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DH6EilMV.js → runtime-tools-settings-panel-YkjhiQfA.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-DYHmEZeZ.js → schema-form-oFGf3G3s.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CcwcdRN8.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BaN84CyW.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-BHwPd_M3.js → settings-page-C7VWs0IT.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DVVOCNbr.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-BjwrS03F.js → share-preview-page-t5oz18rh.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-DUYbYLi7.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BGyD0gB9.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-OHyK25op.js → skills-page-CjF8DTsN.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-DQx9ulmg.js → skills-page.utils-DEDTYs2W.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-wO7DWv4C.js → system-settings-panel-BSYuRgFI.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-YZYSTJH7.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-hybK3oC9.js → theme-store-O597U8q_.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-CVC8BHZu.js → tunnel-api-FKUi9wDL.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-DMvRu2XZ.js → use-autosave-B5eUJ6Zq.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-BCyiduKd.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-DuBK2NDG.js → voice-api-key-field-CMKSwz4C.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-CBbiF1T3.js → voice-config-api-q7B6fOFB.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-B7VXWpDr.js → work-discovery-overlay-jhT2EzeH.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BokVAsWN.js → workflow-run-setup-panel-DZmvLZjb.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-DQC0TpMa.js +5 -0
- package/dist/gateway/static/root/index.html +12 -12
- package/dist/package.js +1 -1
- package/dist/src/agent/commands/command-registry.d.ts +1 -0
- package/dist/src/agent/commands/command-registry.js +12 -22
- package/dist/src/agent/ipc/inbox.d.ts +8 -31
- package/dist/src/agent/ipc/inbox.js +64 -133
- package/dist/src/agent/service/process-direct-one-shot.js +15 -10
- package/dist/src/agent/workflow/catalog.d.ts +1 -8
- package/dist/src/agent/workflow/catalog.js +44 -102
- package/dist/src/agent/workflow/index.d.ts +1 -1
- package/dist/src/agent/workflow/index.js +2 -2
- package/dist/src/automations/domain/types.d.ts +1 -0
- package/dist/src/automations/service/action-executor.js +2 -2
- package/dist/src/automations/service/automation-service.js +2 -2
- package/dist/src/channels/manager.d.ts +1 -1
- package/dist/src/channels/manager.js +2 -2
- package/dist/src/channels/outbound/persist-store.d.ts +2 -8
- package/dist/src/channels/outbound/persist-store.js +11 -38
- package/dist/src/channels/outbound-sender.d.ts +1 -1
- package/dist/src/channels/outbound-sender.js +2 -2
- package/dist/src/cli/commands/connectors.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/schema.js +2 -2
- package/dist/src/connectors/auth-provider-registry.js +1 -1
- package/dist/src/connectors/composio-triggers.d.ts +2 -2
- package/dist/src/connectors/composio-triggers.js +8 -15
- package/dist/src/connectors/connected-source-ingestion.js +1 -1
- package/dist/src/connectors/health.js +1 -1
- package/dist/src/gateway/automation-agent-session.js +3 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/lib/extension-store.js +8 -25
- package/dist/src/gateway/hono/routes/capabilities.js +1 -1
- package/dist/src/gateway/hono/routes/connectors.js +3 -3
- package/dist/src/gateway/hono/routes/mcp.js +1 -1
- package/dist/src/gateway/hono/routes/projects.js +1 -1
- package/dist/src/gateway/hono/routes/you.js +20 -4
- package/dist/src/gateway/service.js +4 -5
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/notes/index.d.ts +1 -1
- package/dist/src/notes/index.js +2 -2
- package/dist/src/notes/paths.d.ts +0 -3
- package/dist/src/notes/paths.js +1 -10
- package/dist/src/notes/service.d.ts +0 -1
- package/dist/src/notes/service.js +0 -3
- package/dist/src/notes/store.d.ts +0 -2
- package/dist/src/notes/store.js +33 -80
- package/dist/src/share/hosted-session-share.d.ts +1 -4
- package/dist/src/share/hosted-session-share.js +14 -47
- package/dist/src/share/share-auto.d.ts +1 -1
- package/dist/src/share/share-auto.js +2 -2
- package/dist/src/share/share-store.d.ts +2 -7
- package/dist/src/share/share-store.js +153 -195
- package/dist/src/share/share-types.d.ts +0 -4
- package/dist/src/share/site-share-store.d.ts +3 -8
- package/dist/src/share/site-share-store.js +94 -153
- package/dist/src/share/site-share-types.d.ts +0 -4
- package/dist/src/storage/sqlite/connection.d.ts +1 -2
- package/dist/src/storage/sqlite/connection.js +50 -47
- package/dist/src/storage/sqlite/durable-queue.d.ts +16 -0
- package/dist/src/storage/sqlite/durable-queue.js +52 -0
- package/dist/src/storage/sqlite/durable-state.d.ts +16 -0
- package/dist/src/storage/sqlite/durable-state.js +43 -0
- package/dist/src/storage/sqlite/migrations/146_understanding_focus_exclusions.sql +9 -0
- package/dist/src/storage/sqlite/migrations/147_durable_application_state.sql +39 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/understanding-focus-exclusions.d.ts +2 -0
- package/dist/src/storage/sqlite/understanding-focus-exclusions.js +24 -0
- package/dist/src/tui/tui-scoped-models.js +9 -6
- package/dist/src/tui/tui-startup-resources.js +1 -1
- package/dist/src/voice/dashscope/streaming-tts-stream.js +49 -0
- package/dist/src/voice/realtime/agentEngine.js +63 -18
- package/dist/src/voice/realtime/conversationTurn.d.ts +20 -0
- package/dist/src/voice/realtime/conversationTurn.js +83 -0
- package/dist/src/voice/realtime/omniEngine.js +72 -18
- package/dist/src/workflows/authoring/workflow-draft-store.d.ts +1 -5
- package/dist/src/workflows/authoring/workflow-draft-store.js +23 -43
- package/dist/src/workflows/index.js +2 -2
- package/dist/src/workflows/store/event-store.d.ts +1 -2
- package/dist/src/workflows/store/event-store.js +19 -64
- package/dist/src/workflows/store/paths.d.ts +0 -2
- package/dist/src/workflows/store/paths.js +1 -7
- package/dist/src/workflows/store/run-store.d.ts +0 -1
- package/dist/src/workflows/store/run-store.js +1 -24
- package/package.json +3 -3
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-0dRjnL2s.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-EpUZsl4h.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-BGQdHrip.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DJGcm7rH.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-BcIr-_GM.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DNHqaZfX.js +0 -2
- package/dist/gateway/static/root/assets/home-page-CaVvW77l.js +0 -1
- package/dist/gateway/static/root/assets/index-Dbh5SXyV.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-D3K1XmOM.js +0 -2
- package/dist/gateway/static/root/assets/messages-BbdwoxVo.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-cache-88f4fzYS.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-CI30cDh3.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-jO_4I5zN.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-BrTGFME0.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CH70D5SH.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-hbfUNtNs.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-Vg3_jua6.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DkcpWSss.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-BL4DEt6p.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CafhNktn.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-anyomXsu.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-BBk9YdBn.js +0 -1
- 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 {
|
|
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 {
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
168
|
+
/** Increment counters when a request lands. Persisted before returning. */
|
|
179
169
|
recordRequest(id, clientIp) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
record
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
return count;
|
|
231
|
+
return count;
|
|
232
|
+
});
|
|
229
233
|
}
|
|
230
234
|
revokeExpired() {
|
|
231
|
-
|
|
232
|
-
|
|
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
|
|
257
|
-
for (const record of
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
244
|
+
return count;
|
|
245
|
+
});
|
|
273
246
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.shares.
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
296
|
-
this.
|
|
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
|
|
92
|
-
|
|
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
|
|
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 ?
|
|
102
|
+
return mountEntry ? isNetworkMountType(mountEntry.fsType) : false;
|
|
97
103
|
}
|
|
98
|
-
function
|
|
99
|
-
if (typeof fs.statfsSync !== "function") return
|
|
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 (
|
|
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
|
|
116
|
+
return isNetworkMountEntryPath(checkedPath);
|
|
106
117
|
}
|
|
107
|
-
return
|
|
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.
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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);
|