donobu 5.29.0 → 5.31.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.
Files changed (43) hide show
  1. package/dist/esm/lib/test/testExtension.d.ts +31 -1
  2. package/dist/esm/lib/test/testExtension.js +273 -0
  3. package/dist/esm/managers/DonobuStack.js +1 -1
  4. package/dist/esm/managers/TestsManager.d.ts +2 -2
  5. package/dist/esm/managers/TestsManager.js +10 -0
  6. package/dist/esm/models/PaginatedResult.d.ts +15 -0
  7. package/dist/esm/models/PaginatedResult.js +13 -2
  8. package/dist/esm/models/TestMetadata.d.ts +630 -0
  9. package/dist/esm/models/TestMetadata.js +10 -1
  10. package/dist/esm/persistence/tests/TestsPersistence.d.ts +2 -2
  11. package/dist/esm/persistence/tests/TestsPersistenceDonobuApi.d.ts +3 -3
  12. package/dist/esm/persistence/tests/TestsPersistenceDonobuApi.js +18 -2
  13. package/dist/esm/persistence/tests/TestsPersistenceRegistry.d.ts +9 -1
  14. package/dist/esm/persistence/tests/TestsPersistenceRegistry.js +9 -2
  15. package/dist/esm/persistence/tests/TestsPersistenceSqlite.d.ts +2 -1
  16. package/dist/esm/persistence/tests/TestsPersistenceSqlite.js +65 -6
  17. package/dist/esm/persistence/tests/TestsPersistenceVolatile.d.ts +22 -3
  18. package/dist/esm/persistence/tests/TestsPersistenceVolatile.js +69 -4
  19. package/dist/esm/reporter/buildReport.js +1 -0
  20. package/dist/esm/reporter/render.js +3 -0
  21. package/dist/esm/reporter/renderMarkdown.js +7 -1
  22. package/dist/lib/test/testExtension.d.ts +31 -1
  23. package/dist/lib/test/testExtension.js +273 -0
  24. package/dist/managers/DonobuStack.js +1 -1
  25. package/dist/managers/TestsManager.d.ts +2 -2
  26. package/dist/managers/TestsManager.js +10 -0
  27. package/dist/models/PaginatedResult.d.ts +15 -0
  28. package/dist/models/PaginatedResult.js +13 -2
  29. package/dist/models/TestMetadata.d.ts +630 -0
  30. package/dist/models/TestMetadata.js +10 -1
  31. package/dist/persistence/tests/TestsPersistence.d.ts +2 -2
  32. package/dist/persistence/tests/TestsPersistenceDonobuApi.d.ts +3 -3
  33. package/dist/persistence/tests/TestsPersistenceDonobuApi.js +18 -2
  34. package/dist/persistence/tests/TestsPersistenceRegistry.d.ts +9 -1
  35. package/dist/persistence/tests/TestsPersistenceRegistry.js +9 -2
  36. package/dist/persistence/tests/TestsPersistenceSqlite.d.ts +2 -1
  37. package/dist/persistence/tests/TestsPersistenceSqlite.js +65 -6
  38. package/dist/persistence/tests/TestsPersistenceVolatile.d.ts +22 -3
  39. package/dist/persistence/tests/TestsPersistenceVolatile.js +69 -4
  40. package/dist/reporter/buildReport.js +1 -0
  41. package/dist/reporter/render.js +3 -0
  42. package/dist/reporter/renderMarkdown.js +7 -1
  43. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { PlaywrightWorkerOptions } from '@playwright/test';
1
+ import type { PlaywrightWorkerOptions, TestInfo } from '@playwright/test';
2
2
  import type { GptClient } from '../../clients/GptClient';
3
3
  import type { BrowserStorageState } from '../../models/BrowserStorageState';
4
4
  import { FlowLogBuffer } from '../../utils/FlowLogBuffer';
@@ -9,6 +9,7 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
9
9
  flowId: string;
10
10
  logBuffer: FlowLogBuffer;
11
11
  };
12
+ pwApiStepLogger: void;
12
13
  storageState?: BrowserStorageState | Promise<BrowserStorageState>;
13
14
  gptClient?: GptClient;
14
15
  page: DonobuExtendedPage;
@@ -26,4 +27,33 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
26
27
  */
