pocketbase-zod-schema 0.7.2 → 1.0.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 (67) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +198 -25
  3. package/dist/cli/index.cjs +3740 -1233
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +3737 -1232
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +3815 -1201
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +3811 -1199
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/cli/utils/index.cjs +52 -13
  12. package/dist/cli/utils/index.cjs.map +1 -1
  13. package/dist/cli/utils/index.d.cts +46 -1
  14. package/dist/cli/utils/index.d.ts +46 -1
  15. package/dist/cli/utils/index.js +51 -13
  16. package/dist/cli/utils/index.js.map +1 -1
  17. package/dist/migration/analyzer.cjs +123 -42
  18. package/dist/migration/analyzer.cjs.map +1 -1
  19. package/dist/migration/analyzer.js +123 -42
  20. package/dist/migration/analyzer.js.map +1 -1
  21. package/dist/migration/diff.cjs +13 -0
  22. package/dist/migration/diff.cjs.map +1 -1
  23. package/dist/migration/diff.js +13 -0
  24. package/dist/migration/diff.js.map +1 -1
  25. package/dist/migration/engine.cjs +3263 -0
  26. package/dist/migration/engine.cjs.map +1 -0
  27. package/dist/migration/engine.d.cts +105 -0
  28. package/dist/migration/engine.d.ts +105 -0
  29. package/dist/migration/engine.js +3173 -0
  30. package/dist/migration/engine.js.map +1 -0
  31. package/dist/migration/generator.cjs +40 -18
  32. package/dist/migration/generator.cjs.map +1 -1
  33. package/dist/migration/generator.d.cts +45 -2
  34. package/dist/migration/generator.d.ts +45 -2
  35. package/dist/migration/generator.js +39 -19
  36. package/dist/migration/generator.js.map +1 -1
  37. package/dist/migration/index.cjs +3479 -1145
  38. package/dist/migration/index.cjs.map +1 -1
  39. package/dist/migration/index.d.cts +21 -4
  40. package/dist/migration/index.d.ts +21 -4
  41. package/dist/migration/index.js +3433 -1142
  42. package/dist/migration/index.js.map +1 -1
  43. package/dist/migration/snapshot.cjs +2200 -804
  44. package/dist/migration/snapshot.cjs.map +1 -1
  45. package/dist/migration/snapshot.d.cts +24 -66
  46. package/dist/migration/snapshot.d.ts +24 -66
  47. package/dist/migration/snapshot.js +2198 -801
  48. package/dist/migration/snapshot.js.map +1 -1
  49. package/dist/migration/utils/index.cjs +51 -9
  50. package/dist/migration/utils/index.cjs.map +1 -1
  51. package/dist/migration/utils/index.d.cts +1 -1
  52. package/dist/migration/utils/index.d.ts +1 -1
  53. package/dist/migration/utils/index.js +50 -10
  54. package/dist/migration/utils/index.js.map +1 -1
  55. package/dist/server.cjs +3837 -1248
  56. package/dist/server.cjs.map +1 -1
  57. package/dist/server.d.cts +6 -4
  58. package/dist/server.d.ts +6 -4
  59. package/dist/server.js +3791 -1245
  60. package/dist/server.js.map +1 -1
  61. package/dist/{type-mapper-Bu9qjCdd.d.cts → type-mapper-BSPTj8t5.d.cts} +23 -1
  62. package/dist/{type-mapper-JBaVuKE2.d.ts → type-mapper-D31WYDee.d.ts} +23 -1
  63. package/dist/types-B_MLb1Ob.d.ts +475 -0
  64. package/dist/types-upZBVMfb.d.cts +475 -0
  65. package/dist/verify-BF72n2df.d.ts +402 -0
  66. package/dist/verify-DTzYpiXX.d.cts +402 -0
  67. package/package.json +7 -1
