@arke-institute/rhiza 0.1.0
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/README.md +106 -0
- package/dist/__tests__/fixtures/index.d.ts +9 -0
- package/dist/__tests__/fixtures/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/index.js +12 -0
- package/dist/__tests__/fixtures/index.js.map +1 -0
- package/dist/__tests__/fixtures/kladoi/index.d.ts +165 -0
- package/dist/__tests__/fixtures/kladoi/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/kladoi/index.js +270 -0
- package/dist/__tests__/fixtures/kladoi/index.js.map +1 -0
- package/dist/__tests__/fixtures/logs/index.d.ts +19 -0
- package/dist/__tests__/fixtures/logs/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/logs/index.js +545 -0
- package/dist/__tests__/fixtures/logs/index.js.map +1 -0
- package/dist/__tests__/fixtures/mock-client.d.ts +127 -0
- package/dist/__tests__/fixtures/mock-client.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mock-client.js +415 -0
- package/dist/__tests__/fixtures/mock-client.js.map +1 -0
- package/dist/__tests__/fixtures/rhizai/index.d.ts +54 -0
- package/dist/__tests__/fixtures/rhizai/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/rhizai/index.js +283 -0
- package/dist/__tests__/fixtures/rhizai/index.js.map +1 -0
- package/dist/__tests__/unit/fixtures.test.d.ts +10 -0
- package/dist/__tests__/unit/fixtures.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fixtures.test.js +275 -0
- package/dist/__tests__/unit/fixtures.test.js.map +1 -0
- package/dist/__tests__/unit/handoff/gather.test.d.ts +8 -0
- package/dist/__tests__/unit/handoff/gather.test.d.ts.map +1 -0
- package/dist/__tests__/unit/handoff/gather.test.js +176 -0
- package/dist/__tests__/unit/handoff/gather.test.js.map +1 -0
- package/dist/__tests__/unit/handoff/interpret.test.d.ts +9 -0
- package/dist/__tests__/unit/handoff/interpret.test.d.ts.map +1 -0
- package/dist/__tests__/unit/handoff/interpret.test.js +370 -0
- package/dist/__tests__/unit/handoff/interpret.test.js.map +1 -0
- package/dist/__tests__/unit/handoff/route.test.d.ts +8 -0
- package/dist/__tests__/unit/handoff/route.test.d.ts.map +1 -0
- package/dist/__tests__/unit/handoff/route.test.js +271 -0
- package/dist/__tests__/unit/handoff/route.test.js.map +1 -0
- package/dist/__tests__/unit/handoff/scatter.test.d.ts +7 -0
- package/dist/__tests__/unit/handoff/scatter.test.d.ts.map +1 -0
- package/dist/__tests__/unit/handoff/scatter.test.js +54 -0
- package/dist/__tests__/unit/handoff/scatter.test.js.map +1 -0
- package/dist/__tests__/unit/resume.test.d.ts +8 -0
- package/dist/__tests__/unit/resume.test.d.ts.map +1 -0
- package/dist/__tests__/unit/resume.test.js +134 -0
- package/dist/__tests__/unit/resume.test.js.map +1 -0
- package/dist/__tests__/unit/status.test.d.ts +8 -0
- package/dist/__tests__/unit/status.test.d.ts.map +1 -0
- package/dist/__tests__/unit/status.test.js +164 -0
- package/dist/__tests__/unit/status.test.js.map +1 -0
- package/dist/__tests__/unit/target.test.d.ts +8 -0
- package/dist/__tests__/unit/target.test.d.ts.map +1 -0
- package/dist/__tests__/unit/target.test.js +116 -0
- package/dist/__tests__/unit/target.test.js.map +1 -0
- package/dist/__tests__/unit/traverse.test.d.ts +8 -0
- package/dist/__tests__/unit/traverse.test.d.ts.map +1 -0
- package/dist/__tests__/unit/traverse.test.js +143 -0
- package/dist/__tests__/unit/traverse.test.js.map +1 -0
- package/dist/__tests__/unit/validation/klados.test.d.ts +16 -0
- package/dist/__tests__/unit/validation/klados.test.d.ts.map +1 -0
- package/dist/__tests__/unit/validation/klados.test.js +275 -0
- package/dist/__tests__/unit/validation/klados.test.js.map +1 -0
- package/dist/__tests__/unit/validation/rhiza.test.d.ts +16 -0
- package/dist/__tests__/unit/validation/rhiza.test.d.ts.map +1 -0
- package/dist/__tests__/unit/validation/rhiza.test.js +612 -0
- package/dist/__tests__/unit/validation/rhiza.test.js.map +1 -0
- package/dist/__tests__/unit/validation/runtime.test.d.ts +11 -0
- package/dist/__tests__/unit/validation/runtime.test.d.ts.map +1 -0
- package/dist/__tests__/unit/validation/runtime.test.js +553 -0
- package/dist/__tests__/unit/validation/runtime.test.js.map +1 -0
- package/dist/__tests__/unit/worker/errors.test.d.ts +2 -0
- package/dist/__tests__/unit/worker/errors.test.d.ts.map +1 -0
- package/dist/__tests__/unit/worker/errors.test.js +226 -0
- package/dist/__tests__/unit/worker/errors.test.js.map +1 -0
- package/dist/__tests__/unit/worker/job.test.d.ts +2 -0
- package/dist/__tests__/unit/worker/job.test.d.ts.map +1 -0
- package/dist/__tests__/unit/worker/job.test.js +233 -0
- package/dist/__tests__/unit/worker/job.test.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/interface.d.ts +142 -0
- package/dist/client/interface.d.ts.map +1 -0
- package/dist/client/interface.js +8 -0
- package/dist/client/interface.js.map +1 -0
- package/dist/client/mock.d.ts +95 -0
- package/dist/client/mock.d.ts.map +1 -0
- package/dist/client/mock.js +795 -0
- package/dist/client/mock.js.map +1 -0
- package/dist/client/types.d.ts +344 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +8 -0
- package/dist/client/types.js.map +1 -0
- package/dist/handoff/gather-api.d.ts +60 -0
- package/dist/handoff/gather-api.d.ts.map +1 -0
- package/dist/handoff/gather-api.js +130 -0
- package/dist/handoff/gather-api.js.map +1 -0
- package/dist/handoff/gather.d.ts +59 -0
- package/dist/handoff/gather.d.ts.map +1 -0
- package/dist/handoff/gather.js +134 -0
- package/dist/handoff/gather.js.map +1 -0
- package/dist/handoff/index.d.ts +19 -0
- package/dist/handoff/index.d.ts.map +1 -0
- package/dist/handoff/index.js +25 -0
- package/dist/handoff/index.js.map +1 -0
- package/dist/handoff/interpret.d.ts +79 -0
- package/dist/handoff/interpret.d.ts.map +1 -0
- package/dist/handoff/interpret.js +197 -0
- package/dist/handoff/interpret.js.map +1 -0
- package/dist/handoff/invoke.d.ts +82 -0
- package/dist/handoff/invoke.d.ts.map +1 -0
- package/dist/handoff/invoke.js +196 -0
- package/dist/handoff/invoke.js.map +1 -0
- package/dist/handoff/route.d.ts +25 -0
- package/dist/handoff/route.d.ts.map +1 -0
- package/dist/handoff/route.js +65 -0
- package/dist/handoff/route.js.map +1 -0
- package/dist/handoff/scatter-api.d.ts +62 -0
- package/dist/handoff/scatter-api.d.ts.map +1 -0
- package/dist/handoff/scatter-api.js +81 -0
- package/dist/handoff/scatter-api.js.map +1 -0
- package/dist/handoff/scatter.d.ts +19 -0
- package/dist/handoff/scatter.d.ts.map +1 -0
- package/dist/handoff/scatter.js +27 -0
- package/dist/handoff/scatter.js.map +1 -0
- package/dist/handoff/target.d.ts +16 -0
- package/dist/handoff/target.d.ts.map +1 -0
- package/dist/handoff/target.js +51 -0
- package/dist/handoff/target.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +52 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +70 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/writer.d.ts +63 -0
- package/dist/logging/writer.d.ts.map +1 -0
- package/dist/logging/writer.js +160 -0
- package/dist/logging/writer.js.map +1 -0
- package/dist/resume/index.d.ts +77 -0
- package/dist/resume/index.d.ts.map +1 -0
- package/dist/resume/index.js +110 -0
- package/dist/resume/index.js.map +1 -0
- package/dist/status/index.d.ts +54 -0
- package/dist/status/index.d.ts.map +1 -0
- package/dist/status/index.js +107 -0
- package/dist/status/index.js.map +1 -0
- package/dist/traverse/index.d.ts +53 -0
- package/dist/traverse/index.d.ts.map +1 -0
- package/dist/traverse/index.js +142 -0
- package/dist/traverse/index.js.map +1 -0
- package/dist/types/batch.d.ts +53 -0
- package/dist/types/batch.d.ts.map +1 -0
- package/dist/types/batch.js +2 -0
- package/dist/types/batch.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/klados.d.ts +63 -0
- package/dist/types/klados.d.ts.map +1 -0
- package/dist/types/klados.js +2 -0
- package/dist/types/klados.js.map +1 -0
- package/dist/types/log.d.ts +107 -0
- package/dist/types/log.d.ts.map +1 -0
- package/dist/types/log.js +2 -0
- package/dist/types/log.js.map +1 -0
- package/dist/types/refs.d.ts +58 -0
- package/dist/types/refs.d.ts.map +1 -0
- package/dist/types/refs.js +43 -0
- package/dist/types/refs.js.map +1 -0
- package/dist/types/request.d.ts +69 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +2 -0
- package/dist/types/request.js.map +1 -0
- package/dist/types/response.d.ts +31 -0
- package/dist/types/response.d.ts.map +1 -0
- package/dist/types/response.js +2 -0
- package/dist/types/response.js.map +1 -0
- package/dist/types/rhiza.d.ts +100 -0
- package/dist/types/rhiza.d.ts.map +1 -0
- package/dist/types/rhiza.js +2 -0
- package/dist/types/rhiza.js.map +1 -0
- package/dist/types/status.d.ts +84 -0
- package/dist/types/status.d.ts.map +1 -0
- package/dist/types/status.js +2 -0
- package/dist/types/status.js.map +1 -0
- package/dist/utils/id.d.ts +15 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +25 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/validate-klados.d.ts +38 -0
- package/dist/validation/validate-klados.d.ts.map +1 -0
- package/dist/validation/validate-klados.js +139 -0
- package/dist/validation/validate-klados.js.map +1 -0
- package/dist/validation/validate-rhiza.d.ts +29 -0
- package/dist/validation/validate-rhiza.d.ts.map +1 -0
- package/dist/validation/validate-rhiza.js +382 -0
- package/dist/validation/validate-rhiza.js.map +1 -0
- package/dist/validation/validate-runtime.d.ts +28 -0
- package/dist/validation/validate-runtime.d.ts.map +1 -0
- package/dist/validation/validate-runtime.js +212 -0
- package/dist/validation/validate-runtime.js.map +1 -0
- package/dist/worker/errors.d.ts +77 -0
- package/dist/worker/errors.d.ts.map +1 -0
- package/dist/worker/errors.js +143 -0
- package/dist/worker/errors.js.map +1 -0
- package/dist/worker/index.d.ts +8 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +8 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/job.d.ts +150 -0
- package/dist/worker/job.d.ts.map +1 -0
- package/dist/worker/job.js +280 -0
- package/dist/worker/job.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Rhiza Client
|
|
3
|
+
*
|
|
4
|
+
* In-memory implementation of the RhizaClient interface.
|
|
5
|
+
* This serves as the API specification - it defines exactly what the
|
|
6
|
+
* real API endpoints should do.
|
|
7
|
+
*
|
|
8
|
+
* Use this for:
|
|
9
|
+
* - Unit tests
|
|
10
|
+
* - Development without network
|
|
11
|
+
* - API contract specification
|
|
12
|
+
*/
|
|
13
|
+
import { validateKladosProperties } from '../validation/validate-klados';
|
|
14
|
+
import { validateRhizaProperties } from '../validation/validate-rhiza';
|
|
15
|
+
import { buildStatusFromLogs } from '../status';
|
|
16
|
+
import { findErrorLeaves } from '../traverse';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Helper function for safe type conversion
|
|
19
|
+
// ============================================================================
|
|
20
|
+
function asError(result) {
|
|
21
|
+
return { error: result.error };
|
|
22
|
+
}
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Mock Client Implementation
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* In-memory mock implementation of RhizaClient.
|
|
28
|
+
*
|
|
29
|
+
* This implementation defines the expected API behavior.
|
|
30
|
+
* The real API should match this behavior exactly.
|
|
31
|
+
*/
|
|
32
|
+
export class MockRhizaClient {
|
|
33
|
+
entities = new Map();
|
|
34
|
+
collections = new Map();
|
|
35
|
+
pendingVerifications = new Map();
|
|
36
|
+
debug;
|
|
37
|
+
idCounter = 0;
|
|
38
|
+
constructor(config = {}) {
|
|
39
|
+
this.debug = config.debug ?? false;
|
|
40
|
+
// Seed initial entities
|
|
41
|
+
if (config.entities) {
|
|
42
|
+
for (const [id, entity] of Object.entries(config.entities)) {
|
|
43
|
+
this.entities.set(id, entity);
|
|
44
|
+
this.addToCollection(entity.collectionId, id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// =========================================================================
|
|
49
|
+
// Entity Operations
|
|
50
|
+
// =========================================================================
|
|
51
|
+
async getEntity(id) {
|
|
52
|
+
const entity = this.entities.get(id);
|
|
53
|
+
if (!entity) {
|
|
54
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
55
|
+
}
|
|
56
|
+
return { data: this.toEntityResponse(entity) };
|
|
57
|
+
}
|
|
58
|
+
async getEntityTip(id) {
|
|
59
|
+
const entity = this.entities.get(id);
|
|
60
|
+
if (!entity) {
|
|
61
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
62
|
+
}
|
|
63
|
+
return { data: { cid: entity.cid } };
|
|
64
|
+
}
|
|
65
|
+
async createEntity(params) {
|
|
66
|
+
const id = this.generateId(params.type);
|
|
67
|
+
const now = new Date().toISOString();
|
|
68
|
+
const cid = this.generateCid();
|
|
69
|
+
const entity = {
|
|
70
|
+
id,
|
|
71
|
+
cid,
|
|
72
|
+
type: params.type,
|
|
73
|
+
properties: params.properties,
|
|
74
|
+
collectionId: params.collectionId,
|
|
75
|
+
relationships: params.relationships ?? [],
|
|
76
|
+
createdAt: now,
|
|
77
|
+
updatedAt: now,
|
|
78
|
+
};
|
|
79
|
+
this.entities.set(id, entity);
|
|
80
|
+
this.addToCollection(params.collectionId, id);
|
|
81
|
+
this.log('createEntity', { id, type: params.type });
|
|
82
|
+
return { data: this.toEntityResponse(entity) };
|
|
83
|
+
}
|
|
84
|
+
async updateEntity(id, params) {
|
|
85
|
+
const entity = this.entities.get(id);
|
|
86
|
+
if (!entity) {
|
|
87
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
88
|
+
}
|
|
89
|
+
// CAS check (optional for mock)
|
|
90
|
+
if (params.expectTip && entity.cid !== params.expectTip) {
|
|
91
|
+
return {
|
|
92
|
+
error: {
|
|
93
|
+
code: 'CAS_CONFLICT',
|
|
94
|
+
message: `Expected tip ${params.expectTip}, but current is ${entity.cid}`,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Update properties
|
|
99
|
+
if (params.properties) {
|
|
100
|
+
entity.properties = { ...entity.properties, ...params.properties };
|
|
101
|
+
}
|
|
102
|
+
// Update relationships
|
|
103
|
+
if (params.relationshipsAdd) {
|
|
104
|
+
entity.relationships.push(...params.relationshipsAdd);
|
|
105
|
+
}
|
|
106
|
+
if (params.relationshipsRemove) {
|
|
107
|
+
const removeSet = new Set(params.relationshipsRemove.map((r) => `${r.predicate}:${r.peer}`));
|
|
108
|
+
entity.relationships = entity.relationships.filter((r) => !removeSet.has(`${r.predicate}:${r.peer}`));
|
|
109
|
+
}
|
|
110
|
+
// Update metadata
|
|
111
|
+
entity.cid = this.generateCid();
|
|
112
|
+
entity.updatedAt = new Date().toISOString();
|
|
113
|
+
this.log('updateEntity', { id });
|
|
114
|
+
return { data: this.toEntityResponse(entity) };
|
|
115
|
+
}
|
|
116
|
+
// =========================================================================
|
|
117
|
+
// Klados Operations
|
|
118
|
+
// =========================================================================
|
|
119
|
+
async getKlados(id) {
|
|
120
|
+
const entity = this.entities.get(id);
|
|
121
|
+
if (!entity) {
|
|
122
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
123
|
+
}
|
|
124
|
+
if (entity.type !== 'klados') {
|
|
125
|
+
return {
|
|
126
|
+
error: {
|
|
127
|
+
code: 'TYPE_MISMATCH',
|
|
128
|
+
message: `Entity ${id} is not a klados`,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Build KladosEntity from stored entity
|
|
133
|
+
return {
|
|
134
|
+
data: {
|
|
135
|
+
id: entity.id,
|
|
136
|
+
cid: entity.cid,
|
|
137
|
+
type: 'klados',
|
|
138
|
+
properties: entity.properties,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async createKlados(params) {
|
|
143
|
+
// Validate klados properties
|
|
144
|
+
const validation = validateKladosProperties(params.properties);
|
|
145
|
+
if (!validation.valid) {
|
|
146
|
+
return {
|
|
147
|
+
error: {
|
|
148
|
+
code: 'VALIDATION_ERROR',
|
|
149
|
+
message: validation.errors[0].message,
|
|
150
|
+
details: { errors: validation.errors },
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Create the entity
|
|
155
|
+
const result = await this.createEntity({
|
|
156
|
+
collectionId: params.collectionId,
|
|
157
|
+
type: 'klados',
|
|
158
|
+
properties: {
|
|
159
|
+
...params.properties,
|
|
160
|
+
status: 'development',
|
|
161
|
+
created_at: new Date().toISOString(),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
if (result.error)
|
|
165
|
+
return asError(result);
|
|
166
|
+
this.log('createKlados', { id: result.data.id });
|
|
167
|
+
return {
|
|
168
|
+
data: {
|
|
169
|
+
id: result.data.id,
|
|
170
|
+
cid: result.data.cid,
|
|
171
|
+
type: 'klados',
|
|
172
|
+
properties: result.data.properties,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async updateKlados(id, params) {
|
|
177
|
+
const current = await this.getKlados(id);
|
|
178
|
+
if (current.error)
|
|
179
|
+
return current;
|
|
180
|
+
const currentProps = current.data.properties;
|
|
181
|
+
const properties = {};
|
|
182
|
+
// Map params to property updates
|
|
183
|
+
if (params.label !== undefined)
|
|
184
|
+
properties.label = params.label;
|
|
185
|
+
if (params.description !== undefined)
|
|
186
|
+
properties.description = params.description;
|
|
187
|
+
if (params.actionsRequired !== undefined)
|
|
188
|
+
properties.actions_required = params.actionsRequired;
|
|
189
|
+
if (params.accepts !== undefined)
|
|
190
|
+
properties.accepts = params.accepts;
|
|
191
|
+
if (params.produces !== undefined)
|
|
192
|
+
properties.produces = params.produces;
|
|
193
|
+
if (params.inputSchema !== undefined)
|
|
194
|
+
properties.input_schema = params.inputSchema;
|
|
195
|
+
// Special handling for endpoint change
|
|
196
|
+
if (params.endpoint !== undefined &&
|
|
197
|
+
params.endpoint !== currentProps.endpoint) {
|
|
198
|
+
properties.endpoint = params.endpoint;
|
|
199
|
+
properties.endpoint_verified_at = null;
|
|
200
|
+
properties.status = 'development';
|
|
201
|
+
}
|
|
202
|
+
// Status change validation
|
|
203
|
+
if (params.status !== undefined) {
|
|
204
|
+
if (params.status === 'active' && !currentProps.endpoint_verified_at) {
|
|
205
|
+
return {
|
|
206
|
+
error: {
|
|
207
|
+
code: 'VALIDATION_ERROR',
|
|
208
|
+
message: 'Cannot set status to active without verified endpoint',
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
properties.status = params.status;
|
|
213
|
+
}
|
|
214
|
+
const result = await this.updateEntity(id, {
|
|
215
|
+
expectTip: params.expectTip,
|
|
216
|
+
properties,
|
|
217
|
+
});
|
|
218
|
+
if (result.error)
|
|
219
|
+
return asError(result);
|
|
220
|
+
this.log('updateKlados', { id });
|
|
221
|
+
return this.getKlados(id);
|
|
222
|
+
}
|
|
223
|
+
async invokeKlados(id, params) {
|
|
224
|
+
const klados = await this.getKlados(id);
|
|
225
|
+
if (klados.error)
|
|
226
|
+
return asError(klados);
|
|
227
|
+
const kladosProps = klados.data.properties;
|
|
228
|
+
// Check if klados is active (or skip in mock for testing)
|
|
229
|
+
// In production, this would require status === 'active'
|
|
230
|
+
// Handle confirmation flow
|
|
231
|
+
if (!params.confirm) {
|
|
232
|
+
return {
|
|
233
|
+
data: {
|
|
234
|
+
status: 'pending_confirmation',
|
|
235
|
+
message: `Klados "${kladosProps.label}" requires confirmation`,
|
|
236
|
+
grants: [
|
|
237
|
+
{
|
|
238
|
+
type: 'klados',
|
|
239
|
+
id,
|
|
240
|
+
label: String(kladosProps.label),
|
|
241
|
+
actions: kladosProps.actions_required,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
expiresAt: new Date(Date.now() + (params.expiresIn ?? 3600) * 1000).toISOString(),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Generate job ID and collection
|
|
249
|
+
const jobId = this.generateId('job');
|
|
250
|
+
const jobCollection = params.jobCollection ?? this.generateId('job_collection');
|
|
251
|
+
// In a real implementation, this would:
|
|
252
|
+
// 1. Create a job collection if needed
|
|
253
|
+
// 2. Create an initial log entry
|
|
254
|
+
// 3. Make HTTP request to klados endpoint
|
|
255
|
+
// 4. Return job info for tracking
|
|
256
|
+
this.log('invokeKlados', { id, jobId, jobCollection });
|
|
257
|
+
return {
|
|
258
|
+
data: {
|
|
259
|
+
status: 'started',
|
|
260
|
+
jobId,
|
|
261
|
+
jobCollection,
|
|
262
|
+
kladosId: id,
|
|
263
|
+
expiresAt: new Date(Date.now() + (params.expiresIn ?? 3600) * 1000).toISOString(),
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async verifyKlados(id, params) {
|
|
268
|
+
const klados = await this.getKlados(id);
|
|
269
|
+
if (klados.error)
|
|
270
|
+
return asError(klados);
|
|
271
|
+
const kladosProps = klados.data.properties;
|
|
272
|
+
if (!params?.confirm) {
|
|
273
|
+
// Phase 1: Generate verification token
|
|
274
|
+
const token = `vt_${this.generateId('verify')}`;
|
|
275
|
+
const expiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
|
|
276
|
+
this.pendingVerifications.set(id, {
|
|
277
|
+
token,
|
|
278
|
+
kladosId: id,
|
|
279
|
+
endpoint: String(kladosProps.endpoint),
|
|
280
|
+
expiresAt,
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
data: {
|
|
284
|
+
verificationToken: token,
|
|
285
|
+
kladosId: id,
|
|
286
|
+
endpoint: String(kladosProps.endpoint),
|
|
287
|
+
instructions: 'Return this token from your endpoint when called with verification=true',
|
|
288
|
+
expiresAt: expiresAt.toISOString(),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Phase 2: Confirm verification
|
|
293
|
+
const pending = this.pendingVerifications.get(id);
|
|
294
|
+
if (!pending) {
|
|
295
|
+
return {
|
|
296
|
+
data: {
|
|
297
|
+
verified: false,
|
|
298
|
+
error: 'no_token',
|
|
299
|
+
message: 'No pending verification. Call without confirm first.',
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (new Date() > pending.expiresAt) {
|
|
304
|
+
this.pendingVerifications.delete(id);
|
|
305
|
+
return {
|
|
306
|
+
data: {
|
|
307
|
+
verified: false,
|
|
308
|
+
error: 'token_expired',
|
|
309
|
+
message: 'Verification token expired. Start again.',
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// In real implementation: fetch endpoint and verify token response
|
|
314
|
+
// For mock, we'll simulate success
|
|
315
|
+
this.pendingVerifications.delete(id);
|
|
316
|
+
const verifiedAt = new Date().toISOString();
|
|
317
|
+
await this.updateEntity(id, {
|
|
318
|
+
properties: {
|
|
319
|
+
endpoint_verified_at: verifiedAt,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
data: {
|
|
324
|
+
verified: true,
|
|
325
|
+
verifiedAt,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// =========================================================================
|
|
330
|
+
// Rhiza Operations
|
|
331
|
+
// =========================================================================
|
|
332
|
+
async getRhiza(id) {
|
|
333
|
+
const entity = this.entities.get(id);
|
|
334
|
+
if (!entity) {
|
|
335
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
336
|
+
}
|
|
337
|
+
if (entity.type !== 'rhiza') {
|
|
338
|
+
return {
|
|
339
|
+
error: { code: 'TYPE_MISMATCH', message: `Entity ${id} is not a rhiza` },
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
data: {
|
|
344
|
+
id: entity.id,
|
|
345
|
+
cid: entity.cid,
|
|
346
|
+
type: 'rhiza',
|
|
347
|
+
properties: entity.properties,
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async createRhiza(params) {
|
|
352
|
+
// Validate rhiza properties
|
|
353
|
+
const validation = validateRhizaProperties(params.properties);
|
|
354
|
+
if (!validation.valid) {
|
|
355
|
+
return {
|
|
356
|
+
error: {
|
|
357
|
+
code: 'VALIDATION_ERROR',
|
|
358
|
+
message: validation.errors[0].message,
|
|
359
|
+
details: { errors: validation.errors },
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Create the entity
|
|
364
|
+
const result = await this.createEntity({
|
|
365
|
+
collectionId: params.collectionId,
|
|
366
|
+
type: 'rhiza',
|
|
367
|
+
properties: {
|
|
368
|
+
...params.properties,
|
|
369
|
+
status: 'development',
|
|
370
|
+
created_at: new Date().toISOString(),
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
if (result.error)
|
|
374
|
+
return asError(result);
|
|
375
|
+
this.log('createRhiza', { id: result.data.id });
|
|
376
|
+
return {
|
|
377
|
+
data: {
|
|
378
|
+
id: result.data.id,
|
|
379
|
+
cid: result.data.cid,
|
|
380
|
+
type: 'rhiza',
|
|
381
|
+
properties: result.data.properties,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
async updateRhiza(id, params) {
|
|
386
|
+
const current = await this.getRhiza(id);
|
|
387
|
+
if (current.error)
|
|
388
|
+
return current;
|
|
389
|
+
const properties = {};
|
|
390
|
+
if (params.label !== undefined)
|
|
391
|
+
properties.label = params.label;
|
|
392
|
+
if (params.description !== undefined)
|
|
393
|
+
properties.description = params.description;
|
|
394
|
+
if (params.version !== undefined)
|
|
395
|
+
properties.version = params.version;
|
|
396
|
+
if (params.entry !== undefined)
|
|
397
|
+
properties.entry = params.entry;
|
|
398
|
+
if (params.flow !== undefined)
|
|
399
|
+
properties.flow = params.flow;
|
|
400
|
+
if (params.status !== undefined)
|
|
401
|
+
properties.status = params.status;
|
|
402
|
+
// Validate if flow changed
|
|
403
|
+
if (params.entry !== undefined || params.flow !== undefined) {
|
|
404
|
+
const mergedProps = {
|
|
405
|
+
...current.data.properties,
|
|
406
|
+
...properties,
|
|
407
|
+
};
|
|
408
|
+
const validation = validateRhizaProperties(mergedProps);
|
|
409
|
+
if (!validation.valid) {
|
|
410
|
+
return {
|
|
411
|
+
error: {
|
|
412
|
+
code: 'VALIDATION_ERROR',
|
|
413
|
+
message: validation.errors[0].message,
|
|
414
|
+
details: { errors: validation.errors },
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const result = await this.updateEntity(id, {
|
|
420
|
+
expectTip: params.expectTip,
|
|
421
|
+
properties,
|
|
422
|
+
});
|
|
423
|
+
if (result.error)
|
|
424
|
+
return asError(result);
|
|
425
|
+
this.log('updateRhiza', { id });
|
|
426
|
+
return this.getRhiza(id);
|
|
427
|
+
}
|
|
428
|
+
async invokeRhiza(id, params) {
|
|
429
|
+
const rhiza = await this.getRhiza(id);
|
|
430
|
+
if (rhiza.error)
|
|
431
|
+
return asError(rhiza);
|
|
432
|
+
const rhizaProps = rhiza.data.properties;
|
|
433
|
+
// In production: runtime validation of all kladoi
|
|
434
|
+
if (!params.confirm) {
|
|
435
|
+
// Collect all grants (all kladoi in flow)
|
|
436
|
+
const flow = rhizaProps.flow;
|
|
437
|
+
const grants = Object.keys(flow).map((kladosId) => ({
|
|
438
|
+
type: 'klados',
|
|
439
|
+
id: kladosId,
|
|
440
|
+
label: kladosId, // Would be fetched from actual klados
|
|
441
|
+
}));
|
|
442
|
+
return {
|
|
443
|
+
data: {
|
|
444
|
+
status: 'pending_confirmation',
|
|
445
|
+
message: `Workflow "${rhizaProps.label}" requires confirmation`,
|
|
446
|
+
grants,
|
|
447
|
+
expiresAt: new Date(Date.now() + (params.expiresIn ?? 3600) * 1000).toISOString(),
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const jobId = this.generateId('job');
|
|
452
|
+
const jobCollection = this.generateId('job_collection');
|
|
453
|
+
// In real implementation: start workflow execution from entry klados
|
|
454
|
+
this.log('invokeRhiza', { id, jobId, jobCollection });
|
|
455
|
+
return {
|
|
456
|
+
data: {
|
|
457
|
+
status: 'started',
|
|
458
|
+
jobId,
|
|
459
|
+
jobCollection,
|
|
460
|
+
rhizaId: id,
|
|
461
|
+
expiresAt: new Date(Date.now() + (params.expiresIn ?? 3600) * 1000).toISOString(),
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async getWorkflowStatus(_rhizaId, jobId) {
|
|
466
|
+
// Find the job collection for this job
|
|
467
|
+
const logs = await this.getJobLogs(jobId);
|
|
468
|
+
if (logs.error) {
|
|
469
|
+
return {
|
|
470
|
+
error: {
|
|
471
|
+
code: 'NOT_FOUND',
|
|
472
|
+
message: `No logs found for job ${jobId}`,
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (!logs.data || logs.data.length === 0) {
|
|
477
|
+
return {
|
|
478
|
+
error: {
|
|
479
|
+
code: 'NOT_FOUND',
|
|
480
|
+
message: `No logs found for job ${jobId}`,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// Use buildStatusFromLogs to compute status
|
|
485
|
+
const status = buildStatusFromLogs(logs.data);
|
|
486
|
+
// Map status - handle 'unknown' case
|
|
487
|
+
let mappedStatus = 'running';
|
|
488
|
+
if (status.status === 'done' ||
|
|
489
|
+
status.status === 'error' ||
|
|
490
|
+
status.status === 'running' ||
|
|
491
|
+
status.status === 'pending') {
|
|
492
|
+
mappedStatus = status.status;
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
data: {
|
|
496
|
+
jobId,
|
|
497
|
+
rhizaId: status.rhizaId,
|
|
498
|
+
status: mappedStatus,
|
|
499
|
+
progress: {
|
|
500
|
+
total: status.progress.total,
|
|
501
|
+
pending: status.progress.pending,
|
|
502
|
+
running: status.progress.running,
|
|
503
|
+
done: status.progress.done,
|
|
504
|
+
error: status.progress.error,
|
|
505
|
+
},
|
|
506
|
+
currentKladoi: status.currentKladoi,
|
|
507
|
+
errors: status.errors?.map((e) => ({
|
|
508
|
+
kladosId: e.kladosId,
|
|
509
|
+
jobId: e.jobId,
|
|
510
|
+
code: e.code,
|
|
511
|
+
message: e.message,
|
|
512
|
+
retryable: e.retryable,
|
|
513
|
+
})),
|
|
514
|
+
startedAt: logs.data[0]?.started_at ?? new Date().toISOString(),
|
|
515
|
+
completedAt: mappedStatus === 'done' || mappedStatus === 'error'
|
|
516
|
+
? new Date().toISOString()
|
|
517
|
+
: undefined,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
async resumeWorkflow(_rhizaId, jobId, params) {
|
|
522
|
+
const logs = await this.getJobLogs(jobId);
|
|
523
|
+
if (logs.error) {
|
|
524
|
+
return {
|
|
525
|
+
error: {
|
|
526
|
+
code: 'NOT_FOUND',
|
|
527
|
+
message: `No logs found for job ${jobId}`,
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (!logs.data || logs.data.length === 0) {
|
|
532
|
+
return {
|
|
533
|
+
data: {
|
|
534
|
+
resumed: 0,
|
|
535
|
+
skipped: 0,
|
|
536
|
+
jobs: [],
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
// Find error leaves using traverse module
|
|
541
|
+
const errorLeaves = findErrorLeaves(logs.data);
|
|
542
|
+
// Filter by jobIds if provided
|
|
543
|
+
let candidates = errorLeaves;
|
|
544
|
+
if (params?.jobIds && params.jobIds.length > 0) {
|
|
545
|
+
const jobIdSet = new Set(params.jobIds);
|
|
546
|
+
candidates = errorLeaves.filter((e) => jobIdSet.has(e.log.job_id));
|
|
547
|
+
}
|
|
548
|
+
const result = {
|
|
549
|
+
resumed: 0,
|
|
550
|
+
skipped: 0,
|
|
551
|
+
jobs: [],
|
|
552
|
+
};
|
|
553
|
+
for (const errorLeaf of candidates) {
|
|
554
|
+
// Check maxJobs limit
|
|
555
|
+
if (params?.maxJobs !== undefined &&
|
|
556
|
+
result.resumed >= params.maxJobs) {
|
|
557
|
+
result.skipped++;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
// Skip non-retryable
|
|
561
|
+
if (!errorLeaf.retryable) {
|
|
562
|
+
result.skipped++;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const newJobId = this.generateId('job_resumed');
|
|
566
|
+
result.resumed++;
|
|
567
|
+
result.jobs.push({
|
|
568
|
+
originalJobId: errorLeaf.log.job_id,
|
|
569
|
+
newJobId,
|
|
570
|
+
kladosId: errorLeaf.log.klados_id,
|
|
571
|
+
errorLogId: errorLeaf.log.id,
|
|
572
|
+
targetEntityId: String(errorLeaf.log.received?.target ?? ''),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
this.log('resumeWorkflow', { jobId, resumed: result.resumed });
|
|
576
|
+
return { data: result };
|
|
577
|
+
}
|
|
578
|
+
// =========================================================================
|
|
579
|
+
// Log Operations
|
|
580
|
+
// =========================================================================
|
|
581
|
+
async createLog(params) {
|
|
582
|
+
const id = this.generateId('log');
|
|
583
|
+
const now = new Date().toISOString();
|
|
584
|
+
// Build log entry - simplified for mock
|
|
585
|
+
const logProperties = {
|
|
586
|
+
id,
|
|
587
|
+
klados_id: params.kladosId,
|
|
588
|
+
rhiza_id: params.rhizaId,
|
|
589
|
+
job_id: params.jobId,
|
|
590
|
+
status: 'running',
|
|
591
|
+
started_at: now,
|
|
592
|
+
received: {
|
|
593
|
+
target: params.received.target,
|
|
594
|
+
input: params.received.input,
|
|
595
|
+
from_logs: params.received.fromLogs,
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
const result = await this.createEntity({
|
|
599
|
+
collectionId: params.jobCollectionId,
|
|
600
|
+
type: 'klados_log',
|
|
601
|
+
properties: logProperties,
|
|
602
|
+
relationships: params.parentLogIds?.map((parentId) => ({
|
|
603
|
+
predicate: 'received_from',
|
|
604
|
+
peer: parentId,
|
|
605
|
+
})),
|
|
606
|
+
});
|
|
607
|
+
if (result.error)
|
|
608
|
+
return asError(result);
|
|
609
|
+
this.log('createLog', { id, kladosId: params.kladosId });
|
|
610
|
+
return { data: logProperties };
|
|
611
|
+
}
|
|
612
|
+
async updateLog(id, params) {
|
|
613
|
+
const entity = this.entities.get(id);
|
|
614
|
+
if (!entity) {
|
|
615
|
+
return { error: { code: 'NOT_FOUND', message: `Log ${id} not found` } };
|
|
616
|
+
}
|
|
617
|
+
const properties = {};
|
|
618
|
+
if (params.status !== undefined)
|
|
619
|
+
properties.status = params.status;
|
|
620
|
+
if (params.completedAt !== undefined)
|
|
621
|
+
properties.completed_at = params.completedAt;
|
|
622
|
+
if (params.produced !== undefined)
|
|
623
|
+
properties.produced = params.produced;
|
|
624
|
+
if (params.error !== undefined)
|
|
625
|
+
properties.error = params.error;
|
|
626
|
+
if (params.handoffs !== undefined)
|
|
627
|
+
properties.handoffs = params.handoffs;
|
|
628
|
+
const result = await this.updateEntity(id, {
|
|
629
|
+
expectTip: params.expectTip,
|
|
630
|
+
properties,
|
|
631
|
+
});
|
|
632
|
+
if (result.error)
|
|
633
|
+
return asError(result);
|
|
634
|
+
this.log('updateLog', { id, status: params.status });
|
|
635
|
+
return {
|
|
636
|
+
data: {
|
|
637
|
+
...entity.properties,
|
|
638
|
+
...properties,
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
async getJobLogs(jobCollectionId) {
|
|
643
|
+
const collectionEntities = this.collections.get(jobCollectionId);
|
|
644
|
+
if (!collectionEntities) {
|
|
645
|
+
return { data: [] };
|
|
646
|
+
}
|
|
647
|
+
const logs = [];
|
|
648
|
+
for (const entityId of collectionEntities) {
|
|
649
|
+
const entity = this.entities.get(entityId);
|
|
650
|
+
if (entity && entity.type === 'klados_log') {
|
|
651
|
+
logs.push(entity.properties);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return { data: logs };
|
|
655
|
+
}
|
|
656
|
+
// =========================================================================
|
|
657
|
+
// Batch Operations
|
|
658
|
+
// =========================================================================
|
|
659
|
+
async createBatch(params) {
|
|
660
|
+
const result = await this.createEntity({
|
|
661
|
+
collectionId: params.jobCollectionId,
|
|
662
|
+
type: 'batch',
|
|
663
|
+
properties: {
|
|
664
|
+
scatter_from: params.scatterFrom,
|
|
665
|
+
gather_target: params.gatherTarget,
|
|
666
|
+
total_slots: params.totalSlots,
|
|
667
|
+
completed_slots: 0,
|
|
668
|
+
status: 'pending',
|
|
669
|
+
slots: params.slots,
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
if (result.error)
|
|
673
|
+
return asError(result);
|
|
674
|
+
this.log('createBatch', {
|
|
675
|
+
id: result.data.id,
|
|
676
|
+
totalSlots: params.totalSlots,
|
|
677
|
+
});
|
|
678
|
+
return {
|
|
679
|
+
data: {
|
|
680
|
+
id: result.data.id,
|
|
681
|
+
cid: result.data.cid,
|
|
682
|
+
type: 'batch',
|
|
683
|
+
properties: result.data.properties,
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
async updateBatch(id, params) {
|
|
688
|
+
const current = await this.getBatch(id);
|
|
689
|
+
if (current.error)
|
|
690
|
+
return current;
|
|
691
|
+
const properties = {};
|
|
692
|
+
if (params.properties.completedSlots !== undefined)
|
|
693
|
+
properties.completed_slots = params.properties.completedSlots;
|
|
694
|
+
if (params.properties.status !== undefined)
|
|
695
|
+
properties.status = params.properties.status;
|
|
696
|
+
if (params.properties.slots !== undefined)
|
|
697
|
+
properties.slots = params.properties.slots;
|
|
698
|
+
if (params.properties.completedAt !== undefined)
|
|
699
|
+
properties.completed_at = params.properties.completedAt;
|
|
700
|
+
if (params.properties.error !== undefined)
|
|
701
|
+
properties.error = params.properties.error;
|
|
702
|
+
const result = await this.updateEntity(id, {
|
|
703
|
+
expectTip: params.expectTip,
|
|
704
|
+
properties,
|
|
705
|
+
});
|
|
706
|
+
if (result.error)
|
|
707
|
+
return asError(result);
|
|
708
|
+
this.log('updateBatch', { id, status: params.properties.status });
|
|
709
|
+
return this.getBatch(id);
|
|
710
|
+
}
|
|
711
|
+
async getBatch(id) {
|
|
712
|
+
const entity = this.entities.get(id);
|
|
713
|
+
if (!entity) {
|
|
714
|
+
return { error: { code: 'NOT_FOUND', message: `Entity ${id} not found` } };
|
|
715
|
+
}
|
|
716
|
+
if (entity.type !== 'batch') {
|
|
717
|
+
return {
|
|
718
|
+
error: { code: 'TYPE_MISMATCH', message: `Entity ${id} is not a batch` },
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
data: {
|
|
723
|
+
id: entity.id,
|
|
724
|
+
cid: entity.cid,
|
|
725
|
+
type: 'batch',
|
|
726
|
+
properties: entity.properties,
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
// =========================================================================
|
|
731
|
+
// Test Helpers (not part of interface)
|
|
732
|
+
// =========================================================================
|
|
733
|
+
/**
|
|
734
|
+
* Reset all state (for testing)
|
|
735
|
+
*/
|
|
736
|
+
reset() {
|
|
737
|
+
this.entities.clear();
|
|
738
|
+
this.collections.clear();
|
|
739
|
+
this.pendingVerifications.clear();
|
|
740
|
+
this.idCounter = 0;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Get all entities of a type (for testing)
|
|
744
|
+
*/
|
|
745
|
+
getEntitiesByType(type) {
|
|
746
|
+
return Array.from(this.entities.values()).filter((e) => e.type === type);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Seed an entity directly (for testing)
|
|
750
|
+
*/
|
|
751
|
+
seedEntity(entity) {
|
|
752
|
+
this.entities.set(entity.id, entity);
|
|
753
|
+
this.addToCollection(entity.collectionId, entity.id);
|
|
754
|
+
}
|
|
755
|
+
// =========================================================================
|
|
756
|
+
// Private Helpers
|
|
757
|
+
// =========================================================================
|
|
758
|
+
generateId(prefix) {
|
|
759
|
+
this.idCounter++;
|
|
760
|
+
return `${prefix}_mock_${this.idCounter}_${Date.now()}`;
|
|
761
|
+
}
|
|
762
|
+
generateCid() {
|
|
763
|
+
return `bafk_mock_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
764
|
+
}
|
|
765
|
+
addToCollection(collectionId, entityId) {
|
|
766
|
+
let collection = this.collections.get(collectionId);
|
|
767
|
+
if (!collection) {
|
|
768
|
+
collection = new Set();
|
|
769
|
+
this.collections.set(collectionId, collection);
|
|
770
|
+
}
|
|
771
|
+
collection.add(entityId);
|
|
772
|
+
}
|
|
773
|
+
toEntityResponse(entity) {
|
|
774
|
+
return {
|
|
775
|
+
id: entity.id,
|
|
776
|
+
cid: entity.cid,
|
|
777
|
+
type: entity.type,
|
|
778
|
+
properties: entity.properties,
|
|
779
|
+
created_at: entity.createdAt,
|
|
780
|
+
updated_at: entity.updatedAt,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
log(method, data) {
|
|
784
|
+
if (this.debug) {
|
|
785
|
+
console.log(`[MockRhizaClient.${method}]`, data);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Create a mock client with optional pre-seeded data
|
|
791
|
+
*/
|
|
792
|
+
export function createMockRhizaClient(config) {
|
|
793
|
+
return new MockRhizaClient(config);
|
|
794
|
+
}
|
|
795
|
+
//# sourceMappingURL=mock.js.map
|