@vttforge/core 0.0.0 → 0.7.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/CHANGELOG.md ADDED
@@ -0,0 +1,308 @@
1
+ # @vttforge/core
2
+
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dcd07d5: Type the three embedded fields, and turn `checkJs` back on for the example.
8
+
9
+ - `EmbeddedDataField` is the model instance, not a plain object — the field builds a schema from the model's own `defineSchema()`, but initializing constructs the model, so derived data and methods come with it.
10
+ - `EmbeddedDocumentField` is the same for a Document class, and nullable out of the box.
11
+ - `TypedSchemaField` is a discriminated union. The field supplies a `type` string validated to equal each entry's key when the entry does not declare one, which is what makes narrowing on `type` work.
12
+
13
+ The example system now compiles with `checkJs: true`, which is what proves any of this against real JavaScript rather than only against type tests.
14
+
15
+ ## 0.6.0
16
+
17
+ ### Minor Changes
18
+
19
+ - c24b2e9: Fix two things `InferSchema` got wrong about a field's runtime type.
20
+
21
+ `ColorField` inferred as `string`. It stores a CSS string but initializes
22
+ into a `Color` instance, so `system.tint` is an object with `.css`, `.rgb`
23
+ and friends — and the old typing made every property access on it a lie the
24
+ compiler accepted. It is also nullable by default, unlike the other
25
+ string-backed fields: the field's own defaults are `nullable: true,
26
+ initial: null`, so reading `.css` off a fresh document was a real crash the
27
+ types allowed. It now infers as `Color | null`, and drops the null when
28
+ `nullable: false` is set.
29
+
30
+ Presence was half-implemented. Only `nullable: true` widened the type;
31
+ `required: false` did not. A field that resolves to `undefined` when absent
32
+ was typed as always present. The rule now follows how a field actually
33
+ resolves a missing value: an explicit `initial` always fills, so it never
34
+ widens; otherwise `required: false` admits `undefined` and `nullable: true`
35
+ admits `null`, and the two compose.
36
+
37
+ `Color` is described structurally rather than imported, so the inference
38
+ surface still carries no dependency on a Foundry type package.
39
+ - 3f09683: Add `registerModule()` for modules that contribute document sub-types.
40
+
41
+ Foundry files a module's sub-type under `<module-id>.<type>`. Register the bare name and there is no error — the type just never appears. `registerModule()` adds the prefix, and `moduleSubType(id, type)` gives you the same string wherever else you need it (registering the sheet, checking `actor.type`, writing `documentTypes` in the manifest).
42
+
43
+ ```ts
44
+ registerModule({
45
+ id: 'pdf-character-sheet',
46
+ itemDataModels: { pdf: PdfItemData }, // → CONFIG.Item.dataModels['pdf-character-sheet.pdf']
47
+ });
48
+ ```
49
+
50
+ `registerSystem()` was the only option before, and it is the wrong shape for a module: it writes bare keys and also replaces the document classes, the initiative formula, and the status-effect array — all of which belong to the system. There is no option to replace them here, and `statusEffects` appends instead of assigning.
51
+ - 98b742c: Give each field its own defaults when inferring a schema.
52
+
53
+ Every field class picks its own defaults, and they disagree. The inference treated them as if they agreed, so three fields were typed as shapes they cannot hold:
54
+
55
+ - `NumberField` is optional and nullable out of the box. `new fields.NumberField()` is `number | null | undefined`, not `number`.
56
+ - `StringField` is optional. A bare one is `string | undefined`.
57
+ - `FilePathField` starts at `null`, the way `ColorField` does. A bare one is `string | null`.
58
+
59
+ The rest were already right, for reasons worth naming: booleans and HTML fields are required and supply their own initial; arrays, sets and schemas are required and build their own empty value; document references are required but nullable.
60
+
61
+ This will surface errors in schemas that leave the options off. The fix is to declare what you meant — `{ required: true, nullable: false, initial: 0 }` — which is what the field needed all along.
62
+ - 8657721: Let `BaseTypeDataModel` learn your schema.
63
+
64
+ Hand it the function that returns your fields and it implements `static defineSchema()` for you. The schema is written once, and `this` inside `prepareDerivedData()` knows its own fields:
65
+
66
+ ```ts
67
+ class CharacterData extends BaseTypeDataModel(defineCharacterSchema) {
68
+ declare armorClass: number;
69
+ prepareDerivedData() {
70
+ this.armorClass = 10 + this.level; // this.level is number
71
+ }
72
+ }
73
+
74
+ type CharacterSystem = CharacterData['$inferData'];
75
+ ```
76
+
77
+ Derived values are not in the schema, so declare them on the subclass.
78
+
79
+ Calling `BaseTypeDataModel()` with no arguments works exactly as before.
80
+ - f11b1f4: Type `SetField` and `ForeignDocumentField` in `InferSchema`.
81
+
82
+ A `SetField` holds a `Set`, not an array. Inferring it as an array handed you `push` and index access on a value that has neither, and the compiler agreed.
83
+
84
+ A `ForeignDocumentField` reads back as the document itself — the data model installs the field as a getter, so the property gives you the instance, not the function that fetched it. Under `idOnly` it stays the id string. Both admit `null` unless the schema sets `nullable: false`.
85
+
86
+ Also exported: `SetFieldInstance`, `SetFieldCtor`, `SetFieldOptions`, `ForeignDocumentFieldInstance`, `ForeignDocumentFieldCtor`, `ForeignDocumentFieldOptions`, and `DocumentClass`.
87
+ - 74c4126: Type the statics on `BaseActorSheet()` and `BaseItemSheet()`.
88
+
89
+ Both returned a bare constructor, so a subclass writing `super.DEFAULT_OPTIONS` — the pattern the docs show and every sheet needs — failed to compile. TypeScript cannot see a static through an untyped constructor. The example system is JavaScript, so nothing caught it.
90
+
91
+ They now return `SheetBaseCtor`, which carries `DEFAULT_OPTIONS` and `DRAG_DROP`. A subclass declaring either needs the `override` modifier, which is TypeScript correctly seeing the inherited static.
92
+
93
+ ## 0.5.0
94
+
95
+ ### Minor Changes
96
+
97
+ - 9462144: Require Node 26.
98
+
99
+ The floor moves from `>=22.14.0` to `>=26.0.0` across every package and the
100
+ four scaffolding templates, and the bundler target for the Node-side
101
+ packages moves from `node22` to `node26`.
102
+
103
+ Node 22 entered maintenance in October 2025 and receives security fixes
104
+ only. Node 26 becomes the active LTS line on 2026-10-28.
105
+
106
+ This is breaking for anyone on Node 22 or 24. It is marked `minor` rather
107
+ than `major` on purpose: these packages are still on 0.x, where a minor
108
+ signals the break, and a major would push every package to 1.0.0 — a claim
109
+ of API stability that has not been audited, on packages two of which are
110
+ still stubs.
111
+
112
+ The templates move to the versions this release publishes. On 0.x a caret
113
+ pins the minor, so their old ranges would not have matched.
114
+
115
+ CI now pins Node through `actions/setup-node` instead of inheriting whatever
116
+ the runner image ships, so the version the packages declare is the version
117
+ they are tested on. It was not before: the workflow took the image's Node,
118
+ and nothing enforced the declared floor because `engine-strict` is not set.
119
+
120
+ ## 0.4.0
121
+
122
+ ### Minor Changes
123
+
124
+ - 50721f9: fix: align with Foundry v13 manifest schema and add `prepareBaseData` hook
125
+
126
+ Three coordinated changes:
127
+
128
+ **`@vttforge/core`** — `BaseTypeDataModel()` now ships a `prepareBaseData()`
129
+ no-op stub alongside `prepareDerivedData()`. Use `prepareBaseData()` to
130
+ initialize fields that Active Effects need to mutate (base max HP, base AC),
131
+ since AEs apply between `prepareBaseData()` and `prepareDerivedData()`.
132
+ `prepareDerivedData()` stays the place for values that depend on the
133
+ AE-mutated state.
134
+
135
+ The misleading `_addDataFieldMigrations()` static stub is removed. The real
136
+ field-rename API is `_addDataFieldMigration(source, oldKey, newKey, apply?)`
137
+ called inside a `static migrateData(source)` override — consumers who need
138
+ it can call it directly on the Foundry-provided base via `super`.
139
+
140
+ **`@vttforge/vite-plugin`** — emits the canonical v13 `styles` form
141
+ (`[{ src: "styles/foo.css" }]`) in the built manifest. Still accepts the
142
+ legacy string form (`["styles/foo.css"]`) and the v13 object form as input,
143
+ so existing consumers don't need to change their source manifest. Additional
144
+ metadata declared on object entries (e.g. `layer` for cascade layer
145
+ placement) is preserved through the rewrite — only `src` is rewritten to
146
+ point at the bundled output.
147
+
148
+ **`@vttforge-examples/simple-system`** — manifest aligned with v13 schema:
149
+ `gridDistance` / `gridUnits` collapsed into the `grid` object; `styles`
150
+ declared in object form; `flags.hotReload` declared at the root of `flags`
151
+ (not under the package namespace) and switched to the object form
152
+ (`{ extensions, paths }`) that Foundry's runtime hot-reload watcher
153
+ actually reads. The previous shape was a double no-op — wrong location AND
154
+ wrong form, so the watcher silently exited without registering any
155
+ extensions.
156
+
157
+ Hot-reload enablement on the Foundry server side is deferred — runtime
158
+ configuration changes interact with felddy's `CONTAINER_PRESERVE_CONFIG`
159
+ flag in ways that require a dedicated design pass. That work is tracked
160
+ separately and will land alongside the developer-facing hot-reload bridge.
161
+
162
+ ## 0.3.0
163
+
164
+ ### Minor Changes
165
+
166
+ - 234a4b2: Extend `BaseActorSheet` and add `BaseItemSheet` — the boilerplate every shipping
167
+ system copy-pastes is now hoisted into the SDK.
168
+ - `static DRAG_DROP` — declare drag sources / drop targets as data; the base
169
+ wires real `foundry.applications.ux.DragDrop` instances in `_onRender` with
170
+ `isEditable`-gated permissions and a default `_onDragStart` that serialises
171
+ `data-item-id` elements as `{ type: "Item", uuid }`.
172
+ - `_prepareContext` auto-fills `context.tabs[group]` for every group declared
173
+ in ApplicationV2's `static TABS`, eliminating manual `_prepareTabs(group)`
174
+ calls in subclass `_prepareContext`.
175
+ - Typed drop dispatch: override `onDropItem(item, event)` / `onDropActor(...)` /
176
+ `onDropFolder(...)` / `onDropActiveEffect(...)` and skip the `fromUuid()`
177
+ ceremony. Returning `undefined` falls through to Foundry's default
178
+ `_onDropX`; return anything else to take ownership.
179
+ - New `BaseItemSheet()` mirror with the same `static DRAG_DROP` + tab
180
+ auto-population, minus the drop dispatch (items rarely receive drops).
181
+ - Exports new `DragDropConfig` type for typed `static DRAG_DROP` declarations.
182
+
183
+ `editImage` is intentionally not reinvented — it already ships on
184
+ `DocumentSheetV2` (inherited by both `ActorSheetV2` and `ItemSheetV2`).
185
+ Templates wire `<img data-edit="img">` and Foundry's built-in action handles
186
+ the `FilePicker` flow.
187
+
188
+ - 4fd5a07: Error registry codegen — `docsUrl` now resolves to a real page.
189
+
190
+ `postbuild` hook (`packages/core/scripts/codegen-errors.mjs`) reads the
191
+ just-built `dist/index.mjs`, calls `listErrorEntries()`, and emits:
192
+ - `dist/errors-manifest.json` — versioned JSON catalogue shipped in the
193
+ tarball alongside the bundled JS/types. Stable shape (`version`,
194
+ `package`, `packageVersion`, `entries[]`) so external tooling (the v0.3
195
+ docs site, IDE extensions, lint rules) has a single source of truth.
196
+ - `docs/errors/VTTF-NNNN.md` at the repo root — one Markdown stub per code,
197
+ committed so the `docsUrl` already resolves while the full VitePress site
198
+ is being built in v0.3.
199
+
200
+ New runtime helper: `getErrorManifest()` returns the same data as
201
+ `listErrorEntries()`, wrapped in a typed `ErrorManifest` envelope with a
202
+ stable `version: 1` field for future format migrations.
203
+
204
+ Plan deviation: the codegen runs as `postbuild` (not `prebuild`) so the
205
+ script imports the just-built ESM directly instead of needing
206
+ `tsx`/`unrun` to load the TS source. Documented inline in the codegen
207
+ script.
208
+
209
+ - 0896bb0: Add `createMigrationRunner()` for declarative schema migrations, plus
210
+ `onReady` lifecycle on `registerSystem()`.
211
+
212
+ `createMigrationRunner({ systemId, migrations, ... })` returns `{ register(),
213
+ run(), targetVersion }`. Call `register()` from `init` to register the
214
+ `schemaVersion` setting; call `run()` from `ready` (gated by
215
+ `game.user.isGM`) to execute every pending migration in order. Migrations use
216
+ semver versions and `foundry.utils.isNewerVersion` for comparison — the same
217
+ contract `system.json`'s `flags.<systemId>.needsMigrationVersion` /
218
+ `compatibleMigrationVersion` use, matching the dnd5e production pattern.
219
+
220
+ Failure semantics: `schemaVersion` is committed per-migration, so a
221
+ mid-sequence throw leaves the world at the last successful version and the
222
+ retry on the next world load picks up exactly where it failed. Migration
223
+ errors are wrapped in `VttfError VTTF-0004` with the original error on
224
+ `.cause`; calling `run()` against a world older than `compatibleVersion`
225
+ throws `VttfError VTTF-0005`.
226
+
227
+ `registerSystem()` gains `onReady?: () => void | Promise<void>` — the natural
228
+ place to wire `migrationRunner.run()`. Not GM-gated; consumer guards inside
229
+ their callback.
230
+
231
+ New error codes (append-only): `VTTF-0004 MigrationFailed`,
232
+ `VTTF-0005 WorldTooOldForMigration`.
233
+
234
+ - 49a8718: Fix `BaseActorSheet` / `BaseItemSheet` tab handling so sheets work without
235
+ per-consumer workarounds.
236
+
237
+ Two issues surfaced when running the example sheet inside a live Foundry v13:
238
+ - **`context.tabs` double-wrap on single-group sheets.** The previous
239
+ `_prepareContext` override unconditionally set `context.tabs[group]`,
240
+ even when ApplicationV2 already populated a flat
241
+ `context.tabs[tabId]` for single-group sheets. The collision forced
242
+ consumers to either unwrap manually or write `context.tabs.<group>.<tabId>`
243
+ in every template. Fixed: BaseActorSheet/BaseItemSheet now only fill
244
+ `context.tabs[group]` for **multi-group** sheets (single-group sheets
245
+ see ApplicationV2's flat shape untouched).
246
+
247
+ - **No default `tab`-style action handler.** ApplicationV2 doesn't ship a
248
+ built-in handler for `data-action="…"` tab navigation buttons, and the
249
+ bare name `tab` is reserved by the framework (custom handlers under that
250
+ name never fire). Fixed: both base sheets now ship a `vttforgeTab`
251
+ action that toggles `.active` on the matching nav button
252
+ (`[data-action="vttforgeTab"][data-group=…][data-tab=…]`) and content
253
+ section (`section.tab[data-group=…][data-tab=…]`) and updates
254
+ `sheet.tabGroups[group]`. Templates that already used the old per-sheet
255
+ workaround need to rename `data-action="tab"` → `data-action="vttforgeTab"`.
256
+
257
+ Discovered during development testing — not derived from any external
258
+ source.
259
+
260
+ Patch bump for the example: drops the `_prepareContext` unwrap workaround
261
+ and the per-sheet `_onTab` static handlers added in the previous PR,
262
+ since both now live in the SDK.
263
+
264
+ ## 0.2.0
265
+
266
+ ### Minor Changes
267
+
268
+ - 5dd98c1: Add `fields()` factory and `InferSchema<T>` for typed `defineSchema()` outputs.
269
+
270
+ Covers the v0.1 partial scope from PRD §7: `NumberField`, `StringField`,
271
+ `BooleanField`, `HTMLField`, `ArrayField`, `SchemaField`, `ColorField`,
272
+ `FilePathField`. Calling `fields()` lazy-resolves `globalThis.foundry.data.fields`
273
+ and throws `VttfError VTTF-0002` outside the Foundry runtime — same pattern as
274
+ `BaseTypeDataModel()` / `BaseActorSheet()`.
275
+
276
+ `InferSchema<S>` derives the `system` shape from a `defineSchema()` return value,
277
+ recursing through `ArrayField` and `SchemaField` and honouring the single
278
+ nullability rule `nullable: true` → `T | null`. Full class-level inference
279
+ (`BaseTypeDataModel<typeof Schema>`), `$inferData`, `EmbeddedDataField`,
280
+ `EmbeddedDocumentField`, `TypedSchemaField`, and the full required×initial
281
+ nullability matrix remain v1.0 scope and will ship from `@vttforge/types`.
282
+
283
+ ## 0.1.0
284
+
285
+ ### Minor Changes
286
+
287
+ - 4900e83: Foundation MVP (PR 4 of 4) — `@vttforge/core` ships its first runtime surface (registerSystem, SystemConfig, BaseTypeDataModel, BaseActorSheet, VttfError + VTTF-NNNN registry) and `@vttforge/styles` ships its first `--vttf-*` token set wrapped in the `vttforge.tokens` cascade layer.
288
+
289
+ Both packages have working consumer entrypoints (verified by an external smoke test loading the built `.mjs` from a throwaway dir) and the SDK contracts match the canonical Foundry v13 patterns (TypeDataModel migration, ActorSheetV2 + HandlebarsApplicationMixin, staged init hooks, marker classes).
290
+
291
+ Status remains pre-1.0 and APIs are explicitly unstable — these are the first releases that have real code instead of placeholder `export {}`.
292
+
293
+ ## 0.0.1
294
+
295
+ Initial functional release (v0.1 MVP slice). Foundry v13+ system runtime helpers.
296
+
297
+ ### Added
298
+
299
+ - `registerSystem({ id, actorDataModels, itemDataModels, actorDocumentClass, itemDocumentClass, combat, statusEffects, onBeforeInit, onAfterInit })` — one-call boot that schedules CONFIG mutations via `Hooks.once("init", ...)`. Idempotent per `id` (throws `VTTF-0001` on duplicate). Sets `CONFIG.ActiveEffect.legacyTransferral = false` by default.
300
+ - `SystemConfig` — typed wrapper around `game.settings.register/get/set`. Tracks registered keys locally; reads/writes against an unregistered key throw `VTTF-0003` instead of returning `undefined`.
301
+ - `BaseTypeDataModel()` — mixin over `foundry.abstract.TypeDataModel` providing a safe default `migrateData` that delegates to `super` (chained-migration guard), a stub `_addDataFieldMigrations`, and a no-op `prepareDerivedData`.
302
+ - `BaseActorSheet()` — mixin over `HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2)` with `DEFAULT_OPTIONS` that ship the `vttforge` marker class so consumers can scope CSS without specificity wars.
303
+ - `VttfError` + `VTTF-NNNN` registry — central, append-only error codes (`VTTF-0001` SystemAlreadyRegistered, `VTTF-0002` MissingFoundryGlobals, `VTTF-0003` UnknownSetting), each with a `name`, `summary`, and `docsUrl` pointing at `https://vttforge.dev/errors/VTTF-NNNN`. Supports native ES2022 `cause` and `AggregateError`.
304
+
305
+ ### Verified
306
+
307
+ - 32 Vitest unit tests across 5 files, all passing on Node 22.14 and Node 24.
308
+ - External consumer smoke test (importing the published `.mjs` from a throwaway dir) confirms every exported symbol behaves as designed when Foundry globals are present or absent.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabricio Cavalcante de Souza and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # @vttforge/core
2
2
 