@@ -0,0 +1,402 @@
1
+ import { R as RecordModel, m as EngineWarning, E as EngineOptions, C as CollectionStore, o as MigrationPlan, s as ReplayResult, q as PlanOptions, n as MigrationExecutionResult, u as RawCollection, M as MigrationDirection } from './types-B_MLb1Ob.js';
2
+
3
+ /**
4
+ * Condition expressions — one evaluator for PocketBase filters and SQL WHERE
5
+ *
6
+ * Data migrations select records two ways: PocketBase's filter syntax
7
+ * (`status = "draft" && views > 10`) and raw SQL through `$dbx`/`app.db()`
8
+ * (`WHERE status = {:status}`). The two dialects overlap almost completely —
9
+ * the same comparison operators over the same field/literal operands — so the
10
+ * simulation parses both with one grammar and evaluates the result against a
11
+ * record.
12
+ *
13
+ * Deliberately small: comparisons, `LIKE`/`~`, `IN`, `IS NULL`, `AND`/`OR`/
14
+ * `NOT`, parentheses, and `{:name}` bindings. Anything outside that raises
15
+ * `ExpressionError`, which the caller reports as an unsupported query rather
16
+ * than silently matching the wrong rows.
17
+ */
18
+ declare class ExpressionError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ /** Resolves a field reference to the value stored on the row being tested */
22
+ interface EvaluationContext {
23
+ field: (name: string) => unknown;
24
+ params?: Record<string, unknown>;
25
+ }
26
+ type Condition = {
27
+ kind: "and";
28
+ operands: Condition[];
29
+ } | {
30
+ kind: "or";
31
+ operands: Condition[];
32
+ } | {
33
+ kind: "not";
34
+ operand: Condition;
35
+ } | {
36
+ kind: "compare";
37
+ operator: CompareOperator;
38
+ left: Operand;
39
+ right: Operand;
40
+ } | {
41
+ kind: "in";
42
+ negated: boolean;
43
+ left: Operand;
44
+ values: Operand[];
45
+ } | {
46
+ kind: "like";
47
+ negated: boolean;
48
+ left: Operand;
49
+ pattern: Operand;
50
+ implicitWildcards: boolean;
51
+ } | {
52
+ kind: "null";
53
+ negated: boolean;
54
+ operand: Operand;
55
+ } | {
56
+ kind: "between";
57
+ negated: boolean;
58
+ operand: Operand;
59
+ from: Operand;
60
+ to: Operand;
61
+ } | {
62
+ kind: "literal";
63
+ value: boolean;
64
+ };
65
+ type CompareOperator = "=" | "!=" | ">" | ">=" | "<" | "<=";
66
+ type Operand = {
67
+ kind: "field";
68
+ name: string;
69
+ } | {
70
+ kind: "value";
71
+ value: unknown;
72
+ } | {
73
+ kind: "param";
74
+ name: string;
75
+ };
76
+ /** Parses a filter or SQL condition into an evaluable tree */
77
+ declare function parseCondition(source: string): Condition;
78
+ declare function evaluateCondition(condition: Condition, context: EvaluationContext): boolean;
79
+
80
+ /**
81
+ * `$dbx` and `app.db()` simulation
82
+ *
83
+ * PocketBase exposes two query surfaces to a migration: `$dbx` expression
84
+ * builders passed to the record finders, and `app.db().newQuery(sql)` for raw
85
+ * SQL. Both are simulated against the in-memory record store (`records.ts`).
86
+ *
87
+ * The SQL support is a deliberate subset — SELECT / INSERT / UPDATE / DELETE
88
+ * against a single collection table, with the condition grammar from
89
+ * `expression.ts`. Anything beyond it raises `UnsupportedQueryError`, which
90
+ * the caller turns into a warning (lenient) or a throw (strict): reporting
91
+ * "this query is not simulated" is useful, quietly matching the wrong rows is
92
+ * not.
93
+ */
94
+
95
+ declare class UnsupportedQueryError extends Error {
96
+ constructor(message: string);
97
+ }
98
+ /** Marks the objects `$dbx.*` returns so the finders can recognize them */
99
+ declare const DBX_EXPRESSION: unique symbol;
100
+ interface DbxExpression {
101
+ [DBX_EXPRESSION]: true;
102
+ /** The SQL fragment, as `$dbx` would build it (for messages and debugging) */
103
+ build(): string;
104
+ matches(record: RecordModel): boolean;
105
+ }
106
+ declare function isDbxExpression(value: unknown): value is DbxExpression;
107
+ declare function createDbx(): Record<string, unknown>;
108
+ type ParsedStatement = {
109
+ kind: "select";
110
+ table: string;
111
+ columns: string[];
112
+ where?: Condition;
113
+ order?: OrderTerm[];
114
+ limit?: number;
115
+ offset?: number;
116
+ } | {
117
+ kind: "update";
118
+ table: string;
119
+ assignments: {
120
+ column: string;
121
+ value: Operand;
122
+ }[];
123
+ where?: Condition;
124
+ } | {
125
+ kind: "delete";
126
+ table: string;
127
+ where?: Condition;
128
+ } | {
129
+ kind: "insert";
130
+ table: string;
131
+ columns: string[];
132
+ values: Operand[];
133
+ };
134
+ interface OrderTerm {
135
+ column: string;
136
+ descending: boolean;
137
+ }
138
+ /**
139
+ * Parses one statement of the supported subset.
140
+ *
141
+ * @throws UnsupportedQueryError for anything outside it
142
+ */
143
+ declare function parseStatement(sql: string): ParsedStatement;
144
+
145
+ /**
146
+ * goja-compatibility lint
147
+ *
148
+ * The engine executes migrations with Node's JavaScript, a superset of the
149
+ * goja dialect PocketBase actually runs. That gap is silent: a migration can
150
+ * replay perfectly here, pass verification, and still fail the moment
151
+ * `pocketbase migrate up` reaches it — because it referenced a Node global,
152
+ * used class fields, or awaited a promise in a runtime with no event loop.
153
+ *
154
+ * This pass closes the gap statically. It parses the file with acorn and
155
+ * reports:
156
+ *
157
+ * - `unknown-global` — a free identifier that is neither declared in the file
158
+ * nor part of the PocketBase JSVM surface (the sandbox globals) or the
159
+ * ECMAScript library goja implements. `require`, `process`, `Buffer`,
160
+ * `fetch`, `setTimeout` all land here.
161
+ * - `unsupported-syntax` — constructs goja's parser rejects: class fields,
162
+ * private members, static blocks, BigInt literals, `import.meta`.
163
+ * - `module-syntax` — `import`/`export`; migrations are scripts.
164
+ * - `async` — `async`/`await`/`Promise`. goja runs migrations synchronously,
165
+ * so an awaited promise never settles.
166
+ * - `unsupported-api` — folded in from execution warnings: a call that
167
+ * resolved to an inert stub did nothing here and will do something (or
168
+ * throw) in production.
169
+ */
170
+
171
+ type GojaLintRule = "syntax" | "unsupported-syntax" | "module-syntax" | "async" | "unknown-global" | "unsupported-api";
172
+ type GojaLintSeverity = "error" | "warning";
173
+ interface GojaLintFinding {
174
+ rule: GojaLintRule;
175
+ severity: GojaLintSeverity;
176
+ message: string;
177
+ file?: string;
178
+ line?: number;
179
+ column?: number;
180
+ }
181
+ interface GojaLintResult {
182
+ file: string;
183
+ findings: GojaLintFinding[];
184
+ /** No error-severity findings */
185
+ ok: boolean;
186
+ }
187
+ interface GojaLintOptions {
188
+ /** Path or name used in messages */
189
+ file?: string;
190
+ /**
191
+ * Extra globals to treat as available — for a PocketBase build that
192
+ * registers its own JSVM bindings.
193
+ */
194
+ allowedGlobals?: string[];
195
+ /**
196
+ * Engine warnings from executing the same file. `unsupported-api` entries
197
+ * become findings, which is how a stubbed call gets surfaced prominently
198
+ * instead of being buried in replay output.
199
+ */
200
+ warnings?: EngineWarning[];
201
+ }
202
+ /** The identifiers a migration may reference without declaring them */
203
+ declare function availableGlobals(extra?: string[]): Set<string>;
204
+ declare function lintMigrationSource(source: string, options?: GojaLintOptions): GojaLintResult;
205
+ declare function lintMigrationFile(filePath: string, options?: GojaLintOptions): GojaLintResult;
206
+ declare function lintMigrationFiles(files: string[], options?: GojaLintOptions): GojaLintResult[];
207
+ /** Turns engine execution warnings into findings */
208
+ declare function gojaFindingsFromWarnings(warnings?: EngineWarning[]): GojaLintFinding[];
209
+ declare function formatGojaLintFinding(finding: GojaLintFinding): string;
210
+
211
+ /**
212
+ * Replayer — folds an ordered list of migration files into a final state
213
+ *
214
+ * Mirrors what PocketBase does on `migrate up`: execute each unapplied file
215
+ * in timestamp order. State reconstruction starts from an empty store; a
216
+ * native snapshot migration (app.importCollections) is just the first file.
217
+ *
218
+ * Which files that is comes from `planMigrationReplay`: by default the newest
219
+ * snapshot plus everything after it, or — when an applied-migrations list is
220
+ * supplied — only the files PocketBase has actually run, starting from the
221
+ * newest applied snapshot.
222
+ */
223
+
224
+ declare function replayMigrations(files: string[], options?: EngineOptions & {
225
+ initialStore?: CollectionStore;
226
+ plan?: MigrationPlan;
227
+ }): ReplayResult;
228
+ /**
229
+ * Replays a pb_migrations directory: the snapshot the state starts from,
230
+ * then every migration after it, in timestamp order.
231
+ *
232
+ * Pass `applied` (from `readAppliedMigrations`) to replay only what PocketBase
233
+ * has actually run — otherwise every file on disk is assumed applied.
234
+ *
235
+ * Returns null when there is nothing to replay (an empty database).
236
+ */
237
+ declare function replayMigrationsDirectory(migrationsPath: string, options?: EngineOptions & PlanOptions): ReplayResult | null;
238
+
239
+ /**
240
+ * Runner — executes one migration file against a CollectionStore
241
+ *
242
+ * Evaluation happens in a vm context built from the sandbox globals; the
243
+ * file's migrate(up, down) call registers its closures. The requested
244
+ * direction then runs transactionally: the store is cloned, the closure is
245
+ * applied to the clone, and the clone replaces the store only on success.
246
+ * The closure is invoked from inside the context (not host code) so the vm
247
+ * timeout also bounds infinite loops within migration bodies.
248
+ *
249
+ * State reconstruction only ever runs `up`, which structurally rules out
250
+ * replaying rollback statements as forward operations. `down` is executed on request (`executeMigrationDownSource`),
251
+ * for rollback verification — see `verify.ts`. Downs run in reverse
252
+ * registration order, mirroring how PocketBase rolls back.
253
+ */
254
+
255
+ declare function executeMigrationSource(source: string, store: CollectionStore, options?: EngineOptions & {
256
+ filename?: string;
257
+ }): MigrationExecutionResult;
258
+ declare function executeMigrationFile(filePath: string, store: CollectionStore, options?: EngineOptions): MigrationExecutionResult;
259
+ /**
260
+ * Runs the file's `down()` closures against the store, newest registration
261
+ * first. A file that registers no `down` leaves the store untouched and
262
+ * reports `applied: false`.
263
+ */
264
+ declare function executeMigrationDownSource(source: string, store: CollectionStore, options?: EngineOptions & {
265
+ filename?: string;
266
+ }): MigrationExecutionResult;
267
+ declare function executeMigrationDownFile(filePath: string, store: CollectionStore, options?: EngineOptions): MigrationExecutionResult;
268
+
269
+ /**
270
+ * Structural comparison of two engine states
271
+ *
272
+ * Down-migration verification asks one question — "is the state after
273
+ * up() + down() the state we started from?" — and needs an answer precise
274
+ * enough to name what drifted. This compares the raw PocketBase-shaped
275
+ * collections of two stores directly, rather than routing through the diff
276
+ * engine, because a rollback that leaves the schema semantically equal but
277
+ * structurally different (a restored field carrying a different id, an index
278
+ * re-added in a different form) is exactly the kind of drift verification
279
+ * exists to catch.
280
+ *
281
+ * Normalization is limited to differences PocketBase itself does not
282
+ * distinguish:
283
+ *
284
+ * - An option a migration never declared and one set to its Go zero value
285
+ * (`""`, `0`, `false`, `[]`, `null`) express the same constraint, so an
286
+ * absent key equals a zero-valued one. Two *declared* values are always
287
+ * compared (`min: 0` vs `min: 5` is a real difference).
288
+ * - API rules are exempt from that rule: `null` (superuser only) and `""`
289
+ * (public) are different permissions, so only absent ≡ `null` holds.
290
+ * - Index order is not meaningful; index lists are compared as sets.
291
+ * - Field order is not compared unless `strictFieldOrder` is set.
292
+ */
293
+
294
+ interface StateDifference {
295
+ kind: "collection-added" | "collection-removed" | "collection-property" | "field-added" | "field-removed" | "field-property" | "field-order" | "indexes";
296
+ /** Collection name (baseline name when the collection exists on both sides) */
297
+ collection: string;
298
+ field?: string;
299
+ property?: string;
300
+ expected?: unknown;
301
+ actual?: unknown;
302
+ /** Human-readable one-liner, suitable for CLI output */
303
+ message: string;
304
+ }
305
+ interface StateCompareOptions {
306
+ /**
307
+ * Collection and field properties to skip.
308
+ * Defaults to PocketBase's own bookkeeping timestamps.
309
+ */
310
+ ignoreKeys?: string[];
311
+ /** Also compare the position of each field within its collection */
312
+ strictFieldOrder?: boolean;
313
+ /** Labels used in messages; defaults to "baseline" / "actual" */
314
+ labels?: {
315
+ expected: string;
316
+ actual: string;
317
+ };
318
+ }
319
+ /**
320
+ * Compares two stores. An empty result means the two states are the same
321
+ * schema; every entry names one concrete divergence.
322
+ */
323
+ declare function compareStores(expected: CollectionStore, actual: CollectionStore, options?: StateCompareOptions): StateDifference[];
324
+ declare function compareRawCollections(expected: RawCollection[], actual: RawCollection[], options?: StateCompareOptions): StateDifference[];
325
+ /** One line per difference, e.g. for CLI output */
326
+ declare function describeStateDifferences(differences: StateDifference[]): string[];
327
+
328
+ /**
329
+ * Down-migration verification
330
+ *
331
+ * A generated `down()` is never exercised by state reconstruction — the
332
+ * replayer only runs `up()` — so a rollback that does not actually roll back
333
+ * stays invisible until someone runs `pocketbase migrate down` in anger.
334
+ * This module closes that gap: for each migration it executes `up()` against
335
+ * a baseline, then `down()` against the result, and asserts the state came
336
+ * back to the baseline.
337
+ *
338
+ * `down()` runs from a fresh evaluation of the file, the way PocketBase runs
339
+ * it — a separate invocation that cannot observe anything `up()` left in the
340
+ * file's module scope.
341
+ *
342
+ * Failures are returned, not thrown: a verification pass reports on every
343
+ * migration it was given, and the caller decides whether an unreversible
344
+ * migration is fatal.
345
+ */
346
+
347
+ interface MigrationSourceRef {
348
+ source: string;
349
+ /** Path or name used in messages */
350
+ file?: string;
351
+ }
352
+ interface MigrationRoundTripResult {
353
+ file: string;
354
+ /** The file registered an up() closure and it committed */
355
+ upApplied: boolean;
356
+ /** The file registered a down() closure and it committed */
357
+ downApplied: boolean;
358
+ /** up() changed the state but the file has no down() to undo it */
359
+ missingDown: boolean;
360
+ /** up() followed by down() returned to the baseline */
361
+ reversible: boolean;
362
+ /** What the rollback failed to restore (empty when reversible) */
363
+ differences: StateDifference[];
364
+ warnings: EngineWarning[];
365
+ /** Set when a phase failed to execute at all */
366
+ error?: {
367
+ phase: MigrationDirection | "evaluate";
368
+ message: string;
369
+ };
370
+ /** State after up() — what the next migration in a sequence starts from */
371
+ storeAfterUp: CollectionStore;
372
+ }
373
+ interface MigrationVerificationReport {
374
+ results: MigrationRoundTripResult[];
375
+ /** Every migration executed and reversed cleanly */
376
+ ok: boolean;
377
+ /** State after every up() — the state the migrations leave behind */
378
+ store: CollectionStore;
379
+ /** Only the results that failed, in input order */
380
+ failures: MigrationRoundTripResult[];
381
+ }
382
+ interface VerifyOptions extends EngineOptions {
383
+ /** State the first migration is applied to. Defaults to an empty store. */
384
+ initialStore?: CollectionStore;
385
+ /** Forwarded to the state comparison */
386
+ compare?: StateCompareOptions;
387
+ }
388
+ /**
389
+ * Verifies one migration against a baseline. The baseline store is left
390
+ * untouched; the state after up() is returned on the result.
391
+ */
392
+ declare function verifyMigrationRoundTrip(migration: MigrationSourceRef, baseline?: CollectionStore, options?: VerifyOptions): MigrationRoundTripResult;
393
+ declare function verifyMigrationFileRoundTrip(filePath: string, baseline?: CollectionStore, options?: VerifyOptions): MigrationRoundTripResult;
394
+ /**
395
+ * Verifies a sequence of migrations the way they will be applied: each one
396
+ * is round-tripped against the state its predecessors leave behind, and its
397
+ * up() is then committed before moving on.
398
+ */
399
+ declare function verifyMigrationSources(migrations: MigrationSourceRef[], options?: VerifyOptions): MigrationVerificationReport;
400
+ declare function verifyMigrationFiles(files: string[], options?: VerifyOptions): MigrationVerificationReport;
401
+
402
+ export { type MigrationVerificationReport as A, type StateDifference as B, type Condition as C, type DbxExpression as D, ExpressionError as E, parseStatement as F, type GojaLintFinding as G, evaluateCondition as H, type EvaluationContext as I, compareRawCollections as J, type MigrationRoundTripResult as M, type StateCompareOptions as S, UnsupportedQueryError as U, type VerifyOptions as V, availableGlobals as a, createDbx as b, compareStores as c, describeStateDifferences as d, executeMigrationDownFile as e, executeMigrationDownSource as f, executeMigrationFile as g, executeMigrationSource as h, formatGojaLintFinding as i, gojaFindingsFromWarnings as j, isDbxExpression as k, lintMigrationFile as l, lintMigrationFiles as m, lintMigrationSource as n, replayMigrationsDirectory as o, parseCondition as p, verifyMigrationFiles as q, replayMigrations as r, verifyMigrationRoundTrip as s, verifyMigrationSources as t, type GojaLintOptions as u, verifyMigrationFileRoundTrip as v, type GojaLintResult as w, type GojaLintRule as x, type GojaLintSeverity as y, type MigrationSourceRef as z };