@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Db } from "mongodb";
|
|
2
|
+
import { cfg } from "../server/config";
|
|
3
|
+
import type { Tenant } from "../types/tenant";
|
|
4
|
+
import { syncTtlRetention, type RetentionResult } from "./ttl";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Collection holding the audit trail — one document per database operation
|
|
8
|
+
* (see `types/activity.d.ts` / `rest.audit`).
|
|
9
|
+
*/
|
|
10
|
+
const AUDIT_COLLECTION = '_audit_'
|
|
11
|
+
|
|
12
|
+
/** Previous name of the audit collection — renamed once at boot by `migrateAuditCollection`. */
|
|
13
|
+
const LEGACY_AUDIT_COLLECTION = '_activities_'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* TTL index used for retention. Created only when a `tenant.audit.retention`
|
|
17
|
+
* is configured — audit entries are kept forever by default.
|
|
18
|
+
*/
|
|
19
|
+
const AUDIT_TTL_INDEX = '_audit_ttl_'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Query indexes, always ensured in the background (same convention as `_vars_`),
|
|
23
|
+
* whatever the tenant config — an audit collection is queried by date, by
|
|
24
|
+
* collection, by status and by trace, and grows fast enough that a COLLSCAN
|
|
25
|
+
* is never acceptable.
|
|
26
|
+
*/
|
|
27
|
+
const AUDIT_INDEXES: { name: string; key: Record<string, 1 | -1> }[] = [
|
|
28
|
+
/** Listing / date ranges (`getActivities` sorts on `ts` by default) */
|
|
29
|
+
{ name: '_audit_ts_', key: { ts: -1 } },
|
|
30
|
+
/** `$match: { 'operation.collection': … }` + recent first */
|
|
31
|
+
{ name: '_audit_collection_ts_', key: { 'operation.collection': 1, ts: -1 } },
|
|
32
|
+
/** `$match: { 'operation.status': 'error' }` + recent first */
|
|
33
|
+
{ name: '_audit_status_ts_', key: { 'operation.status': 1, ts: -1 } },
|
|
34
|
+
/** Correlate every operation of a request (`trace.id`) */
|
|
35
|
+
{ name: '_audit_trace_', key: { 'trace.id': 1 } },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One-off migration: `_activities_` → `_audit_`.
|
|
40
|
+
*
|
|
41
|
+
* Idempotent and never fatal:
|
|
42
|
+
* - legacy collection absent → no-op (the common case after the migration)
|
|
43
|
+
* - target already exists → no-op, the legacy collection is left untouched
|
|
44
|
+
* (never merged nor dropped: losing audit entries silently is worse than a stale collection)
|
|
45
|
+
*/
|
|
46
|
+
async function migrateAuditCollection(db: Db): Promise<boolean> {
|
|
47
|
+
try {
|
|
48
|
+
const legacy = await db.listCollections({ name: LEGACY_AUDIT_COLLECTION }).toArray()
|
|
49
|
+
if (legacy.length === 0) return false
|
|
50
|
+
|
|
51
|
+
const target = await db.listCollections({ name: AUDIT_COLLECTION }).toArray()
|
|
52
|
+
if (target.length > 0) return false
|
|
53
|
+
|
|
54
|
+
await db.renameCollection(LEGACY_AUDIT_COLLECTION, AUDIT_COLLECTION, { dropTarget: false })
|
|
55
|
+
console.log(`🔁 Audit collection renamed: ${LEGACY_AUDIT_COLLECTION} → ${AUDIT_COLLECTION}`)
|
|
56
|
+
return true
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
console.error(
|
|
59
|
+
`Audit collection migration failed (${LEGACY_AUDIT_COLLECTION} → ${AUDIT_COLLECTION})`,
|
|
60
|
+
err?.message,
|
|
61
|
+
)
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ensure the audit query indexes. `createIndex` is idempotent, so this runs on
|
|
68
|
+
* every boot and only fills in what is missing. Never fatal, never blocking
|
|
69
|
+
* (`background: true`).
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ Indexes cost writes, and the audit collection is the most write-heavy one —
|
|
72
|
+
* four indexes are a deliberate trade-off (see the Audit documentation).
|
|
73
|
+
*/
|
|
74
|
+
async function ensureAuditIndexes(db: Db): Promise<string[]> {
|
|
75
|
+
const ensured: string[] = []
|
|
76
|
+
const col: any = db.collection(AUDIT_COLLECTION)
|
|
77
|
+
|
|
78
|
+
for (const index of AUDIT_INDEXES) {
|
|
79
|
+
try {
|
|
80
|
+
await col.createIndex(index.key, { name: index.name, background: true })
|
|
81
|
+
ensured.push(index.name)
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
console.error(`Audit index '${index.name}' failed`, err?.message)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return ensured
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Declarative retention (MongoDB TTL on `ts`) — shared TTL helper:
|
|
92
|
+
* - duration string (`'90d'`, `'24h'`) → ensure the TTL index with that expiry
|
|
93
|
+
* - `false` → explicitly disabled: drop the TTL index
|
|
94
|
+
* - omitted → untouched (entries are kept forever)
|
|
95
|
+
*/
|
|
96
|
+
async function syncAuditRetention(db: Db, retention?: string | false): Promise<RetentionResult> {
|
|
97
|
+
return syncTtlRetention(db, {
|
|
98
|
+
collection: AUDIT_COLLECTION,
|
|
99
|
+
index: AUDIT_TTL_INDEX,
|
|
100
|
+
field: 'ts',
|
|
101
|
+
retention,
|
|
102
|
+
label: 'Audit',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Retention resolution — **the tenant always wins**, `server.audit.retention`
|
|
108
|
+
* is only the fallback:
|
|
109
|
+
*
|
|
110
|
+
* - `tenant.audit.retention` set (a duration, even `false`) → used as-is
|
|
111
|
+
* - otherwise → `server.audit.retention`
|
|
112
|
+
* - neither → `undefined` (kept forever)
|
|
113
|
+
*/
|
|
114
|
+
function resolveAuditRetention(tenant: Tenant): string | false | undefined {
|
|
115
|
+
if (tenant.audit?.retention !== undefined) return tenant.audit.retention
|
|
116
|
+
return cfg.server?.audit?.retention
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Boot setup for a tenant's audit collection, in this order:
|
|
121
|
+
* migration → indexes → retention.
|
|
122
|
+
*
|
|
123
|
+
* The migration comes first on purpose: creating the indexes would create an
|
|
124
|
+
* empty `_audit_`, and the migration would then refuse to rename the legacy
|
|
125
|
+
* collection (leaving its entries stranded).
|
|
126
|
+
*/
|
|
127
|
+
async function setupAuditCollection(db: Db, retention?: string | false): Promise<void> {
|
|
128
|
+
await migrateAuditCollection(db)
|
|
129
|
+
await ensureAuditIndexes(db)
|
|
130
|
+
await syncAuditRetention(db, retention)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
AUDIT_COLLECTION,
|
|
135
|
+
LEGACY_AUDIT_COLLECTION,
|
|
136
|
+
AUDIT_TTL_INDEX,
|
|
137
|
+
AUDIT_INDEXES,
|
|
138
|
+
migrateAuditCollection,
|
|
139
|
+
ensureAuditIndexes,
|
|
140
|
+
syncAuditRetention,
|
|
141
|
+
resolveAuditRetention,
|
|
142
|
+
setupAuditCollection
|
|
143
|
+
}
|
package/database/collection.ts
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "fs/promises"
|
|
|
5
5
|
import type { Collection } from "../types/collection"
|
|
6
6
|
import type { IndexDescription } from "mongodb"
|
|
7
7
|
import type { Field } from "../types/field"
|
|
8
|
+
import { importDefinition } from "../lib/load"
|
|
8
9
|
import { getTenant } from "./tenant"
|
|
9
10
|
import { buildSchema } from "./schema"
|
|
10
11
|
import type { FileCollection } from "../types/file"
|
|
@@ -38,7 +39,8 @@ async function syncCollections() {
|
|
|
38
39
|
if (isDirectory) {
|
|
39
40
|
const glob = new Glob(path.join(COLLECTIONS_PATH, '**/*.model.ts'))
|
|
40
41
|
for await (let file of glob.scan('.')) {
|
|
41
|
-
|
|
42
|
+
// A broken model file is skipped, never fatal for the others
|
|
43
|
+
let collectionModule = await importDefinition(file, 'collection')
|
|
42
44
|
if (collectionModule?.default?._isCollection_) {
|
|
43
45
|
collections.push({
|
|
44
46
|
...collectionModule?.default,
|
|
@@ -49,7 +51,7 @@ async function syncCollections() {
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
//
|
|
54
|
+
// build the arktype schemas for the collections
|
|
53
55
|
for (let collection of collections) {
|
|
54
56
|
collection._schema_ = buildSchema(collection)
|
|
55
57
|
collection._schemaPartial_ = buildSchema(collection, { partial: true })
|
|
@@ -102,7 +104,7 @@ async function initializeOnDatabase(collections: Collection[]) {
|
|
|
102
104
|
|
|
103
105
|
|
|
104
106
|
|
|
105
|
-
//
|
|
107
|
+
// create index on the fields
|
|
106
108
|
let specsFieldsIndexes: IndexDescription[] = []
|
|
107
109
|
for (let field of collection?.fields || []) {
|
|
108
110
|
field.indexOptions = field.indexOptions ?? {}
|
|
@@ -190,7 +192,7 @@ function getCollection(collectionName: string, tenantId: string): Collection | n
|
|
|
190
192
|
return col
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
//
|
|
195
|
+
// Also look in the file collections
|
|
194
196
|
let fileCol = cfg.fileCollections?.find(fc => fc.slug == collectionName && fc._tenant_ == tenantId)
|
|
195
197
|
if (fileCol) {
|
|
196
198
|
return {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Db } from "mongodb";
|
|
2
|
+
import { cfg } from "../server/config";
|
|
3
|
+
import type { ReplicationMetaName } from "../types/replication";
|
|
4
|
+
|
|
5
|
+
/** Collection holding the replication state (`_replication_`). */
|
|
6
|
+
const MARKERS = "_replication_";
|
|
7
|
+
|
|
8
|
+
/** Meta collection slug → the name `replication.exclude` knows it by. */
|
|
9
|
+
const META_BY_SLUG: Record<string, ReplicationMetaName> = {
|
|
10
|
+
[MARKERS]: "replication",
|
|
11
|
+
_audit_: "audit",
|
|
12
|
+
_workflows_: "workflows",
|
|
13
|
+
_locks_: "locks",
|
|
14
|
+
_vars_: "vars",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Replication is armed: enabled, with at least one usable destination. */
|
|
18
|
+
function replicationReady(tenantId: string): boolean {
|
|
19
|
+
const tenant: any = (cfg.tenants ?? []).find((candidate) => candidate.id === tenantId);
|
|
20
|
+
const replication = tenant?.replication;
|
|
21
|
+
|
|
22
|
+
if (!replication || replication.enabled === false) return false;
|
|
23
|
+
return (replication.destinations ?? []).some((destination: any) =>
|
|
24
|
+
destination?.enabled !== false && destination?.id && destination?.uri);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `replication.exclude` names this collection. */
|
|
28
|
+
function isExcluded(tenantId: string, name: string): boolean {
|
|
29
|
+
const tenant: any = (cfg.tenants ?? []).find((candidate) => candidate.id === tenantId);
|
|
30
|
+
return (tenant?.replication?.exclude ?? []).includes(name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether the framework replicates a **meta** collection (`_audit_`, `_vars_`…)
|
|
35
|
+
* for this tenant.
|
|
36
|
+
*/
|
|
37
|
+
function replicatesMetaCollection(tenantId: string, name: ReplicationMetaName): boolean {
|
|
38
|
+
return replicationReady(tenantId) && !isExcluded(tenantId, name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The same question, by **collection slug** — used by the stores that delete on
|
|
43
|
+
* their own instead of going through the collection hooks (agent memory, and the
|
|
44
|
+
* variables / file paths mirror this).
|
|
45
|
+
*
|
|
46
|
+
* It answers exactly what `replicatedCollectionsFor()` would: a meta collection,
|
|
47
|
+
* an agent memory collection (`exclude: ['memory']` covers them all — the tenant
|
|
48
|
+
* names them), or a declared collection that opted in. A collection nobody
|
|
49
|
+
* replicates gets no tombstone: it would never be flushed and would only pile up
|
|
50
|
+
* in `_replication_`.
|
|
51
|
+
*/
|
|
52
|
+
function replicatesCollection(tenantId: string, slug: string): boolean {
|
|
53
|
+
const asMeta = META_BY_SLUG[slug];
|
|
54
|
+
if (asMeta) return replicatesMetaCollection(tenantId, asMeta);
|
|
55
|
+
if (!replicationReady(tenantId)) return false;
|
|
56
|
+
|
|
57
|
+
const isAgentMemory = (cfg.agentMemories ?? [])
|
|
58
|
+
.some((memory) => memory._tenant_ === tenantId && memory.collection === slug);
|
|
59
|
+
if (isAgentMemory) return !isExcluded(tenantId, "memory");
|
|
60
|
+
|
|
61
|
+
const declared = [...(cfg.collections ?? []), ...(cfg.fileCollections ?? [])]
|
|
62
|
+
.some((collection: any) =>
|
|
63
|
+
collection._tenant_ === tenantId
|
|
64
|
+
&& collection.slug === slug
|
|
65
|
+
&& collection.replication?.enabled);
|
|
66
|
+
|
|
67
|
+
return declared && !isExcluded(tenantId, slug);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Upsert delete tombstones consumed by the replication engine.
|
|
72
|
+
*
|
|
73
|
+
* Some collections bypass the collection hooks when deleting (variables,
|
|
74
|
+
* agent memory, file-collection documents) — those paths call this directly so
|
|
75
|
+
* their deletions still propagate to the destinations.
|
|
76
|
+
*/
|
|
77
|
+
async function writeDeleteMarkers(
|
|
78
|
+
db: Db,
|
|
79
|
+
tenantId: string,
|
|
80
|
+
collection: string,
|
|
81
|
+
docIds: string[],
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
if (!docIds.length) return;
|
|
84
|
+
const now = new Date();
|
|
85
|
+
const ops = docIds.map((docId) => ({
|
|
86
|
+
updateOne: {
|
|
87
|
+
filter: { _id: `rep.delete:${tenantId}:${collection}:${docId}` },
|
|
88
|
+
update: {
|
|
89
|
+
$set: { type: "delete", tenant: tenantId, collection, docId, deletedAt: now, updatedAt: now },
|
|
90
|
+
$setOnInsert: { synced: [] },
|
|
91
|
+
},
|
|
92
|
+
upsert: true,
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
await db.collection(MARKERS).bulkWrite(ops as any, { ordered: false }).catch(() => { /* ignore */ });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { writeDeleteMarkers, replicatesMetaCollection, replicatesCollection };
|
package/database/file.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from "path"
|
|
|
4
4
|
import fs from "fs/promises"
|
|
5
5
|
import type { FileCollection } from "../types/file"
|
|
6
6
|
import { getTenant } from "./tenant"
|
|
7
|
+
import { importDefinition } from "../lib/load"
|
|
7
8
|
import type { IndexDescription } from "mongodb"
|
|
8
9
|
|
|
9
10
|
async function syncFileCollections() {
|
|
@@ -18,7 +19,7 @@ async function syncFileCollections() {
|
|
|
18
19
|
if (filesExist && (await fs.stat(FILES_PATH)).isDirectory()) {
|
|
19
20
|
const globFiles = new Glob(path.join(FILES_PATH, '**/*.file.ts'))
|
|
20
21
|
for await (let file of globFiles.scan('.')) {
|
|
21
|
-
let fileModule = await
|
|
22
|
+
let fileModule = await importDefinition(file, 'file collection')
|
|
22
23
|
if (fileModule?.default?._isFileCollection_) {
|
|
23
24
|
fileCollections.push({
|
|
24
25
|
...fileModule?.default,
|
|
@@ -30,7 +31,7 @@ async function syncFileCollections() {
|
|
|
30
31
|
// Also scan *.model.ts in files/ with _isFileCollection_
|
|
31
32
|
const globModels = new Glob(path.join(FILES_PATH, '**/*.model.ts'))
|
|
32
33
|
for await (let file of globModels.scan('.')) {
|
|
33
|
-
let fileModule = await
|
|
34
|
+
let fileModule = await importDefinition(file, 'file collection')
|
|
34
35
|
if (fileModule?.default?._isFileCollection_) {
|
|
35
36
|
fileCollections.push({
|
|
36
37
|
...fileModule?.default,
|
|
@@ -46,7 +47,7 @@ async function syncFileCollections() {
|
|
|
46
47
|
if (collectionsExist && (await fs.stat(COLLECTIONS_PATH)).isDirectory()) {
|
|
47
48
|
const globFiles = new Glob(path.join(COLLECTIONS_PATH, '**/*.file.ts'))
|
|
48
49
|
for await (let file of globFiles.scan('.')) {
|
|
49
|
-
let fileModule = await
|
|
50
|
+
let fileModule = await importDefinition(file, 'file collection')
|
|
50
51
|
if (fileModule?.default?._isFileCollection_) {
|
|
51
52
|
fileCollections.push({
|
|
52
53
|
...fileModule?.default,
|
|
@@ -75,14 +76,14 @@ async function initializeFileCollectionsOnDatabase(files: FileCollection[]) {
|
|
|
75
76
|
const db = tenant?.database?.db
|
|
76
77
|
if (!db) continue;
|
|
77
78
|
|
|
78
|
-
//
|
|
79
|
+
// Create the collection if it does not exist
|
|
79
80
|
const collections = await db.listCollections({ name: fileCollection.slug }).toArray()
|
|
80
81
|
if (collections.length === 0) {
|
|
81
82
|
await db.createCollection(fileCollection.slug)
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
85
|
+
// Create the default indexes (only the missing ones —
|
|
86
|
+
// never force the re-creation of an already existing index)
|
|
86
87
|
const col = db.collection(fileCollection.slug)
|
|
87
88
|
const existingIndexes = await col.listIndexes().toArray()
|
|
88
89
|
const existingNames = new Set(existingIndexes.map((idx) => idx.name))
|