27
28
  donobuFileUploadDrainGuard: void;
28
29
  }>;
30
+ export declare function emitPlaywrightStepLog(step: any): void;
31
+ /** Reset the source-line cache. Test-only. */
32
+ export declare function _resetSourceLineCacheForTests(): void;
33
+ /**
34
+ * Monkey-patch `testInfo._callbacks.onStepEnd` so each Playwright step in
35
+ * the `pw:api` / `expect` categories emits an `appLogger.info` entry once
36
+ * Playwright signals the step has finished. Returns a teardown function
37
+ * that restores the original callback.
38
+ *
39
+ * Why patch `onStepEnd` and not `_addStep`:
40
+ * Playwright captures the step's source location with a stack walk inside
41
+ * `_addStep` (see playwright/lib/worker/testInfo.js). Patching `_addStep`
42
+ * inserts a Donobu library frame into that stack, and Playwright's
43
+ * built-in filter only excludes `@playwright/test` / `playwright-core`
44
+ * paths — so for steps that don't pre-set `data.location` (most notably
45
+ * `expect`), the captured location lands inside our wrapper in
46
+ * `node_modules/donobu/...`. The downstream node_modules filter would
47
+ * then drop the entry. `_callbacks.onStepEnd` runs AFTER the stack
48
+ * capture, so this hook is invisible to Playwright's location resolution.
49
+ *
50
+ * Verified against @playwright/test 1.57+. `_callbacks` / `_stepMap` and
51
+ * the step shape (category, title, params, endWallTime, error, location)
52
+ * are private Playwright internals — install and per-step logging are
53
+ * wrapped in try/catch so any shape drift degrades to a no-op rather than
54
+ * failing the test.
55
+ *
56
+ * Exported for unit testing.
57
+ */
58
+ export declare function installPlaywrightStepLogger(testInfo: TestInfo): () => void;
29
59
  //# sourceMappingURL=testExtension.d.ts.map
@@ -18,6 +18,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.test = void 0;
21
+ exports.emitPlaywrightStepLog = emitPlaywrightStepLog;
22
+ exports._resetSourceLineCacheForTests = _resetSourceLineCacheForTests;
23
+ exports.installPlaywrightStepLogger = installPlaywrightStepLogger;
21
24
  const test_1 = require("@playwright/test");
22
25
  const async_hooks_1 = require("async_hooks");
23
26
  const crypto_1 = require("crypto");
@@ -28,6 +31,7 @@ const v4_1 = require("zod/v4");
28
31
  const envVars_1 = require("../../envVars");
29
32
  const DonobuFlowsManager_1 = require("../../managers/DonobuFlowsManager");
30
33
  const fileUploadWorkerRegistry_1 = require("../../persistence/files/fileUploadWorkerRegistry");
34
+ const ansi_1 = require("../../utils/ansi");
31
35
  const BrowserUtils_1 = require("../../utils/BrowserUtils");
32
36
  const FlowLogBuffer_1 = require("../../utils/FlowLogBuffer");
33
37
  const Logger_1 = require("../../utils/Logger");
@@ -257,6 +261,36 @@ exports.test = test_1.test.extend({
257
261
  },
258
262
  { scope: 'test', auto: true },
259
263
  ],
