@kb-labs/shared-testing-platform 2.96.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 +20 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +1175 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
import { resetPlatform, platform } from '@kb-labs/core-runtime';
|
|
2
|
+
import { mockLogger, mockLLM, mockCache, mockStorage } from '@kb-labs/shared-testing';
|
|
3
|
+
export { MockEmbeddings, mockCache, mockLLM, mockLogger, mockStorage } from '@kb-labs/shared-testing';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
// src/setup-platform.ts
|
|
7
|
+
function setupTestPlatform(options = {}) {
|
|
8
|
+
resetPlatform();
|
|
9
|
+
const adapterMap = [
|
|
10
|
+
["llm", options.llm],
|
|
11
|
+
["cache", options.cache],
|
|
12
|
+
["embeddings", options.embeddings],
|
|
13
|
+
["vectorStore", options.vectorStore],
|
|
14
|
+
["storage", options.storage],
|
|
15
|
+
["analytics", options.analytics],
|
|
16
|
+
["logger", options.logger],
|
|
17
|
+
["eventBus", options.eventBus]
|
|
18
|
+
];
|
|
19
|
+
for (const [key, instance] of adapterMap) {
|
|
20
|
+
if (instance !== void 0) {
|
|
21
|
+
platform.setAdapter(key, instance);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
platform,
|
|
26
|
+
cleanup: () => resetPlatform()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createMockTrace() {
|
|
30
|
+
return {
|
|
31
|
+
traceId: "test-trace-id",
|
|
32
|
+
spanId: "test-span-id",
|
|
33
|
+
parentSpanId: void 0,
|
|
34
|
+
addEvent: () => {
|
|
35
|
+
},
|
|
36
|
+
setAttribute: () => {
|
|
37
|
+
},
|
|
38
|
+
recordError: () => {
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createMockUI() {
|
|
43
|
+
const messages = [];
|
|
44
|
+
const mockColor = (text) => text;
|
|
45
|
+
const mockColors = {
|
|
46
|
+
success: mockColor,
|
|
47
|
+
error: mockColor,
|
|
48
|
+
warning: mockColor,
|
|
49
|
+
info: mockColor,
|
|
50
|
+
primary: mockColor,
|
|
51
|
+
accent: mockColor,
|
|
52
|
+
highlight: mockColor,
|
|
53
|
+
secondary: mockColor,
|
|
54
|
+
emphasis: mockColor,
|
|
55
|
+
muted: mockColor,
|
|
56
|
+
foreground: mockColor,
|
|
57
|
+
dim: mockColor,
|
|
58
|
+
bold: mockColor,
|
|
59
|
+
underline: mockColor,
|
|
60
|
+
inverse: mockColor
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
colors: mockColors,
|
|
64
|
+
symbols: {
|
|
65
|
+
success: "+",
|
|
66
|
+
error: "x",
|
|
67
|
+
warning: "!",
|
|
68
|
+
info: "i",
|
|
69
|
+
bullet: "-",
|
|
70
|
+
clock: "T",
|
|
71
|
+
folder: "D",
|
|
72
|
+
package: "P",
|
|
73
|
+
pointer: ">",
|
|
74
|
+
section: "#",
|
|
75
|
+
separator: "-",
|
|
76
|
+
border: "|",
|
|
77
|
+
topLeft: "+",
|
|
78
|
+
topRight: "+",
|
|
79
|
+
bottomLeft: "+",
|
|
80
|
+
bottomRight: "+",
|
|
81
|
+
leftT: "+",
|
|
82
|
+
rightT: "+",
|
|
83
|
+
step: "o",
|
|
84
|
+
stepDone: "*",
|
|
85
|
+
arrow: ">",
|
|
86
|
+
diamond: "<>"
|
|
87
|
+
},
|
|
88
|
+
write: vi.fn((text) => messages.push(`WRITE: ${text}`)),
|
|
89
|
+
info: vi.fn((msg) => messages.push(`INFO: ${msg}`)),
|
|
90
|
+
success: vi.fn((msg) => messages.push(`SUCCESS: ${msg}`)),
|
|
91
|
+
warn: vi.fn((msg) => messages.push(`WARN: ${msg}`)),
|
|
92
|
+
error: vi.fn((err) => messages.push(`ERROR: ${err instanceof Error ? err.message : err}`)),
|
|
93
|
+
debug: vi.fn((msg) => messages.push(`DEBUG: ${msg}`)),
|
|
94
|
+
spinner: vi.fn((msg) => {
|
|
95
|
+
messages.push(`SPINNER: ${msg}`);
|
|
96
|
+
return {
|
|
97
|
+
update: vi.fn((m) => messages.push(`SPINNER UPDATE: ${m}`)),
|
|
98
|
+
succeed: vi.fn((m) => messages.push(`SPINNER SUCCEED: ${m ?? msg}`)),
|
|
99
|
+
fail: vi.fn((m) => messages.push(`SPINNER FAIL: ${m ?? msg}`)),
|
|
100
|
+
stop: vi.fn()
|
|
101
|
+
};
|
|
102
|
+
}),
|
|
103
|
+
table: vi.fn((data) => messages.push(`TABLE: ${JSON.stringify(data)}`)),
|
|
104
|
+
json: vi.fn((data) => messages.push(`JSON: ${JSON.stringify(data)}`)),
|
|
105
|
+
newline: vi.fn(() => messages.push("")),
|
|
106
|
+
divider: vi.fn(() => messages.push("-".repeat(40))),
|
|
107
|
+
box: vi.fn((content, title) => {
|
|
108
|
+
if (title) {
|
|
109
|
+
messages.push(`+- ${title} -+`);
|
|
110
|
+
}
|
|
111
|
+
messages.push(content);
|
|
112
|
+
if (title) {
|
|
113
|
+
messages.push(`+${"-".repeat(title.length + 4)}+`);
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
sideBox: vi.fn((options) => {
|
|
117
|
+
messages.push(`+- ${options.title} -+`);
|
|
118
|
+
if (options.summary) {
|
|
119
|
+
Object.entries(options.summary).forEach(([key, value]) => {
|
|
120
|
+
messages.push(` ${key}: ${value}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (options.sections) {
|
|
124
|
+
options.sections.forEach((section) => {
|
|
125
|
+
if (section.header) {
|
|
126
|
+
messages.push(` ${section.header}:`);
|
|
127
|
+
}
|
|
128
|
+
section.items.forEach((item) => messages.push(` ${item}`));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (options.timing) {
|
|
132
|
+
messages.push(` Timing: ${options.timing}ms`);
|
|
133
|
+
}
|
|
134
|
+
messages.push("+" + "-".repeat(options.title.length + 4) + "+");
|
|
135
|
+
}),
|
|
136
|
+
chain: vi.fn((items) => {
|
|
137
|
+
items.forEach((item) => messages.push(`CHAIN: ${item.title}`));
|
|
138
|
+
}),
|
|
139
|
+
confirm: vi.fn(async () => true),
|
|
140
|
+
prompt: vi.fn(async () => ""),
|
|
141
|
+
select: async (_msg, choices) => choices[0]?.value,
|
|
142
|
+
multiSelect: async (_msg, choices) => choices.filter((c) => c.checked).map((c) => c.value),
|
|
143
|
+
log: vi.fn()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function createMockRuntime() {
|
|
147
|
+
const mockFS = {
|
|
148
|
+
readFile: vi.fn(async () => ""),
|
|
149
|
+
readFileBuffer: vi.fn(async () => new Uint8Array()),
|
|
150
|
+
writeFile: vi.fn(async () => {
|
|
151
|
+
}),
|
|
152
|
+
readdir: vi.fn(async () => []),
|
|
153
|
+
readdirWithStats: vi.fn(async () => []),
|
|
154
|
+
mkdir: vi.fn(async () => {
|
|
155
|
+
}),
|
|
156
|
+
rm: vi.fn(async () => {
|
|
157
|
+
}),
|
|
158
|
+
copy: vi.fn(async () => {
|
|
159
|
+
}),
|
|
160
|
+
move: vi.fn(async () => {
|
|
161
|
+
}),
|
|
162
|
+
glob: vi.fn(async () => []),
|
|
163
|
+
stat: vi.fn(async () => ({
|
|
164
|
+
isFile: () => false,
|
|
165
|
+
isDirectory: () => false,
|
|
166
|
+
size: 0,
|
|
167
|
+
mtime: Date.now(),
|
|
168
|
+
ctime: Date.now()
|
|
169
|
+
})),
|
|
170
|
+
exists: vi.fn(async () => false),
|
|
171
|
+
resolve: (path) => path,
|
|
172
|
+
relative: (path) => path,
|
|
173
|
+
join: (...segments) => segments.join("/"),
|
|
174
|
+
dirname: (path) => path.split("/").slice(0, -1).join("/"),
|
|
175
|
+
basename: (path) => path.split("/").pop() ?? "",
|
|
176
|
+
extname: (path) => {
|
|
177
|
+
const base = path.split("/").pop() ?? "";
|
|
178
|
+
const idx = base.lastIndexOf(".");
|
|
179
|
+
return idx > 0 ? base.slice(idx) : "";
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const mockFetch = vi.fn(async () => new Response("mock"));
|
|
183
|
+
const mockEnv = vi.fn((_key) => void 0);
|
|
184
|
+
return {
|
|
185
|
+
fs: mockFS,
|
|
186
|
+
fetch: mockFetch,
|
|
187
|
+
env: mockEnv
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function createMockEnvironmentAPI() {
|
|
191
|
+
return {
|
|
192
|
+
create: vi.fn(async () => ({
|
|
193
|
+
environmentId: "env_mock_1",
|
|
194
|
+
provider: "mock",
|
|
195
|
+
status: "ready",
|
|
196
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
197
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
198
|
+
})),
|
|
199
|
+
status: vi.fn(async (environmentId) => ({
|
|
200
|
+
environmentId,
|
|
201
|
+
status: "ready",
|
|
202
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
203
|
+
})),
|
|
204
|
+
destroy: vi.fn(async () => {
|
|
205
|
+
}),
|
|
206
|
+
renewLease: vi.fn(async () => ({
|
|
207
|
+
leaseId: "lease_mock_1",
|
|
208
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
209
|
+
expiresAt: new Date(Date.now() + 10 * 60 * 1e3).toISOString()
|
|
210
|
+
}))
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function createMockWorkspaceAPI() {
|
|
214
|
+
return {
|
|
215
|
+
materialize: vi.fn(async () => ({
|
|
216
|
+
workspaceId: "ws_mock_1",
|
|
217
|
+
provider: "mock",
|
|
218
|
+
status: "ready",
|
|
219
|
+
rootPath: "/tmp/ws_mock_1",
|
|
220
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
221
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
222
|
+
})),
|
|
223
|
+
attach: vi.fn(async (request) => ({
|
|
224
|
+
workspaceId: request.workspaceId,
|
|
225
|
+
environmentId: request.environmentId,
|
|
226
|
+
mountPath: request.mountPath ?? "/workspace",
|
|
227
|
+
attachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
228
|
+
})),
|
|
229
|
+
release: vi.fn(async () => {
|
|
230
|
+
}),
|
|
231
|
+
status: vi.fn(async (workspaceId) => ({
|
|
232
|
+
workspaceId,
|
|
233
|
+
status: "ready",
|
|
234
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
235
|
+
}))
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function createMockSnapshotAPI() {
|
|
239
|
+
return {
|
|
240
|
+
capture: vi.fn(async (request) => ({
|
|
241
|
+
snapshotId: request.snapshotId ?? "snap_mock_1",
|
|
242
|
+
provider: "mock",
|
|
243
|
+
status: "ready",
|
|
244
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
245
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
246
|
+
workspaceId: request.workspaceId,
|
|
247
|
+
environmentId: request.environmentId
|
|
248
|
+
})),
|
|
249
|
+
restore: vi.fn(async (request) => ({
|
|
250
|
+
snapshotId: request.snapshotId,
|
|
251
|
+
restoredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
252
|
+
workspaceId: request.workspaceId,
|
|
253
|
+
environmentId: request.environmentId,
|
|
254
|
+
targetPath: request.targetPath
|
|
255
|
+
})),
|
|
256
|
+
status: vi.fn(async (snapshotId) => ({
|
|
257
|
+
snapshotId,
|
|
258
|
+
status: "ready",
|
|
259
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
260
|
+
})),
|
|
261
|
+
delete: vi.fn(async () => {
|
|
262
|
+
}),
|
|
263
|
+
gc: vi.fn(async (request) => ({
|
|
264
|
+
scanned: 0,
|
|
265
|
+
deleted: 0,
|
|
266
|
+
dryRun: request?.dryRun ?? false
|
|
267
|
+
}))
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function createInfraApiMocks() {
|
|
271
|
+
return {
|
|
272
|
+
environment: createMockEnvironmentAPI(),
|
|
273
|
+
workspace: createMockWorkspaceAPI(),
|
|
274
|
+
snapshot: createMockSnapshotAPI()
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function createMockPluginAPI() {
|
|
278
|
+
const infra = createInfraApiMocks();
|
|
279
|
+
return {
|
|
280
|
+
lifecycle: {
|
|
281
|
+
onCleanup: vi.fn()
|
|
282
|
+
},
|
|
283
|
+
state: {
|
|
284
|
+
get: vi.fn(async () => void 0),
|
|
285
|
+
set: vi.fn(async () => {
|
|
286
|
+
}),
|
|
287
|
+
delete: vi.fn(async () => {
|
|
288
|
+
}),
|
|
289
|
+
has: vi.fn(async () => false),
|
|
290
|
+
getMany: vi.fn(async () => /* @__PURE__ */ new Map()),
|
|
291
|
+
setMany: vi.fn(async () => {
|
|
292
|
+
})
|
|
293
|
+
},
|
|
294
|
+
artifacts: {
|
|
295
|
+
write: vi.fn(async () => "/mock/path"),
|
|
296
|
+
list: vi.fn(async () => []),
|
|
297
|
+
read: vi.fn(async () => ""),
|
|
298
|
+
readBuffer: vi.fn(async () => new Uint8Array()),
|
|
299
|
+
exists: vi.fn(async () => false),
|
|
300
|
+
path: vi.fn(() => "/mock/path")
|
|
301
|
+
},
|
|
302
|
+
shell: {
|
|
303
|
+
exec: vi.fn(async () => ({ code: 0, stdout: "", stderr: "", ok: true }))
|
|
304
|
+
},
|
|
305
|
+
events: {
|
|
306
|
+
emit: vi.fn(async () => {
|
|
307
|
+
})
|
|
308
|
+
},
|
|
309
|
+
invoke: {
|
|
310
|
+
call: vi.fn(async (_pluginId, _input, _options) => void 0)
|
|
311
|
+
},
|
|
312
|
+
workflows: {
|
|
313
|
+
run: vi.fn(async () => "mock-run-id"),
|
|
314
|
+
wait: vi.fn(async () => void 0),
|
|
315
|
+
status: vi.fn(async () => null),
|
|
316
|
+
cancel: vi.fn(async () => {
|
|
317
|
+
}),
|
|
318
|
+
list: vi.fn(async () => [])
|
|
319
|
+
},
|
|
320
|
+
jobs: {
|
|
321
|
+
submit: vi.fn(async () => "mock-job-id"),
|
|
322
|
+
schedule: vi.fn(async () => "mock-scheduled-job-id"),
|
|
323
|
+
wait: vi.fn(async () => void 0),
|
|
324
|
+
status: vi.fn(async () => null),
|
|
325
|
+
cancel: vi.fn(async () => false),
|
|
326
|
+
list: vi.fn(async () => [])
|
|
327
|
+
},
|
|
328
|
+
cron: {
|
|
329
|
+
register: vi.fn(async () => {
|
|
330
|
+
}),
|
|
331
|
+
unregister: vi.fn(async () => {
|
|
332
|
+
}),
|
|
333
|
+
list: vi.fn(async () => []),
|
|
334
|
+
pause: vi.fn(async () => {
|
|
335
|
+
}),
|
|
336
|
+
resume: vi.fn(async () => {
|
|
337
|
+
}),
|
|
338
|
+
trigger: vi.fn(async () => {
|
|
339
|
+
})
|
|
340
|
+
},
|
|
341
|
+
environment: infra.environment,
|
|
342
|
+
workspace: infra.workspace,
|
|
343
|
+
snapshot: infra.snapshot
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
var createMockPlatformApi = createMockPluginAPI;
|
|
347
|
+
function createMockPluginContextV3(options = {}) {
|
|
348
|
+
return createTestContext(options);
|
|
349
|
+
}
|
|
350
|
+
function createTestContext(options = {}) {
|
|
351
|
+
const {
|
|
352
|
+
pluginId = "test-plugin",
|
|
353
|
+
pluginVersion = "0.0.0",
|
|
354
|
+
host = "cli",
|
|
355
|
+
hostContext,
|
|
356
|
+
config,
|
|
357
|
+
cwd = process.cwd(),
|
|
358
|
+
outdir,
|
|
359
|
+
tenantId,
|
|
360
|
+
signal,
|
|
361
|
+
platform: platformOverrides,
|
|
362
|
+
ui: uiOverrides,
|
|
363
|
+
syncSingleton = true
|
|
364
|
+
} = options;
|
|
365
|
+
const resolvedOutdir = outdir ?? `${cwd}/.kb/output`;
|
|
366
|
+
const defaultHostContext = hostContext ?? (() => {
|
|
367
|
+
switch (host) {
|
|
368
|
+
case "cli":
|
|
369
|
+
return { host: "cli", argv: ["test"], flags: {} };
|
|
370
|
+
case "rest":
|
|
371
|
+
return {
|
|
372
|
+
host: "rest",
|
|
373
|
+
method: "GET",
|
|
374
|
+
path: "/test",
|
|
375
|
+
requestId: "test-req-001",
|
|
376
|
+
traceId: "test-trace-001"
|
|
377
|
+
};
|
|
378
|
+
case "workflow":
|
|
379
|
+
return { host: "workflow", workflowId: "test-wf", runId: "test-run", stepId: "test-step" };
|
|
380
|
+
case "webhook":
|
|
381
|
+
return { host: "webhook", event: "test:event", namespaceId: "test-ns", webhookId: "test-webhook-001" };
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
384
|
+
const defaultLogger = mockLogger();
|
|
385
|
+
const defaultLLM = mockLLM();
|
|
386
|
+
const defaultCache = mockCache();
|
|
387
|
+
const defaultStorage = mockStorage();
|
|
388
|
+
const defaultPlatform = {
|
|
389
|
+
logger: defaultLogger,
|
|
390
|
+
llm: defaultLLM,
|
|
391
|
+
embeddings: {
|
|
392
|
+
embed: vi.fn(async () => []),
|
|
393
|
+
embedBatch: vi.fn(async () => [[]]),
|
|
394
|
+
dimensions: 1536,
|
|
395
|
+
getDimensions: vi.fn(async () => 1536)
|
|
396
|
+
},
|
|
397
|
+
vectorStore: {
|
|
398
|
+
search: vi.fn(async () => []),
|
|
399
|
+
upsert: vi.fn(async () => {
|
|
400
|
+
}),
|
|
401
|
+
delete: vi.fn(async () => {
|
|
402
|
+
}),
|
|
403
|
+
count: vi.fn(async () => 0)
|
|
404
|
+
},
|
|
405
|
+
cache: defaultCache,
|
|
406
|
+
storage: defaultStorage,
|
|
407
|
+
analytics: {
|
|
408
|
+
track: vi.fn(async () => {
|
|
409
|
+
}),
|
|
410
|
+
identify: vi.fn(async () => {
|
|
411
|
+
}),
|
|
412
|
+
flush: vi.fn(async () => {
|
|
413
|
+
})
|
|
414
|
+
},
|
|
415
|
+
eventBus: {
|
|
416
|
+
publish: vi.fn(async () => {
|
|
417
|
+
}),
|
|
418
|
+
subscribe: vi.fn(() => () => {
|
|
419
|
+
})
|
|
420
|
+
},
|
|
421
|
+
config: {
|
|
422
|
+
getConfig: vi.fn(async () => ({})),
|
|
423
|
+
getRawConfig: vi.fn(async () => ({}))
|
|
424
|
+
},
|
|
425
|
+
invoke: {
|
|
426
|
+
call: vi.fn(async () => ({ success: true, data: null })),
|
|
427
|
+
isAvailable: vi.fn(async () => false)
|
|
428
|
+
},
|
|
429
|
+
documentDatabase: {
|
|
430
|
+
find: vi.fn(async () => []),
|
|
431
|
+
findStream: vi.fn(async function* () {
|
|
432
|
+
}),
|
|
433
|
+
findById: vi.fn(async () => null),
|
|
434
|
+
count: vi.fn(async () => 0),
|
|
435
|
+
insertOne: vi.fn(async () => ({ id: "mock", createdAt: 0, updatedAt: 0 })),
|
|
436
|
+
insertMany: vi.fn(async () => []),
|
|
437
|
+
updateOne: vi.fn(async () => null),
|
|
438
|
+
updateMany: vi.fn(async () => 0),
|
|
439
|
+
updateById: vi.fn(async () => null),
|
|
440
|
+
deleteMany: vi.fn(async () => 0),
|
|
441
|
+
deleteById: vi.fn(async () => false),
|
|
442
|
+
bulkWrite: vi.fn(async () => ({ inserted: 0, updated: 0, deleted: 0 })),
|
|
443
|
+
transaction: vi.fn(async () => void 0),
|
|
444
|
+
ensureCollection: vi.fn(async () => void 0),
|
|
445
|
+
ping: vi.fn(async () => ({ ok: true, latencyMs: 0 })),
|
|
446
|
+
close: vi.fn(async () => {
|
|
447
|
+
})
|
|
448
|
+
},
|
|
449
|
+
kvStore: {
|
|
450
|
+
get: vi.fn(async () => null),
|
|
451
|
+
getMany: vi.fn(async () => []),
|
|
452
|
+
set: vi.fn(async () => true),
|
|
453
|
+
setMany: vi.fn(async () => {
|
|
454
|
+
}),
|
|
455
|
+
setIfNotExists: vi.fn(async () => true),
|
|
456
|
+
delete: vi.fn(async () => false),
|
|
457
|
+
exists: vi.fn(async () => false),
|
|
458
|
+
cas: vi.fn(async () => false),
|
|
459
|
+
incr: vi.fn(async () => 0),
|
|
460
|
+
ttl: vi.fn(async () => null),
|
|
461
|
+
expire: vi.fn(async () => false),
|
|
462
|
+
persist: vi.fn(async () => false),
|
|
463
|
+
scan: vi.fn(async function* () {
|
|
464
|
+
}),
|
|
465
|
+
ping: vi.fn(async () => ({ ok: true, latencyMs: 0 })),
|
|
466
|
+
close: vi.fn(async () => {
|
|
467
|
+
})
|
|
468
|
+
},
|
|
469
|
+
logs: {
|
|
470
|
+
query: vi.fn(async () => ({ logs: [], total: 0, hasMore: false, source: "buffer" })),
|
|
471
|
+
getById: vi.fn(async () => null),
|
|
472
|
+
search: vi.fn(async () => ({ logs: [], total: 0, hasMore: false })),
|
|
473
|
+
subscribe: vi.fn(() => () => {
|
|
474
|
+
}),
|
|
475
|
+
getStats: vi.fn(async () => ({})),
|
|
476
|
+
getCapabilities: vi.fn(() => ({ hasBuffer: false, hasPersistence: false, hasSearch: false, hasStreaming: false }))
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
const finalPlatform = {
|
|
480
|
+
...defaultPlatform,
|
|
481
|
+
...platformOverrides
|
|
482
|
+
};
|
|
483
|
+
let cleanupFn = () => {
|
|
484
|
+
};
|
|
485
|
+
if (syncSingleton) {
|
|
486
|
+
const result = setupTestPlatform({
|
|
487
|
+
llm: finalPlatform.llm,
|
|
488
|
+
cache: finalPlatform.cache,
|
|
489
|
+
storage: finalPlatform.storage,
|
|
490
|
+
logger: finalPlatform.logger,
|
|
491
|
+
analytics: finalPlatform.analytics,
|
|
492
|
+
embeddings: finalPlatform.embeddings,
|
|
493
|
+
vectorStore: finalPlatform.vectorStore,
|
|
494
|
+
eventBus: finalPlatform.eventBus
|
|
495
|
+
});
|
|
496
|
+
cleanupFn = result.cleanup;
|
|
497
|
+
}
|
|
498
|
+
const defaultUI = createMockUI();
|
|
499
|
+
const finalUI = {
|
|
500
|
+
...defaultUI,
|
|
501
|
+
...uiOverrides
|
|
502
|
+
};
|
|
503
|
+
const ctx = {
|
|
504
|
+
host,
|
|
505
|
+
requestId: "test-trace:test-span",
|
|
506
|
+
pluginId,
|
|
507
|
+
pluginVersion,
|
|
508
|
+
tenantId,
|
|
509
|
+
cwd,
|
|
510
|
+
outdir: resolvedOutdir,
|
|
511
|
+
config,
|
|
512
|
+
signal,
|
|
513
|
+
trace: createMockTrace(),
|
|
514
|
+
hostContext: defaultHostContext,
|
|
515
|
+
ui: finalUI,
|
|
516
|
+
platform: finalPlatform,
|
|
517
|
+
runtime: createMockRuntime(),
|
|
518
|
+
api: createMockPluginAPI()
|
|
519
|
+
};
|
|
520
|
+
return { ctx, cleanup: cleanupFn };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/test-command.ts
|
|
524
|
+
async function testCommand(handler, options = {}) {
|
|
525
|
+
const {
|
|
526
|
+
flags,
|
|
527
|
+
argv = [],
|
|
528
|
+
query,
|
|
529
|
+
body,
|
|
530
|
+
params,
|
|
531
|
+
input: rawInput,
|
|
532
|
+
host = "cli",
|
|
533
|
+
config,
|
|
534
|
+
cwd,
|
|
535
|
+
tenantId,
|
|
536
|
+
signal,
|
|
537
|
+
platform: platform2,
|
|
538
|
+
ui,
|
|
539
|
+
syncSingleton = true
|
|
540
|
+
} = options;
|
|
541
|
+
const ctxOptions = {
|
|
542
|
+
host,
|
|
543
|
+
config,
|
|
544
|
+
cwd,
|
|
545
|
+
tenantId,
|
|
546
|
+
signal,
|
|
547
|
+
platform: platform2,
|
|
548
|
+
ui,
|
|
549
|
+
syncSingleton
|
|
550
|
+
};
|
|
551
|
+
const { ctx, cleanup } = createTestContext(ctxOptions);
|
|
552
|
+
let input;
|
|
553
|
+
if (rawInput !== void 0) {
|
|
554
|
+
input = rawInput;
|
|
555
|
+
} else if (host === "rest") {
|
|
556
|
+
input = {
|
|
557
|
+
...query !== void 0 ? { query } : {},
|
|
558
|
+
...body !== void 0 ? { body } : {},
|
|
559
|
+
...params !== void 0 ? { params } : {}
|
|
560
|
+
};
|
|
561
|
+
} else {
|
|
562
|
+
input = { flags: flags ?? {}, argv };
|
|
563
|
+
}
|
|
564
|
+
let raw;
|
|
565
|
+
try {
|
|
566
|
+
raw = await handler.execute(ctx, input);
|
|
567
|
+
} finally {
|
|
568
|
+
await handler.cleanup?.();
|
|
569
|
+
}
|
|
570
|
+
let exitCode = 0;
|
|
571
|
+
let result;
|
|
572
|
+
let meta;
|
|
573
|
+
if (raw != null && typeof raw === "object" && "exitCode" in raw) {
|
|
574
|
+
const cmdResult = raw;
|
|
575
|
+
exitCode = cmdResult.exitCode;
|
|
576
|
+
result = cmdResult.result;
|
|
577
|
+
meta = cmdResult.meta;
|
|
578
|
+
} else if (raw === void 0 || raw === null) {
|
|
579
|
+
exitCode = 0;
|
|
580
|
+
result = void 0;
|
|
581
|
+
meta = void 0;
|
|
582
|
+
} else {
|
|
583
|
+
exitCode = 0;
|
|
584
|
+
result = raw;
|
|
585
|
+
meta = void 0;
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
exitCode,
|
|
589
|
+
result,
|
|
590
|
+
meta,
|
|
591
|
+
raw,
|
|
592
|
+
ui: ctx.ui,
|
|
593
|
+
ctx,
|
|
594
|
+
cleanup
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/in-memory-document-database.ts
|
|
599
|
+
function nextId() {
|
|
600
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
601
|
+
}
|
|
602
|
+
function clone(value) {
|
|
603
|
+
return value === void 0 || value === null ? value : JSON.parse(JSON.stringify(value));
|
|
604
|
+
}
|
|
605
|
+
function matchScalar(value, ops) {
|
|
606
|
+
if (ops === null || ops === void 0 || typeof ops !== "object") {
|
|
607
|
+
return value === ops;
|
|
608
|
+
}
|
|
609
|
+
const o = ops;
|
|
610
|
+
if ("$eq" in o && o.$eq !== void 0 && value !== o.$eq) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
if ("$ne" in o && o.$ne !== void 0 && value === o.$ne) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if ("$gt" in o && o.$gt !== void 0 && !(value > o.$gt)) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
if ("$gte" in o && o.$gte !== void 0 && !(value >= o.$gte)) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
if ("$lt" in o && o.$lt !== void 0 && !(value < o.$lt)) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
if ("$lte" in o && o.$lte !== void 0 && !(value <= o.$lte)) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
if ("$in" in o && Array.isArray(o.$in) && !o.$in.includes(value)) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if ("$nin" in o && Array.isArray(o.$nin) && o.$nin.includes(value)) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
if ("$exists" in o && typeof o.$exists === "boolean") {
|
|
635
|
+
const present = value !== void 0;
|
|
636
|
+
if (present !== o.$exists) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if ("$startsWith" in o && typeof o.$startsWith === "string") {
|
|
641
|
+
if (typeof value !== "string" || !value.startsWith(o.$startsWith)) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if ("$contains" in o && typeof o.$contains === "string") {
|
|
646
|
+
if (typeof value !== "string" || !value.includes(o.$contains)) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if ("$endsWith" in o && typeof o.$endsWith === "string") {
|
|
651
|
+
if (typeof value !== "string" || !value.endsWith(o.$endsWith)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
function matchFilter(doc, filter) {
|
|
658
|
+
const source = doc;
|
|
659
|
+
if (filter.$and && Array.isArray(filter.$and)) {
|
|
660
|
+
if (!filter.$and.every((f) => matchFilter(doc, f))) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (filter.$or && Array.isArray(filter.$or)) {
|
|
665
|
+
if (!filter.$or.some((f) => matchFilter(doc, f))) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
for (const key of Object.keys(filter)) {
|
|
670
|
+
if (key === "$and" || key === "$or") {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const fieldValue = source[key];
|
|
674
|
+
const expectation = filter[key];
|
|
675
|
+
if (!matchScalar(fieldValue, expectation)) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
function applyUpdate(doc, update) {
|
|
682
|
+
const next = { ...doc };
|
|
683
|
+
if (update.$set) {
|
|
684
|
+
for (const [k, v] of Object.entries(update.$set)) {
|
|
685
|
+
next[k] = v;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (update.$unset) {
|
|
689
|
+
for (const k of Object.keys(update.$unset)) {
|
|
690
|
+
delete next[k];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (update.$inc) {
|
|
694
|
+
for (const [k, delta] of Object.entries(update.$inc)) {
|
|
695
|
+
const current = typeof next[k] === "number" ? next[k] : 0;
|
|
696
|
+
if (typeof delta !== "number" || !Number.isFinite(delta)) {
|
|
697
|
+
throw new Error(`$inc value for "${k}" must be a finite number`);
|
|
698
|
+
}
|
|
699
|
+
next[k] = current + delta;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
next.updatedAt = Date.now();
|
|
703
|
+
return next;
|
|
704
|
+
}
|
|
705
|
+
function applyProjection(doc, projection) {
|
|
706
|
+
if (!projection) {
|
|
707
|
+
return doc;
|
|
708
|
+
}
|
|
709
|
+
const entries = Object.entries(projection);
|
|
710
|
+
const includes = entries.filter(([, v]) => v === 1).map(([k]) => k);
|
|
711
|
+
const excludes = entries.filter(([, v]) => v === 0).map(([k]) => k);
|
|
712
|
+
if (includes.length > 0 && excludes.length > 0) {
|
|
713
|
+
throw new Error("projection cannot mix include (1) and exclude (0)");
|
|
714
|
+
}
|
|
715
|
+
const source = doc;
|
|
716
|
+
if (includes.length > 0) {
|
|
717
|
+
const out2 = {};
|
|
718
|
+
for (const k of includes) {
|
|
719
|
+
out2[k] = source[k];
|
|
720
|
+
}
|
|
721
|
+
return out2;
|
|
722
|
+
}
|
|
723
|
+
const out = { ...source };
|
|
724
|
+
for (const k of excludes) {
|
|
725
|
+
delete out[k];
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
function sortDocs(docs, sort) {
|
|
730
|
+
if (!sort) {
|
|
731
|
+
return docs;
|
|
732
|
+
}
|
|
733
|
+
const entries = Object.entries(sort);
|
|
734
|
+
return [...docs].sort((a, b) => {
|
|
735
|
+
for (const [field, dir] of entries) {
|
|
736
|
+
const av = a[field];
|
|
737
|
+
const bv = b[field];
|
|
738
|
+
if (av === bv) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const cmp = av < bv ? -1 : 1;
|
|
742
|
+
return dir === 1 ? cmp : -cmp;
|
|
743
|
+
}
|
|
744
|
+
return 0;
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
function checkUniqueIndexes(collection, doc, ignoreId) {
|
|
748
|
+
for (const idx of collection.indexes) {
|
|
749
|
+
if (!idx.unique) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const paths = Array.isArray(idx.path) ? idx.path : [idx.path];
|
|
753
|
+
const newKey = paths.map((p) => JSON.stringify(doc[p])).join("|");
|
|
754
|
+
for (const existing of collection.docs.values()) {
|
|
755
|
+
if (ignoreId !== void 0 && existing.id === ignoreId) {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const existingKey = paths.map((p) => JSON.stringify(existing[p])).join("|");
|
|
759
|
+
if (existingKey === newKey) {
|
|
760
|
+
throw new Error(`unique index violation on [${paths.join(", ")}]`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
var InMemoryDocumentDatabase = class {
|
|
766
|
+
collections = /* @__PURE__ */ new Map();
|
|
767
|
+
now;
|
|
768
|
+
closed = false;
|
|
769
|
+
constructor(opts = {}) {
|
|
770
|
+
this.now = opts.now ?? (() => Date.now());
|
|
771
|
+
}
|
|
772
|
+
getCollection(name) {
|
|
773
|
+
let c = this.collections.get(name);
|
|
774
|
+
if (!c) {
|
|
775
|
+
c = { docs: /* @__PURE__ */ new Map(), indexes: [] };
|
|
776
|
+
this.collections.set(name, c);
|
|
777
|
+
}
|
|
778
|
+
return c;
|
|
779
|
+
}
|
|
780
|
+
throwIfClosed() {
|
|
781
|
+
if (this.closed) {
|
|
782
|
+
throw new Error("InMemoryDocumentDatabase is closed");
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async ensureCollection(name, options) {
|
|
786
|
+
this.throwIfClosed();
|
|
787
|
+
const c = this.getCollection(name);
|
|
788
|
+
if (options?.indexes) {
|
|
789
|
+
for (const idx of options.indexes) {
|
|
790
|
+
const paths = Array.isArray(idx.path) ? idx.path.join(",") : idx.path;
|
|
791
|
+
const exists = c.indexes.some((existing) => {
|
|
792
|
+
const existingPaths = Array.isArray(existing.path) ? existing.path.join(",") : existing.path;
|
|
793
|
+
return existingPaths === paths;
|
|
794
|
+
});
|
|
795
|
+
if (!exists) {
|
|
796
|
+
c.indexes.push(idx);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async find(collection, filter, options) {
|
|
802
|
+
this.throwIfClosed();
|
|
803
|
+
const c = this.getCollection(collection);
|
|
804
|
+
let docs = Array.from(c.docs.values());
|
|
805
|
+
docs = docs.filter((d) => matchFilter(d, filter));
|
|
806
|
+
docs = sortDocs(docs, options?.sort);
|
|
807
|
+
if (options?.skip) {
|
|
808
|
+
docs = docs.slice(options.skip);
|
|
809
|
+
}
|
|
810
|
+
if (options?.limit !== void 0) {
|
|
811
|
+
docs = docs.slice(0, options.limit);
|
|
812
|
+
}
|
|
813
|
+
return docs.map((d) => applyProjection(clone(d), options?.project));
|
|
814
|
+
}
|
|
815
|
+
findStream(collection, filter, options) {
|
|
816
|
+
const self = this;
|
|
817
|
+
return {
|
|
818
|
+
async *[Symbol.asyncIterator]() {
|
|
819
|
+
const results = await self.find(collection, filter, options);
|
|
820
|
+
for (const r of results) {
|
|
821
|
+
yield r;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
async findById(collection, id) {
|
|
827
|
+
this.throwIfClosed();
|
|
828
|
+
const doc = this.getCollection(collection).docs.get(id);
|
|
829
|
+
return doc ? clone(doc) : null;
|
|
830
|
+
}
|
|
831
|
+
async count(collection, filter) {
|
|
832
|
+
this.throwIfClosed();
|
|
833
|
+
const docs = Array.from(this.getCollection(collection).docs.values());
|
|
834
|
+
return docs.filter((d) => matchFilter(d, filter)).length;
|
|
835
|
+
}
|
|
836
|
+
async insertOne(collection, doc) {
|
|
837
|
+
this.throwIfClosed();
|
|
838
|
+
const c = this.getCollection(collection);
|
|
839
|
+
const now = this.now();
|
|
840
|
+
const full = { ...doc, id: nextId(), createdAt: now, updatedAt: now };
|
|
841
|
+
checkUniqueIndexes(c, full);
|
|
842
|
+
c.docs.set(full.id, clone(full));
|
|
843
|
+
return clone(full);
|
|
844
|
+
}
|
|
845
|
+
async insertMany(collection, docs) {
|
|
846
|
+
this.throwIfClosed();
|
|
847
|
+
const c = this.getCollection(collection);
|
|
848
|
+
const now = this.now();
|
|
849
|
+
const staged = [];
|
|
850
|
+
for (const d of docs) {
|
|
851
|
+
const full = { ...d, id: nextId(), createdAt: now, updatedAt: now };
|
|
852
|
+
checkUniqueIndexes(c, full);
|
|
853
|
+
for (const s of staged) {
|
|
854
|
+
try {
|
|
855
|
+
checkUniqueIndexes({ docs: /* @__PURE__ */ new Map([[s.id, s]]), indexes: c.indexes }, full);
|
|
856
|
+
} catch (e) {
|
|
857
|
+
throw e;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
staged.push(full);
|
|
861
|
+
}
|
|
862
|
+
for (const s of staged) {
|
|
863
|
+
c.docs.set(s.id, clone(s));
|
|
864
|
+
}
|
|
865
|
+
return staged.map(clone);
|
|
866
|
+
}
|
|
867
|
+
async updateOne(collection, filter, update, options) {
|
|
868
|
+
this.throwIfClosed();
|
|
869
|
+
const c = this.getCollection(collection);
|
|
870
|
+
for (const existing of c.docs.values()) {
|
|
871
|
+
if (matchFilter(existing, filter)) {
|
|
872
|
+
const next = applyUpdate(existing, update);
|
|
873
|
+
checkUniqueIndexes(c, next, existing.id);
|
|
874
|
+
c.docs.set(existing.id, clone(next));
|
|
875
|
+
return clone(next);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (options?.upsert) {
|
|
879
|
+
const now = this.now();
|
|
880
|
+
const seed = { id: nextId(), createdAt: now, updatedAt: now };
|
|
881
|
+
for (const [k, v] of Object.entries(filter)) {
|
|
882
|
+
if (k === "$and" || k === "$or") {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (v !== null && typeof v === "object") {
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
seed[k] = v;
|
|
889
|
+
}
|
|
890
|
+
const next = applyUpdate(seed, update);
|
|
891
|
+
checkUniqueIndexes(c, next);
|
|
892
|
+
c.docs.set(next.id, clone(next));
|
|
893
|
+
return clone(next);
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
async updateMany(collection, filter, update) {
|
|
898
|
+
this.throwIfClosed();
|
|
899
|
+
const c = this.getCollection(collection);
|
|
900
|
+
let n = 0;
|
|
901
|
+
for (const existing of c.docs.values()) {
|
|
902
|
+
if (matchFilter(existing, filter)) {
|
|
903
|
+
const next = applyUpdate(existing, update);
|
|
904
|
+
checkUniqueIndexes(c, next, existing.id);
|
|
905
|
+
c.docs.set(existing.id, clone(next));
|
|
906
|
+
n++;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return n;
|
|
910
|
+
}
|
|
911
|
+
async updateById(collection, id, update) {
|
|
912
|
+
this.throwIfClosed();
|
|
913
|
+
const c = this.getCollection(collection);
|
|
914
|
+
const existing = c.docs.get(id);
|
|
915
|
+
if (!existing) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
const next = applyUpdate(existing, update);
|
|
919
|
+
checkUniqueIndexes(c, next, id);
|
|
920
|
+
c.docs.set(id, clone(next));
|
|
921
|
+
return clone(next);
|
|
922
|
+
}
|
|
923
|
+
async deleteMany(collection, filter) {
|
|
924
|
+
this.throwIfClosed();
|
|
925
|
+
const c = this.getCollection(collection);
|
|
926
|
+
let n = 0;
|
|
927
|
+
for (const [id, doc] of c.docs) {
|
|
928
|
+
if (matchFilter(doc, filter)) {
|
|
929
|
+
c.docs.delete(id);
|
|
930
|
+
n++;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return n;
|
|
934
|
+
}
|
|
935
|
+
async deleteById(collection, id) {
|
|
936
|
+
this.throwIfClosed();
|
|
937
|
+
return this.getCollection(collection).docs.delete(id);
|
|
938
|
+
}
|
|
939
|
+
async bulkWrite(collection, ops) {
|
|
940
|
+
this.throwIfClosed();
|
|
941
|
+
const c = this.getCollection(collection);
|
|
942
|
+
const snapshot = new Map(c.docs);
|
|
943
|
+
const result = { inserted: 0, updated: 0, deleted: 0 };
|
|
944
|
+
try {
|
|
945
|
+
for (const op of ops) {
|
|
946
|
+
if (op.type === "insert") {
|
|
947
|
+
await this.insertOne(collection, op.doc);
|
|
948
|
+
result.inserted++;
|
|
949
|
+
} else if (op.type === "update") {
|
|
950
|
+
const target = await this.updateOne(collection, op.filter, op.update, { upsert: op.upsert });
|
|
951
|
+
if (target) {
|
|
952
|
+
result.updated++;
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
result.deleted += await this.deleteMany(collection, op.filter);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
959
|
+
} catch (err) {
|
|
960
|
+
c.docs.clear();
|
|
961
|
+
for (const [k, v] of snapshot) {
|
|
962
|
+
c.docs.set(k, v);
|
|
963
|
+
}
|
|
964
|
+
throw err;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
async transaction(fn) {
|
|
968
|
+
this.throwIfClosed();
|
|
969
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
970
|
+
for (const [name, c] of this.collections) {
|
|
971
|
+
snapshot.set(name, new Map(c.docs));
|
|
972
|
+
}
|
|
973
|
+
const tx = {
|
|
974
|
+
find: this.find.bind(this),
|
|
975
|
+
findById: this.findById.bind(this),
|
|
976
|
+
count: this.count.bind(this),
|
|
977
|
+
insertOne: this.insertOne.bind(this),
|
|
978
|
+
insertMany: this.insertMany.bind(this),
|
|
979
|
+
updateOne: this.updateOne.bind(this),
|
|
980
|
+
updateMany: this.updateMany.bind(this),
|
|
981
|
+
updateById: this.updateById.bind(this),
|
|
982
|
+
deleteMany: this.deleteMany.bind(this),
|
|
983
|
+
deleteById: this.deleteById.bind(this)
|
|
984
|
+
};
|
|
985
|
+
try {
|
|
986
|
+
return await fn(tx);
|
|
987
|
+
} catch (err) {
|
|
988
|
+
for (const [name, docs] of snapshot) {
|
|
989
|
+
const c = this.collections.get(name);
|
|
990
|
+
if (c) {
|
|
991
|
+
c.docs.clear();
|
|
992
|
+
for (const [k, v] of docs) {
|
|
993
|
+
c.docs.set(k, v);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
throw err;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async ping() {
|
|
1001
|
+
return { ok: !this.closed, latencyMs: 0 };
|
|
1002
|
+
}
|
|
1003
|
+
async close() {
|
|
1004
|
+
this.closed = true;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
function createInMemoryDocumentDatabase(opts) {
|
|
1008
|
+
return new InMemoryDocumentDatabase(opts);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/in-memory-kv-store.ts
|
|
1012
|
+
var InMemoryKVStore = class {
|
|
1013
|
+
map = /* @__PURE__ */ new Map();
|
|
1014
|
+
closed = false;
|
|
1015
|
+
throwIfClosed() {
|
|
1016
|
+
if (this.closed) {
|
|
1017
|
+
throw new Error("InMemoryKVStore is closed");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
isExpired(entry, at = Date.now()) {
|
|
1021
|
+
return entry.expiresAt !== null && entry.expiresAt <= at;
|
|
1022
|
+
}
|
|
1023
|
+
getLive(key) {
|
|
1024
|
+
const e = this.map.get(key);
|
|
1025
|
+
if (!e) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
if (this.isExpired(e)) {
|
|
1029
|
+
this.map.delete(key);
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
return e;
|
|
1033
|
+
}
|
|
1034
|
+
async get(key) {
|
|
1035
|
+
this.throwIfClosed();
|
|
1036
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1037
|
+
throw new Error("KV key must be a non-empty string");
|
|
1038
|
+
}
|
|
1039
|
+
const e = this.getLive(key);
|
|
1040
|
+
return e ? e.value : null;
|
|
1041
|
+
}
|
|
1042
|
+
async getMany(keys) {
|
|
1043
|
+
this.throwIfClosed();
|
|
1044
|
+
return Promise.all(keys.map((k) => this.get(k)));
|
|
1045
|
+
}
|
|
1046
|
+
async set(key, value, options) {
|
|
1047
|
+
this.throwIfClosed();
|
|
1048
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1049
|
+
throw new Error("KV key must be a non-empty string");
|
|
1050
|
+
}
|
|
1051
|
+
if (options?.ifNotExists && options?.ifExists) {
|
|
1052
|
+
throw new Error("ifNotExists and ifExists are mutually exclusive");
|
|
1053
|
+
}
|
|
1054
|
+
const present = this.getLive(key) !== null;
|
|
1055
|
+
if (options?.ifNotExists && present) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
if (options?.ifExists && !present) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
const expiresAt = typeof options?.ttlMs === "number" ? Date.now() + options.ttlMs : null;
|
|
1062
|
+
this.map.set(key, { value, expiresAt });
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
async setMany(entries) {
|
|
1066
|
+
this.throwIfClosed();
|
|
1067
|
+
for (const e of entries) {
|
|
1068
|
+
if (typeof e.key !== "string" || e.key.length === 0) {
|
|
1069
|
+
throw new Error("KV key must be a non-empty string");
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const now = Date.now();
|
|
1073
|
+
for (const e of entries) {
|
|
1074
|
+
this.map.set(e.key, {
|
|
1075
|
+
value: e.value,
|
|
1076
|
+
expiresAt: typeof e.ttlMs === "number" ? now + e.ttlMs : null
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
async setIfNotExists(key, value, options) {
|
|
1081
|
+
return this.set(key, value, { ttlMs: options?.ttlMs, ifNotExists: true });
|
|
1082
|
+
}
|
|
1083
|
+
async delete(key) {
|
|
1084
|
+
this.throwIfClosed();
|
|
1085
|
+
const existed = this.getLive(key) !== null;
|
|
1086
|
+
this.map.delete(key);
|
|
1087
|
+
return existed;
|
|
1088
|
+
}
|
|
1089
|
+
async exists(key) {
|
|
1090
|
+
this.throwIfClosed();
|
|
1091
|
+
return this.getLive(key) !== null;
|
|
1092
|
+
}
|
|
1093
|
+
async cas(key, expected, next, options) {
|
|
1094
|
+
this.throwIfClosed();
|
|
1095
|
+
const current = this.getLive(key);
|
|
1096
|
+
const currentValue = current ? current.value : null;
|
|
1097
|
+
if (JSON.stringify(currentValue) !== JSON.stringify(expected)) {
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
const expiresAt = typeof options?.ttlMs === "number" ? Date.now() + options.ttlMs : current?.expiresAt ?? null;
|
|
1101
|
+
this.map.set(key, { value: next, expiresAt });
|
|
1102
|
+
return true;
|
|
1103
|
+
}
|
|
1104
|
+
async incr(key, delta = 1, options) {
|
|
1105
|
+
this.throwIfClosed();
|
|
1106
|
+
if (!Number.isFinite(delta)) {
|
|
1107
|
+
throw new Error("incr delta must be a finite number");
|
|
1108
|
+
}
|
|
1109
|
+
const current = this.getLive(key);
|
|
1110
|
+
if (current && typeof current.value !== "number") {
|
|
1111
|
+
throw new Error(`incr target "${key}" is not a number`);
|
|
1112
|
+
}
|
|
1113
|
+
const nextValue = (current ? current.value : 0) + delta;
|
|
1114
|
+
const expiresAt = typeof options?.ttlMs === "number" ? Date.now() + options.ttlMs : current?.expiresAt ?? null;
|
|
1115
|
+
this.map.set(key, { value: nextValue, expiresAt });
|
|
1116
|
+
return nextValue;
|
|
1117
|
+
}
|
|
1118
|
+
async ttl(key) {
|
|
1119
|
+
this.throwIfClosed();
|
|
1120
|
+
const e = this.getLive(key);
|
|
1121
|
+
if (!e || e.expiresAt === null) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
return Math.max(0, e.expiresAt - Date.now());
|
|
1125
|
+
}
|
|
1126
|
+
async expire(key, ttlMs) {
|
|
1127
|
+
this.throwIfClosed();
|
|
1128
|
+
const e = this.getLive(key);
|
|
1129
|
+
if (!e) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
e.expiresAt = Date.now() + ttlMs;
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
async persist(key) {
|
|
1136
|
+
this.throwIfClosed();
|
|
1137
|
+
const e = this.getLive(key);
|
|
1138
|
+
if (!e) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
e.expiresAt = null;
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
scan(prefix = "", _options) {
|
|
1145
|
+
const self = this;
|
|
1146
|
+
return {
|
|
1147
|
+
async *[Symbol.asyncIterator]() {
|
|
1148
|
+
self.throwIfClosed();
|
|
1149
|
+
for (const [key, entry] of [...self.map.entries()]) {
|
|
1150
|
+
if (self.isExpired(entry)) {
|
|
1151
|
+
self.map.delete(key);
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (!key.startsWith(prefix)) {
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
yield { key, value: entry.value };
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
async ping() {
|
|
1163
|
+
return { ok: !this.closed, latencyMs: 0 };
|
|
1164
|
+
}
|
|
1165
|
+
async close() {
|
|
1166
|
+
this.closed = true;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
function createInMemoryKVStore() {
|
|
1170
|
+
return new InMemoryKVStore();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export { InMemoryDocumentDatabase, InMemoryKVStore, createInMemoryDocumentDatabase, createInMemoryKVStore, createInfraApiMocks, createMockEnvironmentAPI, createMockPlatformApi, createMockPluginAPI, createMockPluginContextV3, createMockRuntime, createMockSnapshotAPI, createMockTrace, createMockUI, createMockWorkspaceAPI, createTestContext, setupTestPlatform, testCommand };
|
|
1174
|
+
//# sourceMappingURL=index.js.map
|
|
1175
|
+
//# sourceMappingURL=index.js.map
|