@anteros/core 0.0.40 → 0.0.42
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/database/audit.ts +143 -0
- package/database/collection.ts +6 -4
- package/database/deleteLog.ts +98 -0
- package/database/file.ts +7 -6
- package/database/mongodbadapter.ts +420 -185
- package/database/replication.ts +858 -0
- package/database/schema.ts +140 -125
- package/database/tenant.ts +4 -0
- package/database/ttl.ts +68 -0
- package/database/vars.ts +405 -0
- package/database/workflow.ts +466 -240
- package/index.ts +16 -0
- package/lib/agent.ts +1327 -0
- package/lib/agentMemory.ts +586 -0
- package/lib/agents.ts +157 -0
- package/lib/ai.ts +0 -0
- package/lib/api.ts +246 -0
- package/lib/attachment.ts +351 -0
- package/lib/audit.ts +271 -0
- package/lib/auditResult.ts +133 -0
- package/lib/define.ts +62 -1
- package/lib/endpoints.ts +80 -0
- package/lib/files.ts +66 -12
- package/lib/jsonSchema.ts +183 -0
- package/lib/lifecycle.ts +147 -0
- package/lib/load.ts +17 -0
- package/lib/mcp.ts +3 -2
- package/lib/messages.ts +299 -0
- package/lib/providers.ts +867 -0
- package/lib/redact.ts +117 -0
- package/lib/routes.ts +14 -1
- package/lib/scriptWorker.ts +4 -1
- package/lib/scripts.ts +6 -1
- package/lib/sockets.ts +3 -1
- package/lib/workflow.ts +209 -37
- package/lib/zip.ts +109 -0
- package/package.json +1 -1
- package/server/access.ts +96 -0
- package/server/agents.ts +451 -0
- package/server/api.ts +121 -95
- package/server/boot.ts +196 -53
- package/server/flags.ts +136 -0
- package/server/hono.ts +9 -4
- package/server/mcp.ts +17 -63
- package/server/routes.ts +19 -24
- package/tests/agent-memory.test.ts +617 -0
- package/tests/agent-working-memory.test.ts +402 -0
- package/tests/agent.test.ts +1239 -0
- package/tests/agents-api.test.ts +593 -0
- package/tests/api.test.ts +51 -0
- package/tests/attachment.test.ts +241 -0
- package/tests/audit-file.test.ts +296 -0
- package/tests/audit-operations.test.ts +184 -0
- package/tests/audit-redact.test.ts +259 -0
- package/tests/audit-results.test.ts +188 -0
- package/tests/audit-retention-config.test.ts +76 -0
- package/tests/audit-run-action.test.ts +120 -0
- package/tests/audit.test.ts +320 -0
- package/tests/db-cache.test.ts +7 -7
- package/tests/endpoints.test.ts +80 -0
- package/tests/file-collection-replication.test.ts +98 -0
- package/tests/fixtures/agent-tenant/agents/support.agent.ts +9 -0
- package/tests/fixtures/agent-tenant/agents/weather.agent.ts +21 -0
- package/tests/fixtures/agents-api/agents/assistant.agent.ts +46 -0
- package/tests/fixtures/agents-api/agents/plain.agent.ts +23 -0
- package/tests/fixtures/agents-api/agents/private.agent.ts +15 -0
- package/tests/fixtures/agents-api/collections/notes.model.ts +26 -0
- package/tests/fixtures/agents-api/routes/probe.route.ts +21 -0
- package/tests/fixtures/agents-api/services/probe.service.ts +19 -0
- package/tests/fixtures/attachment/data.csv +2 -0
- package/tests/fixtures/fake-provider.ts +128 -0
- package/tests/fixtures/lc-disabled/lifecycle.ts +11 -0
- package/tests/fixtures/lc-hang/lifecycle.ts +13 -0
- package/tests/fixtures/lc-normal/lifecycle.ts +23 -0
- package/tests/fixtures/lc-throw/lifecycle.ts +6 -0
- package/tests/fixtures/repl-only/app.ts +26 -0
- package/tests/fixtures/repl-only/tenant/collections/orders.model.ts +14 -0
- package/tests/fixtures/repl-only/tenant/lifecycle.ts +14 -0
- package/tests/fixtures/repl-only/tenant/scripts/probe.run.ts +8 -0
- package/tests/fixtures/replication-meta/agents/chat.agent.ts +16 -0
- package/tests/fixtures/replication-meta/collections/counters.model.ts +13 -0
- package/tests/fixtures/replication-meta/collections/orders.model.ts +9 -0
- package/tests/fixtures/replication-meta/workflows/run.workflow.ts +8 -0
- package/tests/fixtures/vars-tenant/vars/config.var.ts +23 -0
- package/tests/fixtures/vars-tenant/vars/private.var.ts +9 -0
- package/tests/fixtures/workflow-engine/workflows/failing.workflow.ts +34 -0
- package/tests/fixtures/workflow-engine/workflows/flaky.workflow.ts +23 -0
- package/tests/fixtures/workflow-engine/workflows/hang.workflow.ts +17 -0
- package/tests/fixtures/workflow-engine/workflows/simple.workflow.ts +19 -0
- package/tests/fixtures/workflow-engine/workflows/slow.workflow.ts +23 -0
- package/tests/fixtures/workflow-engine/workflows/versioned.workflow.ts +19 -0
- package/tests/fixtures/workflow-tenant/workflows/broken.workflow.ts +3 -0
- package/tests/fixtures/workflow-tenant/workflows/disabled.workflow.ts +8 -0
- package/tests/fixtures/workflow-tenant/workflows/invoice.workflow.ts +21 -0
- package/tests/fixtures/workflow-tenant/workflows/no-id.workflow.ts +7 -0
- package/tests/fixtures/workflow-tenant/workflows/order-duplicate.workflow.ts +13 -0
- package/tests/fixtures/workflow-tenant/workflows/order.workflow.ts +17 -0
- package/tests/hono.test.ts +6 -3
- package/tests/lifecycle.test.ts +96 -0
- package/tests/locks.test.ts +120 -0
- package/tests/mcp.test.ts +11 -11
- package/tests/messages.test.ts +258 -0
- package/tests/replication-control.test.ts +128 -0
- package/tests/replication-initial-sync.test.ts +86 -0
- package/tests/replication-meta.test.ts +243 -0
- package/tests/replication-only.test.ts +358 -0
- package/tests/replication.test.ts +188 -0
- package/tests/upload.test.ts +141 -8
- package/tests/vars-api.test.ts +123 -0
- package/tests/vars-replication.test.ts +96 -0
- package/tests/vars.test.ts +216 -0
- package/tests/workflow-engine.test.ts +340 -0
- package/tests/workflow-retention.test.ts +103 -0
- package/tests/workflow.test.ts +186 -0
- package/types/agent.d.ts +837 -0
- package/types/collection.d.ts +3 -0
- package/types/config.d.ts +80 -0
- package/types/file.d.ts +11 -8
- package/types/hook.d.ts +6 -0
- package/types/lifecycle.d.ts +77 -0
- package/types/mcp.d.ts +11 -1
- package/types/replication.d.ts +228 -0
- package/types/route.d.ts +7 -1
- package/types/scripts.d.ts +6 -0
- package/types/service.d.ts +7 -1
- package/types/tenant.d.ts +36 -0
- package/types/vars.d.ts +147 -0
- package/types/websocket.d.ts +6 -0
- package/types/workflow.d.ts +50 -3
- package/utils/func.ts +67 -52
package/types/collection.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { useRest } from "../database/rest";
|
|
|
8
8
|
import type { jwt } from "../utils/func";
|
|
9
9
|
import type { ActionsApiList, ApiAccess, ApiActions } from "./api";
|
|
10
10
|
import type { Server as SocketIO } from "socket.io";
|
|
11
|
+
import type { CollectionReplicationConfig } from "./replication";
|
|
11
12
|
|
|
12
13
|
/** Context passed to a collection custom action (`define.Action`). */
|
|
13
14
|
export type CollectionActionContext = {
|
|
@@ -85,6 +86,8 @@ export type Collection = {
|
|
|
85
86
|
/** Default TTL for cached queries — human string ('5m', '2h') or ms */
|
|
86
87
|
ttl?: string | number;
|
|
87
88
|
};
|
|
89
|
+
/** Opt-in one-way replication to destination databases — see `ReplicationConfig`. */
|
|
90
|
+
replication?: CollectionReplicationConfig;
|
|
88
91
|
/**
|
|
89
92
|
* The tenant id of the collection
|
|
90
93
|
* @type {string}
|
package/types/config.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type { Service } from "./service";
|
|
|
7
7
|
import type { Script } from "./scripts";
|
|
8
8
|
import type { FileCollection } from "./file";
|
|
9
9
|
import type { McpTool, McpResource } from "./mcp";
|
|
10
|
+
import type { VarDefinition } from "./vars";
|
|
11
|
+
import type { AgentDefinition } from "./agent";
|
|
12
|
+
import type { AuditFileConfig } from "../lib/audit";
|
|
10
13
|
export type ServerConfig = {
|
|
11
14
|
debug?: boolean;
|
|
12
15
|
version?: string;
|
|
@@ -21,9 +24,82 @@ export type ServerConfig = {
|
|
|
21
24
|
metricsPort?: number;
|
|
22
25
|
/** When true, trusts proxy headers (CF-Connecting-IP / X-Forwarded-For) as the client IP. Default: false. */
|
|
23
26
|
trustProxy?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Audit trail defaults for every tenant. A tenant declaring its own
|
|
29
|
+
* `tenant.audit` always wins (even `false`).
|
|
30
|
+
*/
|
|
31
|
+
audit?: {
|
|
32
|
+
/**
|
|
33
|
+
* How long `_audit_` entries are kept (MongoDB TTL on `ts`).
|
|
34
|
+
* Duration string: `'90d'`, `'24h'`.
|
|
35
|
+
* `false` → explicitly disabled (drops the TTL index if one exists).
|
|
36
|
+
* Omitted → untouched: entries are kept forever.
|
|
37
|
+
*/
|
|
38
|
+
retention?: string | false;
|
|
39
|
+
/**
|
|
40
|
+
* Local append-only JSONL copy of the audit trail (one file per day,
|
|
41
|
+
* rotated on size, pruned by age).
|
|
42
|
+
* `true` → defaults, `string` → directory, object → full options.
|
|
43
|
+
* `false` / omitted → disabled.
|
|
44
|
+
*/
|
|
45
|
+
file?: AuditFileConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Object keys whose value is replaced by `"[redacted]"` in every audit
|
|
48
|
+
* entry (`operation.input`, `operation.result` and `meta`), at any depth,
|
|
49
|
+
* case-insensitively. `false` → disabled.
|
|
50
|
+
*
|
|
51
|
+
* Default: password, passwd, pwd, token, authorization, apikey, api_key,
|
|
52
|
+
* key, secret, cookie, otp, pin.
|
|
53
|
+
*/
|
|
54
|
+
redact?: string[] | false;
|
|
55
|
+
/**
|
|
56
|
+
* What an audit entry keeps of the operation's **result**
|
|
57
|
+
* (default: `'summary'`).
|
|
58
|
+
*
|
|
59
|
+
* - `'summary'` (default) → the **generated identifiers** (`_id`,
|
|
60
|
+
* `insertedIds`) plus the operational counters (`matchedCount`,
|
|
61
|
+
* `modifiedCount`, `deletedCount`, `nIndexesWas`…). Never a document:
|
|
62
|
+
* the data lives in the collection (and in its replications).
|
|
63
|
+
* - `'none'` → the generated identifiers only.
|
|
64
|
+
* - `'full'` → the raw result, documents included.
|
|
65
|
+
*
|
|
66
|
+
* Reads (`find`, `findOne`, `aggregate`, streams, `watch`) and custom
|
|
67
|
+
* actions/services never store a result, whatever this setting.
|
|
68
|
+
*/
|
|
69
|
+
results?: 'none' | 'summary' | 'full';
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Workflow defaults for every tenant — a tenant declaring its own
|
|
73
|
+
* `tenant.workflows` always wins.
|
|
74
|
+
*/
|
|
75
|
+
workflows?: {
|
|
76
|
+
/**
|
|
77
|
+
* How long **finished** workflow runs are kept (MongoDB TTL on
|
|
78
|
+
* `completedAt`). Duration string: `'30d'`, `'90d'`.
|
|
79
|
+
* `false` → explicitly disabled. Omitted → untouched.
|
|
80
|
+
*/
|
|
81
|
+
retention?: string | false;
|
|
82
|
+
};
|
|
24
83
|
body?: {
|
|
25
84
|
maxSize?: number;
|
|
26
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Boot mode (default: `'full'`).
|
|
88
|
+
*
|
|
89
|
+
* - `'full'` — HTTP API **and** the replication engine.
|
|
90
|
+
* - `'replication-only'` — a process dedicated to the replication engine: no
|
|
91
|
+
* HTTP API, no websocket handlers, no scripts, no services, no MCP tools,
|
|
92
|
+
* no middlewares. Loaded: tenant databases, collections, file collections,
|
|
93
|
+
* variables and lifecycle hooks (`beforeBoot` / `onDestroy`).
|
|
94
|
+
* - `'no-replication'` — a full server that never starts the replication
|
|
95
|
+
* engine (no scheduling, no tombstones, no `_replication_` state).
|
|
96
|
+
* - `'api-only'` — the HTTP API and nothing around it: no replication, no
|
|
97
|
+
* scripts, no sockets.
|
|
98
|
+
*
|
|
99
|
+
* Flags always win over this setting: `--replication-only`, `--no-replication`,
|
|
100
|
+
* `--api-only`, `--no-scripts`, `--no-sockets`.
|
|
101
|
+
*/
|
|
102
|
+
mode?: 'full' | 'replication-only' | 'no-replication' | 'api-only';
|
|
27
103
|
cors?: {
|
|
28
104
|
origin: string | string[] | ((ctx: { origin: string, c: Context }) => string | string[]);
|
|
29
105
|
credentials?: boolean;
|
|
@@ -123,4 +199,8 @@ export type Config = ServerConfig & {
|
|
|
123
199
|
fileCollections?: FileCollection[]
|
|
124
200
|
mcpTools?: McpTool[]
|
|
125
201
|
mcpResources?: McpResource[]
|
|
202
|
+
vars?: VarDefinition[]
|
|
203
|
+
agents?: AgentDefinition[]
|
|
204
|
+
/** Mongo collections used as agent memory — published by `syncAgents()` (see the Memory page). */
|
|
205
|
+
agentMemories?: Array<{ _tenant_: string; collection: string }>
|
|
126
206
|
}
|
package/types/file.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { HooksCollection } from "./hook";
|
|
|
4
4
|
import type Joi from "joi";
|
|
5
5
|
import type { useRest } from "../database/rest";
|
|
6
6
|
import type { jwt } from "../utils/func";
|
|
7
|
+
import type { CollectionReplicationConfig } from "./replication";
|
|
7
8
|
|
|
8
9
|
type FileAccessHandler = (ctx: {
|
|
9
10
|
rest: InstanceType<typeof useRest>;
|
|
@@ -15,11 +16,11 @@ type FileAccessHandler = (ctx: {
|
|
|
15
16
|
type FileApiAccess = {
|
|
16
17
|
/** Default access rule applied when no per-operation rule is set */
|
|
17
18
|
'*'?: boolean | FileAccessHandler;
|
|
18
|
-
/** Access control for POST /
|
|
19
|
+
/** Access control for POST /api/:tenant_id/upload/:slug */
|
|
19
20
|
upload?: boolean | FileAccessHandler;
|
|
20
|
-
/** Access control for GET /
|
|
21
|
+
/** Access control for GET /api/:tenant_id/files/:slug/:file */
|
|
21
22
|
read?: boolean | FileAccessHandler;
|
|
22
|
-
/** Access control for DELETE /
|
|
23
|
+
/** Access control for DELETE /api/:tenant_id/files/:slug/:file */
|
|
23
24
|
delete?: boolean | FileAccessHandler;
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -30,11 +31,11 @@ type FileApiAccess = {
|
|
|
30
31
|
* for handling file uploads, storage, and serving.
|
|
31
32
|
*
|
|
32
33
|
* Each file collection generates:
|
|
33
|
-
* POST /
|
|
34
|
-
* GET /
|
|
34
|
+
* POST /api/:tenant_id/upload/:slug — upload a file
|
|
35
|
+
* GET /api/:tenant_id/files/:slug/:file — serve/download a file
|
|
35
36
|
*/
|
|
36
37
|
export type FileCollection = {
|
|
37
|
-
/** Unique identifier used in routes: `/
|
|
38
|
+
/** Unique identifier used in routes: `/api/:tenant_id/upload/:slug` */
|
|
38
39
|
slug: string;
|
|
39
40
|
hooks?: {
|
|
40
41
|
beforeOperation?: HooksCollection['beforeOperation'];
|
|
@@ -42,6 +43,8 @@ export type FileCollection = {
|
|
|
42
43
|
};
|
|
43
44
|
/** Metadata fields stored alongside the file document */
|
|
44
45
|
fields?: Field[];
|
|
46
|
+
/** Opt this file collection into the data replication engine (its documents). */
|
|
47
|
+
replication?: CollectionReplicationConfig;
|
|
45
48
|
/** Upload validation rules */
|
|
46
49
|
upload?: {
|
|
47
50
|
/** Allowed MIME types for upload.
|
|
@@ -51,7 +54,7 @@ export type FileCollection = {
|
|
|
51
54
|
maxSize?: number;
|
|
52
55
|
/**
|
|
53
56
|
* Image transformations (resize, format, etc.).
|
|
54
|
-
* Applied on-the-fly when serving via GET /
|
|
57
|
+
* Applied on-the-fly when serving via GET /api/:tenant_id/files/:slug/:file
|
|
55
58
|
*/
|
|
56
59
|
transformations?: {
|
|
57
60
|
/** Resize to max width in pixels */
|
|
@@ -103,7 +106,7 @@ export type FileCollection = {
|
|
|
103
106
|
/** Private key path for SSH auth */
|
|
104
107
|
privateKey?: string;
|
|
105
108
|
}[];
|
|
106
|
-
/**
|
|
109
|
+
/** Save the file metadata in the database. Default: true */
|
|
107
110
|
trackMetaData?: boolean;
|
|
108
111
|
api?: {
|
|
109
112
|
access?: FileApiAccess;
|
package/types/hook.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { MongoRest } from "../database/mongodbadapter";
|
|
2
2
|
import type { Server as SocketIO } from "socket.io";
|
|
3
3
|
import type { ActionsApiList } from "./api";
|
|
4
|
+
import type { TenantAgents } from "./agent";
|
|
5
|
+
import type { Api } from "../lib/api";
|
|
4
6
|
|
|
5
7
|
export type HookMeta = {
|
|
6
8
|
action: ActionsApiList;
|
|
@@ -19,6 +21,10 @@ export type HookMeta = {
|
|
|
19
21
|
/** Context passed to a collection hook (`define.Hook`). */
|
|
20
22
|
export type HookContext = {
|
|
21
23
|
rest: MongoRest;
|
|
24
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
25
|
+
agents: TenantAgents;
|
|
26
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
27
|
+
api: Api;
|
|
22
28
|
action: ActionsApiList;
|
|
23
29
|
meta: HookMeta;
|
|
24
30
|
io: SocketIO;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { useRest } from "../database/rest";
|
|
2
|
+
import type { Tenant } from "./tenant";
|
|
3
|
+
import type { Config } from "./config";
|
|
4
|
+
import type { logger } from "../utils/logger";
|
|
5
|
+
import type { Server as SocketIO } from "socket.io";
|
|
6
|
+
import type { TenantReplicationApi } from "./replication";
|
|
7
|
+
import type { TenantAgents } from "./agent";
|
|
8
|
+
import type { Api } from "../lib/api";
|
|
9
|
+
|
|
10
|
+
/** Signal that triggered the shutdown. */
|
|
11
|
+
export type LifecycleReason = "SIGINT" | "SIGTERM";
|
|
12
|
+
|
|
13
|
+
/** Base context shared by every lifecycle hook. `rest` is tenant-scoped. */
|
|
14
|
+
export type LifecycleContext = {
|
|
15
|
+
tenant: Tenant;
|
|
16
|
+
rest: InstanceType<typeof useRest>;
|
|
17
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
18
|
+
agents: TenantAgents;
|
|
19
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
20
|
+
api: Api;
|
|
21
|
+
logger: typeof logger;
|
|
22
|
+
cfg: Config;
|
|
23
|
+
/** Tenant-scoped replication API — `reset()`, `seed()`, `now()`, `state()` (no tenant id needed). */
|
|
24
|
+
replication: TenantReplicationApi;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Context of `beforeBoot` — runs before replication and before the HTTP server listens. */
|
|
28
|
+
export type LifecycleBootContext = LifecycleContext;
|
|
29
|
+
|
|
30
|
+
/** Context of `afterBoot` — runs once the server is listening. */
|
|
31
|
+
export type LifecycleReadyContext = LifecycleContext & {
|
|
32
|
+
io: SocketIO;
|
|
33
|
+
server: ReturnType<typeof Bun.serve>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Context of `onDestroy` — runs on SIGINT/SIGTERM, before replication stops and DB clients close. */
|
|
37
|
+
export type LifecycleDestroyContext = LifecycleContext & {
|
|
38
|
+
reason: LifecycleReason;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Per-tenant lifecycle — one `lifecycle.ts` at the tenant root.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* export default define.Lifecycle({
|
|
47
|
+
* beforeBoot: async ({ rest, tenant }) => { await rest.insertOne('settings', { tenant: tenant.id }) },
|
|
48
|
+
* afterBoot: async ({ server, logger }) => { logger.info(`ready on :${server.port}`) },
|
|
49
|
+
* onDestroy: async ({ reason }) => { await flushSomething(reason) },
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export type Lifecycle = {
|
|
54
|
+
_isLifecycle_?: boolean;
|
|
55
|
+
/** Injected by the loader — the tenant this lifecycle belongs to. */
|
|
56
|
+
_tenant_?: string;
|
|
57
|
+
/** Turn this lifecycle off without removing the file (default: true). */
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Blocking — runs after databases are connected and collections are loaded,
|
|
61
|
+
* before replication starts and before the HTTP server listens. Ideal for
|
|
62
|
+
* migrations, seeding, warmup. Throwing aborts the boot.
|
|
63
|
+
*/
|
|
64
|
+
beforeBoot?: (ctx: LifecycleBootContext) => Promise<void> | void;
|
|
65
|
+
/**
|
|
66
|
+
* Runs once the server is listening (after `Bun.serve`). Ideal for
|
|
67
|
+
* notifications and cache warming. Errors are logged, never fatal.
|
|
68
|
+
*/
|
|
69
|
+
afterBoot?: (ctx: LifecycleReadyContext) => Promise<void> | void;
|
|
70
|
+
/**
|
|
71
|
+
* Runs on SIGINT/SIGTERM, before replication stops and tenant DB clients
|
|
72
|
+
* close — so `rest` is still usable. Errors are logged; shutdown proceeds.
|
|
73
|
+
*/
|
|
74
|
+
onDestroy?: (ctx: LifecycleDestroyContext) => Promise<void> | void;
|
|
75
|
+
/** Max time (ms) allowed for `onDestroy` before shutdown proceeds anyway (default: 10000). */
|
|
76
|
+
destroyTimeout?: number;
|
|
77
|
+
};
|
package/types/mcp.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type Joi from "joi";
|
|
|
2
2
|
import type { ContentBlock } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import type { Context } from "hono";
|
|
4
4
|
import type { useRest } from "../database/rest";
|
|
5
|
+
import type { TenantAgents } from "./agent";
|
|
6
|
+
import type { Api } from "../lib/api";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* MCP tool result — the Model Context Protocol `CallToolResult` shape.
|
|
@@ -15,7 +17,7 @@ export type McpToolResult = {
|
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* MCP tool definition — auto-loaded from `{tenant.dir}/mcp/tools/**\/*.tool.ts`.
|
|
18
|
-
* Served over the MCP protocol at `GET/POST /
|
|
20
|
+
* Served over the MCP protocol at `GET/POST /api/:tenant_id/mcp` (Streamable HTTP).
|
|
19
21
|
*/
|
|
20
22
|
export type McpTool = {
|
|
21
23
|
_isMcpTool_?: boolean;
|
|
@@ -32,6 +34,10 @@ export type McpTool = {
|
|
|
32
34
|
exec: (ctx: {
|
|
33
35
|
c: Context;
|
|
34
36
|
rest: InstanceType<typeof useRest>;
|
|
37
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
38
|
+
agents: TenantAgents;
|
|
39
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
40
|
+
api: Api;
|
|
35
41
|
/** Parsed & validated arguments */
|
|
36
42
|
args: Record<string, any>;
|
|
37
43
|
}) => McpToolResult | Promise<McpToolResult>;
|
|
@@ -73,6 +79,10 @@ export type McpResource = {
|
|
|
73
79
|
read: (ctx: {
|
|
74
80
|
c: Context;
|
|
75
81
|
rest: InstanceType<typeof useRest>;
|
|
82
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
83
|
+
agents: TenantAgents;
|
|
84
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
85
|
+
api: Api;
|
|
76
86
|
/** Params extracted from the URI template (e.g. `{ id: '123' }`) */
|
|
77
87
|
params: Record<string, string>;
|
|
78
88
|
/** The exact URI the client requested */
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { MongoClientOptions } from "mongodb";
|
|
2
|
+
|
|
3
|
+
/** When to run replication — either a cron expression or a fixed interval. */
|
|
4
|
+
export type ReplicationSchedule =
|
|
5
|
+
| { cron: string }
|
|
6
|
+
| { interval: string | number };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initial synchronization mode — how replication starts when no `_replication_`
|
|
10
|
+
* state exists yet.
|
|
11
|
+
* - `"full"` — backfill everything (default)
|
|
12
|
+
* - `"skip"` — ignore existing documents, start from now
|
|
13
|
+
* - `"latest"` — start from the latest `key` in the source
|
|
14
|
+
* - ISO string / `Date` — start from this date
|
|
15
|
+
*/
|
|
16
|
+
export type ReplicationInitialSync = "full" | "skip" | "latest" | (string & {}) | Date;
|
|
17
|
+
|
|
18
|
+
/** A destination database that receives replicated documents. */
|
|
19
|
+
export type ReplicationDestination = {
|
|
20
|
+
/** Unique identifier — used as the `_replication_` state key (required). */
|
|
21
|
+
id: string;
|
|
22
|
+
/** MongoDB connection string (required). */
|
|
23
|
+
uri: string;
|
|
24
|
+
/** Database name override — defaults to the name in the URI. */
|
|
25
|
+
name?: string;
|
|
26
|
+
/** Extra MongoDB client options (pool size, timeouts…). */
|
|
27
|
+
options?: MongoClientOptions;
|
|
28
|
+
/** Restrict replication to these collection slugs (default: all enabled). */
|
|
29
|
+
collections?: string[];
|
|
30
|
+
/** Turn this destination off without removing it. */
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Framework (internal) collections that replication covers **by default**.
|
|
36
|
+
*
|
|
37
|
+
* The name is the collection name without its underscores: `_audit_` → `'audit'`,
|
|
38
|
+
* `_workflows_` → `'workflows'`, `_locks_` → `'locks'`, `_replication_` →
|
|
39
|
+
* `'replication'`, `_vars_` → `'vars'`, `_memories_` → `'memory'`.
|
|
40
|
+
*
|
|
41
|
+
* Every one of them is left out with `replication.exclude: ['audit', …]`.
|
|
42
|
+
*/
|
|
43
|
+
export type ReplicationMetaName = 'audit' | 'workflows' | 'locks' | 'replication' | 'vars' | 'memory';
|
|
44
|
+
|
|
45
|
+
/** Per-tenant replication configuration (source → destination, one-way). */
|
|
46
|
+
export type ReplicationConfig = {
|
|
47
|
+
/** Enable/disable replication for this scope (default: true when `destinations` is non-empty). */
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
/** Scheduling — `{ cron }` or `{ interval }` (default: every 5 minutes). */
|
|
50
|
+
schedule?: ReplicationSchedule;
|
|
51
|
+
/** Date key used for incremental replication (default: `'updatedAt'`). */
|
|
52
|
+
key?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Rewind the cursor by this many milliseconds on each run, so documents
|
|
55
|
+
* written slightly out of order (clock skew) are not missed (default: 0).
|
|
56
|
+
*/
|
|
57
|
+
lookback?: number;
|
|
58
|
+
/** Documents transferred per batch (default: 1000). */
|
|
59
|
+
batchSize?: number;
|
|
60
|
+
/** Run a first replication right after boot (default: true). */
|
|
61
|
+
runOnBoot?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* How the initial synchronization starts when no `_replication_` state exists
|
|
64
|
+
* yet (default: `'full'`). Use `'skip'` or `'latest'` to avoid the full
|
|
65
|
+
* backfill when the destination is already pre-loaded (e.g. via `mongorestore`).
|
|
66
|
+
*/
|
|
67
|
+
initialSync?: ReplicationInitialSync;
|
|
68
|
+
/**
|
|
69
|
+
* Framework collections to **leave out** of replication. All of them are
|
|
70
|
+
* replicated by default (`audit`, `workflows`, `locks`, `replication`,
|
|
71
|
+
* `vars`) — excluding one is what you opt out of.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* replication: { exclude: ['locks', 'replication'] }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ `vars` covers *every* namespace of `_vars_`, including the ones that
|
|
78
|
+
* never declared `replication: { enabled: true }`; a namespace can still opt
|
|
79
|
+
* out with `replication: { enabled: false }`, but this list wins over it.
|
|
80
|
+
*/
|
|
81
|
+
exclude?: ReplicationMetaName[];
|
|
82
|
+
/** Destination databases (required). */
|
|
83
|
+
destinations: ReplicationDestination[];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Per-collection opt-in replication. */
|
|
87
|
+
export type CollectionReplicationConfig = {
|
|
88
|
+
/** Opt this collection into replication (required). */
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
/** Date key override — defaults to `replication.key`. */
|
|
91
|
+
key?: string;
|
|
92
|
+
/** Initial sync override — defaults to `replication.initialSync`. */
|
|
93
|
+
initialSync?: ReplicationInitialSync;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type ReplicationStatus = "idle" | "running" | "success" | "error";
|
|
97
|
+
|
|
98
|
+
/** Cursor of the last replicated document — `value` is the date key, `id` the tie-breaker. */
|
|
99
|
+
export type ReplicationCursor = {
|
|
100
|
+
value: Date | null;
|
|
101
|
+
id: any;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type ReplicationStats = {
|
|
105
|
+
inserted: number;
|
|
106
|
+
updated: number;
|
|
107
|
+
deleted: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
batches: number;
|
|
110
|
+
durationMs: number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** State document persisted in `_replication_`, one per (tenant, destination, collection). */
|
|
114
|
+
export type ReplicationState = {
|
|
115
|
+
_id: string;
|
|
116
|
+
type: "state";
|
|
117
|
+
tenant: string;
|
|
118
|
+
destination: string;
|
|
119
|
+
collection: string;
|
|
120
|
+
key: string;
|
|
121
|
+
cursor: ReplicationCursor;
|
|
122
|
+
status: ReplicationStatus;
|
|
123
|
+
lastRunAt: Date | null;
|
|
124
|
+
lastError: string | null;
|
|
125
|
+
stats: ReplicationStats;
|
|
126
|
+
/** PID of the process that ran this replication (`reusePort` / forked workers) */
|
|
127
|
+
pid?: number;
|
|
128
|
+
/** Hostname of that process — a PID is only meaningful on its own host */
|
|
129
|
+
hostname?: string;
|
|
130
|
+
createdAt?: Date;
|
|
131
|
+
updatedAt: Date;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** Tombstone document persisted in `_replication_` when a replicated document is deleted. */
|
|
135
|
+
export type ReplicationDeleteMarker = {
|
|
136
|
+
_id: string;
|
|
137
|
+
type: "delete";
|
|
138
|
+
tenant: string;
|
|
139
|
+
collection: string;
|
|
140
|
+
docId: string;
|
|
141
|
+
deletedAt: Date;
|
|
142
|
+
/** Destination ids that already applied the delete. */
|
|
143
|
+
synced: string[];
|
|
144
|
+
updatedAt: Date;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Scope of a `replication.reset()` call. */
|
|
148
|
+
export type ReplicationResetOptions = {
|
|
149
|
+
/** Only reset this destination id (default: every destination). */
|
|
150
|
+
destination?: string;
|
|
151
|
+
/** Only reset this collection slug (default: every replicated collection). */
|
|
152
|
+
collection?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Also delete pending delete tombstones (default: true). Ignored when
|
|
155
|
+
* `destination` is set — tombstones are shared across destinations.
|
|
156
|
+
*/
|
|
157
|
+
tombstones?: boolean;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** Result of a `replication.reset()` call. */
|
|
161
|
+
export type ReplicationResetResult = {
|
|
162
|
+
tenant: string;
|
|
163
|
+
/** State documents deleted (one per destination + collection). */
|
|
164
|
+
state: number;
|
|
165
|
+
/** Delete tombstones deleted. */
|
|
166
|
+
tombstones: number;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Where to (re)start replication from — accepted by `replication.seed()`.
|
|
171
|
+
* - `"now"` — start from now (same as `initialSync: 'skip'`)
|
|
172
|
+
* - `"latest"` — start from the latest `key` in the source
|
|
173
|
+
* - ISO string / `Date` — start from this date
|
|
174
|
+
*/
|
|
175
|
+
export type ReplicationSeedValue = "now" | "latest" | (string & {}) | Date;
|
|
176
|
+
|
|
177
|
+
/** Scope of a `replication.seed()` call. */
|
|
178
|
+
export type ReplicationSeedOptions = {
|
|
179
|
+
/** Only seed this destination id (default: every destination). */
|
|
180
|
+
destination?: string;
|
|
181
|
+
/** Only seed this collection slug (default: every replicated collection). */
|
|
182
|
+
collection?: string;
|
|
183
|
+
/** Optional `_id` tie-breaker for the cursor. */
|
|
184
|
+
id?: any;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** Result of a `replication.seed()` call. */
|
|
188
|
+
export type ReplicationSeedResult = {
|
|
189
|
+
tenant: string;
|
|
190
|
+
/** State documents written (one per destination + collection). */
|
|
191
|
+
seeded: number;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** Shape of the `replication` namespace exported by the package (multi-tenant). */
|
|
195
|
+
export type ReplicationApi = {
|
|
196
|
+
start: () => Promise<void>;
|
|
197
|
+
stop: () => Promise<void>;
|
|
198
|
+
now: (tenantId?: string) => Promise<ReplicationRunResult[]>;
|
|
199
|
+
state: (tenantId: string, destinationId?: string, collection?: string) => Promise<ReplicationState[]>;
|
|
200
|
+
reset: (tenantId: string, opts?: ReplicationResetOptions) => Promise<ReplicationResetResult>;
|
|
201
|
+
seed: (tenantId: string, value: ReplicationSeedValue, opts?: ReplicationSeedOptions) => Promise<ReplicationSeedResult>;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Tenant-scoped replication API — like `rest`, the tenant is implicit. This is
|
|
206
|
+
* the `replication` passed to lifecycle hooks. Engine control (`start`/`stop`)
|
|
207
|
+
* stays on the global `ReplicationApi`.
|
|
208
|
+
*/
|
|
209
|
+
export type TenantReplicationApi = {
|
|
210
|
+
now: () => Promise<ReplicationRunResult[]>;
|
|
211
|
+
state: (destinationId?: string, collection?: string) => Promise<ReplicationState[]>;
|
|
212
|
+
reset: (opts?: ReplicationResetOptions) => Promise<ReplicationResetResult>;
|
|
213
|
+
seed: (value: ReplicationSeedValue, opts?: ReplicationSeedOptions) => Promise<ReplicationSeedResult>;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/** Outcome of a single (tenant, destination, collection) replication. */
|
|
217
|
+
export type ReplicationRunResult = {
|
|
218
|
+
tenant: string;
|
|
219
|
+
destination: string;
|
|
220
|
+
collection: string;
|
|
221
|
+
inserted: number;
|
|
222
|
+
updated: number;
|
|
223
|
+
deleted: number;
|
|
224
|
+
failed: number;
|
|
225
|
+
batches: number;
|
|
226
|
+
durationMs: number;
|
|
227
|
+
error?: string;
|
|
228
|
+
};
|
package/types/route.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Context } from "hono";
|
|
2
2
|
import type { useRest } from "../database/rest";
|
|
3
|
+
import type { TenantAgents } from "./agent";
|
|
4
|
+
import type { Api } from "../lib/api";
|
|
3
5
|
|
|
4
6
|
type routeContext = {
|
|
5
7
|
rest: InstanceType<typeof useRest>;
|
|
8
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
9
|
+
agents: TenantAgents;
|
|
10
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
11
|
+
api: Api;
|
|
6
12
|
jwt: typeof jwt;
|
|
7
|
-
io: InstanceType<
|
|
13
|
+
io: InstanceType<IO>;
|
|
8
14
|
c: Context
|
|
9
15
|
}
|
|
10
16
|
|
package/types/scripts.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useRest } from "../database/rest"
|
|
2
2
|
import type { Tenant } from "./tenant"
|
|
3
|
+
import type { TenantAgents } from "./agent"
|
|
4
|
+
import type { Api } from "../lib/api"
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
export type Script = {
|
|
@@ -15,6 +17,10 @@ export type Script = {
|
|
|
15
17
|
timeout?: number;
|
|
16
18
|
exec: (ctx: {
|
|
17
19
|
rest: InstanceType<typeof useRest>
|
|
20
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
21
|
+
agents: TenantAgents;
|
|
22
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
23
|
+
api: Api;
|
|
18
24
|
/** Report progress — only wired for heavy scripts (sent via IPC to the parent) */
|
|
19
25
|
progress?: (percent: number, info?: any) => void;
|
|
20
26
|
/** Tenant this script runs for */
|
package/types/service.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Server as SocketIO } from "socket.io";
|
|
|
2
2
|
import type { useRest } from "../database/rest";
|
|
3
3
|
import type { fn } from "../lib/error";
|
|
4
4
|
import type { jwt } from "../utils/func";
|
|
5
|
+
import type { TenantAgents } from "./agent";
|
|
6
|
+
import type { Api } from "../lib/api";
|
|
5
7
|
|
|
6
8
|
export type Service = {
|
|
7
9
|
_isService_?: boolean;
|
|
@@ -31,7 +33,11 @@ export type Service = {
|
|
|
31
33
|
jwt: typeof jwt;
|
|
32
34
|
error: typeof fn.error;
|
|
33
35
|
io: SocketIO;
|
|
34
|
-
rest: InstanceType<typeof useRest
|
|
36
|
+
rest: InstanceType<typeof useRest>;
|
|
37
|
+
/** Tenant-scoped LLM registry — `agents.get('support')`, bound to `rest`. */
|
|
38
|
+
agents: TenantAgents;
|
|
39
|
+
/** The in-process facade — `api.collection("orders")`, `api.service("analytics")`, `api.vars`, `api.files`, `api.agent(id)`. */
|
|
40
|
+
api: Api;
|
|
35
41
|
}) => Promise<any>
|
|
36
42
|
}
|
|
37
43
|
}
|
package/types/tenant.d.ts
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
import type { Db, MongoClient, MongoClientOptions } from "mongodb";
|
|
2
|
+
import type { ReplicationConfig } from "./replication";
|
|
3
|
+
import type { AuditFileConfig } from "../lib/audit";
|
|
2
4
|
export type Tenant = {
|
|
3
5
|
id: string;
|
|
4
6
|
name?: string;
|
|
5
7
|
description?: string;
|
|
6
8
|
dir: string;
|
|
9
|
+
/** Per-tenant replication — each tenant has its own destinations and schedule. */
|
|
10
|
+
replication?: ReplicationConfig;
|
|
11
|
+
/**
|
|
12
|
+
* Audit trail (`_audit_`) — query indexes are always ensured in the background,
|
|
13
|
+
* retention is opt-in.
|
|
14
|
+
*/
|
|
15
|
+
audit?: {
|
|
16
|
+
/**
|
|
17
|
+
* How long audit entries are kept, as a MongoDB TTL index on `ts`.
|
|
18
|
+
* Duration string: `'90d'`, `'24h'`, `'30m'`.
|
|
19
|
+
* `false` → explicitly disabled (drops the TTL index if one exists).
|
|
20
|
+
* Omitted → untouched: entries are kept forever.
|
|
21
|
+
*/
|
|
22
|
+
retention?: string | false;
|
|
23
|
+
/**
|
|
24
|
+
* Local append-only JSONL copy of this tenant's audit trail.
|
|
25
|
+
* `true` → defaults, `string` → directory, object → full options.
|
|
26
|
+
* `false` → explicitly disabled (overrides `server.audit.file`).
|
|
27
|
+
*/
|
|
28
|
+
file?: AuditFileConfig;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Workflow runs (`_workflows_`). Indexes are always ensured in the
|
|
32
|
+
* background; retention is opt-in and only prunes **finished** runs.
|
|
33
|
+
*/
|
|
34
|
+
workflows?: {
|
|
35
|
+
/**
|
|
36
|
+
* How long finished runs are kept, as a MongoDB TTL index on `completedAt`
|
|
37
|
+
* (runs still running, paused or failed keep no `completedAt` and are never
|
|
38
|
+
* pruned). Duration string: `'30d'`, `'90d'`.
|
|
39
|
+
* `false` → explicitly disabled. Omitted → untouched.
|
|
40
|
+
*/
|
|
41
|
+
retention?: string | false;
|
|
42
|
+
};
|
|
7
43
|
routes?: {
|
|
8
44
|
prefix?: string;
|
|
9
45
|
},
|