3
- Runtime utilities for building FoundryVTT systems and modules.
3
+ Core runtime utilities for VTTForge — the modern SDK for building [FoundryVTT](https://foundryvtt.com) v13+ systems and modules.
4
4
 
5
- > 🚧 **Placeholder release.** This is a reserved name on the npm registry. The first usable version is coming soon.
5
+ > **Status:** v0.0.1 placeholder. Real APIs land in v0.1.0.
6
6
 
7
- Track progress and contribute at **https://github.com/vttforge/vttforge**.
7
+ ## Planned API surface (v0.1.0)
8
8
 
9
- ---
10
-
11
- VTTForge is an independent, community-developed project. It is not affiliated with, endorsed by, or sponsored by Foundry Gaming LLC. "Foundry Virtual Tabletop", "Foundry VTT", and "FVTT" are trademarks of Foundry Gaming LLC.
9
+ - `BaseTypeDataModel` — eliminates stub `migrateData()` and centralises `InferSchema<T>` inference.
10
+ - `BaseActorSheet` / `BaseItemSheet` — declarative replacements for `_getTabs`, DragDrop wiring, `_onEditImage`, `_onDrop`.
11
+ - `SystemConfig` typed wrapper around `game.settings`, eliminates hardcoded system-ID strings.
12
+ - `registerSystem()` — one-call init that replaces the manual `Hooks.once("init", …)` block.
13
+ - `createMigrationRunner` — declarative data migrations with version gates.
14
+ - Error registry — `VttfError` + central `VTTF-NNNN` codes with `docsUrl` pointing at `vttforge.dev/errors/`.
@@ -0,0 +1,32 @@
1
+ {
2
+ "version": 1,
3
+ "package": "@vttforge/core",
4
+ "packageVersion": "0.7.0",
5
+ "entries": [
6
+ {
7
+ "code": "VTTF-0001",
8
+ "name": "SystemAlreadyRegistered",
9
+ "summary": "registerSystem() was called more than once for the same system id. This is almost always a hot-reload artefact or a duplicate import."
10
+ },
11
+ {
12
+ "code": "VTTF-0002",
13
+ "name": "MissingFoundryGlobals",
14
+ "summary": "VTTForge code ran in an environment without Foundry globals (game, Hooks, CONFIG). Initialise inside the Foundry runtime, not in a Node test without mocks."
15
+ },
16
+ {
17
+ "code": "VTTF-0003",
18
+ "name": "UnknownSetting",
19
+ "summary": "SystemConfig.get() / set() was called with a key that was never passed to SystemConfig.register(). Register the setting in your init hook before reading it."
20
+ },
21
+ {
22
+ "code": "VTTF-0004",
23
+ "name": "MigrationFailed",
24
+ "summary": "A migration function passed to createMigrationRunner() threw. The original error is available on .cause. The schemaVersion setting is not advanced past the failed migration so retrying on the next world load picks up where the failure left off."
25
+ },
26
+ {
27
+ "code": "VTTF-0005",
28
+ "name": "WorldTooOldForMigration",
29
+ "summary": "createMigrationRunner() was called on a world whose stored schemaVersion is older than the configured compatibleVersion floor. Upgrade the world to a supported intermediate version before continuing — running migrations across the gap would corrupt data."
30
+ }
31
+ ]
32
+ }