264
+ /**
265
+ * Auto fixture that monkey-patches `testInfo._addStep` so each Playwright
266
+ * step in the `pw:api` (clicks, fills, navigations) and `expect`
267
+ * (assertions) categories emits an `appLogger.info` entry on completion.
268
+ * Routing native Playwright operations through the same per-flow
269
+ * `FlowLogBuffer` that AI tool-call logs populate makes the live
270
+ * `/api/flows/:id/logs` feed and persisted `logs.json` capture meaningful
271
+ * step-by-step data even for non-AI Playwright tests.
272
+ *
273
+ * Depends on `flowLoggingContext` so the ALS / `processLocalLogBuffer` are
274
+ * already set up by the time our patched `complete` fires.
275
+ *
276
+ * Verified against @playwright/test 1.57. `_addStep` and the TestStepImpl
277
+ * shape (category, title, params, wallTime, endWallTime, error, complete)
278
+ * are private Playwright internals — install and per-step logging are
279
+ * wrapped in try/catch so any shape drift degrades to a no-op rather than
280
+ * failing the test.
281
+ */
282
+ pwApiStepLogger: [
283
+ async ({ flowLoggingContext: _ }, use, testInfo) => {
284
+ const teardown = installPlaywrightStepLogger(testInfo);
285
+ try {
286
+ await use();
287
+ }
288
+ finally {
289
+ teardown();
290
+ }
291
+ },
292
+ { scope: 'test', auto: true },
293
+ ],
260
294
  /**
261
295
  * Drain in-flight file uploads (videos, screenshots, etc.) to Donobu
262
296
  * Cloud at the end of every Playwright worker's lifetime.
@@ -447,6 +481,245 @@ function collectNativeSteps(rawSteps) {
447
481
  result.sort((a, b) => a.endWallTime - b.endWallTime);
448
482
  return result;
449
483
  }
484
+ // ---------------------------------------------------------------------------
485
+ // Live Playwright step → appLogger pipeline
486
+ // ---------------------------------------------------------------------------
487
+ // Used by the `pwApiStepLogger` fixture above to surface native Playwright
488
+ // operations (clicks, fills, navigations, expects) in the same per-flow
489
+ // FlowLogBuffer that AI tool-call logs already populate.
490
+ const LOGGED_STEP_CATEGORIES = new Set([
491
+ 'pw:api',
492
+ 'expect',
493
+ ]);
494
+ const STEP_PARAM_ALLOWLIST = [
495
+ 'url',
496
+ 'selector',
497
+ 'text',
498
+ 'value',
499
+ 'timeout',
500
+ 'name',
501
+ 'key',
502
+ // For expect steps Playwright stores the asserted value here
503
+ // (e.g., `expect(x).toBe('$51.00')` → params: { expected: '$51.00' }).
504
+ 'expected',
505
+ ];
506
+ /**
507
+ * Pull a small, JSON-safe subset of `step.params` for logging. Playwright's
508
+ * step.params can hold non-serializable values (Locator handles, Page refs,
509
+ * etc.) that would either cycle or bloat the log feed; we keep only known
510
+ * primitive fields.
511
+ */
512
+ function pickStepParams(rawParams) {
513
+ if (!rawParams || typeof rawParams !== 'object') {
514
+ return undefined;
515
+ }
516
+ const result = {};
517
+ let hasAny = false;
518
+ for (const key of STEP_PARAM_ALLOWLIST) {
519
+ const value = rawParams[key];
520
+ if (value === undefined) {
521
+ continue;
522
+ }
523
+ const t = typeof value;
524
+ if (t === 'string' || t === 'number' || t === 'boolean') {
525
+ result[key] = value;
526
+ hasAny = true;
527
+ }
528
+ }
529
+ return hasAny ? result : undefined;
530
+ }
531
+ const NODE_MODULES_LOCATION_REGEX = /[\\/]node_modules[\\/]/;
532
+ function emitPlaywrightStepLog(step) {
533
+ const category = step?.category ?? '';
534
+ if (!LOGGED_STEP_CATEGORIES.has(category)) {
535
+ return;
536
+ }
537
+ // Drop steps that originated inside a third-party (or Donobu library)
538
+ // package. The user's test code never lives in node_modules, so any step
539
+ // attributed there is plumbing — Donobu's own AssertTool, internal CDP
540
+ // wrappers, etc. — not the user's intent.
541
+ const locationFile = (step.location &&
542
+ typeof step.location === 'object' &&
543
+ typeof (step.location.file ?? step.location.fileName) === 'string' &&
544
+ (step.location.file ?? step.location.fileName)) ||
545
+ '';
546
+ if (locationFile && NODE_MODULES_LOCATION_REGEX.test(locationFile)) {
547
+ return;
548
+ }
549
+ const title = step.title ?? '';
550
+ const startedAt = typeof step.wallTime === 'number' ? step.wallTime : undefined;
551
+ const completedAt = typeof step.endWallTime === 'number' ? step.endWallTime : undefined;
552
+ const durationMs = startedAt !== undefined && completedAt !== undefined
553
+ ? completedAt - startedAt
554
+ : undefined;
555
+ const error = step.error && typeof step.error === 'object'
556
+ ? {
557
+ name: typeof step.error.name === 'string' ? step.error.name : 'Error',
558
+ // Playwright matchers emit ANSI-colored diff output. Strip it so
559
+ // raw escape sequences don't leak into the log feed.
560
+ message: (0, ansi_1.stripAnsi)(typeof step.error.message === 'string' ? step.error.message : ''),
561
+ }
562
+ : undefined;
563
+ const location = step.location && typeof step.location === 'object'
564
+ ? {
565
+ file: step.location.file ?? step.location.fileName ?? '',
566
+ line: step.location.line ?? 0,
567
+ column: step.location.column ?? 0,
568
+ }
569
+ : undefined;
570
+ const params = pickStepParams(step.params);
571
+ // Default message: Playwright's title verbatim, plus a primitive
572
+ // `expected` value for matchers Playwright didn't already inline.
573
+ let message = `${title}${formatExpectedSuffix(title, params?.expected)}`;
574
+ // For expect steps, prefer the actual user source line at
575
+ // location.file:line — `Expect "toBe"` is too bare; the source shows
576
+ // both the LHS being asserted and the matcher together
577
+ // (`expect(text).toBe('$51.01')`). Fall back to the matcher message if
578
+ // the source can't be read.
579
+ if (category === 'expect' && location?.file && location.line > 0) {
580
+ const sourceLine = readSourceLine(location.file, location.line);
581
+ if (sourceLine) {
582
+ message = sourceLine;
583
+ }
584
+ }
585
+ const meta = {
586
+ category,
587
+ title,
588
+ params,
589
+ durationMs,
590
+ startedAt,
591
+ completedAt,
592
+ error,
593
+ location,
594
+ };
595
+ // A populated `step.error` means Playwright reported the step as failed —
596
+ // surface it at error level so it stands out in the feed.
597
+ if (error) {
598
+ Logger_1.appLogger.error(message, meta);
599
+ }
600
+ else {
601
+ Logger_1.appLogger.info(message, meta);
602
+ }
603
+ }
604
+ function formatExpectedSuffix(title, expected) {
605
+ if (expected === undefined || expected === null) {
606
+ return '';
607
+ }
608
+ const t = typeof expected;
609
+ if (t !== 'string' && t !== 'number' && t !== 'boolean') {
610
+ return '';
611
+ }
612
+ const formatted = JSON.stringify(expected);
613
+ if (title.includes(formatted)) {
614
+ return '';
615
+ }
616
+ return ` ${formatted}`;
617
+ }
618
+ /**
619
+ * Per-worker cache of source file contents (split into lines), keyed by
620
+ * absolute file path. Source files don't change during a worker's
621
+ * lifetime, so one read per file is sufficient.
622
+ *
623
+ * `null` means a previous read failed; we cache that to avoid retrying.
624
+ */
625
+ const sourceLineCache = new Map();
626
+ /** Reset the source-line cache. Test-only. */
627
+ function _resetSourceLineCacheForTests() {
628
+ sourceLineCache.clear();
629
+ }
630
+ /**
631
+ * Read the trimmed line at `file:line` (1-based), with a per-worker
632
+ * cache. Drops a trailing semicolon for cleanliness. Returns undefined
633
+ * if the file or line can't be read.
634
+ */
635
+ function readSourceLine(file, line) {
636
+ let lines = sourceLineCache.get(file);
637
+ if (lines === undefined) {
638
+ try {
639
+ lines = (0, fs_1.readFileSync)(file, 'utf8').split('\n');
640
+ }
641
+ catch {
642
+ lines = null;
643
+ }
644
+ sourceLineCache.set(file, lines);
645
+ }
646
+ if (!lines) {
647
+ return undefined;
648
+ }
649
+ const raw = lines[line - 1];
650
+ if (typeof raw !== 'string') {
651
+ return undefined;
652
+ }
653
+ let trimmed = raw.trim();
654
+ if (trimmed.endsWith(';')) {
655
+ trimmed = trimmed.slice(0, -1);
656
+ }
657
+ return trimmed || undefined;
658
+ }
659
+ /**
660
+ * Monkey-patch `testInfo._callbacks.onStepEnd` so each Playwright step in
661
+ * the `pw:api` / `expect` categories emits an `appLogger.info` entry once
662
+ * Playwright signals the step has finished. Returns a teardown function
663
+ * that restores the original callback.
664
+ *
665
+ * Why patch `onStepEnd` and not `_addStep`:
666
+ * Playwright captures the step's source location with a stack walk inside
667
+ * `_addStep` (see playwright/lib/worker/testInfo.js). Patching `_addStep`
668
+ * inserts a Donobu library frame into that stack, and Playwright's
669
+ * built-in filter only excludes `@playwright/test` / `playwright-core`
670
+ * paths — so for steps that don't pre-set `data.location` (most notably
671
+ * `expect`), the captured location lands inside our wrapper in
672
+ * `node_modules/donobu/...`. The downstream node_modules filter would
673
+ * then drop the entry. `_callbacks.onStepEnd` runs AFTER the stack
674
+ * capture, so this hook is invisible to Playwright's location resolution.
675
+ *
676
+ * Verified against @playwright/test 1.57+. `_callbacks` / `_stepMap` and
677
+ * the step shape (category, title, params, endWallTime, error, location)
678
+ * are private Playwright internals — install and per-step logging are
679
+ * wrapped in try/catch so any shape drift degrades to a no-op rather than
680
+ * failing the test.
681
+ *
682
+ * Exported for unit testing.
683
+ */
684
+ function installPlaywrightStepLogger(testInfo) {
685
+ const ti = testInfo;
686
+ const callbacks = ti._callbacks;
687
+ const stepMap = ti._stepMap;
688
+ const originalOnStepEnd = callbacks && typeof callbacks.onStepEnd === 'function'
689
+ ? callbacks.onStepEnd
690
+ : null;
691
+ if (!callbacks || !originalOnStepEnd || !stepMap) {
692
+ return () => { };
693
+ }
694
+ let installed = false;
695
+ try {
696
+ callbacks.onStepEnd = function patchedOnStepEnd(payload) {
697
+ const ret = originalOnStepEnd.call(this, payload);
698
+ try {
699
+ const stepId = payload?.stepId;
700
+ if (typeof stepId === 'string') {
701
+ const step = stepMap.get(stepId);
702
+ if (step && LOGGED_STEP_CATEGORIES.has(step.category)) {
703
+ emitPlaywrightStepLog(step);
704
+ }
705
+ }
706
+ }
707
+ catch (err) {
708
+ Logger_1.appLogger.debug('Failed to log Playwright step', err);
709
+ }
710
+ return ret;
711
+ };
712
+ installed = true;
713
+ }
714
+ catch (err) {
715
+ Logger_1.appLogger.warn('Failed to install Playwright step logger; native Playwright steps will not appear in flow logs', err);
716
+ }
717
+ return () => {
718
+ if (installed) {
719
+ callbacks.onStepEnd = originalOnStepEnd;
720
+ }
721
+ };
722
+ }
450
723
  /**
451
724
  * Fetch tool-call screenshots from the flow's persistence layer and attach
452
725
  * them to the Playwright report. Each screenshot is attached as
@@ -49,7 +49,7 @@ async function setupDonobuStack(donobuDeploymentEnvironment, controlPanelFactory
49
49
  const envPersistenceFactory = await EnvPersistenceRegistry_1.EnvPersistenceRegistryImpl.fromEnvironment(envPersistenceVolatile ?? null, environ, persistencePlugins);
50
50
  const envDataManager = new EnvDataManager_1.EnvDataManager(envPersistenceFactory);
51
51
  const flowsManager = new DonobuFlowsManager_1.DonobuFlowsManager(donobuDeploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, resolvedToolRegistry, targetRuntimePlugins);
52
- const testsPersistenceRegistry = await TestsPersistenceRegistry_1.TestsPersistenceRegistryImpl.fromEnvironment(environ);
52
+ const testsPersistenceRegistry = await TestsPersistenceRegistry_1.TestsPersistenceRegistryImpl.fromEnvironment(environ, flowsPersistenceRegistry);
53
53
  const testsManager = new TestsManager_1.TestsManager(testsPersistenceRegistry, flowsManager);
54
54
  const suitesPersistenceRegistry = await SuitesPersistenceRegistry_1.SuitesPersistenceRegistryImpl.fromEnvironment(environ);
55
55
  const suitesManager = new SuitesManager_1.SuitesManager(suitesPersistenceRegistry, testsPersistenceRegistry);
@@ -1,7 +1,7 @@
1
1
  import type { CreateDonobuFlow } from '../models/CreateDonobuFlow';
2
2
  import type { CreateTest } from '../models/CreateTest';
3
3
  import type { PaginatedResult } from '../models/PaginatedResult';
4
- import type { TestMetadata, TestsQuery } from '../models/TestMetadata';
4
+ import type { TestListItem, TestMetadata, TestsQuery } from '../models/TestMetadata';
5
5
  import type { TestsPersistenceRegistry } from '../persistence/tests/TestsPersistenceRegistry';
6
6
  import type { DonobuFlowsManager } from './DonobuFlowsManager';
7
7
  export declare class TestsManager {
@@ -10,7 +10,7 @@ export declare class TestsManager {
10
10
  constructor(testsPersistenceRegistry: TestsPersistenceRegistry, flowsManager: DonobuFlowsManager);
11
11
  createTest(params: CreateTest): Promise<TestMetadata>;
12
12
  getTestById(testId: string): Promise<TestMetadata>;
13
- getTests(query: TestsQuery): Promise<PaginatedResult<TestMetadata>>;
13
+ getTests(query: TestsQuery): Promise<PaginatedResult<TestListItem>>;
14
14
  /**
15
15
  * Update a test in the persistence layer where it exists.
16
16
  * Iterates layers, updates in the first one that has it, ignores
@@ -63,6 +63,13 @@ class TestsManager {
63
63
  // TestMetadata has no `created_at` field — fall back to "now" so newly
64
64
  // returned items sort to the most-recent end. For other sort columns,
65
65
  // map the snake_case API name to the camelCase JS field.
66
+ //
67
+ // `flow_count` and `latest_flow_created_at` are only known at the
68
+ // persistence layer (SQLite computes them via JOIN; the cloud handles
69
+ // them server-side). Returning 0 here means each layer's pre-sorted
70
+ // results stay in their own order during the federated merge, but
71
+ // ordering across layers degrades to "first seen wins" — fine for the
72
+ // common single-primary-layer case.
66
73
  const fieldFor = (test) => {
67
74
  switch (sortBy) {
68
75
  case 'created_at':
@@ -73,6 +80,9 @@ class TestsManager {
73
80
  return test.suiteId ?? '';
74
81
  case 'next_run_mode':
75
82
  return test.nextRunMode;
83
+ case 'flow_count':
84
+ case 'latest_flow_created_at':
85
+ return 0;
76
86
  default:
77
87
  return '';
78
88
  }
@@ -1,3 +1,18 @@
1
+ import { z } from 'zod/v4';
2
+ /**
3
+ * Create a schema to validate a paginated result.
4
+ *
5
+ * @param itemSchema - The Zod schema for the items in the result.
6
+ * @returns The Zod schema for the paginated result.
7
+ */
8
+ export declare const createPaginatedResultSchema: <T extends z.ZodTypeAny>(itemSchema: T) => z.ZodObject<{
9
+ items: z.ZodArray<T>;
10
+ nextPageToken: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ export declare const PaginatedResultSchema: z.ZodObject<{
13
+ items: z.ZodArray<z.ZodUnknown>;
14
+ nextPageToken: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>;
1
16
  /**
2
17
  * Result of a paginated query.
3
18
  */
@@ -1,6 +1,17 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PaginatedResultSchema = exports.createPaginatedResultSchema = void 0;
4
+ const v4_1 = require("zod/v4");
2
5
  /**
3
- * Result of a paginated query.
6
+ * Create a schema to validate a paginated result.
7
+ *
8
+ * @param itemSchema - The Zod schema for the items in the result.
9
+ * @returns The Zod schema for the paginated result.
4
10
  */
5
- Object.defineProperty(exports, "__esModule", { value: true });
11
+ const createPaginatedResultSchema = (itemSchema) => v4_1.z.object({
12
+ items: v4_1.z.array(itemSchema),
13
+ nextPageToken: v4_1.z.string().optional(),
14
+ });
15
+ exports.createPaginatedResultSchema = createPaginatedResultSchema;
16
+ exports.PaginatedResultSchema = (0, exports.createPaginatedResultSchema)(v4_1.z.unknown());
6
17
  //# sourceMappingURL=PaginatedResult.js.map