@kognitivedev/cloud-web-search 0.2.28

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.
@@ -0,0 +1,575 @@
1
+ import { CloudWebSearchValidationError } from "./errors";
2
+ import type {
3
+ CloudWebSearchDecodeOptions,
4
+ CloudWebSearchExecutionParameters,
5
+ CloudWebSearchJobEventRecord,
6
+ CloudWebSearchJobRecord,
7
+ CloudWebSearchJobResultEnvelope,
8
+ CloudWebSearchJobStatus,
9
+ CloudWebSearchMode,
10
+ CloudWebSearchProgressEntry,
11
+ CloudWebSearchResult,
12
+ CloudWebSearchSource,
13
+ CloudWebSearchTimeRange,
14
+ CreateCloudWebSearchJobInput,
15
+ ResearchPlan,
16
+ ResearchPlanTodo,
17
+ } from "./types";
18
+
19
+ const JOB_STATUSES = new Set<CloudWebSearchJobStatus>(["queued", "in_progress", "completed", "error", "cancelled"]);
20
+ const MODES = new Set<CloudWebSearchMode>(["search", "research"]);
21
+ const TIME_RANGES = new Set<CloudWebSearchTimeRange>(["day", "week", "month", "year"]);
22
+
23
+ type PlainObject = Record<string, unknown>;
24
+
25
+ export function isPlainObject(value: unknown): value is PlainObject {
26
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
27
+ return false;
28
+ }
29
+ const prototype = Object.getPrototypeOf(value);
30
+ return prototype === Object.prototype || prototype === null;
31
+ }
32
+
33
+ export function cloneUnknown<T>(value: T): T {
34
+ if (Array.isArray(value)) {
35
+ return value.map((entry) => cloneUnknown(entry)) as T;
36
+ }
37
+ if (value instanceof Date) {
38
+ return new Date(value.getTime()) as T;
39
+ }
40
+ if (isPlainObject(value)) {
41
+ const cloned: Record<string, unknown> = {};
42
+ for (const [key, entry] of Object.entries(value)) {
43
+ cloned[key] = cloneUnknown(entry);
44
+ }
45
+ return cloned as T;
46
+ }
47
+ return value;
48
+ }
49
+
50
+ export function isJobStatus(value: unknown): value is CloudWebSearchJobStatus {
51
+ return typeof value === "string" && JOB_STATUSES.has(value as CloudWebSearchJobStatus);
52
+ }
53
+
54
+ function isMode(value: unknown): value is CloudWebSearchMode {
55
+ return typeof value === "string" && MODES.has(value as CloudWebSearchMode);
56
+ }
57
+
58
+ function isTimeRange(value: unknown): value is CloudWebSearchTimeRange {
59
+ return typeof value === "string" && TIME_RANGES.has(value as CloudWebSearchTimeRange);
60
+ }
61
+
62
+ function invalid(path: string, expected: string, actual: unknown): never {
63
+ throw new CloudWebSearchValidationError(path, expected, actual);
64
+ }
65
+
66
+ function expectObject(value: unknown, path: string): PlainObject {
67
+ if (!isPlainObject(value)) {
68
+ invalid(path, "object", value);
69
+ }
70
+ return value;
71
+ }
72
+
73
+ function expectArray(value: unknown, path: string): unknown[] {
74
+ if (!Array.isArray(value)) {
75
+ invalid(path, "array", value);
76
+ }
77
+ return value;
78
+ }
79
+
80
+ function expectString(value: unknown, path: string): string {
81
+ if (typeof value !== "string") {
82
+ invalid(path, "string", value);
83
+ }
84
+ return value;
85
+ }
86
+
87
+ function expectNonEmptyString(value: unknown, path: string): string {
88
+ const stringValue = expectString(value, path).trim();
89
+ if (!stringValue) {
90
+ invalid(path, "non-empty string", value);
91
+ }
92
+ return stringValue;
93
+ }
94
+
95
+ function expectNullableString(value: unknown, path: string): string | null {
96
+ if (value === null) {
97
+ return null;
98
+ }
99
+ return expectString(value, path);
100
+ }
101
+
102
+ function expectOptionalNullableString(value: unknown, path: string): string | null | undefined {
103
+ if (value === undefined) {
104
+ return undefined;
105
+ }
106
+ return expectNullableString(value, path);
107
+ }
108
+
109
+ function expectFiniteNumber(value: unknown, path: string): number {
110
+ if (typeof value !== "number" || !Number.isFinite(value)) {
111
+ invalid(path, "finite number", value);
112
+ }
113
+ return value;
114
+ }
115
+
116
+ function expectPositiveInteger(value: unknown, path: string): number {
117
+ const numberValue = expectFiniteNumber(value, path);
118
+ if (!Number.isInteger(numberValue) || numberValue <= 0) {
119
+ invalid(path, "positive integer", value);
120
+ }
121
+ return numberValue;
122
+ }
123
+
124
+ function expectOptionalRecord(value: unknown, path: string): Record<string, unknown> | undefined {
125
+ if (value === undefined || value === null) {
126
+ return undefined;
127
+ }
128
+ return cloneUnknown(expectObject(value, path));
129
+ }
130
+
131
+ function expectOptionalNullableRecord(value: unknown, path: string): Record<string, unknown> | null | undefined {
132
+ if (value === undefined) {
133
+ return undefined;
134
+ }
135
+ if (value === null) {
136
+ return null;
137
+ }
138
+ return cloneUnknown(expectObject(value, path));
139
+ }
140
+
141
+ function expectDateish(value: unknown, path: string): string | Date {
142
+ if (typeof value === "string") {
143
+ return value;
144
+ }
145
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
146
+ return new Date(value.getTime());
147
+ }
148
+ invalid(path, "ISO timestamp string or Date", value);
149
+ }
150
+
151
+ function expectOptionalNullableDateish(value: unknown, path: string): string | Date | null | undefined {
152
+ if (value === undefined) {
153
+ return undefined;
154
+ }
155
+ if (value === null) {
156
+ return null;
157
+ }
158
+ return expectDateish(value, path);
159
+ }
160
+
161
+ function normalizeTimestamp(value: unknown, path: string): string {
162
+ if (typeof value === "string") {
163
+ return value;
164
+ }
165
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
166
+ return value.toISOString();
167
+ }
168
+ invalid(path, "ISO timestamp string or Date", value);
169
+ }
170
+
171
+ function parseOutput<TOutput>(
172
+ value: unknown,
173
+ options: CloudWebSearchDecodeOptions<TOutput> | undefined,
174
+ ): TOutput {
175
+ if (options?.parseOutput) {
176
+ return options.parseOutput(cloneUnknown(value));
177
+ }
178
+ return cloneUnknown(value) as TOutput;
179
+ }
180
+
181
+ function decodeStringArray(value: unknown, path: string): string[] {
182
+ return expectArray(value, path).map((entry, index) => expectString(entry, `${path}[${index}]`));
183
+ }
184
+
185
+ export function decodeCloudWebSearchSource(value: unknown, path: string): CloudWebSearchSource {
186
+ const object = expectObject(value, path);
187
+ const source: CloudWebSearchSource = {};
188
+
189
+ if ("title" in object) {
190
+ source.title = object.title === null ? null : expectString(object.title, `${path}.title`);
191
+ }
192
+ if ("url" in object) {
193
+ source.url = object.url === null ? null : expectString(object.url, `${path}.url`);
194
+ }
195
+ if ("host" in object) {
196
+ source.host = object.host === null ? null : expectString(object.host, `${path}.host`);
197
+ }
198
+ if ("snippet" in object) {
199
+ source.snippet = object.snippet === null ? null : expectString(object.snippet, `${path}.snippet`);
200
+ }
201
+ if ("relevantContent" in object) {
202
+ source.relevantContent = object.relevantContent === null
203
+ ? null
204
+ : expectString(object.relevantContent, `${path}.relevantContent`);
205
+ }
206
+ if ("keyFacts" in object) {
207
+ source.keyFacts = decodeStringArray(object.keyFacts, `${path}.keyFacts`);
208
+ }
209
+ if ("confidence" in object) {
210
+ if (object.confidence === null) {
211
+ source.confidence = null;
212
+ } else {
213
+ source.confidence = expectFiniteNumber(object.confidence, `${path}.confidence`);
214
+ }
215
+ }
216
+ if ("score" in object) {
217
+ if (object.score === null) {
218
+ source.score = null;
219
+ } else {
220
+ source.score = expectFiniteNumber(object.score, `${path}.score`);
221
+ }
222
+ }
223
+ if ("faviconUrl" in object) {
224
+ source.faviconUrl = object.faviconUrl === null ? null : expectString(object.faviconUrl, `${path}.faviconUrl`);
225
+ }
226
+
227
+ return source;
228
+ }
229
+
230
+ export function decodeCloudWebSearchResult<TOutput = unknown>(
231
+ value: unknown,
232
+ options?: CloudWebSearchDecodeOptions<TOutput>,
233
+ path = "result",
234
+ ): CloudWebSearchResult<TOutput> {
235
+ const object = expectObject(value, path);
236
+ const sources = expectArray(object.sources, `${path}.sources`).map((source, index) =>
237
+ decodeCloudWebSearchSource(source, `${path}.sources[${index}]`));
238
+
239
+ const result: CloudWebSearchResult<TOutput> = {
240
+ sources,
241
+ };
242
+
243
+ if ("output" in object) {
244
+ result.output = parseOutput(object.output, options);
245
+ }
246
+ if ("text" in object) {
247
+ result.text = object.text === null ? null : expectString(object.text, `${path}.text`);
248
+ }
249
+
250
+ const metadata = expectOptionalRecord(object.metadata, `${path}.metadata`);
251
+ if (metadata !== undefined) {
252
+ result.metadata = metadata;
253
+ }
254
+
255
+ return result;
256
+ }
257
+
258
+ export function decodeCloudWebSearchProgressEntry(value: unknown, path: string): CloudWebSearchProgressEntry {
259
+ const object = expectObject(value, path);
260
+ const metadata = expectOptionalRecord(object.metadata, `${path}.metadata`);
261
+
262
+ return {
263
+ stage: expectNonEmptyString(object.stage, `${path}.stage`),
264
+ message: expectNonEmptyString(object.message, `${path}.message`),
265
+ timestamp: normalizeTimestamp(object.timestamp, `${path}.timestamp`),
266
+ ...(metadata ? { metadata } : {}),
267
+ };
268
+ }
269
+
270
+ function decodeResearchPlanTodo(value: unknown, path: string): ResearchPlanTodo {
271
+ const object = expectObject(value, path);
272
+
273
+ return {
274
+ id: expectNonEmptyString(object.id, `${path}.id`),
275
+ title: expectNonEmptyString(object.title, `${path}.title`),
276
+ objective: expectNonEmptyString(object.objective, `${path}.objective`),
277
+ ...(object.searchQuery === undefined ? {} : {
278
+ searchQuery: expectString(object.searchQuery, `${path}.searchQuery`),
279
+ }),
280
+ ...(object.rationale === undefined ? {} : {
281
+ rationale: expectString(object.rationale, `${path}.rationale`),
282
+ }),
283
+ };
284
+ }
285
+
286
+ function decodeResearchPlan(value: unknown, path: string): ResearchPlan {
287
+ const object = expectObject(value, path);
288
+
289
+ return {
290
+ summary: expectNonEmptyString(object.summary, `${path}.summary`),
291
+ todos: expectArray(object.todos, `${path}.todos`).map((todo, index) =>
292
+ decodeResearchPlanTodo(todo, `${path}.todos[${index}]`)),
293
+ };
294
+ }
295
+
296
+ export function decodeCloudWebSearchJobRecord<TOutput = unknown>(
297
+ value: unknown,
298
+ options?: CloudWebSearchDecodeOptions<TOutput>,
299
+ path = "job",
300
+ ): CloudWebSearchJobRecord<TOutput> {
301
+ const object = expectObject(value, path);
302
+ const mode = object.mode;
303
+ if (!isMode(mode)) {
304
+ invalid(`${path}.mode`, "\"search\" or \"research\"", mode);
305
+ }
306
+ const status = object.status;
307
+ if (!isJobStatus(status)) {
308
+ invalid(`${path}.status`, "\"queued\", \"in_progress\", \"completed\", \"error\", or \"cancelled\"", status);
309
+ }
310
+
311
+ const record: CloudWebSearchJobRecord<TOutput> = {
312
+ id: expectNonEmptyString(object.id, `${path}.id`),
313
+ projectId: expectNonEmptyString(object.projectId, `${path}.projectId`),
314
+ mode,
315
+ parameters: cloneUnknown(expectObject(object.parameters, `${path}.parameters`)),
316
+ status,
317
+ progress: expectArray(object.progress, `${path}.progress`).map((entry, index) =>
318
+ decodeCloudWebSearchProgressEntry(entry, `${path}.progress[${index}]`)),
319
+ createdAt: expectDateish(object.createdAt, `${path}.createdAt`),
320
+ updatedAt: expectDateish(object.updatedAt, `${path}.updatedAt`),
321
+ };
322
+
323
+ if ("searchQuery" in object) {
324
+ record.searchQuery = expectOptionalNullableString(object.searchQuery, `${path}.searchQuery`);
325
+ }
326
+ if ("searchInstructions" in object) {
327
+ record.searchInstructions = expectOptionalNullableString(object.searchInstructions, `${path}.searchInstructions`);
328
+ }
329
+ if ("responseInstructions" in object) {
330
+ record.responseInstructions = expectOptionalNullableString(object.responseInstructions, `${path}.responseInstructions`);
331
+ }
332
+ if ("responseSchema" in object) {
333
+ record.responseSchema = expectOptionalNullableRecord(object.responseSchema, `${path}.responseSchema`);
334
+ }
335
+ if ("researchPlan" in object) {
336
+ record.researchPlan = object.researchPlan === null
337
+ ? null
338
+ : decodeResearchPlan(object.researchPlan, `${path}.researchPlan`);
339
+ }
340
+ if ("results" in object) {
341
+ record.results = object.results === null
342
+ ? null
343
+ : decodeCloudWebSearchResult(object.results, options, `${path}.results`);
344
+ }
345
+ if ("errorMessage" in object) {
346
+ record.errorMessage = expectOptionalNullableString(object.errorMessage, `${path}.errorMessage`);
347
+ }
348
+ if ("cancelRequestedAt" in object) {
349
+ record.cancelRequestedAt = expectOptionalNullableDateish(object.cancelRequestedAt, `${path}.cancelRequestedAt`);
350
+ }
351
+ if ("cancelledAt" in object) {
352
+ record.cancelledAt = expectOptionalNullableDateish(object.cancelledAt, `${path}.cancelledAt`);
353
+ }
354
+
355
+ return record;
356
+ }
357
+
358
+ export function decodeCloudWebSearchJobEventRecord(
359
+ value: unknown,
360
+ path = "event",
361
+ ): CloudWebSearchJobEventRecord {
362
+ const object = expectObject(value, path);
363
+
364
+ return {
365
+ id: expectNonEmptyString(object.id, `${path}.id`),
366
+ jobId: expectNonEmptyString(object.jobId, `${path}.jobId`),
367
+ projectId: expectNonEmptyString(object.projectId, `${path}.projectId`),
368
+ eventType: expectNonEmptyString(object.eventType, `${path}.eventType`),
369
+ stage: expectNullableString(object.stage, `${path}.stage`),
370
+ status: expectNullableString(object.status, `${path}.status`),
371
+ message: expectNullableString(object.message, `${path}.message`),
372
+ payload: cloneUnknown(object.payload),
373
+ createdAt: expectDateish(object.createdAt, `${path}.createdAt`),
374
+ };
375
+ }
376
+
377
+ export function decodeCloudWebSearchJobListEnvelope<TOutput = unknown>(
378
+ value: unknown,
379
+ options?: CloudWebSearchDecodeOptions<TOutput>,
380
+ path = "jobs.list",
381
+ ): { jobs: CloudWebSearchJobRecord<TOutput>[] } {
382
+ const object = expectObject(value, path);
383
+
384
+ return {
385
+ jobs: expectArray(object.jobs, `${path}.jobs`).map((job, index) =>
386
+ decodeCloudWebSearchJobRecord(job, options, `${path}.jobs[${index}]`)),
387
+ };
388
+ }
389
+
390
+ export function decodeCloudWebSearchJobResultEnvelope<TOutput = unknown>(
391
+ value: unknown,
392
+ options?: CloudWebSearchDecodeOptions<TOutput>,
393
+ path = "jobs.result",
394
+ ): CloudWebSearchJobResultEnvelope<TOutput> {
395
+ const object = expectObject(value, path);
396
+
397
+ return {
398
+ jobId: expectNonEmptyString(object.jobId, `${path}.jobId`),
399
+ status: expectString(object.status, `${path}.status`) === "completed"
400
+ ? "completed"
401
+ : invalid(`${path}.status`, "\"completed\"", object.status),
402
+ result: object.result === null
403
+ ? null
404
+ : decodeCloudWebSearchResult(object.result, options, `${path}.result`),
405
+ };
406
+ }
407
+
408
+ export function decodeJobSnapshotPayload<TOutput = unknown>(
409
+ value: unknown,
410
+ options?: CloudWebSearchDecodeOptions<TOutput>,
411
+ path = "stream.job.snapshot",
412
+ ): { job: CloudWebSearchJobRecord<TOutput> } {
413
+ const object = expectObject(value, path);
414
+
415
+ return {
416
+ job: decodeCloudWebSearchJobRecord(object.job, options, `${path}.job`),
417
+ };
418
+ }
419
+
420
+ export function decodeDirectProgressPayload(
421
+ value: unknown,
422
+ path = "stream.job.progress",
423
+ ): { jobId: string; status: CloudWebSearchJobStatus; progress: CloudWebSearchProgressEntry } {
424
+ const object = expectObject(value, path);
425
+ const status = object.status;
426
+ if (!isJobStatus(status)) {
427
+ invalid(`${path}.status`, "\"queued\", \"in_progress\", \"completed\", \"error\", or \"cancelled\"", status);
428
+ }
429
+
430
+ return {
431
+ jobId: expectNonEmptyString(object.jobId, `${path}.jobId`),
432
+ status,
433
+ progress: decodeCloudWebSearchProgressEntry(object.progress, `${path}.progress`),
434
+ };
435
+ }
436
+
437
+ export function decodeDirectCompletedPayload<TOutput = unknown>(
438
+ value: unknown,
439
+ options?: CloudWebSearchDecodeOptions<TOutput>,
440
+ path = "stream.job.completed",
441
+ ): { job: CloudWebSearchJobRecord<TOutput>; result: CloudWebSearchResult<TOutput> | null } {
442
+ const object = expectObject(value, path);
443
+
444
+ return {
445
+ job: decodeCloudWebSearchJobRecord(object.job, options, `${path}.job`),
446
+ result: object.result === null
447
+ ? null
448
+ : decodeCloudWebSearchResult(object.result, options, `${path}.result`),
449
+ };
450
+ }
451
+
452
+ export function decodeDirectErrorPayload<TOutput = unknown>(
453
+ value: unknown,
454
+ options?: CloudWebSearchDecodeOptions<TOutput>,
455
+ path = "stream.job.error",
456
+ ): { job: CloudWebSearchJobRecord<TOutput>; errorMessage: string } {
457
+ const object = expectObject(value, path);
458
+
459
+ return {
460
+ job: decodeCloudWebSearchJobRecord(object.job, options, `${path}.job`),
461
+ errorMessage: expectNonEmptyString(object.errorMessage, `${path}.errorMessage`),
462
+ };
463
+ }
464
+
465
+ export function decodeDirectCancelledPayload<TOutput = unknown>(
466
+ value: unknown,
467
+ options?: CloudWebSearchDecodeOptions<TOutput>,
468
+ path = "stream.job.cancelled",
469
+ ): { job: CloudWebSearchJobRecord<TOutput> } {
470
+ const object = expectObject(value, path);
471
+
472
+ return {
473
+ job: decodeCloudWebSearchJobRecord(object.job, options, `${path}.job`),
474
+ };
475
+ }
476
+
477
+ export function decodeKeepalivePayload(
478
+ value: unknown,
479
+ path = "stream.job.keepalive",
480
+ ): { jobId: string; timestamp: string } {
481
+ const object = expectObject(value, path);
482
+
483
+ return {
484
+ jobId: expectNonEmptyString(object.jobId, `${path}.jobId`),
485
+ timestamp: normalizeTimestamp(object.timestamp, `${path}.timestamp`),
486
+ };
487
+ }
488
+
489
+ function decodeExecutionParameters(
490
+ value: unknown,
491
+ path: string,
492
+ ): CloudWebSearchExecutionParameters {
493
+ const object = expectObject(value, path);
494
+ const parameters: CloudWebSearchExecutionParameters = {};
495
+
496
+ for (const [key, entry] of Object.entries(object)) {
497
+ switch (key) {
498
+ case "maxResults":
499
+ case "maxPages":
500
+ parameters[key] = expectPositiveInteger(entry, `${path}.${key}`);
501
+ break;
502
+ case "region":
503
+ parameters.region = expectNonEmptyString(entry, `${path}.region`);
504
+ break;
505
+ case "timeRange":
506
+ if (!isTimeRange(entry)) {
507
+ invalid(`${path}.timeRange`, "\"day\", \"week\", \"month\", or \"year\"", entry);
508
+ }
509
+ parameters.timeRange = entry;
510
+ break;
511
+ case "includeDomains":
512
+ parameters.includeDomains = decodeStringArray(entry, `${path}.includeDomains`);
513
+ break;
514
+ case "excludeDomains":
515
+ parameters.excludeDomains = decodeStringArray(entry, `${path}.excludeDomains`);
516
+ break;
517
+ case "outputSchema":
518
+ parameters.outputSchema = cloneUnknown(expectObject(entry, `${path}.outputSchema`));
519
+ break;
520
+ default:
521
+ parameters[key] = cloneUnknown(entry);
522
+ }
523
+ }
524
+
525
+ return parameters;
526
+ }
527
+
528
+ export function validateCreateCloudWebSearchJobInput(
529
+ value: CreateCloudWebSearchJobInput,
530
+ path = "input",
531
+ ): CreateCloudWebSearchJobInput {
532
+ const object = expectObject(value, path);
533
+ const mode = object.mode;
534
+ if (!isMode(mode)) {
535
+ invalid(`${path}.mode`, "\"search\" or \"research\"", mode);
536
+ }
537
+
538
+ if (mode === "search") {
539
+ if ("instructions" in object) {
540
+ invalid(`${path}.instructions`, "field to be omitted in search mode", object.instructions);
541
+ }
542
+ if ("responseInstructions" in object) {
543
+ invalid(`${path}.responseInstructions`, "field to be omitted in search mode", object.responseInstructions);
544
+ }
545
+ if ("responseSchema" in object) {
546
+ invalid(`${path}.responseSchema`, "field to be omitted in search mode", object.responseSchema);
547
+ }
548
+
549
+ return {
550
+ mode,
551
+ query: expectNonEmptyString(object.query, `${path}.query`),
552
+ ...(object.parameters === undefined ? {} : {
553
+ parameters: decodeExecutionParameters(object.parameters, `${path}.parameters`),
554
+ }),
555
+ };
556
+ }
557
+
558
+ if ("query" in object) {
559
+ invalid(`${path}.query`, "field to be omitted in research mode", object.query);
560
+ }
561
+
562
+ return {
563
+ mode,
564
+ instructions: expectNonEmptyString(object.instructions, `${path}.instructions`),
565
+ ...(object.responseInstructions === undefined ? {} : {
566
+ responseInstructions: expectNonEmptyString(object.responseInstructions, `${path}.responseInstructions`),
567
+ }),
568
+ ...(object.responseSchema === undefined ? {} : {
569
+ responseSchema: cloneUnknown(expectObject(object.responseSchema, `${path}.responseSchema`)),
570
+ }),
571
+ ...(object.parameters === undefined ? {} : {
572
+ parameters: decodeExecutionParameters(object.parameters, `${path}.parameters`),
573
+ }),
574
+ };
575
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "rootDir": "src",
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "noEmit": false,
9
+ "incremental": false
10
+ },
11
+ "include": ["src"],
12
+ "exclude": ["src/__tests__"]
13
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ },
8
+ });