@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 +308 -0
- package/LICENSE +21 -0
- package/README.md +9 -6
- package/dist/errors-manifest.json +32 -0
- package/dist/index.d.mts +1049 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +911 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -9
package/dist/index.d.mts
ADDED
|
@@ -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
|