@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.
Files changed (130) hide show
  1. package/database/audit.ts +143 -0
  2. package/database/collection.ts +6 -4
  3. package/database/deleteLog.ts +98 -0
  4. package/database/file.ts +7 -6
  5. package/database/mongodbadapter.ts +420 -185
  6. package/database/replication.ts +858 -0
  7. package/database/schema.ts +140 -125
  8. package/database/tenant.ts +4 -0
  9. package/database/ttl.ts +68 -0
  10. package/database/vars.ts +405 -0
  11. package/database/workflow.ts +466 -240
  12. package/index.ts +16 -0
  13. package/lib/agent.ts +1327 -0
  14. package/lib/agentMemory.ts +586 -0
  15. package/lib/agents.ts +157 -0
  16. package/lib/ai.ts +0 -0
  17. package/lib/api.ts +246 -0
  18. package/lib/attachment.ts +351 -0
  19. package/lib/audit.ts +271 -0
  20. package/lib/auditResult.ts +133 -0
  21. package/lib/define.ts +62 -1
  22. package/lib/endpoints.ts +80 -0
  23. package/lib/files.ts +66 -12
  24. package/lib/jsonSchema.ts +183 -0
  25. package/lib/lifecycle.ts +147 -0
  26. package/lib/load.ts +17 -0
  27. package/lib/mcp.ts +3 -2
  28. package/lib/messages.ts +299 -0
  29. package/lib/providers.ts +867 -0
  30. package/lib/redact.ts +117 -0
  31. package/lib/routes.ts +14 -1
  32. package/lib/scriptWorker.ts +4 -1
  33. package/lib/scripts.ts +6 -1
  34. package/lib/sockets.ts +3 -1
  35. package/lib/workflow.ts +209 -37
  36. package/lib/zip.ts +109 -0
  37. package/package.json +1 -1
  38. package/server/access.ts +96 -0
  39. package/server/agents.ts +451 -0
  40. package/server/api.ts +121 -95
  41. package/server/boot.ts +196 -53
  42. package/server/flags.ts +136 -0
  43. package/server/hono.ts +9 -4
  44. package/server/mcp.ts +17 -63
  45. package/server/routes.ts +19 -24
  46. package/tests/agent-memory.test.ts +617 -0
  47. package/tests/agent-working-memory.test.ts +402 -0
  48. package/tests/agent.test.ts +1239 -0
  49. package/tests/agents-api.test.ts +593 -0
  50. package/tests/api.test.ts +51 -0
  51. package/tests/attachment.test.ts +241 -0
  52. package/tests/audit-file.test.ts +296 -0
  53. package/tests/audit-operations.test.ts +184 -0
  54. package/tests/audit-redact.test.ts +259 -0
  55. package/tests/audit-results.test.ts +188 -0
  56. package/tests/audit-retention-config.test.ts +76 -0
  57. package/tests/audit-run-action.test.ts +120 -0
  58. package/tests/audit.test.ts +320 -0
  59. package/tests/db-cache.test.ts +7 -7
  60. package/tests/endpoints.test.ts +80 -0
  61. package/tests/file-collection-replication.test.ts +98 -0
  62. package/tests/fixtures/agent-tenant/agents/support.agent.ts +9 -0
  63. package/tests/fixtures/agent-tenant/agents/weather.agent.ts +21 -0
  64. package/tests/fixtures/agents-api/agents/assistant.agent.ts +46 -0
  65. package/tests/fixtures/agents-api/agents/plain.agent.ts +23 -0
  66. package/tests/fixtures/agents-api/agents/private.agent.ts +15 -0
  67. package/tests/fixtures/agents-api/collections/notes.model.ts +26 -0
  68. package/tests/fixtures/agents-api/routes/probe.route.ts +21 -0
  69. package/tests/fixtures/agents-api/services/probe.service.ts +19 -0
  70. package/tests/fixtures/attachment/data.csv +2 -0
  71. package/tests/fixtures/fake-provider.ts +128 -0
  72. package/tests/fixtures/lc-disabled/lifecycle.ts +11 -0
  73. package/tests/fixtures/lc-hang/lifecycle.ts +13 -0
  74. package/tests/fixtures/lc-normal/lifecycle.ts +23 -0
  75. package/tests/fixtures/lc-throw/lifecycle.ts +6 -0
  76. package/tests/fixtures/repl-only/app.ts +26 -0
  77. package/tests/fixtures/repl-only/tenant/collections/orders.model.ts +14 -0
  78. package/tests/fixtures/repl-only/tenant/lifecycle.ts +14 -0
  79. package/tests/fixtures/repl-only/tenant/scripts/probe.run.ts +8 -0
  80. package/tests/fixtures/replication-meta/agents/chat.agent.ts +16 -0
  81. package/tests/fixtures/replication-meta/collections/counters.model.ts +13 -0
  82. package/tests/fixtures/replication-meta/collections/orders.model.ts +9 -0
  83. package/tests/fixtures/replication-meta/workflows/run.workflow.ts +8 -0
  84. package/tests/fixtures/vars-tenant/vars/config.var.ts +23 -0
  85. package/tests/fixtures/vars-tenant/vars/private.var.ts +9 -0
  86. package/tests/fixtures/workflow-engine/workflows/failing.workflow.ts +34 -0
  87. package/tests/fixtures/workflow-engine/workflows/flaky.workflow.ts +23 -0
  88. package/tests/fixtures/workflow-engine/workflows/hang.workflow.ts +17 -0
  89. package/tests/fixtures/workflow-engine/workflows/simple.workflow.ts +19 -0
  90. package/tests/fixtures/workflow-engine/workflows/slow.workflow.ts +23 -0
  91. package/tests/fixtures/workflow-engine/workflows/versioned.workflow.ts +19 -0
  92. package/tests/fixtures/workflow-tenant/workflows/broken.workflow.ts +3 -0
  93. package/tests/fixtures/workflow-tenant/workflows/disabled.workflow.ts +8 -0
  94. package/tests/fixtures/workflow-tenant/workflows/invoice.workflow.ts +21 -0
  95. package/tests/fixtures/workflow-tenant/workflows/no-id.workflow.ts +7 -0
  96. package/tests/fixtures/workflow-tenant/workflows/order-duplicate.workflow.ts +13 -0
  97. package/tests/fixtures/workflow-tenant/workflows/order.workflow.ts +17 -0
  98. package/tests/hono.test.ts +6 -3
  99. package/tests/lifecycle.test.ts +96 -0
  100. package/tests/locks.test.ts +120 -0
  101. package/tests/mcp.test.ts +11 -11
  102. package/tests/messages.test.ts +258 -0
  103. package/tests/replication-control.test.ts +128 -0
  104. package/tests/replication-initial-sync.test.ts +86 -0
  105. package/tests/replication-meta.test.ts +243 -0
  106. package/tests/replication-only.test.ts +358 -0
  107. package/tests/replication.test.ts +188 -0
  108. package/tests/upload.test.ts +141 -8
  109. package/tests/vars-api.test.ts +123 -0
  110. package/tests/vars-replication.test.ts +96 -0
  111. package/tests/vars.test.ts +216 -0
  112. package/tests/workflow-engine.test.ts +340 -0
  113. package/tests/workflow-retention.test.ts +103 -0
  114. package/tests/workflow.test.ts +186 -0
  115. package/types/agent.d.ts +837 -0
  116. package/types/collection.d.ts +3 -0
  117. package/types/config.d.ts +80 -0
  118. package/types/file.d.ts +11 -8
  119. package/types/hook.d.ts +6 -0
  120. package/types/lifecycle.d.ts +77 -0
  121. package/types/mcp.d.ts +11 -1
  122. package/types/replication.d.ts +228 -0
  123. package/types/route.d.ts +7 -1
  124. package/types/scripts.d.ts +6 -0
  125. package/types/service.d.ts +7 -1
  126. package/types/tenant.d.ts +36 -0
  127. package/types/vars.d.ts +147 -0
  128. package/types/websocket.d.ts +6 -0
  129. package/types/workflow.d.ts +50 -3
  130. package/utils/func.ts +67 -52
@@ -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 /upload/:tenant_id/:slug */
19
+ /** Access control for POST /api/:tenant_id/upload/:slug */
19
20
  upload?: boolean | FileAccessHandler;
20
- /** Access control for GET /files/:tenant_id/:slug/:file */
21
+ /** Access control for GET /api/:tenant_id/files/:slug/:file */
21
22
  read?: boolean | FileAccessHandler;
22
- /** Access control for DELETE /files/:tenant_id/:slug/:file */
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 /upload/:tenant_id/:slug — upload a file
34
- * GET /files/:tenant_id/:slug/:file — serve/download a file
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: `/upload/:tenant_id/:slug` */
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 /files/:tenant_id/:slug/:file
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
- /** Enregistrer les métadonnées du fichier en base. Défaut: true */
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 /mcp/:tenant_id` (Streamable HTTP).
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<typeof IO>;
13
+ io: InstanceType<IO>;
8
14
  c: Context
9
15
  }
10
16
 
@@ -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 */
@@ -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
  },