@amodalai/runtime 0.1.26 → 0.2.0
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/src/__fixtures__/README.md +84 -0
- package/dist/src/__fixtures__/smoke-agent/amodal.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/automations/test-auto.md +5 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/access.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/spec.json +4 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-api/surface.md +9 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/access.json +3 -0
- package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +8 -0
- package/dist/src/__fixtures__/smoke-agent/evals/basic-eval.md +12 -0
- package/dist/src/__fixtures__/smoke-agent/knowledge/test-knowledge.md +3 -0
- package/dist/src/__fixtures__/smoke-agent/skills/test-skill/SKILL.md +11 -0
- package/dist/src/__fixtures__/smoke-agent/stores/test-items.json +11 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.d.ts +18 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js +22 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js.map +1 -0
- package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/tool.json +17 -0
- package/dist/src/__fixtures__/smoke.test.js +718 -0
- package/dist/src/__fixtures__/smoke.test.js.map +1 -0
- package/dist/src/__tests__/test-providers.d.ts +40 -0
- package/dist/src/__tests__/test-providers.js +61 -0
- package/dist/src/__tests__/test-providers.js.map +1 -0
- package/dist/src/agent/local-server.d.ts +3 -3
- package/dist/src/agent/local-server.js +213 -122
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +175 -0
- package/dist/src/agent/loop-types.js +20 -0
- package/dist/src/agent/loop-types.js.map +1 -0
- package/dist/src/agent/loop.d.ts +31 -0
- package/dist/src/agent/loop.js +139 -0
- package/dist/src/agent/loop.js.map +1 -0
- package/dist/src/agent/loop.test.js +1030 -0
- package/dist/src/agent/loop.test.js.map +1 -0
- package/dist/src/agent/mcp-config.d.ts +28 -0
- package/dist/src/agent/mcp-config.js +57 -0
- package/dist/src/agent/mcp-config.js.map +1 -0
- package/dist/src/agent/page-builder.js +6 -1
- package/dist/src/agent/page-builder.js.map +1 -1
- package/dist/src/agent/proactive/proactive-runner.d.ts +24 -8
- package/dist/src/agent/proactive/proactive-runner.js +30 -32
- package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
- package/dist/src/agent/proactive/proactive-runner.test.d.ts +1 -1
- package/dist/src/agent/proactive/proactive-runner.test.js +75 -87
- package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
- package/dist/src/agent/routes/admin-chat.d.ts +15 -3
- package/dist/src/agent/routes/admin-chat.js +63 -18
- package/dist/src/agent/routes/admin-chat.js.map +1 -1
- package/dist/src/agent/routes/automations.js +5 -6
- package/dist/src/agent/routes/automations.js.map +1 -1
- package/dist/src/agent/routes/evals.d.ts +3 -2
- package/dist/src/agent/routes/evals.js +25 -12
- package/dist/src/agent/routes/evals.js.map +1 -1
- package/dist/src/agent/routes/files.js +7 -9
- package/dist/src/agent/routes/files.js.map +1 -1
- package/dist/src/agent/routes/inspect.d.ts +6 -2
- package/dist/src/agent/routes/inspect.js +31 -17
- package/dist/src/agent/routes/inspect.js.map +1 -1
- package/dist/src/agent/routes/inspect.test.js +18 -42
- package/dist/src/agent/routes/inspect.test.js.map +1 -1
- package/dist/src/agent/routes/stores.js +9 -12
- package/dist/src/agent/routes/stores.js.map +1 -1
- package/dist/src/agent/routes/task.d.ts +15 -3
- package/dist/src/agent/routes/task.js +16 -7
- package/dist/src/agent/routes/task.js.map +1 -1
- package/dist/src/agent/routes/task.test.d.ts +1 -1
- package/dist/src/agent/routes/task.test.js +70 -53
- package/dist/src/agent/routes/task.test.js.map +1 -1
- package/dist/src/agent/routes/webhooks.js +12 -3
- package/dist/src/agent/routes/webhooks.js.map +1 -1
- package/dist/src/agent/session-store.d.ts +11 -2
- package/dist/src/agent/session-store.js +1 -1
- package/dist/src/agent/session-store.js.map +1 -1
- package/dist/src/agent/snapshot-server.d.ts +2 -22
- package/dist/src/agent/snapshot-server.js +50 -27
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/compacting.d.ts +14 -0
- package/dist/src/agent/states/compacting.js +258 -0
- package/dist/src/agent/states/compacting.js.map +1 -0
- package/dist/src/agent/states/confirming.d.ts +10 -0
- package/dist/src/agent/states/confirming.js +76 -0
- package/dist/src/agent/states/confirming.js.map +1 -0
- package/dist/src/agent/states/dispatching.d.ts +18 -0
- package/dist/src/agent/states/dispatching.js +241 -0
- package/dist/src/agent/states/dispatching.js.map +1 -0
- package/dist/src/agent/states/executing.d.ts +21 -0
- package/dist/src/agent/states/executing.js +308 -0
- package/dist/src/agent/states/executing.js.map +1 -0
- package/dist/src/agent/states/streaming.d.ts +10 -0
- package/dist/src/agent/states/streaming.js +155 -0
- package/dist/src/agent/states/streaming.js.map +1 -0
- package/dist/src/agent/states/thinking.d.ts +13 -0
- package/dist/src/agent/states/thinking.js +233 -0
- package/dist/src/agent/states/thinking.js.map +1 -0
- package/dist/src/agent/token-estimate.d.ts +17 -0
- package/dist/src/agent/token-estimate.js +13 -0
- package/dist/src/agent/token-estimate.js.map +1 -0
- package/dist/src/agent/tool-executor-local.js +9 -18
- package/dist/src/agent/tool-executor-local.js.map +1 -1
- package/dist/src/agent/tool-executor-local.test.js +3 -5
- package/dist/src/agent/tool-executor-local.test.js.map +1 -1
- package/dist/src/api/create-agent.d.ts +15 -0
- package/dist/src/api/create-agent.js +137 -0
- package/dist/src/api/create-agent.js.map +1 -0
- package/dist/src/api/types.d.ts +68 -0
- package/dist/src/api/types.js +7 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/context/compiler.d.ts +13 -0
- package/dist/src/context/compiler.js +358 -0
- package/dist/src/context/compiler.js.map +1 -0
- package/dist/src/context/compiler.test.js +532 -0
- package/dist/src/context/compiler.test.js.map +1 -0
- package/dist/src/context/types.d.ts +110 -0
- package/dist/src/context/types.js +7 -0
- package/dist/src/context/types.js.map +1 -0
- package/dist/src/index.d.ts +33 -6
- package/dist/src/index.js +35 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/providers/create-provider.d.ts +23 -0
- package/dist/src/providers/create-provider.js +185 -0
- package/dist/src/providers/create-provider.js.map +1 -0
- package/dist/src/{agent/stores-e2e.test.d.ts → providers/create-provider.test.d.ts} +1 -1
- package/dist/src/providers/create-provider.test.js +95 -0
- package/dist/src/providers/create-provider.test.js.map +1 -0
- package/dist/src/providers/failover.d.ts +38 -0
- package/dist/src/providers/failover.js +147 -0
- package/dist/src/providers/failover.js.map +1 -0
- package/dist/src/providers/failover.test.d.ts +6 -0
- package/dist/src/providers/failover.test.js +169 -0
- package/dist/src/providers/failover.test.js.map +1 -0
- package/dist/src/providers/types.d.ts +110 -0
- package/dist/src/providers/types.js +7 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/routes/ai-stream.d.ts +13 -10
- package/dist/src/routes/ai-stream.js +76 -41
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/chat-new.test.d.ts +6 -0
- package/dist/src/routes/chat-new.test.js +107 -0
- package/dist/src/routes/chat-new.test.js.map +1 -0
- package/dist/src/routes/chat-stream-new.test.d.ts +6 -0
- package/dist/src/routes/chat-stream-new.test.js +135 -0
- package/dist/src/routes/chat-stream-new.test.js.map +1 -0
- package/dist/src/routes/chat-stream.d.ts +14 -4
- package/dist/src/routes/chat-stream.js +47 -29
- package/dist/src/routes/chat-stream.js.map +1 -1
- package/dist/src/routes/chat.d.ts +13 -4
- package/dist/src/routes/chat.js +60 -23
- package/dist/src/routes/chat.js.map +1 -1
- package/dist/src/routes/health.d.ts +3 -2
- package/dist/src/routes/health.js.map +1 -1
- package/dist/src/routes/route-helpers.d.ts +50 -0
- package/dist/src/routes/route-helpers.js +80 -0
- package/dist/src/routes/route-helpers.js.map +1 -0
- package/dist/src/routes/session-resolver.d.ts +72 -0
- package/dist/src/routes/session-resolver.js +123 -0
- package/dist/src/routes/session-resolver.js.map +1 -0
- package/dist/src/routes/session-resolver.test.d.ts +6 -0
- package/dist/src/routes/session-resolver.test.js +206 -0
- package/dist/src/routes/session-resolver.test.js.map +1 -0
- package/dist/src/routes/webhooks.d.ts +3 -1
- package/dist/src/routes/webhooks.js +12 -4
- package/dist/src/routes/webhooks.js.map +1 -1
- package/dist/src/security/permission-checker.d.ts +80 -0
- package/dist/src/security/permission-checker.js +75 -0
- package/dist/src/security/permission-checker.js.map +1 -0
- package/dist/src/security/permission-checker.test.d.ts +6 -0
- package/dist/src/security/permission-checker.test.js +208 -0
- package/dist/src/security/permission-checker.test.js.map +1 -0
- package/dist/src/server.d.ts +12 -11
- package/dist/src/server.js +44 -46
- package/dist/src/server.js.map +1 -1
- package/dist/src/server.test.d.ts +1 -1
- package/dist/src/server.test.js +6 -144
- package/dist/src/server.test.js.map +1 -1
- package/dist/src/session/manager.d.ts +98 -0
- package/dist/src/session/manager.js +364 -0
- package/dist/src/session/manager.js.map +1 -0
- package/dist/src/session/manager.test.d.ts +6 -0
- package/dist/src/session/manager.test.js +315 -0
- package/dist/src/session/manager.test.js.map +1 -0
- package/dist/src/session/session-builder.d.ts +71 -0
- package/dist/src/session/session-builder.js +364 -0
- package/dist/src/session/session-builder.js.map +1 -0
- package/dist/src/session/session-builder.test.d.ts +6 -0
- package/dist/src/session/session-builder.test.js +352 -0
- package/dist/src/session/session-builder.test.js.map +1 -0
- package/dist/src/session/store.d.ts +57 -0
- package/dist/src/session/store.js +167 -0
- package/dist/src/session/store.js.map +1 -0
- package/dist/src/session/store.test.d.ts +6 -0
- package/dist/src/session/store.test.js +145 -0
- package/dist/src/session/store.test.js.map +1 -0
- package/dist/src/session/stream-hooks.d.ts +39 -0
- package/dist/src/session/stream-hooks.js +7 -0
- package/dist/src/session/stream-hooks.js.map +1 -0
- package/dist/src/session/tool-context-factory.d.ts +60 -0
- package/dist/src/session/tool-context-factory.js +190 -0
- package/dist/src/session/tool-context-factory.js.map +1 -0
- package/dist/src/session/tool-context-factory.test.d.ts +6 -0
- package/dist/src/session/tool-context-factory.test.js +287 -0
- package/dist/src/session/tool-context-factory.test.js.map +1 -0
- package/dist/src/session/types.d.ts +188 -0
- package/dist/src/session/types.js +7 -0
- package/dist/src/session/types.js.map +1 -0
- package/dist/src/stores/drizzle-store-backend.d.ts +49 -0
- package/dist/src/stores/drizzle-store-backend.js +306 -0
- package/dist/src/stores/drizzle-store-backend.js.map +1 -0
- package/dist/src/stores/drizzle-store-backend.test.d.ts +6 -0
- package/dist/src/stores/drizzle-store-backend.test.js +215 -0
- package/dist/src/stores/drizzle-store-backend.test.js.map +1 -0
- package/dist/src/stores/index.d.ts +4 -0
- package/dist/src/stores/index.js +2 -0
- package/dist/src/stores/index.js.map +1 -1
- package/dist/src/stores/pglite-store-backend.d.ts +16 -19
- package/dist/src/stores/pglite-store-backend.js +85 -239
- package/dist/src/stores/pglite-store-backend.js.map +1 -1
- package/dist/src/stores/postgres-store-backend.d.ts +30 -0
- package/dist/src/stores/postgres-store-backend.js +100 -0
- package/dist/src/stores/postgres-store-backend.js.map +1 -0
- package/dist/src/stores/schema.d.ts +491 -0
- package/dist/src/stores/schema.js +57 -0
- package/dist/src/stores/schema.js.map +1 -0
- package/dist/src/tools/admin-file-tools.d.ts +13 -0
- package/dist/src/tools/admin-file-tools.js +200 -0
- package/dist/src/tools/admin-file-tools.js.map +1 -0
- package/dist/src/tools/admin-file-tools.test.d.ts +6 -0
- package/dist/src/tools/admin-file-tools.test.js +152 -0
- package/dist/src/tools/admin-file-tools.test.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.d.ts +41 -0
- package/dist/src/tools/custom-tool-adapter.js +190 -0
- package/dist/src/tools/custom-tool-adapter.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.test.d.ts +6 -0
- package/dist/src/tools/custom-tool-adapter.test.js +244 -0
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -0
- package/dist/src/tools/dispatch-tool.d.ts +52 -0
- package/dist/src/tools/dispatch-tool.js +71 -0
- package/dist/src/tools/dispatch-tool.js.map +1 -0
- package/dist/src/tools/dispatch-tool.test.d.ts +6 -0
- package/dist/src/tools/dispatch-tool.test.js +75 -0
- package/dist/src/tools/dispatch-tool.test.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.d.ts +18 -0
- package/dist/src/tools/mcp-tool-adapter.js +135 -0
- package/dist/src/tools/mcp-tool-adapter.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.test.d.ts +6 -0
- package/dist/src/tools/mcp-tool-adapter.test.js +227 -0
- package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -0
- package/dist/src/tools/registry.d.ts +25 -0
- package/dist/src/tools/registry.js +72 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/dist/src/tools/registry.test.d.ts +6 -0
- package/dist/src/tools/registry.test.js +121 -0
- package/dist/src/tools/registry.test.js.map +1 -0
- package/dist/src/tools/request-tool.d.ts +42 -0
- package/dist/src/tools/request-tool.js +190 -0
- package/dist/src/tools/request-tool.js.map +1 -0
- package/dist/src/tools/request-tool.test.d.ts +6 -0
- package/dist/src/tools/request-tool.test.js +254 -0
- package/dist/src/tools/request-tool.test.js.map +1 -0
- package/dist/src/tools/store-tools.d.ts +29 -0
- package/dist/src/tools/store-tools.js +224 -0
- package/dist/src/tools/store-tools.js.map +1 -0
- package/dist/src/tools/store-tools.test.d.ts +6 -0
- package/dist/src/tools/store-tools.test.js +216 -0
- package/dist/src/tools/store-tools.test.js.map +1 -0
- package/dist/src/tools/types.d.ts +111 -0
- package/dist/src/tools/types.js +7 -0
- package/dist/src/tools/types.js.map +1 -0
- package/dist/src/types.d.ts +20 -12
- package/dist/src/types.js +3 -2
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -4
- package/dist/src/__tests__/sse-contract.test.js +0 -464
- package/dist/src/__tests__/sse-contract.test.js.map +0 -1
- package/dist/src/__tests__/tools.test.js +0 -583
- package/dist/src/__tests__/tools.test.js.map +0 -1
- package/dist/src/agent/agent-runner.d.ts +0 -33
- package/dist/src/agent/agent-runner.js +0 -1040
- package/dist/src/agent/agent-runner.js.map +0 -1
- package/dist/src/agent/custom-tools-e2e.test.d.ts +0 -6
- package/dist/src/agent/custom-tools-e2e.test.js +0 -566
- package/dist/src/agent/custom-tools-e2e.test.js.map +0 -1
- package/dist/src/agent/request-helper.d.ts +0 -16
- package/dist/src/agent/request-helper.js +0 -96
- package/dist/src/agent/request-helper.js.map +0 -1
- package/dist/src/agent/stores-e2e.test.js +0 -433
- package/dist/src/agent/stores-e2e.test.js.map +0 -1
- package/dist/src/agent/tool-context-builder.d.ts +0 -11
- package/dist/src/agent/tool-context-builder.js +0 -102
- package/dist/src/agent/tool-context-builder.js.map +0 -1
- package/dist/src/agent/tool-context-builder.test.d.ts +0 -6
- package/dist/src/agent/tool-context-builder.test.js +0 -152
- package/dist/src/agent/tool-context-builder.test.js.map +0 -1
- package/dist/src/agent/write-repo-file.test.js +0 -270
- package/dist/src/agent/write-repo-file.test.js.map +0 -1
- package/dist/src/cron/heartbeat-runner.d.ts +0 -21
- package/dist/src/cron/heartbeat-runner.js +0 -79
- package/dist/src/cron/heartbeat-runner.js.map +0 -1
- package/dist/src/cron/heartbeat-runner.test.d.ts +0 -6
- package/dist/src/cron/heartbeat-runner.test.js +0 -120
- package/dist/src/cron/heartbeat-runner.test.js.map +0 -1
- package/dist/src/cron/heartbeat-scheduler.d.ts +0 -26
- package/dist/src/cron/heartbeat-scheduler.js +0 -55
- package/dist/src/cron/heartbeat-scheduler.js.map +0 -1
- package/dist/src/cron/heartbeat-scheduler.test.d.ts +0 -6
- package/dist/src/cron/heartbeat-scheduler.test.js +0 -61
- package/dist/src/cron/heartbeat-scheduler.test.js.map +0 -1
- package/dist/src/routes/ai-stream.test.d.ts +0 -6
- package/dist/src/routes/ai-stream.test.js +0 -586
- package/dist/src/routes/ai-stream.test.js.map +0 -1
- package/dist/src/routes/ask-user-response.d.ts +0 -30
- package/dist/src/routes/ask-user-response.js +0 -61
- package/dist/src/routes/ask-user-response.js.map +0 -1
- package/dist/src/routes/ask-user-response.test.d.ts +0 -6
- package/dist/src/routes/ask-user-response.test.js +0 -88
- package/dist/src/routes/ask-user-response.test.js.map +0 -1
- package/dist/src/routes/chat-stream.test.d.ts +0 -6
- package/dist/src/routes/chat-stream.test.js +0 -155
- package/dist/src/routes/chat-stream.test.js.map +0 -1
- package/dist/src/routes/chat.test.d.ts +0 -6
- package/dist/src/routes/chat.test.js +0 -99
- package/dist/src/routes/chat.test.js.map +0 -1
- package/dist/src/routes/widget-actions.d.ts +0 -49
- package/dist/src/routes/widget-actions.js +0 -78
- package/dist/src/routes/widget-actions.js.map +0 -1
- package/dist/src/session/custom-tool-adapter.d.ts +0 -74
- package/dist/src/session/custom-tool-adapter.js +0 -180
- package/dist/src/session/custom-tool-adapter.js.map +0 -1
- package/dist/src/session/history-converter.d.ts +0 -21
- package/dist/src/session/history-converter.js +0 -59
- package/dist/src/session/history-converter.js.map +0 -1
- package/dist/src/session/history-converter.test.d.ts +0 -6
- package/dist/src/session/history-converter.test.js +0 -130
- package/dist/src/session/history-converter.test.js.map +0 -1
- package/dist/src/session/session-manager.d.ts +0 -219
- package/dist/src/session/session-manager.js +0 -915
- package/dist/src/session/session-manager.js.map +0 -1
- package/dist/src/session/session-manager.test.d.ts +0 -6
- package/dist/src/session/session-manager.test.js +0 -455
- package/dist/src/session/session-manager.test.js.map +0 -1
- package/dist/src/session/session-runner.d.ts +0 -45
- package/dist/src/session/session-runner.js +0 -719
- package/dist/src/session/session-runner.js.map +0 -1
- package/dist/src/session/session-runner.test.d.ts +0 -6
- package/dist/src/session/session-runner.test.js +0 -834
- package/dist/src/session/session-runner.test.js.map +0 -1
- /package/dist/src/{__tests__/sse-contract.test.d.ts → __fixtures__/smoke.test.d.ts} +0 -0
- /package/dist/src/{__tests__/tools.test.d.ts → agent/loop.test.d.ts} +0 -0
- /package/dist/src/{agent/write-repo-file.test.d.ts → context/compiler.test.d.ts} +0 -0
|
@@ -3,279 +3,125 @@
|
|
|
3
3
|
* Copyright 2025 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
-
import { resolveTtl } from './ttl-resolver.js';
|
|
7
|
-
import { log } from '../logger.js';
|
|
8
6
|
/**
|
|
9
7
|
* PGLite-backed store backend.
|
|
10
8
|
*
|
|
11
|
-
* Uses in-process WASM Postgres via @electric-sql/pglite.
|
|
12
|
-
* Data is stored in a configurable directory (default:
|
|
9
|
+
* Uses in-process WASM Postgres via @electric-sql/pglite + Drizzle ORM.
|
|
10
|
+
* Data is stored in a configurable directory (default: in-memory).
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* This file is a thin factory over DrizzleStoreBackend — it constructs
|
|
13
|
+
* the PGLite client, runs DDL, wraps it in drizzle(), and hands off all
|
|
14
|
+
* query logic to the shared backend.
|
|
16
15
|
*/
|
|
16
|
+
import { drizzle } from 'drizzle-orm/pglite';
|
|
17
|
+
import { DrizzleStoreBackend } from './drizzle-store-backend.js';
|
|
18
|
+
import { StoreError } from '../errors.js';
|
|
19
|
+
import { log as defaultLogger } from '../logger.js';
|
|
20
|
+
const CREATE_TABLES_DDL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS store_documents (
|
|
22
|
+
app_id TEXT NOT NULL,
|
|
23
|
+
store TEXT NOT NULL,
|
|
24
|
+
key TEXT NOT NULL,
|
|
25
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
26
|
+
payload JSONB NOT NULL,
|
|
27
|
+
meta JSONB NOT NULL,
|
|
28
|
+
expires_at TIMESTAMPTZ,
|
|
29
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
30
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
31
|
+
PRIMARY KEY (app_id, store, key)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_store_documents_store
|
|
35
|
+
ON store_documents (app_id, store);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_store_documents_expires
|
|
38
|
+
ON store_documents (expires_at)
|
|
39
|
+
WHERE expires_at IS NOT NULL;
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS store_document_versions (
|
|
42
|
+
id SERIAL PRIMARY KEY,
|
|
43
|
+
app_id TEXT NOT NULL,
|
|
44
|
+
store TEXT NOT NULL,
|
|
45
|
+
key TEXT NOT NULL,
|
|
46
|
+
version INTEGER NOT NULL,
|
|
47
|
+
payload JSONB NOT NULL,
|
|
48
|
+
meta JSONB NOT NULL,
|
|
49
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_store_versions_lookup
|
|
53
|
+
ON store_document_versions (app_id, store, key, version DESC);
|
|
54
|
+
`;
|
|
17
55
|
export class PGLiteStoreBackend {
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
-
db = null;
|
|
20
56
|
dataDir;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
enqueue(fn) {
|
|
31
|
-
const task = this.writeQueue.then(fn, fn);
|
|
32
|
-
this.writeQueue = task.then(() => { }, () => { });
|
|
33
|
-
return task;
|
|
57
|
+
logger;
|
|
58
|
+
inner = null;
|
|
59
|
+
constructor(dataDirOrOpts) {
|
|
60
|
+
const opts = typeof dataDirOrOpts === 'string' || dataDirOrOpts === undefined
|
|
61
|
+
? { dataDir: dataDirOrOpts }
|
|
62
|
+
: dataDirOrOpts;
|
|
63
|
+
this.dataDir = opts.dataDir;
|
|
64
|
+
this.logger = opts.logger ?? defaultLogger;
|
|
34
65
|
}
|
|
35
66
|
async initialize(stores) {
|
|
67
|
+
if (this.inner)
|
|
68
|
+
return;
|
|
36
69
|
if (this.dataDir) {
|
|
37
70
|
const { mkdirSync } = await import('node:fs');
|
|
38
71
|
mkdirSync(this.dataDir, { recursive: true });
|
|
39
72
|
}
|
|
40
73
|
const { PGlite } = await import('@electric-sql/pglite');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
meta JSONB NOT NULL,
|
|
53
|
-
expires_at TIMESTAMPTZ,
|
|
54
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
55
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
56
|
-
PRIMARY KEY (app_id, store, key)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
CREATE INDEX IF NOT EXISTS idx_store_documents_store
|
|
60
|
-
ON store_documents (app_id, store);
|
|
61
|
-
|
|
62
|
-
CREATE INDEX IF NOT EXISTS idx_store_documents_expires
|
|
63
|
-
ON store_documents (expires_at)
|
|
64
|
-
WHERE expires_at IS NOT NULL;
|
|
65
|
-
|
|
66
|
-
CREATE TABLE IF NOT EXISTS store_document_versions (
|
|
67
|
-
id SERIAL PRIMARY KEY,
|
|
68
|
-
app_id TEXT NOT NULL,
|
|
69
|
-
store TEXT NOT NULL,
|
|
70
|
-
key TEXT NOT NULL,
|
|
71
|
-
version INTEGER NOT NULL,
|
|
72
|
-
payload JSONB NOT NULL,
|
|
73
|
-
meta JSONB NOT NULL,
|
|
74
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
CREATE INDEX IF NOT EXISTS idx_store_versions_lookup
|
|
78
|
-
ON store_document_versions (app_id, store, key, version DESC);
|
|
79
|
-
`);
|
|
74
|
+
const pglite = new PGlite(this.dataDir ?? undefined);
|
|
75
|
+
await pglite.exec(CREATE_TABLES_DDL);
|
|
76
|
+
const db = drizzle(pglite);
|
|
77
|
+
this.inner = new DrizzleStoreBackend({
|
|
78
|
+
db,
|
|
79
|
+
stores,
|
|
80
|
+
logger: this.logger,
|
|
81
|
+
onClose: async () => {
|
|
82
|
+
await pglite.close();
|
|
83
|
+
},
|
|
84
|
+
});
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
if (!this.
|
|
83
|
-
throw new
|
|
86
|
+
ensure() {
|
|
87
|
+
if (!this.inner) {
|
|
88
|
+
throw new StoreError('PGLite store backend is not initialized or was closed', {
|
|
89
|
+
store: '(uninitialized)',
|
|
90
|
+
operation: 'ensure',
|
|
91
|
+
});
|
|
84
92
|
}
|
|
93
|
+
return this.inner;
|
|
85
94
|
}
|
|
86
95
|
async get(appId, store, key) {
|
|
87
|
-
this.
|
|
88
|
-
try {
|
|
89
|
-
const result = await this.db.query(`SELECT key, app_id, store, version, payload, meta, expires_at
|
|
90
|
-
FROM store_documents
|
|
91
|
-
WHERE app_id = $1 AND store = $2 AND key = $3`, [appId, store, key]);
|
|
92
|
-
if (result.rows.length === 0)
|
|
93
|
-
return null;
|
|
94
|
-
return this.rowToDocument(result.rows[0]);
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
log.error(`get error: ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
96
|
+
return this.ensure().get(appId, store, key);
|
|
100
97
|
}
|
|
101
98
|
async put(appId, store, key, payload, meta) {
|
|
102
|
-
this.
|
|
103
|
-
return this.enqueue(async () => {
|
|
104
|
-
const storeConfig = this.stores.get(store);
|
|
105
|
-
const ttlSeconds = resolveTtl(storeConfig?.ttl, payload);
|
|
106
|
-
const maxVersions = storeConfig?.history?.versions;
|
|
107
|
-
const fullMeta = {
|
|
108
|
-
computedAt: new Date().toISOString(),
|
|
109
|
-
stale: false,
|
|
110
|
-
...meta,
|
|
111
|
-
ttl: ttlSeconds,
|
|
112
|
-
};
|
|
113
|
-
const expiresAt = ttlSeconds
|
|
114
|
-
? new Date(Date.now() + ttlSeconds * 1000).toISOString()
|
|
115
|
-
: null;
|
|
116
|
-
try {
|
|
117
|
-
// Use upsert to avoid read-then-write race
|
|
118
|
-
const existing = await this.db.query(`SELECT version, payload, meta FROM store_documents
|
|
119
|
-
WHERE app_id = $1 AND store = $2 AND key = $3`, [appId, store, key]);
|
|
120
|
-
if (existing.rows.length > 0) {
|
|
121
|
-
const oldVersion = Number(existing.rows[0].version);
|
|
122
|
-
const newVersion = oldVersion + 1;
|
|
123
|
-
if (maxVersions && maxVersions > 0) {
|
|
124
|
-
await this.db.query(`INSERT INTO store_document_versions (app_id, store, key, version, payload, meta)
|
|
125
|
-
VALUES ($1, $2, $3, $4, $5, $6)`, [appId, store, key, oldVersion, JSON.stringify(existing.rows[0].payload), JSON.stringify(existing.rows[0].meta)]);
|
|
126
|
-
await this.db.query(`DELETE FROM store_document_versions
|
|
127
|
-
WHERE app_id = $1 AND store = $2 AND key = $3
|
|
128
|
-
AND version <= (
|
|
129
|
-
SELECT COALESCE(MAX(version), 0) - $4
|
|
130
|
-
FROM store_document_versions
|
|
131
|
-
WHERE app_id = $1 AND store = $2 AND key = $3
|
|
132
|
-
)`, [appId, store, key, maxVersions]);
|
|
133
|
-
}
|
|
134
|
-
await this.db.query(`UPDATE store_documents
|
|
135
|
-
SET version = $4, payload = $5, meta = $6, expires_at = $7, updated_at = NOW()
|
|
136
|
-
WHERE app_id = $1 AND store = $2 AND key = $3`, [appId, store, key, newVersion, JSON.stringify(payload), JSON.stringify(fullMeta), expiresAt]);
|
|
137
|
-
return { stored: true, key, version: newVersion, previousVersion: oldVersion };
|
|
138
|
-
}
|
|
139
|
-
await this.db.query(`INSERT INTO store_documents (app_id, store, key, version, payload, meta, expires_at)
|
|
140
|
-
VALUES ($1, $2, $3, 1, $4, $5, $6)`, [appId, store, key, JSON.stringify(payload), JSON.stringify(fullMeta), expiresAt]);
|
|
141
|
-
return { stored: true, key, version: 1 };
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
log.error(`put error (${store}/${key}): ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
145
|
-
return { stored: false, key, version: 0 };
|
|
146
|
-
}
|
|
147
|
-
});
|
|
99
|
+
return this.ensure().put(appId, store, key, payload, meta);
|
|
148
100
|
}
|
|
149
|
-
async list(appId, store, options
|
|
150
|
-
this.
|
|
151
|
-
const { filter, sort, limit = 100, offset = 0, includeStale = false } = options;
|
|
152
|
-
try {
|
|
153
|
-
let where = 'WHERE app_id = $1 AND store = $2';
|
|
154
|
-
const params = [appId, store];
|
|
155
|
-
let paramIndex = 3;
|
|
156
|
-
if (!includeStale) {
|
|
157
|
-
where += ` AND (expires_at IS NULL OR expires_at > NOW())`;
|
|
158
|
-
}
|
|
159
|
-
if (filter) {
|
|
160
|
-
for (const [field, value] of Object.entries(filter)) {
|
|
161
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
162
|
-
throw new Error(`Invalid store filter field name: ${field}`);
|
|
163
|
-
}
|
|
164
|
-
where += ` AND payload->>'${field}' = $${paramIndex}`;
|
|
165
|
-
params.push(String(value));
|
|
166
|
-
paramIndex++;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
const countResult = await this.db.query(`SELECT COUNT(*)::int AS total FROM store_documents ${where}`, params);
|
|
170
|
-
const total = Number(countResult.rows[0].total);
|
|
171
|
-
let orderBy = 'ORDER BY updated_at DESC';
|
|
172
|
-
if (sort) {
|
|
173
|
-
const desc = sort.startsWith('-');
|
|
174
|
-
const field = desc ? sort.slice(1) : sort;
|
|
175
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
176
|
-
throw new Error(`Invalid store sort field name: ${field}`);
|
|
177
|
-
}
|
|
178
|
-
const dir = desc ? 'DESC' : 'ASC';
|
|
179
|
-
orderBy = `ORDER BY payload->>'${field}' ${dir}`;
|
|
180
|
-
}
|
|
181
|
-
const result = await this.db.query(`SELECT key, app_id, store, version, payload, meta, expires_at
|
|
182
|
-
FROM store_documents ${where} ${orderBy}
|
|
183
|
-
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [...params, limit, offset]);
|
|
184
|
-
const documents = result.rows.map((row) => this.rowToDocument(row));
|
|
185
|
-
return { documents, total, hasMore: offset + documents.length < total };
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
log.error(`list error (${store}): ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
189
|
-
return { documents: [], total: 0, hasMore: false };
|
|
190
|
-
}
|
|
101
|
+
async list(appId, store, options) {
|
|
102
|
+
return this.ensure().list(appId, store, options);
|
|
191
103
|
}
|
|
192
104
|
async delete(appId, store, key) {
|
|
193
|
-
this.
|
|
194
|
-
return this.enqueue(async () => {
|
|
195
|
-
try {
|
|
196
|
-
await this.db.query(`DELETE FROM store_document_versions WHERE app_id = $1 AND store = $2 AND key = $3`, [appId, store, key]);
|
|
197
|
-
const result = await this.db.query(`DELETE FROM store_documents WHERE app_id = $1 AND store = $2 AND key = $3`, [appId, store, key]);
|
|
198
|
-
return (result.affectedRows ?? 0) > 0;
|
|
199
|
-
}
|
|
200
|
-
catch (err) {
|
|
201
|
-
log.error(`delete error (${store}/${key}): ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
105
|
+
return this.ensure().delete(appId, store, key);
|
|
205
106
|
}
|
|
206
107
|
async history(appId, store, key) {
|
|
207
|
-
this.
|
|
208
|
-
try {
|
|
209
|
-
const result = await this.db.query(`SELECT key, $1::text AS app_id, store, version, payload, meta
|
|
210
|
-
FROM store_document_versions
|
|
211
|
-
WHERE app_id = $1 AND store = $2 AND key = $3
|
|
212
|
-
ORDER BY version DESC`, [appId, store, key]);
|
|
213
|
-
return result.rows.map((row) => ({
|
|
214
|
-
key: String(row['key']),
|
|
215
|
-
appId: String(row['app_id']),
|
|
216
|
-
store: String(row['store']),
|
|
217
|
-
version: Number(row['version']),
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
219
|
-
payload: (typeof row['payload'] === 'string' ? JSON.parse(row['payload']) : row['payload']),
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
221
|
-
meta: (typeof row['meta'] === 'string' ? JSON.parse(row['meta']) : row['meta']),
|
|
222
|
-
}));
|
|
223
|
-
}
|
|
224
|
-
catch (err) {
|
|
225
|
-
log.error(`history error (${store}/${key}): ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
226
|
-
return [];
|
|
227
|
-
}
|
|
108
|
+
return this.ensure().history(appId, store, key);
|
|
228
109
|
}
|
|
229
110
|
async purgeExpired(appId, store) {
|
|
230
|
-
this.
|
|
231
|
-
return this.enqueue(async () => {
|
|
232
|
-
try {
|
|
233
|
-
let query = `DELETE FROM store_documents WHERE app_id = $1 AND expires_at IS NOT NULL AND expires_at <= NOW()`;
|
|
234
|
-
const params = [appId];
|
|
235
|
-
if (store) {
|
|
236
|
-
query += ` AND store = $2`;
|
|
237
|
-
params.push(store);
|
|
238
|
-
}
|
|
239
|
-
const result = await this.db.query(query, params);
|
|
240
|
-
return result.affectedRows ?? 0;
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
log.error(`purgeExpired error: ${err instanceof Error ? err.message : String(err)}`, 'store');
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
});
|
|
111
|
+
return this.ensure().purgeExpired(appId, store);
|
|
247
112
|
}
|
|
248
113
|
async close() {
|
|
249
|
-
if (this.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
catch { /* best effort */ }
|
|
254
|
-
this.db = null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
rowToDocument(row) {
|
|
258
|
-
const expiresAt = row['expires_at'];
|
|
259
|
-
const isStale = expiresAt ? new Date(String(expiresAt)) <= new Date() : false;
|
|
260
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
261
|
-
const meta = (typeof row['meta'] === 'string' ? JSON.parse(row['meta']) : row['meta']);
|
|
262
|
-
meta.stale = isStale;
|
|
263
|
-
return {
|
|
264
|
-
key: String(row['key']),
|
|
265
|
-
appId: String(row['app_id']),
|
|
266
|
-
store: String(row['store']),
|
|
267
|
-
version: Number(row['version']),
|
|
268
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
269
|
-
payload: (typeof row['payload'] === 'string' ? JSON.parse(row['payload']) : row['payload']),
|
|
270
|
-
meta,
|
|
271
|
-
};
|
|
114
|
+
if (!this.inner)
|
|
115
|
+
return;
|
|
116
|
+
await this.inner.close();
|
|
117
|
+
this.inner = null;
|
|
272
118
|
}
|
|
273
119
|
}
|
|
274
120
|
/**
|
|
275
121
|
* Create a PGLite store backend.
|
|
276
122
|
*/
|
|
277
|
-
export async function createPGLiteStoreBackend(stores,
|
|
278
|
-
const backend = new PGLiteStoreBackend(
|
|
123
|
+
export async function createPGLiteStoreBackend(stores, dataDirOrOpts) {
|
|
124
|
+
const backend = new PGLiteStoreBackend(dataDirOrOpts);
|
|
279
125
|
await backend.initialize(stores);
|
|
280
126
|
return backend;
|
|
281
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pglite-store-backend.js","sourceRoot":"","sources":["../../../src/stores/pglite-store-backend.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"pglite-store-backend.js","sourceRoot":"","sources":["../../../src/stores/pglite-store-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import type { LoadedStore } from '@amodalai/core';
|
|
7
|
+
import { DrizzleStoreBackend } from './drizzle-store-backend.js';
|
|
8
|
+
import type { Logger } from '../logger.js';
|
|
9
|
+
export interface PostgresStoreBackendOptions {
|
|
10
|
+
/** Postgres connection string, e.g. `postgres://user:pass@host:5432/db`. */
|
|
11
|
+
connectionString: string;
|
|
12
|
+
/** Pool size (default 10). */
|
|
13
|
+
max?: number;
|
|
14
|
+
/** Per-statement timeout in ms (default 30_000). */
|
|
15
|
+
statementTimeoutMs?: number;
|
|
16
|
+
/** Logger. Defaults to the runtime's global logger. */
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a Postgres store backend.
|
|
21
|
+
*
|
|
22
|
+
* Opens a connection pool with `statement_timeout` set so hung queries
|
|
23
|
+
* can't block the write queue, runs idempotent DDL, and returns a
|
|
24
|
+
* DrizzleStoreBackend wired to the pool. The returned backend's
|
|
25
|
+
* `close()` will drain and end the pool.
|
|
26
|
+
*
|
|
27
|
+
* Accepts either a connection string (shorthand) or a full options
|
|
28
|
+
* object — matches `createPGLiteStoreBackend`'s call-site ergonomics.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createPostgresStoreBackend(stores: LoadedStore[], optsOrUrl: PostgresStoreBackendOptions | string): Promise<DrizzleStoreBackend>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Postgres-backed store backend for the hosted runtime.
|
|
8
|
+
*
|
|
9
|
+
* Thin factory over DrizzleStoreBackend using drizzle-orm/node-postgres.
|
|
10
|
+
* Shares all query logic and DDL with the PGLite backend via the
|
|
11
|
+
* `stores/schema.ts` Drizzle schema.
|
|
12
|
+
*
|
|
13
|
+
* Tables are created in the connection's default schema (typically
|
|
14
|
+
* `public`). Callers who need isolation should use a dedicated database
|
|
15
|
+
* per tenant/environment rather than schema namespacing — that avoids
|
|
16
|
+
* the search_path / connection-pool interaction hazards that
|
|
17
|
+
* schema-scoping introduces.
|
|
18
|
+
*/
|
|
19
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
20
|
+
import { DrizzleStoreBackend } from './drizzle-store-backend.js';
|
|
21
|
+
import { log as defaultLogger } from '../logger.js';
|
|
22
|
+
// Default per-statement timeout. Protects against hung queries blocking
|
|
23
|
+
// the write queue — satisfies the CLAUDE.md "External calls (fetch,
|
|
24
|
+
// database, MCP) without AbortSignal.timeout()" rule at the pool level.
|
|
25
|
+
const DEFAULT_STATEMENT_TIMEOUT_MS = 30_000;
|
|
26
|
+
const CREATE_TABLES_DDL = `
|
|
27
|
+
CREATE TABLE IF NOT EXISTS store_documents (
|
|
28
|
+
app_id TEXT NOT NULL,
|
|
29
|
+
store TEXT NOT NULL,
|
|
30
|
+
key TEXT NOT NULL,
|
|
31
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
32
|
+
payload JSONB NOT NULL,
|
|
33
|
+
meta JSONB NOT NULL,
|
|
34
|
+
expires_at TIMESTAMPTZ,
|
|
35
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
36
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
37
|
+
PRIMARY KEY (app_id, store, key)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_store_documents_store
|
|
41
|
+
ON store_documents (app_id, store);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_store_documents_expires
|
|
44
|
+
ON store_documents (expires_at)
|
|
45
|
+
WHERE expires_at IS NOT NULL;
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS store_document_versions (
|
|
48
|
+
id SERIAL PRIMARY KEY,
|
|
49
|
+
app_id TEXT NOT NULL,
|
|
50
|
+
store TEXT NOT NULL,
|
|
51
|
+
key TEXT NOT NULL,
|
|
52
|
+
version INTEGER NOT NULL,
|
|
53
|
+
payload JSONB NOT NULL,
|
|
54
|
+
meta JSONB NOT NULL,
|
|
55
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_store_versions_lookup
|
|
59
|
+
ON store_document_versions (app_id, store, key, version DESC);
|
|
60
|
+
`;
|
|
61
|
+
/**
|
|
62
|
+
* Create a Postgres store backend.
|
|
63
|
+
*
|
|
64
|
+
* Opens a connection pool with `statement_timeout` set so hung queries
|
|
65
|
+
* can't block the write queue, runs idempotent DDL, and returns a
|
|
66
|
+
* DrizzleStoreBackend wired to the pool. The returned backend's
|
|
67
|
+
* `close()` will drain and end the pool.
|
|
68
|
+
*
|
|
69
|
+
* Accepts either a connection string (shorthand) or a full options
|
|
70
|
+
* object — matches `createPGLiteStoreBackend`'s call-site ergonomics.
|
|
71
|
+
*/
|
|
72
|
+
export async function createPostgresStoreBackend(stores, optsOrUrl) {
|
|
73
|
+
const opts = typeof optsOrUrl === 'string' ? { connectionString: optsOrUrl } : optsOrUrl;
|
|
74
|
+
const logger = opts.logger ?? defaultLogger;
|
|
75
|
+
// Dynamic import so `pg` stays an optional peer at the package level —
|
|
76
|
+
// PGLite users don't need it installed.
|
|
77
|
+
const pg = await import('pg');
|
|
78
|
+
const { Pool } = pg.default ?? pg;
|
|
79
|
+
const pool = new Pool({
|
|
80
|
+
connectionString: opts.connectionString,
|
|
81
|
+
max: opts.max ?? 10,
|
|
82
|
+
statement_timeout: opts.statementTimeoutMs ?? DEFAULT_STATEMENT_TIMEOUT_MS,
|
|
83
|
+
});
|
|
84
|
+
await pool.query(CREATE_TABLES_DDL);
|
|
85
|
+
const db = drizzle(pool);
|
|
86
|
+
logger.info('postgres_store_backend_ready', {
|
|
87
|
+
max: opts.max ?? 10,
|
|
88
|
+
statementTimeoutMs: opts.statementTimeoutMs ?? DEFAULT_STATEMENT_TIMEOUT_MS,
|
|
89
|
+
storeCount: stores.length,
|
|
90
|
+
});
|
|
91
|
+
return new DrizzleStoreBackend({
|
|
92
|
+
db,
|
|
93
|
+
stores,
|
|
94
|
+
logger,
|
|
95
|
+
onClose: async () => {
|
|
96
|
+
await pool.end();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=postgres-store-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-store-backend.js","sourceRoot":"","sources":["../../../src/stores/postgres-store-backend.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAGlD,OAAO,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAC,GAAG,IAAI,aAAa,EAAC,MAAM,cAAc,CAAC;AAGlD,wEAAwE;AACxE,oEAAoE;AACpE,wEAAwE;AACxE,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAE5C,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCzB,CAAC;AAaF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAqB,EACrB,SAA+C;IAE/C,MAAM,IAAI,GACR,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAC,gBAAgB,EAAE,SAAS,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE5E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC;IAE5C,uEAAuE;IACvE,wCAAwC;IACxC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAC,IAAI,EAAC,GAAG,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC;IAEhC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC;QACpB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;QACnB,iBAAiB,EAAE,IAAI,CAAC,kBAAkB,IAAI,4BAA4B;KAC3E,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAEpC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE;QAC1C,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;QACnB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,4BAA4B;QAC3E,UAAU,EAAE,MAAM,CAAC,MAAM;KAC1B,CAAC,CAAC;IAEH,OAAO,IAAI,mBAAmB,CAAC;QAC7B,EAAE;QACF,MAAM;QACN,MAAM;QACN,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|