@jarenjs/db 0.86.0 → 0.89.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.
@@ -49,9 +49,16 @@ Selections support table and subquery sources, inner/left/cross joins, correlate
49
49
  `sql.scalar`/`sql.exists`, CASE, IN/NOT IN, DISTINCT, grouped and distinct
50
50
  aggregates, HAVING, UNION/UNION ALL and ordered windows. The declaration file
51
51
  lists the closed operators and functions, including trim/coalesce, LIKE, JSON
52
- extraction, casts and SQLite date functions. Neither arbitrary function names
52
+ extraction/type inspection, casts and SQLite date functions. Neither arbitrary function names
53
53
  nor a raw-expression escape hatch is accepted.
54
54
 
55
+ `sql.call('json_type', [document, path])` distinguishes a missing path (SQL NULL)
56
+ from JSON null (text `'null'`) and reports SQLite's native scalar/container type
57
+ names. Omitting the path inspects the whole document. SQL-null input stays null;
58
+ malformed JSON raises SQLite's error. It composes with CASE and synchronous
59
+ cursors without decoding or rewriting the original column. See
60
+ [SQLite JSON type inspection](https://www.sqlite.org/json1.html#the_json_type_function).
61
+
55
62
  ## Exact writes and bytes
56
63
 
57
64
  ```js
@@ -114,7 +121,9 @@ applyTableMigration(connection, migration);
114
121
  Columns retain declaration order and exact INTEGER/REAL/TEXT/BLOB/NUMERIC/ANY
115
122
  types. Definitions support ordered primary keys, rowid or AUTOINCREMENT identity,
116
123
  nullability, database defaults, generated columns, STRICT/WITHOUT ROWID, named
117
- UNIQUE/CHECK/foreign-key constraints and delete/update actions. Index terms
124
+ UNIQUE/CHECK/foreign-key constraints and delete/update actions. A column can also
125
+ declare `references: { table, columns: [name], onDelete, onUpdate, deferred }`.
126
+ Index terms
118
127
  support expressions, direction and collation, with an optional partial predicate.
119
128
  Triggers support BEFORE/AFTER, INSERT/UPDATE/DELETE, UPDATE OF, OLD/NEW conditions,
120
129
  mutation steps and RAISE. Schema expressions use the same structural emitter,
@@ -126,14 +135,17 @@ Entity `.physical(...)` metadata can also supply column `type`, `defaultValue`,
126
135
  `planEntity(...).createSql` now emits explicit table DDL for complete writable
127
136
  declarations; views and incomplete generated/default definitions remain adoption
128
137
  metadata. `openStore` still verifies physical tables without creating them.
129
- Identical physical models produce an empty `planModelMigration`; changed
130
- physical tables use the live-schema `planTableMigration` API.
138
+ Identical physical models produce an empty `planModelMigration`. Changed
139
+ physical declarations remain a specific `JD0021` policy refusal; use the
140
+ live-schema `planTableMigration` API and review the resulting structural plan.
141
+ The model diff does not infer application-owned DDL or business backfills.
131
142
 
132
143
  ## Guarded upgrades
133
144
 
134
145
  Planning inspects the existing schema without modifying it. A differing existing
135
- table requires `allowRebuild: true`; additions currently use the same guarded
136
- rebuild. Every removed column needs `dropColumns`; removing an existing explicit
146
+ table requires `allowRebuild: true` when using `planTableMigration`; use the narrow
147
+ schema operations below to append a column without rebuilding. Every removed
148
+ column needs `dropColumns`; removing an existing explicit
137
149
  index/trigger requires `dropObjects`. Unmentioned indexes and triggers survive.
138
150
  An optional `copy` maps writable non-key target columns to structural expressions;
139
151
  new columns otherwise use their defaults. The plan retains the source schema and
@@ -145,6 +157,8 @@ unchanged values/storage classes, drops/renames atomically, restores indexes and
145
157
  triggers, and checks foreign keys and the target schema. Primary-key columns,
146
158
  unshadowed hidden rowids, raw text, bytes and AUTOINCREMENT high-water marks are
147
159
  preserved. A key change, rowid-ownership change or ambiguous rowid alias refuses.
160
+ This includes converting an `INTEGER PRIMARY KEY DESC` with an independent
161
+ hidden rowid into an ordinary INTEGER primary key that owns the rowid.
148
162
  No migration history table or model adoption is needed.
149
163
 
150
164
  Rebuilds require SQLite's foreign-key transition outside a transaction. For a
@@ -157,6 +171,115 @@ refuses before DDL. Node/Bun regressions cover populated history and references,
157
171
  failed copies, repeat reopening, nested rollback, and process death after DROP
158
172
  with WAL recovery. These tests do not establish power-loss durability.
159
173
 
174
+ ### Composing a guarded plan with migration history
175
+
176
+ Pass the complete saved table artifact as `{ kind: 'table', plan }` in
177
+ `planPhysicalMigration` or `.step({ kind: 'table', plan })` in the migration
178
+ pen. It retains `version`, `id`, `table`, `source`, `after`, `rebuild`,
179
+ `temporary`, `unchanged`, `statements`, `finish` and `checksum`; incomplete
180
+ plans refuse. Do not flatten its SQL arrays into separate DDL steps: the
181
+ guarded executor also preserves storage classes and allocated identities.
182
+
183
+ `migrate` supplies the existing history/receipt transaction around that
184
+ executor. It admits the writer with IMMEDIATE semantics before source reads,
185
+ uses savepoints for nested work and restores FK/legacy settings exactly.
186
+ `{ connection }` borrows the same driver handle; `{ driver, path? }` owns its
187
+ resource. Both compose with optional historical transforms/assertions and a
188
+ complete `physicalTarget` copied from a disposable target fixture. With
189
+ `shadowFixture`, replay uses the same migration and history executor.
190
+ The [runnable example](MIGRATION-FORMAT.md#runnable-physical-lifecycle) proves
191
+ this composition and a checked second run.
192
+
193
+ Complete physical targets retain physical column and constraint order,
194
+ quoted text, index predicates/collations and trigger programs. Only token
195
+ whitespace/comments and CREATE-prefix `IF NOT EXISTS` are formatting;
196
+ quote styles and case are not inferred equivalent. The separate exact
197
+ source snapshot/checksum remains the stale-plan guard.
198
+
199
+ ## Additive and object operations
200
+
201
+ `planSchemaChange(connection, operation)` returns a reviewable
202
+ `{ version, operation, sql, source, settings, checksum }`. It reads the main schema
203
+ and relevant connection settings without executing DDL. `applySchemaChange`
204
+ checks that source and settings again under an IMMEDIATE transaction before
205
+ executing the single statement. It returns `{ changed }`, the number of schema
206
+ operations that changed the catalog, rather than the number of affected rows.
207
+ Both methods require the same available synchronous SQLite ownership as rebuilds.
208
+
209
+ ```js
210
+ import { planSchemaChange, applySchemaChange, sql } from '@jarenjs/db/relational';
211
+ const addition = planSchemaChange(connection, {
212
+ op: 'addColumn', table: 'entries', column: {
213
+ name: 'revision', type: 'INTEGER', nullable: false, default: 1,
214
+ check: sql.binary('>=', sql.column('revision'), 1),
215
+ },
216
+ });
217
+ // Review addition.sql and addition.source before execution.
218
+ applySchemaChange(connection, addition);
219
+ applySchemaChange(connection, planSchemaChange(connection, {
220
+ op: 'dropIndex', name: 'obsolete_index', ifExists: true,
221
+ }));
222
+ ```
223
+
224
+ The closed operations are `addColumn` (`table`, `column`), `dropIndex` (`name`,
225
+ optional `ifExists`), `renameTable` (`table`, `to`) and `dropTable` (`table`,
226
+ optional `ifExists`). Identifiers address **main** explicitly; a temporary table
227
+ with the same spelling cannot redirect an operation. A dot inside a name is a
228
+ literal character. Attached database operations are not part of this surface.
229
+ The column definition and expression renderer are shared with `planTable`.
230
+
231
+ ADD COLUMN appends a declaration and preserves existing rowids, values, storage
232
+ classes, column order, indexes and triggers. It does not derive a complete table
233
+ definition, normalize unknown constraints or copy the table. Literal defaults
234
+ are supported; identity columns, STORED generated columns and expression defaults
235
+ refuse. Ordinary NOT NULL additions require a non-null default. REFERENCES
236
+ additions require a NULL default (or no default), and a single referenced column.
237
+ SQLite checks existing rows for a new CHECK or generated NOT NULL constraint;
238
+ those checks can scan the table even though the operation does not copy it.
239
+ See [SQLite ADD COLUMN restrictions](https://www.sqlite.org/lang_altertable.html#alter_table_add_column).
240
+
241
+ Drop operations fail on absence unless `ifExists: true` is supplied. A missing
242
+ object then returns `changed: 0`. DROP INDEX affects the named index, not its
243
+ table or triggers. DROP TABLE removes the table and its owned indexes/triggers;
244
+ SQLite's active foreign-key actions still apply. RENAME follows SQLite's current
245
+ dependency rewriting behavior and the reviewed `legacy_alter_table` setting.
246
+ See [DROP TABLE](https://www.sqlite.org/lang_droptable.html) and
247
+ [RENAME TABLE](https://www.sqlite.org/lang_altertable.html#alter_table_rename).
248
+
249
+ These are **single-source plans**, not durable migration receipts. Replan after
250
+ any schema change. Reapplying a successful ADD/RENAME plan refuses as stale;
251
+ it does not infer completion from a same-named object. An ordered migration must
252
+ check its trusted schema/version receipt before planning the next operation and
253
+ record completion in the same transaction. Unknown members or unsupported column
254
+ declarations refuse with `JD0005`; modified/stale plans refuse with `JD0021`.
255
+ Native object, data-constraint and dependency errors are left to SQLite. A
256
+ checksum detects accidental plan edits; it does not authorize untrusted plans.
257
+
258
+ ## Explicit identity-changing upgrades
259
+
260
+ The ordinary rebuild planner continues to refuse key reassignment or row loss.
261
+ A reviewed upgrade can use the primitive operations under
262
+ `withForeignKeysSuspended`, with its own explicit data policy:
263
+
264
+ 1. Check the durable migration receipt or the exact supported legacy schema.
265
+ 2. Create a distinct replacement table with `planTable`.
266
+ 3. Use relational insert-select with a scoped correlated selection, deterministic
267
+ identity choice and explicit exclusion predicate. Assert selected, inserted
268
+ and excluded counts, and any business-specific preservation requirements.
269
+ 4. Plan/apply `dropTable` for the original and then `renameTable` for the
270
+ replacement. Create the required indexes/triggers and validate dependents.
271
+ 5. Record completion inside the same transaction so another opening does no work.
272
+
273
+ Create the replacement before dropping the original; renaming the original first
274
+ can redirect dependent references. Plan each primitive inside the FK scope, after
275
+ the preceding schema operation. The helper checks references before commit and
276
+ restores connection settings. The caller must review incoming key references,
277
+ views, triggers, immutable neighbors and each row disposition. Conflicts fail and
278
+ roll back; there is no inferred permission to discard rows. The installed native
279
+ [qualification fixture](../../../test/db/fixtures/schema-upgrade.mjs) demonstrates
280
+ scoped minimum-ID selection, explicit exclusions, rollback, repeated reopening
281
+ and recovery after actual process death at DROP.
282
+
160
283
  ## Read-only inspection and disk snapshots
161
284
 
162
285
  `readSchema` from `@jarenjs/db/model` inventories tables without adopting them,
@@ -188,3 +311,15 @@ calls. No global strong model cache is introduced. Keep connection/query caches
188
311
  bounded and reuse compiled metadata. Smaller import graphs alone do not prove
189
312
  the complete application meets its RSS budget; measure application memory with
190
313
  representative workloads.
314
+
315
+ Entity mutation engines retain prepared statements by their complete emitted SQL
316
+ in the bounded core LRU cache. Bound values, output projections and row/byte limits
317
+ belong to each execution; changing a payload does not retain another document and
318
+ another copy of the same statement. Distinct SQL stays isolated. This reduces
319
+ retained payload memory, at the cost of rebinding/compiling an identical mutation
320
+ document on each call. The synthetic retention probe at
321
+ `test/db/fixtures/mutation-memory.mjs` reports both varying-payload and identical
322
+ mutation timings, heap and RSS on Node (`--expose-gc`) and Bun. Such samples do not
323
+ replace a complete application's resource gate. Metadata remains caller-owned;
324
+ keep one compiled mapping per used model, one query state per connection, and
325
+ release facade/cache references on close. Driver cursors remain ephemeral.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/db",
3
3
  "private": false,
4
- "version": "0.86.0",
4
+ "version": "0.89.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -108,9 +108,9 @@
108
108
  "prepack": "npm run build:types"
109
109
  },
110
110
  "dependencies": {
111
- "@jarenjs/core": "^0.86.0",
112
- "@jarenjs/json": "^0.86.0",
113
- "@jarenjs/validate": "^0.86.0"
111
+ "@jarenjs/core": "^0.89.0",
112
+ "@jarenjs/json": "^0.89.0",
113
+ "@jarenjs/validate": "^0.89.0"
114
114
  },
115
115
  "bin": {
116
116
  "jaren-db": "./src/cli.js"
@@ -29,7 +29,7 @@
29
29
  "type": "string"
30
30
  },
31
31
  "steps": {
32
- "description": "Ordered steps; the order is the contract. A pure widening may carry NO steps \u2014 the migration then only moves the recorded shape.",
32
+ "description": "Ordered steps; the order is the contract. A pure widening may carry NO steps the migration then only moves the recorded shape.",
33
33
  "type": "array",
34
34
  "items": {
35
35
  "$ref": "#/definitions/step"
@@ -80,6 +80,53 @@
80
80
  },
81
81
  "additionalProperties": false
82
82
  }
83
+ },
84
+ "target": {
85
+ "type": "object",
86
+ "properties": {
87
+ "objects": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "object",
91
+ "required": [
92
+ "type",
93
+ "name",
94
+ "owner",
95
+ "sql"
96
+ ],
97
+ "properties": {
98
+ "type": {
99
+ "enum": [
100
+ "table",
101
+ "view",
102
+ "index",
103
+ "trigger"
104
+ ]
105
+ },
106
+ "name": {
107
+ "type": "string"
108
+ },
109
+ "owner": {
110
+ "type": "string"
111
+ },
112
+ "sql": {
113
+ "type": "string"
114
+ }
115
+ }
116
+ }
117
+ },
118
+ "tables": {
119
+ "type": "array",
120
+ "uniqueItems": true,
121
+ "items": {
122
+ "type": "string"
123
+ }
124
+ }
125
+ },
126
+ "required": [
127
+ "objects"
128
+ ],
129
+ "additionalProperties": false
83
130
  }
84
131
  },
85
132
  "additionalProperties": false
@@ -113,6 +160,9 @@
113
160
  },
114
161
  {
115
162
  "$ref": "#/definitions/rebuildStep"
163
+ },
164
+ {
165
+ "$ref": "#/definitions/tableStep"
116
166
  }
117
167
  ]
118
168
  },
@@ -158,6 +208,18 @@
158
208
  },
159
209
  "note": {
160
210
  "type": "string"
211
+ },
212
+ "model": {
213
+ "description": "The immutable model describing this step’s current physical layout.",
214
+ "type": "object",
215
+ "properties": {
216
+ "$model": {
217
+ "const": "0.1"
218
+ }
219
+ },
220
+ "required": [
221
+ "$model"
222
+ ]
161
223
  }
162
224
  },
163
225
  "required": [
@@ -189,6 +251,18 @@
189
251
  },
190
252
  "note": {
191
253
  "type": "string"
254
+ },
255
+ "model": {
256
+ "description": "The immutable model describing this step’s current physical layout.",
257
+ "type": "object",
258
+ "properties": {
259
+ "$model": {
260
+ "const": "0.1"
261
+ }
262
+ },
263
+ "required": [
264
+ "$model"
265
+ ]
192
266
  }
193
267
  },
194
268
  "required": [
@@ -199,7 +273,7 @@
199
273
  "additionalProperties": false
200
274
  },
201
275
  "deriveStep": {
202
- "description": "Recompute named STORED derived index columns from the documents already in a collection. A spatial column is stored only under the physical mapping that cannot index a registered deterministic function (the generated mapping populates itself); a vector column is stored under both. The step is idempotent \u2014 a derived value is a pure function of the document \u2014 so a replay writes what the first run wrote.",
276
+ "description": "Recompute named STORED derived index columns from the documents already in a collection. A spatial column is stored only under the physical mapping that cannot index a registered deterministic function (the generated mapping populates itself); a vector column is stored under both. The step is idempotent a derived value is a pure function of the document so a replay writes what the first run wrote.",
203
277
  "type": "object",
204
278
  "properties": {
205
279
  "kind": {
@@ -273,7 +347,7 @@
273
347
  "additionalProperties": false
274
348
  },
275
349
  "sqlStep": {
276
- "description": "A DATA step spelled directly as one SQL statement (MIGRATION-FORMAT \u00a79.4): the planner's fold of a dropped column into the document, the move of a document member into its new column, an R*Tree backfill. Executed exactly as a ddl step; distinct in kind so a reviewer reads intent and a dry run shows it as data.",
350
+ "description": "A DATA step spelled directly as one SQL statement (MIGRATION-FORMAT §9.4): the planner's fold of a dropped column into the document, the move of a document member into its new column, an R*Tree backfill. Executed exactly as a ddl step; distinct in kind so a reviewer reads intent and a dry run shows it as data.",
277
351
  "type": "object",
278
352
  "properties": {
279
353
  "kind": {
@@ -294,7 +368,7 @@
294
368
  "additionalProperties": false
295
369
  },
296
370
  "rebuildStep": {
297
- "description": "The ALTER TABLE procedure SQLite lacks (MIGRATION-FORMAT \u00a710), self-contained: create the target shape under `<table>__rebuild`, copy the rows through the rendered column mapping, drop the old table, rename, recreate the indexes, then `PRAGMA foreign_key_check` inside the transaction.",
371
+ "description": "The ALTER TABLE procedure SQLite lacks (MIGRATION-FORMAT §10), self-contained: create the target shape under `<table>__rebuild`, copy the rows through the rendered column mapping, drop the old table, rename, recreate the indexes, then `PRAGMA foreign_key_check` inside the transaction.",
298
372
  "type": "object",
299
373
  "properties": {
300
374
  "kind": {
@@ -314,7 +388,7 @@
314
388
  }
315
389
  },
316
390
  "copy": {
317
- "description": "The INSERT \u2026 SELECT that carries every row across the column mapping.",
391
+ "description": "The INSERT SELECT that carries every row across the column mapping.",
318
392
  "type": "string",
319
393
  "minLength": 1
320
394
  },
@@ -338,6 +412,96 @@
338
412
  "indexes"
339
413
  ],
340
414
  "additionalProperties": false
415
+ },
416
+ "tableStep": {
417
+ "description": "A complete reviewed planTableMigration artifact, executed with its source, row, storage and identity guards.",
418
+ "type": "object",
419
+ "properties": {
420
+ "kind": {
421
+ "const": "table"
422
+ },
423
+ "plan": {
424
+ "type": "object",
425
+ "required": [
426
+ "version",
427
+ "id",
428
+ "table",
429
+ "checksum",
430
+ "source",
431
+ "after",
432
+ "rebuild",
433
+ "temporary",
434
+ "unchanged",
435
+ "statements",
436
+ "finish"
437
+ ],
438
+ "properties": {
439
+ "version": {
440
+ "const": 1
441
+ },
442
+ "id": {
443
+ "type": "string",
444
+ "minLength": 1
445
+ },
446
+ "table": {
447
+ "type": "string",
448
+ "minLength": 1
449
+ },
450
+ "checksum": {
451
+ "type": "string",
452
+ "minLength": 1
453
+ },
454
+ "source": {
455
+ "type": "array",
456
+ "items": {
457
+ "type": "object"
458
+ }
459
+ },
460
+ "after": {
461
+ "type": "array",
462
+ "items": {
463
+ "type": "object"
464
+ }
465
+ },
466
+ "rebuild": {
467
+ "type": "boolean"
468
+ },
469
+ "temporary": {
470
+ "type": "string",
471
+ "minLength": 1
472
+ },
473
+ "unchanged": {
474
+ "type": "array",
475
+ "items": {
476
+ "type": "string",
477
+ "minLength": 1
478
+ }
479
+ },
480
+ "statements": {
481
+ "type": "array",
482
+ "items": {
483
+ "type": "string",
484
+ "minLength": 1
485
+ }
486
+ },
487
+ "finish": {
488
+ "type": "array",
489
+ "items": {
490
+ "type": "string",
491
+ "minLength": 1
492
+ }
493
+ }
494
+ }
495
+ },
496
+ "note": {
497
+ "type": "string"
498
+ }
499
+ },
500
+ "required": [
501
+ "kind",
502
+ "plan"
503
+ ],
504
+ "additionalProperties": false
341
505
  }
342
506
  }
343
507
  }
@@ -80,6 +80,53 @@
80
80
  },
81
81
  "additionalProperties": false
82
82
  }
83
+ },
84
+ "target": {
85
+ "type": "object",
86
+ "properties": {
87
+ "objects": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "object",
91
+ "required": [
92
+ "type",
93
+ "name",
94
+ "owner",
95
+ "sql"
96
+ ],
97
+ "properties": {
98
+ "type": {
99
+ "enum": [
100
+ "table",
101
+ "view",
102
+ "index",
103
+ "trigger"
104
+ ]
105
+ },
106
+ "name": {
107
+ "type": "string"
108
+ },
109
+ "owner": {
110
+ "type": "string"
111
+ },
112
+ "sql": {
113
+ "type": "string"
114
+ }
115
+ }
116
+ }
117
+ },
118
+ "tables": {
119
+ "type": "array",
120
+ "uniqueItems": true,
121
+ "items": {
122
+ "type": "string"
123
+ }
124
+ }
125
+ },
126
+ "required": [
127
+ "objects"
128
+ ],
129
+ "additionalProperties": false
83
130
  }
84
131
  },
85
132
  "additionalProperties": false
@@ -113,6 +160,9 @@
113
160
  },
114
161
  {
115
162
  "$ref": "#/$defs/rebuildStep"
163
+ },
164
+ {
165
+ "$ref": "#/$defs/tableStep"
116
166
  }
117
167
  ]
118
168
  },
@@ -158,6 +208,18 @@
158
208
  },
159
209
  "note": {
160
210
  "type": "string"
211
+ },
212
+ "model": {
213
+ "description": "The immutable model describing this step’s current physical layout.",
214
+ "type": "object",
215
+ "properties": {
216
+ "$model": {
217
+ "const": "0.1"
218
+ }
219
+ },
220
+ "required": [
221
+ "$model"
222
+ ]
161
223
  }
162
224
  },
163
225
  "required": [
@@ -189,6 +251,18 @@
189
251
  },
190
252
  "note": {
191
253
  "type": "string"
254
+ },
255
+ "model": {
256
+ "description": "The immutable model describing this step’s current physical layout.",
257
+ "type": "object",
258
+ "properties": {
259
+ "$model": {
260
+ "const": "0.1"
261
+ }
262
+ },
263
+ "required": [
264
+ "$model"
265
+ ]
192
266
  }
193
267
  },
194
268
  "required": [
@@ -338,6 +412,96 @@
338
412
  "indexes"
339
413
  ],
340
414
  "additionalProperties": false
415
+ },
416
+ "tableStep": {
417
+ "description": "A complete reviewed planTableMigration artifact, executed with its source, row, storage and identity guards.",
418
+ "type": "object",
419
+ "properties": {
420
+ "kind": {
421
+ "const": "table"
422
+ },
423
+ "plan": {
424
+ "type": "object",
425
+ "required": [
426
+ "version",
427
+ "id",
428
+ "table",
429
+ "checksum",
430
+ "source",
431
+ "after",
432
+ "rebuild",
433
+ "temporary",
434
+ "unchanged",
435
+ "statements",
436
+ "finish"
437
+ ],
438
+ "properties": {
439
+ "version": {
440
+ "const": 1
441
+ },
442
+ "id": {
443
+ "type": "string",
444
+ "minLength": 1
445
+ },
446
+ "table": {
447
+ "type": "string",
448
+ "minLength": 1
449
+ },
450
+ "checksum": {
451
+ "type": "string",
452
+ "minLength": 1
453
+ },
454
+ "source": {
455
+ "type": "array",
456
+ "items": {
457
+ "type": "object"
458
+ }
459
+ },
460
+ "after": {
461
+ "type": "array",
462
+ "items": {
463
+ "type": "object"
464
+ }
465
+ },
466
+ "rebuild": {
467
+ "type": "boolean"
468
+ },
469
+ "temporary": {
470
+ "type": "string",
471
+ "minLength": 1
472
+ },
473
+ "unchanged": {
474
+ "type": "array",
475
+ "items": {
476
+ "type": "string",
477
+ "minLength": 1
478
+ }
479
+ },
480
+ "statements": {
481
+ "type": "array",
482
+ "items": {
483
+ "type": "string",
484
+ "minLength": 1
485
+ }
486
+ },
487
+ "finish": {
488
+ "type": "array",
489
+ "items": {
490
+ "type": "string",
491
+ "minLength": 1
492
+ }
493
+ }
494
+ }
495
+ },
496
+ "note": {
497
+ "type": "string"
498
+ }
499
+ },
500
+ "required": [
501
+ "kind",
502
+ "plan"
503
+ ],
504
+ "additionalProperties": false
341
505
  }
342
506
  }
343
507
  }