@anteros/core 0.0.40 → 0.0.41

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 (59) hide show
  1. package/database/audit.ts +193 -0
  2. package/database/collection.ts +3 -3
  3. package/database/deleteLog.ts +34 -0
  4. package/database/file.ts +3 -3
  5. package/database/mongodbadapter.ts +321 -147
  6. package/database/replication.ts +780 -0
  7. package/database/schema.ts +140 -125
  8. package/database/tenant.ts +4 -0
  9. package/database/vars.ts +405 -0
  10. package/database/workflow.ts +6 -6
  11. package/index.ts +10 -0
  12. package/lib/audit.ts +271 -0
  13. package/lib/auditResult.ts +120 -0
  14. package/lib/define.ts +20 -1
  15. package/lib/files.ts +64 -11
  16. package/lib/lifecycle.ts +141 -0
  17. package/lib/redact.ts +117 -0
  18. package/lib/workflow.ts +1 -1
  19. package/package.json +1 -1
  20. package/server/api.ts +109 -25
  21. package/server/boot.ts +185 -53
  22. package/server/flags.ts +136 -0
  23. package/server/hono.ts +1 -1
  24. package/tests/audit-file.test.ts +296 -0
  25. package/tests/audit-operations.test.ts +184 -0
  26. package/tests/audit-redact.test.ts +233 -0
  27. package/tests/audit-results.test.ts +188 -0
  28. package/tests/audit-retention-config.test.ts +76 -0
  29. package/tests/audit-run-action.test.ts +120 -0
  30. package/tests/audit.test.ts +320 -0
  31. package/tests/db-cache.test.ts +7 -7
  32. package/tests/file-collection-replication.test.ts +98 -0
  33. package/tests/fixtures/lc-disabled/lifecycle.ts +11 -0
  34. package/tests/fixtures/lc-hang/lifecycle.ts +13 -0
  35. package/tests/fixtures/lc-normal/lifecycle.ts +23 -0
  36. package/tests/fixtures/lc-throw/lifecycle.ts +6 -0
  37. package/tests/fixtures/repl-only/app.ts +26 -0
  38. package/tests/fixtures/repl-only/tenant/collections/orders.model.ts +14 -0
  39. package/tests/fixtures/repl-only/tenant/lifecycle.ts +14 -0
  40. package/tests/fixtures/repl-only/tenant/scripts/probe.run.ts +8 -0
  41. package/tests/fixtures/vars-tenant/vars/config.var.ts +23 -0
  42. package/tests/fixtures/vars-tenant/vars/private.var.ts +6 -0
  43. package/tests/lifecycle.test.ts +96 -0
  44. package/tests/replication-control.test.ts +128 -0
  45. package/tests/replication-initial-sync.test.ts +86 -0
  46. package/tests/replication-only.test.ts +358 -0
  47. package/tests/replication.test.ts +188 -0
  48. package/tests/upload.test.ts +102 -1
  49. package/tests/vars-api.test.ts +123 -0
  50. package/tests/vars-replication.test.ts +95 -0
  51. package/tests/vars.test.ts +216 -0
  52. package/types/collection.d.ts +3 -0
  53. package/types/config.d.ts +64 -0
  54. package/types/file.d.ts +4 -1
  55. package/types/lifecycle.d.ts +71 -0
  56. package/types/replication.d.ts +199 -0
  57. package/types/tenant.d.ts +23 -0
  58. package/types/vars.d.ts +147 -0
  59. 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)