@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
@@ -1,5 +1,6 @@
1
1
 
2
2
  import type { Collection as CollectionType } from "../types/collection";
3
+ import type { Field } from "../types/field";
3
4
  import Joi, { type AnySchema } from "joi";
4
5
 
5
6
 
@@ -10,6 +11,142 @@ let s = Joi.object({
10
11
 
11
12
 
12
13
 
14
+ /**
15
+ * Build the Joi schema for a single field. Assumes `f.type` is set.
16
+ * Extracted so other features (e.g. `define.Vars`) can validate with the exact
17
+ * same rules as collections — without the `Joi.object(...).min(1)` wrapper.
18
+ */
19
+ function fieldToSchema(f: Field): AnySchema | undefined {
20
+ let schema: AnySchema | undefined
21
+
22
+ if (f.type == 'string') {
23
+ schema = Joi.string()
24
+ }
25
+
26
+ if (f.type == 'password') {
27
+ schema = Joi.string()
28
+ }
29
+
30
+ if (f.type == 'number') {
31
+ schema = Joi.number()
32
+ }
33
+
34
+ if (f.type == 'integer') {
35
+ schema = Joi.number().integer()
36
+ }
37
+
38
+ if (f.type == 'boolean') {
39
+ schema = Joi.boolean()
40
+ }
41
+
42
+ if (f.type.match(/(date|datetime-local)/)) {
43
+ schema = Joi.date()
44
+ }
45
+
46
+ if (f.type == 'array') {
47
+ schema = Joi.array()
48
+ }
49
+
50
+ if (f.type == 'json') {
51
+ schema = Joi.object()
52
+ }
53
+
54
+ if (f.type == 'uuid') {
55
+ schema = Joi.string().uuid()
56
+ }
57
+ if (f.type == 'email') {
58
+ schema = Joi.string().email()
59
+ }
60
+
61
+ if (f.type == 'url') {
62
+ schema = Joi.string().uri()
63
+ }
64
+
65
+ if (f.type == 'slug') {
66
+ let slug = Joi.string().pattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
67
+ if (f.slugOptions?.min != null) slug = slug.min(f.slugOptions.min)
68
+ if (f.slugOptions?.max != null) slug = slug.max(f.slugOptions.max)
69
+ schema = slug
70
+ }
71
+
72
+ if (f.type == 'ipv4') {
73
+ schema = Joi.string().ip({ version: 'ipv4' })
74
+ }
75
+
76
+ if (f.type == 'ipv6') {
77
+ schema = Joi.string().ip({ version: 'ipv6' })
78
+ }
79
+
80
+ if (f.type == 'enum' && !f.enumOptions?.multiple) {
81
+ schema = Joi.string().valid(...f.enumOptions?.items || [])
82
+ }
83
+
84
+ if (f.type == 'enum' && f.enumOptions?.multiple) {
85
+ schema = Joi.array().items(Joi.string().valid(...f.enumOptions?.items || []))
86
+ }
87
+
88
+ if (f.type == 'random') {
89
+ schema = Joi.string()
90
+ }
91
+
92
+ if (f.type == 'random' && f?.randomOptions?.toNumber) {
93
+ schema = Joi.number()
94
+ }
95
+
96
+ if (f?.type.match(/(geojson\.Point|geojson\.LineString|geojson\.Polygon)/)) {
97
+ schema = Joi.object({
98
+ type: Joi.string().valid("Point", "LineString", "Polygon", "MultiPoint").required(),
99
+ coordinates: Joi.alternatives().conditional("type", [
100
+ {
101
+ is: "Point",
102
+ then: Joi.array().items(Joi.number()).length(2).required(), // [lng, lat]
103
+ },
104
+ {
105
+ is: Joi.string().valid("LineString", "MultiPoint"),
106
+ then: Joi.array().items(Joi.array().items(Joi.number()).length(2)).required(), // [[lng, lat], ...]
107
+ },
108
+ {
109
+ is: "Polygon",
110
+ then: Joi.array().items(Joi.array().items(Joi.array().items(Joi.number()).length(2))).required(), // [[[lng, lat], ...]]
111
+ },
112
+ ]),
113
+ });
114
+ }
115
+
116
+ if (f?.type == 'relationship') {
117
+ schema = Joi.string().optional().messages({
118
+ 'string.base': `${f.name} must be a string (ObjectId)`,
119
+ })
120
+ }
121
+
122
+ if (f?.type == 'relationship' && f?.relation?.hasMany) {
123
+ schema = Joi.array().items(Joi.string().optional()).messages({
124
+ 'string.base': `${f.name} must be a string (ObjectId)`,
125
+ })
126
+ }
127
+
128
+ if (f.validate?.schema) {
129
+ schema = f.validate.schema
130
+ }
131
+
132
+ if (f?.required) {
133
+ schema = schema?.required()
134
+ } else {
135
+ schema = schema?.optional()
136
+ }
137
+
138
+ if (f?.nullable) {
139
+ schema = schema?.allow(null)
140
+ }
141
+
142
+ if (f?.empty) {
143
+ schema = schema?.allow('')
144
+ }
145
+
146
+ return schema
147
+ }
148
+
149
+
13
150
  function buildSchema(col: CollectionType, opts = {
14
151
  partial: false
15
152
  }) {
@@ -25,130 +162,8 @@ function buildSchema(col: CollectionType, opts = {
25
162
  if (f.type && f.name) {
26
163
 
27
164
  let fieldName = f.name
28
-
29
- if (f.type == 'string') {
30
- propertiesSchema[fieldName] = Joi.string()
31
- }
32
-
33
- if (f.type == 'password') {
34
- propertiesSchema[fieldName] = Joi.string()
35
- }
36
-
37
- if (f.type == 'number') {
38
- propertiesSchema[fieldName] = Joi.number()
39
- }
40
-
41
- if (f.type == 'integer') {
42
- propertiesSchema[fieldName] = Joi.number().integer()
43
- }
44
-
45
- if (f.type == 'boolean') {
46
- propertiesSchema[fieldName] = Joi.boolean()
47
- }
48
-
49
- if (f.type.match(/(date|datetime-local)/)) {
50
- propertiesSchema[fieldName] = Joi.date()
51
- }
52
-
53
- if (f.type == 'array') {
54
- propertiesSchema[fieldName] = Joi.array()
55
- }
56
-
57
- if (f.type == 'json') {
58
- propertiesSchema[fieldName] = Joi.object()
59
- }
60
-
61
- if (f.type == 'uuid') {
62
- propertiesSchema[fieldName] = Joi.string().uuid()
63
- }
64
- if (f.type == 'email') {
65
- propertiesSchema[fieldName] = Joi.string().email()
66
- }
67
-
68
- if (f.type == 'url') {
69
- propertiesSchema[fieldName] = Joi.string().uri()
70
- }
71
-
72
- if (f.type == 'slug') {
73
- let schema = Joi.string().pattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
74
- if (f.slugOptions?.min != null) schema = schema.min(f.slugOptions.min)
75
- if (f.slugOptions?.max != null) schema = schema.max(f.slugOptions.max)
76
- propertiesSchema[fieldName] = schema
77
- }
78
-
79
- if (f.type == 'ipv4') {
80
- propertiesSchema[fieldName] = Joi.string().ip({ version: 'ipv4' })
81
- }
82
-
83
- if (f.type == 'ipv6') {
84
- propertiesSchema[fieldName] = Joi.string().ip({ version: 'ipv6' })
85
- }
86
-
87
- if (f.type == 'enum' && !f.enumOptions?.multiple) {
88
- propertiesSchema[fieldName] = Joi.string().valid(...f.enumOptions?.items || [])
89
- }
90
-
91
- if (f.type == 'enum' && f.enumOptions?.multiple) {
92
- propertiesSchema[fieldName] = Joi.array().items(Joi.string().valid(...f.enumOptions?.items || []))
93
- }
94
-
95
- if (f.type == 'random') {
96
- propertiesSchema[fieldName] = Joi.string()
97
- }
98
-
99
- if (f.type == 'random' && f?.randomOptions?.toNumber) {
100
- propertiesSchema[fieldName] = Joi.number()
101
- }
102
-
103
- if (f?.type.match(/(geojson\.Point|geojson\.LineString|geojson\.Polygon)/)) {
104
- propertiesSchema[fieldName] = Joi.object({
105
- type: Joi.string().valid("Point", "LineString", "Polygon", "MultiPoint").required(),
106
- coordinates: Joi.alternatives().conditional("type", [
107
- {
108
- is: "Point",
109
- then: Joi.array().items(Joi.number()).length(2).required(), // [lng, lat]
110
- },
111
- {
112
- is: Joi.string().valid("LineString", "MultiPoint"),
113
- then: Joi.array().items(Joi.array().items(Joi.number()).length(2)).required(), // [[lng, lat], ...]
114
- },
115
- {
116
- is: "Polygon",
117
- then: Joi.array().items(Joi.array().items(Joi.array().items(Joi.number()).length(2))).required(), // [[[lng, lat], ...]]
118
- },
119
- ]),
120
- });
121
- }
122
-
123
- if (f?.type == 'relationship') {
124
- propertiesSchema[fieldName] = Joi.string().optional().messages({
125
- 'string.base': `${f.name} must be a string (ObjectId)`,
126
- })
127
- }
128
-
129
- if (f?.type == 'relationship' && f?.relation?.hasMany) {
130
- propertiesSchema[fieldName] = Joi.array().items(Joi.string().optional()).messages({
131
- 'string.base': `${f.name} must be a string (ObjectId)`,
132
- })
133
- }
134
-
135
- if (f.validate?.schema) {
136
- propertiesSchema[fieldName] = f.validate.schema
137
- }
138
-
139
- if (f?.required) {
140
- propertiesSchema[fieldName] = propertiesSchema[fieldName]?.required()!
141
- } else {
142
- propertiesSchema[fieldName] = propertiesSchema[fieldName]?.optional()!
143
- }
144
-
145
- if (f?.nullable) {
146
- propertiesSchema[fieldName] = propertiesSchema[fieldName]?.allow(null)
147
- }
148
-
149
- if (f?.empty) {
150
- propertiesSchema[fieldName] = propertiesSchema[fieldName]?.allow('')
151
- }
165
+ const fieldSchema = fieldToSchema(f)
166
+ if (fieldSchema) propertiesSchema[fieldName] = fieldSchema
152
167
 
153
168
  }
154
169
  }
@@ -164,4 +179,4 @@ function buildSchemaForkOptional(schemaPassed: AnySchema): AnySchema {
164
179
  )
165
180
  }
166
181
 
167
- export { buildSchema, buildSchemaForkOptional }
182
+ export { buildSchema, buildSchemaForkOptional, fieldToSchema }
@@ -1,6 +1,7 @@
1
1
  import type { Tenant } from "../types/tenant";
2
2
  import { cfg } from "../server/config";
3
3
  import { useRest } from "./rest";
4
+ import { setupAuditCollection, resolveAuditRetention } from "./audit";
4
5
  async function syncTenants() {
5
6
  try {
6
7
  for await (let tenant of cfg.tenants ?? []) {
@@ -17,6 +18,9 @@ async function syncTenants() {
17
18
  const { client, db } = await rest.connect()
18
19
  tenant.database.client = client
19
20
  tenant.database.db = db
21
+ // One-off audit setup: legacy rename, query indexes, retention
22
+ // (tenant config wins, `server.audit.retention` is the fallback)
23
+ await setupAuditCollection(db, resolveAuditRetention(tenant))
20
24
  }
21
25
  } catch (err: any) {
22
26
  console.error('Error bootstrapping tenants', cfg.debug ? err : err?.message)
@@ -0,0 +1,68 @@
1
+ import type { Db } from "mongodb";
2
+ import { parseDuration } from "../utils/func";
3
+
4
+ export type RetentionResult = 'applied' | 'dropped' | 'unchanged' | 'skipped'
5
+
6
+ /**
7
+ * Declarative TTL retention for a collection:
8
+ *
9
+ * - a duration string (`'90d'`, `'24h'`) → ensure the TTL index with that expiry
10
+ * - `false` → explicitly disabled: drop the TTL index
11
+ * - omitted → untouched
12
+ *
13
+ * MongoDB cannot change `expireAfterSeconds` in place through `createIndex`:
14
+ * `collMod` does it instantly, dropping/recreating is the fallback. Never fatal —
15
+ * a failure is logged and `'skipped'` is returned.
16
+ */
17
+ async function syncTtlRetention(
18
+ db: Db,
19
+ opts: { collection: string; index: string; field: string; retention?: string | false; label?: string },
20
+ ): Promise<RetentionResult> {
21
+ const { collection, index, field, retention } = opts;
22
+ const label = opts.label ?? collection;
23
+
24
+ if (retention === undefined) return 'skipped';
25
+
26
+ const col: any = db.collection(collection);
27
+
28
+ try {
29
+ const existing = (await col.listIndexes().toArray())
30
+ .find((entry: any) => entry.name === index);
31
+
32
+ if (retention === false) {
33
+ if (!existing) return 'unchanged';
34
+ await col.dropIndex(index);
35
+ console.log(`🧹 ${label} retention disabled — ${index} dropped`);
36
+ return 'dropped';
37
+ }
38
+
39
+ const parsed = parseDuration(retention);
40
+ if (parsed === null) {
41
+ console.error(`Invalid ${label} retention '${retention}' — expected a duration like '90d' or '24h'`);
42
+ return 'skipped';
43
+ }
44
+ const expireAfterSeconds = Math.round(parsed / 1000);
45
+
46
+ if (!existing) {
47
+ await col.createIndex({ [field]: 1 }, { name: index, expireAfterSeconds, background: true });
48
+ console.log(`🧹 ${label} retention enabled — entries expire after ${retention}`);
49
+ return 'applied';
50
+ }
51
+
52
+ if (existing.expireAfterSeconds === expireAfterSeconds) return 'unchanged';
53
+
54
+ try {
55
+ await db.command({ collMod: collection, index: { name: index, expireAfterSeconds } });
56
+ } catch {
57
+ await col.dropIndex(index);
58
+ await col.createIndex({ [field]: 1 }, { name: index, expireAfterSeconds, background: true });
59
+ }
60
+ console.log(`🧹 ${label} retention updated — entries expire after ${retention}`);
61
+ return 'applied';
62
+ } catch (err: any) {
63
+ console.error(`${label} retention failed (${retention})`, err?.message);
64
+ return 'skipped';
65
+ }
66
+ }
67
+
68
+ export { syncTtlRetention }