@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.
@@ -0,0 +1,1049 @@
1
+ //#region src/base-actor-sheet.d.ts
2
+ /**
3
+ * BaseActorSheet — `ActorSheetV2 + HandlebarsApplicationMixin` baseline with the
4
+ * boilerplate every shipping system copy-pastes hoisted into the SDK.
5
+ *
6
+ * What this adds beyond stock Foundry v13:
7
+ *
8
+ * - **`static DRAG_DROP`** — declare drag sources / drop targets as data, get
9
+ * `foundry.applications.ux.DragDrop` instances wired in `_onRender` with
10
+ * `isEditable`-gated permissions and a sensible default `_onDragStart` that
11
+ * serialises `data-item-id` elements as `{ type: "Item", uuid }`.
12
+ * - **`_prepareContext` auto-fills `context.tabs[group]`** for every group
13
+ * declared in ApplicationV2's `static TABS`, so subclass `_prepareContext`
14
+ * implementations stop having to call `_prepareTabs(group)` by hand.
15
+ * - **Typed drop dispatch** — override `onDropItem(item, event)` /
16
+ * `onDropActor(actor, event)` / `onDropFolder(folder, event)` /
17
+ * `onDropActiveEffect(effect, event)` and skip the `fromUuid()` ceremony.
18
+ * Returning `undefined` falls through to Foundry's default `_onDropX`
19
+ * behaviour; return any other value to take ownership.
20
+ *
21
+ * Intentional non-additions:
22
+ *
23
+ * - `editImage` action — already shipped by `DocumentSheetV2` (inherited by
24
+ * `ActorSheetV2`). Templates wire `<img data-edit="img">` and Foundry's
25
+ * built-in action handles the `FilePicker` flow.
26
+ * - `_getTabs()` — ApplicationV2 already owns the tab state machine; we only
27
+ * eliminate the `_prepareTabs` call in `_prepareContext`.
28
+ *
29
+ * Resolved lazily so subclasses can be declared at module load without
30
+ * Foundry globals existing yet (test boot, ESM hoist).
31
+ */
32
+ /**
33
+ * Declarative DragDrop entry consumed by `_onRender`. Mirrors the
34
+ * `foundry.applications.ux.DragDrop` constructor config. Permissions and
35
+ * callbacks fall back to sensible defaults that honour `this.isEditable` and
36
+ * the default `_onDragStart` / `_onDrop`.
37
+ */
38
+ interface DragDropConfig {
39
+ readonly dragSelector?: string;
40
+ readonly dropSelector?: string;
41
+ readonly permissions?: {
42
+ readonly dragstart?: () => boolean;
43
+ readonly drop?: () => boolean;
44
+ };
45
+ readonly callbacks?: Record<string, (...args: any[]) => unknown>;
46
+ }
47
+ /**
48
+ * The statics a VTTForge sheet base carries.
49
+ *
50
+ * The factory used to return a bare constructor, so a subclass writing
51
+ * `super.DEFAULT_OPTIONS` — the pattern the docs show and every sheet needs —
52
+ * failed to compile. TypeScript cannot see a static through an untyped
53
+ * constructor. The example system never caught it because it is JavaScript.
54
+ *
55
+ * `DEFAULT_OPTIONS` is deliberately loose: a subclass merges its own shape
56
+ * into it, and pinning ours would reject the merge.
57
+ */
58
+ interface SheetBaseStatics {
59
+ readonly DEFAULT_OPTIONS: Record<string, any>;
60
+ readonly DRAG_DROP: ReadonlyArray<DragDropConfig>;
61
+ }
62
+ /**
63
+ * What the factory hands back: something you can `extend`, whose statics the
64
+ * compiler can see.
65
+ */
66
+ interface SheetBaseCtor extends SheetBaseStatics {
67
+ new (...args: any[]): any;
68
+ }
69
+ /**
70
+ * Marker class that consumer CSS uses for scoping. Always present on every
71
+ * VTTForge-derived sheet so rules like `.vttforge .actor-sheet { ... }` work.
72
+ */
73
+ declare const VTTFORGE_SHEET_CLASS = "vttforge";
74
+ /**
75
+ * Build the `BaseActorSheet` for the current Foundry runtime. See module
76
+ * header for the boilerplate this base eliminates.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * class CharacterSheet extends BaseActorSheet() {
81
+ * static DEFAULT_OPTIONS = foundry.utils.mergeObject(
82
+ * super.DEFAULT_OPTIONS,
83
+ * { classes: ['my-system'], position: { width: 720 } },
84
+ * );
85
+ * static PARTS = { ... };
86
+ * static TABS = {
87
+ * primary: {
88
+ * tabs: [
89
+ * { id: 'features', group: 'primary', label: 'Features' },
90
+ * { id: 'inventory', group: 'primary', label: 'Inventory' },
91
+ * ],
92
+ * initial: 'features',
93
+ * },
94
+ * };
95
+ * static DRAG_DROP = [{ dragSelector: '.item[draggable=true]', dropSelector: null }];
96
+ * async onDropItem(item, event) {
97
+ * if (item.type !== 'weapon') return false;
98
+ * // …fall through to super by returning undefined.
99
+ * }
100
+ * }
101
+ * ```
102
+ */
103
+ declare function BaseActorSheet(): SheetBaseCtor;
104
+ //#endregion
105
+ //#region src/base-item-sheet.d.ts
106
+ /**
107
+ * Build the `BaseItemSheet` for the current Foundry runtime.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * class WeaponSheet extends BaseItemSheet() {
112
+ * static DEFAULT_OPTIONS = foundry.utils.mergeObject(
113
+ * super.DEFAULT_OPTIONS,
114
+ * { classes: ['my-system'], position: { width: 540 } },
115
+ * );
116
+ * static PARTS = { ... };
117
+ * static TABS = {
118
+ * primary: {
119
+ * tabs: [
120
+ * { id: 'description', group: 'primary', label: 'Description' },
121
+ * { id: 'details', group: 'primary', label: 'Details' },
122
+ * ],
123
+ * initial: 'description',
124
+ * },
125
+ * };
126
+ * }
127
+ * ```
128
+ */
129
+ declare function BaseItemSheet(): SheetBaseCtor;
130
+ //#endregion
131
+ //#region src/data/field-options.d.ts
132
+ /**
133
+ * Option interfaces for the Foundry v13 data fields covered by `@vttforge/core`'s
134
+ * v0.1 `InferSchema<T>` surface.
135
+ *
136
+ * Mirrors the shape documented at https://foundryvtt.com/api/v13/ for each
137
+ * `foundry.data.fields.*` class. Properties are structural — they exist purely
138
+ * so the conditional types in `./infer-schema.ts` can extract semantics like
139
+ * `nullable: true` without us pulling in `fvtt-types` (deferred to
140
+ * `@vttforge/types` v1.0).
141
+ */
142
+ /**
143
+ * Properties shared by every Foundry data field option object.
144
+ *
145
+ * Mirrors the base `DataFieldOptions` interface documented at
146
+ * https://foundryvtt.com/api/v13/classes/foundry.data.fields.DataField.html.
147
+ */
148
+ interface DataFieldOptions {
149
+ readonly required?: boolean;
150
+ readonly nullable?: boolean;
151
+ readonly initial?: unknown;
152
+ readonly readonly?: boolean;
153
+ readonly gmOnly?: boolean;
154
+ readonly label?: string;
155
+ readonly hint?: string;
156
+ readonly validationError?: string;
157
+ readonly validate?: (value: unknown) => boolean | undefined;
158
+ }
159
+ interface NumberFieldOptions extends DataFieldOptions {
160
+ readonly integer?: boolean;
161
+ readonly positive?: boolean;
162
+ readonly min?: number;
163
+ readonly max?: number;
164
+ readonly step?: number;
165
+ readonly choices?: readonly number[] | Record<string, number>;
166
+ }
167
+ interface StringFieldOptions extends DataFieldOptions {
168
+ readonly blank?: boolean;
169
+ readonly trim?: boolean;
170
+ readonly textSearch?: boolean;
171
+ readonly choices?: readonly string[] | Record<string, string>;
172
+ }
173
+ type BooleanFieldOptions = DataFieldOptions;
174
+ type HTMLFieldOptions = StringFieldOptions;
175
+ type ColorFieldOptions = StringFieldOptions;
176
+ interface FilePathFieldOptions extends StringFieldOptions {
177
+ readonly categories?: ReadonlyArray<'IMAGE' | 'VIDEO' | 'AUDIO' | 'TEXT' | 'FONT' | 'GRAPHICS'>;
178
+ readonly base64?: boolean;
179
+ readonly wildcard?: boolean;
180
+ }
181
+ interface ArrayFieldOptions extends DataFieldOptions {
182
+ readonly min?: number;
183
+ readonly max?: number;
184
+ }
185
+ type SchemaFieldOptions = DataFieldOptions;
186
+ /**
187
+ * `SetField` takes the same options as `ArrayField` — it is a subclass whose
188
+ * only difference is what `initialize` hands back.
189
+ */
190
+ type SetFieldOptions = ArrayFieldOptions;
191
+ interface ForeignDocumentFieldOptions extends DataFieldOptions {
192
+ /**
193
+ * Keep the stored id instead of resolving the document.
194
+ *
195
+ * With this off, the field initializes to a getter — reading the property
196
+ * gives you a function, and calling it looks the document up. That is why
197
+ * the two cases infer to different types.
198
+ */
199
+ readonly idOnly?: boolean;
200
+ }
201
+ /** `EmbeddedDataField` builds a SchemaField from the model's own schema. */
202
+ type EmbeddedDataFieldOptions = SchemaFieldOptions;
203
+ /** `EmbeddedDocumentField` is the same, but nullable out of the box. */
204
+ type EmbeddedDocumentFieldOptions = SchemaFieldOptions;
205
+ /** `TypedSchemaField` is required by default and takes no options of its own. */
206
+ type TypedSchemaFieldOptions = DataFieldOptions;
207
+ //#endregion
208
+ //#region src/data/fields.d.ts
209
+ declare const BRAND: unique symbol;
210
+ /**
211
+ * Anything that satisfies the `FieldInstance` shape — used as the inner-field
212
+ * constraint on `ArrayField` and as the value type of `SchemaField`'s child
213
+ * map. Keeps the conditional types in `./infer-schema.ts` straightforward.
214
+ */
215
+ interface FieldInstance {
216
+ readonly [BRAND]: string;
217
+ readonly options: unknown;
218
+ }
219
+ interface NumberFieldInstance<O extends NumberFieldOptions = NumberFieldOptions> extends FieldInstance {
220
+ readonly [BRAND]: 'number';
221
+ readonly options: O;
222
+ }
223
+ interface StringFieldInstance<O extends StringFieldOptions = StringFieldOptions> extends FieldInstance {
224
+ readonly [BRAND]: 'string';
225
+ readonly options: O;
226
+ }
227
+ interface BooleanFieldInstance<O extends BooleanFieldOptions = BooleanFieldOptions> extends FieldInstance {
228
+ readonly [BRAND]: 'boolean';
229
+ readonly options: O;
230
+ }
231
+ interface HTMLFieldInstance<O extends HTMLFieldOptions = HTMLFieldOptions> extends FieldInstance {
232
+ readonly [BRAND]: 'html';
233
+ readonly options: O;
234
+ }
235
+ interface ColorFieldInstance<O extends ColorFieldOptions = ColorFieldOptions> extends FieldInstance {
236
+ readonly [BRAND]: 'color';
237
+ readonly options: O;
238
+ }
239
+ interface FilePathFieldInstance<O extends FilePathFieldOptions = FilePathFieldOptions> extends FieldInstance {
240
+ readonly [BRAND]: 'filePath';
241
+ readonly options: O;
242
+ }
243
+ interface ArrayFieldInstance<Inner extends FieldInstance = FieldInstance, O extends ArrayFieldOptions = ArrayFieldOptions> extends FieldInstance {
244
+ readonly [BRAND]: 'array';
245
+ readonly element: Inner;
246
+ readonly options: O;
247
+ }
248
+ /**
249
+ * A `SetField` holds a `Set`, not an array.
250
+ *
251
+ * It extends `ArrayField` and validates the same way, but `initialize`
252
+ * wraps the result in `new Set(...)` — so a schema that declares one and
253
+ * types it as an array gets `.push` and index access from the compiler on a
254
+ * value that has neither.
255
+ */
256
+ interface SetFieldInstance<Inner extends FieldInstance = FieldInstance, O extends SetFieldOptions = SetFieldOptions> extends FieldInstance {
257
+ readonly [BRAND]: 'set';
258
+ readonly element: Inner;
259
+ readonly options: O;
260
+ }
261
+ /**
262
+ * A reference to another document, stored as its id.
263
+ *
264
+ * What you read back depends on `idOnly`. With it, the id string. Without
265
+ * it, the document itself: the field resolves to a getter, so reading the
266
+ * property looks the document up in its collection and hands back the
267
+ * instance — or `null` when it is gone or lives in a compendium.
268
+ *
269
+ * The field is nullable by default, so both shapes admit `null`.
270
+ */
271
+ interface ForeignDocumentFieldInstance<Doc extends DocumentClass = DocumentClass, O extends ForeignDocumentFieldOptions = ForeignDocumentFieldOptions> extends FieldInstance {
272
+ readonly [BRAND]: 'foreignDocument';
273
+ readonly model: Doc;
274
+ readonly options: O;
275
+ }
276
+ /**
277
+ * A nested data model.
278
+ *
279
+ * It is a `SchemaField` built from the model class's own `defineSchema()`, so
280
+ * the value is an instance of that model — not a plain object. Reading it
281
+ * gives you the model's derived data and methods too.
282
+ */
283
+ interface EmbeddedDataFieldInstance<Model extends DataModelClass = DataModelClass, O extends EmbeddedDataFieldOptions = EmbeddedDataFieldOptions> extends FieldInstance {
284
+ readonly [BRAND]: 'embeddedData';
285
+ readonly model: Model;
286
+ readonly options: O;
287
+ }
288
+ /**
289
+ * A single embedded document, stored inline.
290
+ *
291
+ * Like `EmbeddedDataField` but for a Document class, and nullable by default:
292
+ * the field's own defaults turn `nullable` on, so an absent one reads `null`.
293
+ */
294
+ interface EmbeddedDocumentFieldInstance<Doc extends DataModelClass = DataModelClass, O extends EmbeddedDocumentFieldOptions = EmbeddedDocumentFieldOptions> extends FieldInstance {
295
+ readonly [BRAND]: 'embeddedDocument';
296
+ readonly model: Doc;
297
+ readonly options: O;
298
+ }
299
+ /**
300
+ * One of several shapes, told apart by a `type` property.
301
+ *
302
+ * Each entry becomes its own SchemaField. When an entry does not declare a
303
+ * `type` field, the field adds one — a required string whose value must equal
304
+ * that entry's key — which is what makes the result a discriminated union you
305
+ * can narrow on.
306
+ */
307
+ interface TypedSchemaFieldInstance<T extends Record<string, Record<string, FieldInstance>> = Record<string, Record<string, FieldInstance>>, O extends TypedSchemaFieldOptions = TypedSchemaFieldOptions> extends FieldInstance {
308
+ readonly [BRAND]: 'typedSchema';
309
+ readonly types: T;
310
+ readonly options: O;
311
+ }
312
+ interface SchemaFieldInstance<S extends Record<string, FieldInstance> = Record<string, FieldInstance>, O extends SchemaFieldOptions = SchemaFieldOptions> extends FieldInstance {
313
+ readonly [BRAND]: 'schema';
314
+ readonly fields: S;
315
+ readonly options: O;
316
+ }
317
+ interface NumberFieldCtor {
318
+ new <O extends NumberFieldOptions = NumberFieldOptions>(options?: O): NumberFieldInstance<O>;
319
+ }
320
+ interface StringFieldCtor {
321
+ new <O extends StringFieldOptions = StringFieldOptions>(options?: O): StringFieldInstance<O>;
322
+ }
323
+ interface BooleanFieldCtor {
324
+ new <O extends BooleanFieldOptions = BooleanFieldOptions>(options?: O): BooleanFieldInstance<O>;
325
+ }
326
+ interface HTMLFieldCtor {
327
+ new <O extends HTMLFieldOptions = HTMLFieldOptions>(options?: O): HTMLFieldInstance<O>;
328
+ }
329
+ interface ColorFieldCtor {
330
+ new <O extends ColorFieldOptions = ColorFieldOptions>(options?: O): ColorFieldInstance<O>;
331
+ }
332
+ interface FilePathFieldCtor {
333
+ new <O extends FilePathFieldOptions = FilePathFieldOptions>(options?: O): FilePathFieldInstance<O>;
334
+ }
335
+ interface ArrayFieldCtor {
336
+ new <Inner extends FieldInstance, O extends ArrayFieldOptions = ArrayFieldOptions>(element: Inner, options?: O): ArrayFieldInstance<Inner, O>;
337
+ }
338
+ interface SetFieldCtor {
339
+ new <Inner extends FieldInstance, O extends SetFieldOptions = SetFieldOptions>(element: Inner, options?: O): SetFieldInstance<Inner, O>;
340
+ }
341
+ /**
342
+ * Any document class — what `ForeignDocumentField` takes as its first
343
+ * argument. Declared structurally so the inference surface stays free of a
344
+ * dependency on a Foundry type package.
345
+ */
346
+ type DocumentClass = abstract new (...args: never[]) => object;
347
+ interface ForeignDocumentFieldCtor {
348
+ new <Doc extends DocumentClass, O extends ForeignDocumentFieldOptions = ForeignDocumentFieldOptions>(model: Doc, options?: O): ForeignDocumentFieldInstance<Doc, O>;
349
+ }
350
+ /** Any DataModel subclass — what the embedded fields take as their type. */
351
+ type DataModelClass = abstract new (...args: never[]) => object;
352
+ interface EmbeddedDataFieldCtor {
353
+ new <Model extends DataModelClass, O extends EmbeddedDataFieldOptions = EmbeddedDataFieldOptions>(model: Model, options?: O): EmbeddedDataFieldInstance<Model, O>;
354
+ }
355
+ interface EmbeddedDocumentFieldCtor {
356
+ new <Doc extends DataModelClass, O extends EmbeddedDocumentFieldOptions = EmbeddedDocumentFieldOptions>(model: Doc, options?: O): EmbeddedDocumentFieldInstance<Doc, O>;
357
+ }
358
+ interface TypedSchemaFieldCtor {
359
+ new <T extends Record<string, Record<string, FieldInstance>>, O extends TypedSchemaFieldOptions = TypedSchemaFieldOptions>(types: T, options?: O): TypedSchemaFieldInstance<T, O>;
360
+ }
361
+ interface SchemaFieldCtor {
362
+ new <S extends Record<string, FieldInstance>, O extends SchemaFieldOptions = SchemaFieldOptions>(fields: S, options?: O): SchemaFieldInstance<S, O>;
363
+ }
364
+ /**
365
+ * Typed bag returned by `fields()`. Each property is the corresponding
366
+ * `foundry.data.fields.*` class — the runtime value is Foundry's own
367
+ * constructor; the type is our overlay.
368
+ */
369
+ interface FieldsApi {
370
+ readonly NumberField: NumberFieldCtor;
371
+ readonly StringField: StringFieldCtor;
372
+ readonly BooleanField: BooleanFieldCtor;
373
+ readonly HTMLField: HTMLFieldCtor;
374
+ readonly ColorField: ColorFieldCtor;
375
+ readonly FilePathField: FilePathFieldCtor;
376
+ readonly ArrayField: ArrayFieldCtor;
377
+ readonly SetField: SetFieldCtor;
378
+ readonly ForeignDocumentField: ForeignDocumentFieldCtor;
379
+ readonly SchemaField: SchemaFieldCtor;
380
+ readonly EmbeddedDataField: EmbeddedDataFieldCtor;
381
+ readonly EmbeddedDocumentField: EmbeddedDocumentFieldCtor;
382
+ readonly TypedSchemaField: TypedSchemaFieldCtor;
383
+ }
384
+ /**
385
+ * Resolve `globalThis.foundry.data.fields` and return it typed as `FieldsApi`.
386
+ *
387
+ * Call this inside `defineSchema()` (or any code that runs after Foundry's
388
+ * `init` hook). Calling at module scope will throw when imported from Node
389
+ * tests — the global only exists inside the Foundry runtime.
390
+ *
391
+ * @throws `VttfError` with code `VTTF-0002` when `foundry.data.fields` is
392
+ * missing.
393
+ */
394
+ declare function fields(): FieldsApi;
395
+ //#endregion
396
+ //#region src/data/color.d.ts
397
+ /**
398
+ * The shape a `ColorField` hands back.
399
+ *
400
+ * Foundry initializes the field into one of its own `Color` instances — a
401
+ * boxed 24-bit integer with derived accessors — rather than the CSS string it
402
+ * stores. Described structurally here rather than imported, because the
403
+ * inference surface deliberately does not depend on the Foundry type package.
404
+ *
405
+ * Members mirror the documented public accessors. `toString(radix)` is
406
+ * included because a colour is routinely interpolated straight into markup.
407
+ */
408
+ interface Color {
409
+ /** False when the underlying value is not a valid colour. */
410
+ readonly valid: boolean;
411
+ /** CSS hexadecimal string, e.g. `#ff0000`. */
412
+ readonly css: string;
413
+ /** Normalized `[r, g, b]`, each 0–1. */
414
+ readonly rgb: [number, number, number];
415
+ readonly r: number;
416
+ readonly g: number;
417
+ readonly b: number;
418
+ /** The largest of the three channels. */
419
+ readonly maximum: number;
420
+ /** The smallest of the three channels. */
421
+ readonly minimum: number;
422
+ /** Byte order flipped, for APIs that want BGR. */
423
+ readonly littleEndian: number;
424
+ readonly hsv: [number, number, number];
425
+ readonly hsl: [number, number, number];
426
+ /** The colour in linear space, for shader work. */
427
+ readonly linear: Color;
428
+ toString: (radix?: number) => string;
429
+ valueOf: () => number;
430
+ }
431
+ //#endregion
432
+ //#region src/data/infer-schema.d.ts
433
+ /**
434
+ * Flatten an intersection / mapped type into a plain object literal so IDE
435
+ * hovers stay readable (Matt Pocock's `Prettify`). Use on every public
436
+ * conditional-type surface — PRD §7 TS-hygiene rule.
437
+ */
438
+ type Prettify<T> = { [K in keyof T]: T[K]; } & {};
439
+ /**
440
+ * Whether a field's options gave an explicit `initial`.
441
+ *
442
+ * Presence of the key is the question, not its value — `{ initial: undefined }`
443
+ * is not an initial.
444
+ */
445
+ type HasInitial<O> = O extends {
446
+ initial: unknown;
447
+ } ? true : false;
448
+ /**
449
+ * What a field class decides on its own behalf, for the options a schema
450
+ * leaves out.
451
+ *
452
+ * Every field type sets its own defaults, and they disagree. A number field
453
+ * is optional and nullable out of the box; a boolean field is required and
454
+ * starts at `false`. Reading a schema without knowing which defaults apply
455
+ * gets the shape wrong for exactly the fields people write most.
456
+ */
457
+ interface FieldDefaults {
458
+ /** Whether the field is required when the schema does not say. */
459
+ required: boolean;
460
+ /** Whether the field admits `null` when the schema does not say. */
461
+ nullable: boolean;
462
+ /** Whether the field supplies a value when the schema gives no `initial`. */
463
+ populated: boolean;
464
+ }
465
+ /** Read an option the schema may have set, falling back to the field's default. */
466
+ type Resolve<O, Key extends string, Fallback extends boolean> = O extends Record<Key, true> ? true : O extends Record<Key, false> ? false : Fallback;
467
+ /**
468
+ * Widen a field's base type by what it can actually hold.
469
+ *
470
+ * Two independent widenings:
471
+ *
472
+ * - nullable admits `null`.
473
+ * - not required, with nothing to fall back on, admits `undefined`: cleaning
474
+ * asks the field for an initial value and keeps whatever it gets, which is
475
+ * `undefined` when there is no initial to give.
476
+ *
477
+ * A required field never widens to `undefined`. It would fail validation
478
+ * before the document existed, so there is no state to type.
479
+ */
480
+ type Presence<O, T, D extends FieldDefaults> = T | (Resolve<O, 'nullable', D['nullable']> extends true ? null : never) | (Resolve<O, 'required', D['required']> extends true ? never : HasInitial<O> extends true ? never : D['populated'] extends true ? never : undefined);
481
+ /** Optional, nullable, nothing to fall back on. */
482
+ type NumberDefaults = {
483
+ required: false;
484
+ nullable: true;
485
+ populated: false;
486
+ };
487
+ /** Optional and non-nullable, so an unset one is simply absent. */
488
+ type StringDefaults = {
489
+ required: false;
490
+ nullable: false;
491
+ populated: false;
492
+ };
493
+ /** Required, and starts at `false`. */
494
+ type BooleanDefaults = {
495
+ required: true;
496
+ nullable: false;
497
+ populated: true;
498
+ };
499
+ /** Required and blank-friendly, so an unset one is the empty string. */
500
+ type HTMLDefaults = {
501
+ required: true;
502
+ nullable: false;
503
+ populated: true;
504
+ };
505
+ /** Optional and nullable, but starts at `null` rather than absent. */
506
+ type NullStartDefaults = {
507
+ required: false;
508
+ nullable: true;
509
+ populated: true;
510
+ };
511
+ /** Required, and builds its own empty value. */
512
+ type ContainerDefaults = {
513
+ required: true;
514
+ nullable: false;
515
+ populated: true;
516
+ };
517
+ /** Required but nullable — an id that points at nothing is `null`. */
518
+ type ReferenceDefaults = {
519
+ required: true;
520
+ nullable: true;
521
+ populated: false;
522
+ };
523
+ /**
524
+ * What a `ColorField` holds once the model is initialized.
525
+ *
526
+ * Not a string. The field casts its stored value to a CSS string, but
527
+ * `initialize` hands back a `Color` instance — so `system.tint` is an object
528
+ * with `.css`, `.rgb`, `.hex` and friends, and typing it as `string` makes
529
+ * every property access on it a lie the compiler accepts.
530
+ *
531
+ * It starts at `null`, so reading `.css` off a fresh document crashes unless
532
+ * the schema gives it an initial. That is what this type refuses.
533
+ */
534
+ type ColorFieldValue<O> = Presence<O, Color, NullStartDefaults>;
535
+ /**
536
+ * What a `ForeignDocumentField` holds once the model is initialized.
537
+ *
538
+ * With `idOnly`, the stored id string. Without it the field resolves to a
539
+ * getter, and the data model installs it as one — so reading the property
540
+ * gives the document instance, not the function that fetched it. It yields
541
+ * `null` when the id points at nothing, or when the parent lives in a
542
+ * compendium.
543
+ *
544
+ * Two caveats this does not encode, both narrow enough to document rather
545
+ * than type:
546
+ *
547
+ * - On the server the field keeps the id string in both cases, because there
548
+ * are no collections to resolve against. System code runs on both sides,
549
+ * but typing that union would put a string check in front of every read of
550
+ * a document reference.
551
+ * - Under `readonly: true` the data model takes the read-only branch before
552
+ * the getter branch, so the property keeps the resolver function itself.
553
+ * The field turns that flag off by default and no schema has reason to
554
+ * turn it back on.
555
+ */
556
+ type ForeignDocumentValue<Doc extends DocumentClass, O> = Presence<O, O extends {
557
+ idOnly: true;
558
+ } ? string : InstanceType<Doc>, ReferenceDefaults>;
559
+ /**
560
+ * What a `TypedSchemaField` holds: one shape per entry, each carrying the
561
+ * key it was filed under as its `type`.
562
+ *
563
+ * The field supplies that `type` when an entry does not declare one — a
564
+ * required string validated to equal the key — so narrowing on `type` picks
565
+ * exactly one branch.
566
+ */
567
+ type TypedSchemaValue<T extends Record<string, Record<string, FieldInstance>>> = { [K in keyof T]: Prettify<InferSchema<T[K]> & {
568
+ type: K;
569
+ }>; }[keyof T];
570
+ /**
571
+ * Map a single field instance to its runtime TypeScript type. `never` for
572
+ * shapes we don't recognise — the v1.0 `@vttforge/types` package will widen
573
+ * this matrix to the remaining Foundry fields.
574
+ */
575
+ type InferField<F> = F extends NumberFieldInstance<infer O> ? Presence<O, number, NumberDefaults> : F extends StringFieldInstance<infer O> ? Presence<O, string, StringDefaults> : F extends BooleanFieldInstance<infer O> ? Presence<O, boolean, BooleanDefaults> : F extends HTMLFieldInstance<infer O> ? Presence<O, string, HTMLDefaults> : F extends ColorFieldInstance<infer O> ? ColorFieldValue<O> : F extends FilePathFieldInstance<infer O> ? Presence<O, string, NullStartDefaults> : F extends ForeignDocumentFieldInstance<infer Doc, infer O> ? ForeignDocumentValue<Doc, O> : F extends ArrayFieldInstance<infer Inner, infer O> ? Presence<O, InferField<Inner>[], ContainerDefaults> : F extends SetFieldInstance<infer Inner, infer O> ? Presence<O, Set<InferField<Inner>>, ContainerDefaults> : F extends SchemaFieldInstance<infer S, infer O> ? Presence<O, InferSchema<S>, ContainerDefaults> : F extends EmbeddedDocumentFieldInstance<infer Doc, infer O> ? Presence<O, InstanceType<Doc>, ReferenceDefaults> : F extends EmbeddedDataFieldInstance<infer Model, infer O> ? Presence<O, InstanceType<Model>, ContainerDefaults> : F extends TypedSchemaFieldInstance<infer T, infer O> ? Presence<O, TypedSchemaValue<T>, ContainerDefaults> : never;
576
+ /**
577
+ * Map a `defineSchema()` return value to the corresponding `system` shape.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * class CharacterData extends BaseTypeDataModel() {
582
+ * static defineSchema() {
583
+ * const f = fields();
584
+ * return {
585
+ * level: new f.NumberField({ required: true, initial: 1 }),
586
+ * biography: new f.HTMLField(),
587
+ * };
588
+ * }
589
+ * }
590
+ * type CharacterSystem = InferSchema<ReturnType<typeof CharacterData.defineSchema>>;
591
+ * // → { level: number; biography: string }
592
+ * ```
593
+ */
594
+ type InferSchema<S extends Record<string, FieldInstance>> = Prettify<{ [K in keyof S]: InferField<S[K]>; }>;
595
+ //#endregion
596
+ //#region src/base-type-data-model.d.ts
597
+ type AnyConstructor = new (...args: any[]) => any;
598
+ /**
599
+ * Resolve the runtime base class, then build a mixin that adds VTTForge defaults.
600
+ *
601
+ * Why a function: subclasses are declared once at module load, but Foundry
602
+ * globals may not exist yet (test boot, ESM hoist). Calling `BaseTypeDataModel()`
603
+ * lazy-resolves the global at the moment of subclassing.
604
+ */
605
+ /** The two hooks this base fills in, so a subclass can omit either. */
606
+ interface TypeDataModelHooks {
607
+ prepareBaseData(): void;
608
+ prepareDerivedData(): void;
609
+ }
610
+ /**
611
+ * What an instance looks like when the schema is known.
612
+ *
613
+ * The schema's fields ARE the instance properties — inside
614
+ * `prepareDerivedData()` you read `this.level`, not `this.system.level`, and
615
+ * `actor.system` is this instance.
616
+ *
617
+ * Derived values are not in the schema, so they are not here either. Declare
618
+ * them on the subclass:
619
+ *
620
+ * ```ts
621
+ * declare armorClass: number;
622
+ * ```
623
+ */
624
+ type TypedTypeDataModel<S extends Record<string, FieldInstance>> = InferSchema<S> & TypeDataModelHooks & {
625
+ /**
626
+ * Phantom property carrying the schema's inferred shape. Never assigned,
627
+ * never present at runtime — it exists so the type has a name:
628
+ *
629
+ * ```ts
630
+ * type CharacterSystem = CharacterData['$inferData'];
631
+ * ```
632
+ */
633
+ readonly $inferData: InferSchema<S>;
634
+ };
635
+ interface TypedTypeDataModelCtor<S extends Record<string, FieldInstance>> {
636
+ new (...args: any[]): TypedTypeDataModel<S>;
637
+ defineSchema(): S;
638
+ migrateData(data: Record<string, unknown>): Record<string, unknown>;
639
+ }
640
+ /**
641
+ * Build a base class with no knowledge of the schema.
642
+ *
643
+ * `this` inside the hooks is untyped. Pass your schema function instead to
644
+ * get the fields typed.
645
+ */
646
+ declare function BaseTypeDataModel(): AnyConstructor;
647
+ /**
648
+ * Build a base class that knows its schema.
649
+ *
650
+ * Hand it the function that returns your fields and it implements
651
+ * `static defineSchema()` for you, so the schema is written once:
652
+ *
653
+ * ```ts
654
+ * const defineCharacterSchema = () => {
655
+ * const f = fields();
656
+ * return { level: new f.NumberField({ required: true, nullable: false, initial: 1 }) };
657
+ * };
658
+ *
659
+ * class CharacterData extends BaseTypeDataModel(defineCharacterSchema) {
660
+ * declare armorClass: number;
661
+ * prepareDerivedData() {
662
+ * this.armorClass = 10 + this.level; // this.level is number
663
+ * }
664
+ * }
665
+ * ```
666
+ *
667
+ * It has to be a function, not an object: `fields()` reads a Foundry global
668
+ * that does not exist when the module is first evaluated.
669
+ *
670
+ * A subclass may still declare its own `static defineSchema()`; that one wins,
671
+ * the same as any other static.
672
+ */
673
+ declare function BaseTypeDataModel<S extends Record<string, FieldInstance>>(defineSchema: () => S): TypedTypeDataModelCtor<S>;
674
+ //#endregion
675
+ //#region src/errors/registry.d.ts
676
+ /**
677
+ * VTTF-NNNN error registry — append-only, stable across majors.
678
+ *
679
+ * Every error VTTForge throws has a numeric code (`VTTF-NNNN`) and a PascalCase
680
+ * `name` for stack-trace readability. Codes are URLs — `https://vttforge.dev/errors/VTTF-0001`
681
+ * eventually links to a docs page generated from this registry.
682
+ *
683
+ * Never renumber an entry. To deprecate, mark with `deprecated: true` and add a
684
+ * `replacedBy` pointer. Adding a new code: pick the next unused integer.
685
+ */
686
+ type VttfErrorCode = `VTTF-${string}`;
687
+ interface VttfErrorEntry {
688
+ readonly code: VttfErrorCode;
689
+ readonly name: string;
690
+ readonly summary: string;
691
+ readonly deprecated?: boolean;
692
+ readonly replacedBy?: VttfErrorCode;
693
+ }
694
+ /**
695
+ * Look up a registered entry by code. Throws if the code is unknown — the
696
+ * registry is the source of truth, so missing codes mean a typo.
697
+ */
698
+ declare function getErrorEntry(code: VttfErrorCode): VttfErrorEntry;
699
+ /**
700
+ * Return every entry currently in the registry. Used by codegen to emit the
701
+ * runtime constants and the JSON manifest that powers the docs pages.
702
+ */
703
+ declare function listErrorEntries(): readonly VttfErrorEntry[];
704
+ declare function docsUrlFor(code: VttfErrorCode): string;
705
+ /**
706
+ * VttfError — every error VTTForge throws extends this.
707
+ *
708
+ * - `code` is the registry key (string-narrowed).
709
+ * - `name` is the PascalCase name from the registry — shows up in stack traces.
710
+ * - `docsUrl` points at the docs page.
711
+ * - `cause` uses the native ES2022 mechanism. Multiple causes => pass an
712
+ * `AggregateError` as the cause.
713
+ */
714
+ declare class VttfError extends Error {
715
+ readonly code: VttfErrorCode;
716
+ readonly docsUrl: string;
717
+ constructor(code: VttfErrorCode, message?: string, options?: ErrorOptions);
718
+ }
719
+ //#endregion
720
+ //#region src/errors/manifest.d.ts
721
+ declare const ERROR_MANIFEST_VERSION: 1;
722
+ interface ErrorManifest {
723
+ readonly version: typeof ERROR_MANIFEST_VERSION;
724
+ readonly package: '@vttforge/core';
725
+ readonly entries: ReadonlyArray<VttfErrorEntry>;
726
+ }
727
+ /**
728
+ * Snapshot the current registry as a manifest object. Recomputed on every
729
+ * call — cheap (the registry is a frozen literal). For the JSON projection
730
+ * shipped with the package, see `dist/errors-manifest.json`.
731
+ */
732
+ declare function getErrorManifest(): ErrorManifest;
733
+ //#endregion
734
+ //#region src/foundry-globals.d.ts
735
+ /**
736
+ * Minimal type-only contracts for the Foundry v13+ globals VTTForge core touches.
737
+ *
738
+ * Intentionally narrow — full Foundry typing lives in `@vttforge/types` (v1.0)
739
+ * built on top of `fvtt-types`. We mirror just the surface we use so the core
740
+ * package compiles without pulling in fvtt-types' git-SHA dependency.
741
+ *
742
+ * Every consumer is expected to run inside the Foundry runtime; we read these
743
+ * via `globalThis` and never bundle Foundry itself.
744
+ */
745
+ type HookCallback<Args extends readonly unknown[] = readonly unknown[]> = (...args: Args) => unknown | Promise<unknown>;
746
+ interface HooksApi {
747
+ once<Args extends readonly unknown[]>(event: string, fn: HookCallback<Args>): number;
748
+ on<Args extends readonly unknown[]>(event: string, fn: HookCallback<Args>): number;
749
+ off(event: string, idOrFn: number | HookCallback): boolean;
750
+ call(event: string, ...args: readonly unknown[]): boolean;
751
+ callAll(event: string, ...args: readonly unknown[]): boolean;
752
+ }
753
+ type SettingScope = 'world' | 'client';
754
+ interface SettingConfig<T = unknown> {
755
+ readonly name?: string;
756
+ readonly hint?: string;
757
+ readonly scope: SettingScope;
758
+ readonly config?: boolean;
759
+ readonly type: unknown;
760
+ readonly default: T;
761
+ readonly choices?: Readonly<Record<string, string>>;
762
+ readonly range?: {
763
+ readonly min: number;
764
+ readonly max: number;
765
+ readonly step?: number;
766
+ };
767
+ readonly onChange?: (value: T) => void;
768
+ }
769
+ interface GameSettingsApi {
770
+ register<T>(namespace: string, key: string, config: SettingConfig<T>): void;
771
+ get<T = unknown>(namespace: string, key: string): T;
772
+ set<T>(namespace: string, key: string, value: T): Promise<T>;
773
+ }
774
+ interface GameApi {
775
+ readonly settings: GameSettingsApi;
776
+ readonly user?: {
777
+ readonly isGM: boolean;
778
+ };
779
+ }
780
+ type ConfigCollection<T = unknown> = Record<string, T>;
781
+ interface ActorConfig {
782
+ documentClass?: unknown;
783
+ dataModels: ConfigCollection;
784
+ }
785
+ interface ItemConfig {
786
+ documentClass?: unknown;
787
+ dataModels: ConfigCollection;
788
+ }
789
+ interface CombatConfig {
790
+ initiative?: {
791
+ formula: string;
792
+ decimals?: number;
793
+ };
794
+ }
795
+ interface ActiveEffectConfig {
796
+ legacyTransferral?: boolean;
797
+ }
798
+ interface FoundryConfig {
799
+ Actor: ActorConfig;
800
+ Item: ItemConfig;
801
+ Combat: CombatConfig;
802
+ ActiveEffect: ActiveEffectConfig;
803
+ statusEffects?: unknown[];
804
+ [key: string]: unknown;
805
+ }
806
+ //#endregion
807
+ //#region src/migrations/types.d.ts
808
+ interface Migration {
809
+ /** Semver version this migration brings the world to. */
810
+ readonly version: string;
811
+ /** Optional human-readable description — logged when the migration runs and shown in error messages. */
812
+ readonly description?: string;
813
+ /** The migration body. May be sync or async. Should be idempotent (safe to re-run after partial failure). */
814
+ readonly fn: () => void | Promise<void>;
815
+ }
816
+ interface MigrationLogger {
817
+ info(message: string): void;
818
+ warn(message: string): void;
819
+ error(message: string): void;
820
+ }
821
+ interface MigrationRunnerOptions {
822
+ /** System id — used as the `game.settings` namespace. */
823
+ readonly systemId: string;
824
+ /** Migrations in ascending version order. Empty array is allowed (`run()` is a no-op then). */
825
+ readonly migrations: ReadonlyArray<Migration>;
826
+ /** Settings key under `systemId`. Defaults to `'schemaVersion'`. */
827
+ readonly settingKey?: string;
828
+ /**
829
+ * Compatibility floor — worlds with a stored schemaVersion strictly older than this
830
+ * throw `VttfError VTTF-0005` instead of running migrations. Mirrors the
831
+ * `flags.<systemId>.compatibleMigrationVersion` declaration in `system.json`.
832
+ */
833
+ readonly compatibleVersion?: string;
834
+ /**
835
+ * Override the semver comparator. Defaults to `foundry.utils.isNewerVersion`
836
+ * resolved at call time. Test-only injection.
837
+ */
838
+ readonly isNewerVersion?: (next: string, current: string) => boolean;
839
+ /**
840
+ * Override `game.settings`. Defaults to `globalThis.game.settings` resolved
841
+ * at call time. Test-only injection.
842
+ */
843
+ readonly settings?: GameSettingsApi;
844
+ /**
845
+ * Override the logger. Defaults to a `console` + `ui.notifications` adapter.
846
+ * Test-only injection.
847
+ */
848
+ readonly logger?: MigrationLogger;
849
+ }
850
+ interface MigrationRunner {
851
+ /** The target version (last migration's `version`, or `'0.0.0'` if the list is empty). */
852
+ readonly targetVersion: string;
853
+ /**
854
+ * Register the `schemaVersion` setting. Call once from your `init` hook so
855
+ * Foundry knows about it before any world load. Re-calls are idempotent (the
856
+ * underlying `game.settings.register` enforces that).
857
+ */
858
+ register(): void;
859
+ /**
860
+ * Run every migration whose version is newer than the stored
861
+ * `schemaVersion`, in order. Returns the list of versions actually executed
862
+ * (empty when the world is already up to date). Throws `VttfError VTTF-0004`
863
+ * wrapping the original error if any migration throws; throws
864
+ * `VttfError VTTF-0005` if the stored version is older than `compatibleVersion`.
865
+ *
866
+ * Call from your `ready` hook, gated by `game.user.isGM` — this method does
867
+ * NOT enforce GM-only itself so consumers can compose differently if needed.
868
+ */
869
+ run(): Promise<ReadonlyArray<string>>;
870
+ }
871
+ //#endregion
872
+ //#region src/migrations/runner.d.ts
873
+ /**
874
+ * Build a migration runner for a system. See module header for the failure
875
+ * semantics; see `Migration` JSDoc for the per-entry shape.
876
+ *
877
+ * @example
878
+ * ```ts
879
+ * const migrations = createMigrationRunner({
880
+ * systemId: 'my-system',
881
+ * migrations: [
882
+ * { version: '1.0.0', description: 'Rename bio → biography', fn: migrateV1 },
883
+ * { version: '2.0.0', description: 'Add hp.temp', fn: migrateV2 },
884
+ * ],
885
+ * compatibleVersion: '0.9.0',
886
+ * });
887
+ *
888
+ * registerSystem({
889
+ * id: 'my-system',
890
+ * onAfterInit: () => migrations.register(),
891
+ * onReady: async () => {
892
+ * if (!game.user.isGM) return;
893
+ * await migrations.run();
894
+ * },
895
+ * });
896
+ * ```
897
+ */
898
+ declare function createMigrationRunner(options: MigrationRunnerOptions): MigrationRunner;
899
+ //#endregion
900
+ //#region src/register-module.d.ts
901
+ /**
902
+ * registerModule — the module counterpart to `registerSystem`.
903
+ *
904
+ * A module is a guest in someone else's world, and Foundry enforces that. The
905
+ * two differences that matter:
906
+ *
907
+ * - **Sub-type keys are namespaced.** A system registers `character`; a module
908
+ * registering the same thing must register `<module-id>.character`, and the
909
+ * manifest must declare it under `documentTypes`. Forget the prefix and the
910
+ * type silently never appears. This function adds it for you.
911
+ * - **A module never owns the globals.** Document classes, the initiative
912
+ * formula and the status-effect array belong to the system. So there is no
913
+ * option here to replace them — `statusEffects` only appends, which is what
914
+ * a module is allowed to do.
915
+ */
916
+ interface ModuleRegistration {
917
+ /** Module id — must match the folder name and `module.json` `id`. */
918
+ readonly id: string;
919
+ /**
920
+ * Actor sub-types this module contributes, keyed by the bare type name.
921
+ * Registered under `<id>.<type>`, so declare them the same way in
922
+ * `documentTypes.Actor` in your manifest.
923
+ */
924
+ readonly actorDataModels?: Readonly<Record<string, unknown>>;
925
+ /** Item sub-types, same rule as `actorDataModels`. */
926
+ readonly itemDataModels?: Readonly<Record<string, unknown>>;
927
+ /**
928
+ * Status effects to append to `CONFIG.statusEffects`.
929
+ *
930
+ * Appended, never assigned: the array belongs to the system, and replacing
931
+ * it would delete conditions the world depends on.
932
+ */
933
+ readonly statusEffects?: readonly unknown[];
934
+ /** Runs before any CONFIG mutation — the usual home for the module API. */
935
+ readonly onBeforeInit?: () => void;
936
+ /** Runs after the mutations above, inside the same `init` hook. */
937
+ readonly onAfterInit?: () => void;
938
+ /**
939
+ * Runs once on `ready`.
940
+ *
941
+ * **Not GM-gated.** Guard inside your callback when the work is GM-only.
942
+ */
943
+ readonly onReady?: () => void | Promise<void>;
944
+ }
945
+ /**
946
+ * The key Foundry files a module's document sub-type under.
947
+ *
948
+ * Use it wherever you name the type outside `registerModule` — registering the
949
+ * sheet, checking `actor.type`, writing `documentTypes` in the manifest. The
950
+ * prefix is easy to get wrong by hand and fails silently when you do.
951
+ *
952
+ * @example
953
+ * ```ts
954
+ * moduleSubType('pdf-character-sheet', 'pdf'); // 'pdf-character-sheet.pdf'
955
+ * ```
956
+ */
957
+ declare function moduleSubType(moduleId: string, type: string): string;
958
+ /**
959
+ * Register a Foundry module with VTTForge.
960
+ *
961
+ * Calling twice with the same `id` throws VTTF-0001 — almost always a
962
+ * hot-reload artefact or a duplicate import. The CONFIG mutations are deferred
963
+ * until Foundry's `init` hook fires.
964
+ */
965
+ declare function registerModule(config: ModuleRegistration): ModuleRegistration;
966
+ //#endregion
967
+ //#region src/register-system.d.ts
968
+ interface SystemRegistration {
969
+ /** System id — must match the folder name and `system.json` `id`. */
970
+ readonly id: string;
971
+ /** Map of `documentTypes.Actor` key → TypeDataModel class. */
972
+ readonly actorDataModels?: Readonly<Record<string, unknown>>;
973
+ /** Map of `documentTypes.Item` key → TypeDataModel class. */
974
+ readonly itemDataModels?: Readonly<Record<string, unknown>>;
975
+ /** Replacement for `CONFIG.Actor.documentClass`. */
976
+ readonly actorDocumentClass?: unknown;
977
+ /** Replacement for `CONFIG.Item.documentClass`. */
978
+ readonly itemDocumentClass?: unknown;
979
+ /** Global initiative formula — assigned to `CONFIG.Combat.initiative`. */
980
+ readonly combat?: CombatConfig;
981
+ /** Disables legacy Active Effect transferral. Defaults to true. */
982
+ readonly activeEffect?: ActiveEffectConfig;
983
+ /**
984
+ * Replaces `CONFIG.statusEffects` (systems own this array — modules push).
985
+ * If omitted, the existing array is kept untouched.
986
+ */
987
+ readonly statusEffects?: readonly unknown[];
988
+ /**
989
+ * Optional pre-init hook for work that has to run before any of the CONFIG
990
+ * mutations (rare — usually used to assign `globalThis.<systemId>` API).
991
+ */
992
+ readonly onBeforeInit?: () => void;
993
+ /** Optional post-init hook for work that depends on the mutations above. */
994
+ readonly onAfterInit?: () => void;
995
+ /**
996
+ * Optional `ready` hook — fires once after Foundry has finished bootstrap.
997
+ * The natural home for migration runners (`createMigrationRunner().run()`).
998
+ *
999
+ * **Not GM-gated.** Guard inside your callback (`if (!game.user.isGM) return;`)
1000
+ * when the work is GM-only — migrations always are.
1001
+ */
1002
+ readonly onReady?: () => void | Promise<void>;
1003
+ }
1004
+ /**
1005
+ * Register a Foundry system with VTTForge. Idempotency: the same `id` calling
1006
+ * twice throws VTTF-0001 — almost always a hot-reload or duplicate import bug.
1007
+ *
1008
+ * Returns the registration object so consumers can inspect what was applied
1009
+ * (useful in tests). The actual CONFIG mutations are deferred until Foundry's
1010
+ * `init` hook fires.
1011
+ */
1012
+ declare function registerSystem(config: SystemRegistration): SystemRegistration;
1013
+ //#endregion
1014
+ //#region src/system-config.d.ts
1015
+ declare class SystemConfig {
1016
+ #private;
1017
+ readonly systemId: string;
1018
+ constructor(systemId: string);
1019
+ register<T>(key: string, config: SettingConfig<T>): void;
1020
+ get<T>(key: string): T;
1021
+ set<T>(key: string, value: T): Promise<T>;
1022
+ isRegistered(key: string): boolean;
1023
+ }
1024
+ //#endregion
1025
+ //#region src/index.d.ts
1026
+ /**
1027
+ * @vttforge/core — runtime utilities for FoundryVTT v13+ systems and modules.
1028
+ *
1029
+ * v0.1 surface:
1030
+ *
1031
+ * - registerSystem() — one-call init, replaces Hooks.once("init")
1032
+ * - registerModule() — the same for modules, with namespaced sub-types
1033
+ * - SystemConfig — typed wrapper around game.settings
1034
+ * - BaseTypeDataModel() — TypeDataModel with safe migrateData default
1035
+ * - BaseActorSheet() — ActorSheetV2 + HandlebarsApplicationMixin
1036
+ * - BaseItemSheet() — ItemSheetV2 + HandlebarsApplicationMixin
1037
+ * - fields() — typed bag of foundry.data.fields constructors
1038
+ * - InferSchema<T> — derive `system` shape from defineSchema()
1039
+ * - createMigrationRunner() — declarative schema migrations (register + run)
1040
+ * - VttfError + error registry — VTTF-NNNN codes with docs URLs
1041
+ *
1042
+ * Foundry classes are resolved from `globalThis.foundry` lazily so the package
1043
+ * imports cleanly in Node/tests; concrete Foundry typing arrives with
1044
+ * `@vttforge/types` in v1.0.
1045
+ */
1046
+ declare const VTTFORGE_CORE_VERSION = "0.2.0";
1047
+ //#endregion
1048
+ export { type ActiveEffectConfig, type ArrayFieldCtor, type ArrayFieldInstance, type ArrayFieldOptions, BaseActorSheet, BaseItemSheet, BaseTypeDataModel, type BooleanFieldCtor, type BooleanFieldInstance, type BooleanFieldOptions, type ColorFieldCtor, type ColorFieldInstance, type ColorFieldOptions, type CombatConfig, type DataFieldOptions, type DataModelClass, type DocumentClass, type DragDropConfig, ERROR_MANIFEST_VERSION, type EmbeddedDataFieldCtor, type EmbeddedDataFieldInstance, type EmbeddedDataFieldOptions, type EmbeddedDocumentFieldCtor, type EmbeddedDocumentFieldInstance, type EmbeddedDocumentFieldOptions, type ErrorManifest, type FieldInstance, type FieldsApi, type FilePathFieldCtor, type FilePathFieldInstance, type FilePathFieldOptions, type ForeignDocumentFieldCtor, type ForeignDocumentFieldInstance, type ForeignDocumentFieldOptions, type FoundryConfig, type GameApi, type GameSettingsApi, type HTMLFieldCtor, type HTMLFieldInstance, type HTMLFieldOptions, type HookCallback, type HooksApi, type InferField, type InferSchema, type Migration, type MigrationLogger, type MigrationRunner, type MigrationRunnerOptions, type ModuleRegistration, type NumberFieldCtor, type NumberFieldInstance, type NumberFieldOptions, type Prettify, type SchemaFieldCtor, type SchemaFieldInstance, type SchemaFieldOptions, type SetFieldCtor, type SetFieldInstance, type SetFieldOptions, type SettingConfig, type SettingScope, type SheetBaseCtor, type SheetBaseStatics, type StringFieldCtor, type StringFieldInstance, type StringFieldOptions, SystemConfig, type SystemRegistration, type TypeDataModelHooks, type TypedSchemaFieldCtor, type TypedSchemaFieldInstance, type TypedSchemaFieldOptions, type TypedTypeDataModel, type TypedTypeDataModelCtor, VTTFORGE_CORE_VERSION, VTTFORGE_SHEET_CLASS, VttfError, type VttfErrorCode, type VttfErrorEntry, createMigrationRunner, docsUrlFor, fields, getErrorEntry, getErrorManifest, listErrorEntries, moduleSubType, registerModule, registerSystem };
1049
+ //# sourceMappingURL=index.d.mts.map