@shayc/open-board-format 0.4.1 → 0.5.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 +22 -0
- package/README.md +44 -8
- package/dist/index.d.mts +113 -19
- package/dist/index.mjs +174 -47
- package/dist/index.mjs.map +1 -1
- package/docs/external/open-board-format.md +487 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @shayc/open-board-format
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 24535c7: Throw a typed `OBFError` instead of plain `Error`. Every failure now carries a discriminated `error.info`, so consumers branch on `error.info.code` (e.g. `"missing-resource"`, `"invalid-board"`, `"not-zip"`) and read structured fields off each variant — no message parsing.
|
|
8
|
+
|
|
9
|
+
- New exports: `OBFError` (class), and the `OBFErrorInfo`, `OBFErrorCode`, and `OBFIssue` types.
|
|
10
|
+
- Validation failures (`invalid-board`, `invalid-manifest`) expose the Zod `issues` list on `error.info`. The underlying error — the `ZodError`, or the `JSON.parse`/fflate failure for `not-json`/`unreadable-zip`/`zip-failed` — is always on the standard `error.cause` and never duplicated on `info`.
|
|
11
|
+
- An `internal` code marks a library-invariant violation (a bug here), not something callers can recover from.
|
|
12
|
+
- **Breaking:** thrown errors are now `OBFError` (`error.name` is `"OBFError"`) and message strings changed. Branch on `error.info.code` rather than matching `error.message`. The internal `buildJsonParseErrorMessage` helper was removed.
|
|
13
|
+
|
|
14
|
+
## 0.4.2
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- d2beda3: docs: ship the OBF spec mirror (`docs/external/open-board-format.md`) in the npm package
|
|
19
|
+
|
|
20
|
+
The README already points at this file; including it makes that link resolve
|
|
21
|
+
inside `node_modules` and gives offline tooling and coding agents the full
|
|
22
|
+
format semantics (specialty actions, string-list fallback, ID uniqueness)
|
|
23
|
+
that type declarations can't carry.
|
|
24
|
+
|
|
3
25
|
## 0.4.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -175,16 +175,52 @@ import { OBFButtonSchema, OBFManifestSchema } from "@shayc/open-board-format";
|
|
|
175
175
|
|
|
176
176
|
## Errors
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
Every failure throws an `OBFError`. Branch on `error.info.code` — a discriminated union where each `code` carries exactly the fields relevant to that failure. Don't match on `error.message`; the message is human-readable and may change between releases.
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
```ts
|
|
181
|
+
import { loadBoard, OBFError } from "@shayc/open-board-format";
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
await loadBoard(file);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (!(error instanceof OBFError)) throw error;
|
|
187
|
+
|
|
188
|
+
switch (error.info.code) {
|
|
189
|
+
case "missing-resource":
|
|
190
|
+
// `kind`, `mediaId`, and `path` are all typed and present here
|
|
191
|
+
console.warn(`Missing ${error.info.kind} at ${error.info.path}`);
|
|
192
|
+
break;
|
|
193
|
+
case "invalid-board":
|
|
194
|
+
// `issues` is the Zod issue list — which field failed and why
|
|
195
|
+
console.error(error.info.issues);
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
console.error(error.message);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
186
202
|
|
|
187
|
-
|
|
203
|
+
The `code` values, grouped by what they describe:
|
|
204
|
+
|
|
205
|
+
| Group | `info.code` | Key fields (on `info`) |
|
|
206
|
+
| ----------- | ------------------- | -------------------------------- |
|
|
207
|
+
| Decoding | `not-json` | `source` |
|
|
208
|
+
| | `not-zip` | — |
|
|
209
|
+
| | `unreadable-zip` | — |
|
|
210
|
+
| Validation | `invalid-board` | `issues`, `boardId?` |
|
|
211
|
+
| | `invalid-manifest` | `issues` |
|
|
212
|
+
| Read (OBZ) | `missing-manifest` | — |
|
|
213
|
+
| | `missing-board` | `boardId`, `path` |
|
|
214
|
+
| | `board-id-mismatch` | `path`, `declaredId`, `actualId` |
|
|
215
|
+
| Write (OBZ) | `unknown-root` | `rootBoardId` |
|
|
216
|
+
| | `duplicate-board` | `boardId` |
|
|
217
|
+
| | `missing-resource` | `kind`, `mediaId`, `path` |
|
|
218
|
+
| | `conflicting-paths` | `kind`, `mediaId`, `paths` |
|
|
219
|
+
| | `path-collision` | `path` |
|
|
220
|
+
| | `zip-failed` | — |
|
|
221
|
+
| Internal | `internal` | `detail` |
|
|
222
|
+
|
|
223
|
+
`OBFErrorInfo` and `OBFErrorCode` are exported for exhaustive handling. The underlying error, when there is one, is always on the standard `error.cause` — never duplicated on `info`. Validation failures (`invalid-board`, `invalid-manifest`) put the `ZodError` there, so you can call `z.treeifyError(error.cause)` for nested, UI-friendly output, while `info.issues` (Zod's issue type, re-exported as `OBFIssue`) gives you the flat list directly. For `not-json` / `*-zip` failures `error.cause` is the underlying parser or fflate error. An `internal` code signals a bug in this library that callers can't recover from — please report it.
|
|
188
224
|
|
|
189
225
|
## Security
|
|
190
226
|
|
package/dist/index.d.mts
CHANGED
|
@@ -354,6 +354,89 @@ declare const OBFManifestSchema: z.ZodObject<{
|
|
|
354
354
|
*/
|
|
355
355
|
type OBFManifest = z.infer<typeof OBFManifestSchema>;
|
|
356
356
|
//#endregion
|
|
357
|
+
//#region src/errors.d.ts
|
|
358
|
+
/**
|
|
359
|
+
* A single schema validation problem — Zod's issue shape, re-exported under a
|
|
360
|
+
* domain name. `z.core.$ZodIssue` is the type Zod v4 designates for libraries
|
|
361
|
+
* built on it (the bare `z.ZodIssue` is deprecated in its favor); aliasing it
|
|
362
|
+
* gives consumers a stable OBF name without reaching into Zod's `core` export.
|
|
363
|
+
*/
|
|
364
|
+
type OBFIssue = z.core.$ZodIssue;
|
|
365
|
+
/**
|
|
366
|
+
* Discriminated description of why an {@link OBFError} was thrown.
|
|
367
|
+
*
|
|
368
|
+
* Switch on `code`; each variant carries the fields relevant to it. When a
|
|
369
|
+
* failure wraps an underlying error it lives on the standard `error.cause`,
|
|
370
|
+
* never duplicated here. The only optional field is `invalid-board`'s
|
|
371
|
+
* `boardId`, absent when validation runs on a value with no known id.
|
|
372
|
+
*/
|
|
373
|
+
type OBFErrorInfo = /** Input was not parseable JSON. */{
|
|
374
|
+
code: "not-json";
|
|
375
|
+
source: "board" | "manifest";
|
|
376
|
+
} /** An OBZ archive was expected, but the bytes are not a ZIP. */ | {
|
|
377
|
+
code: "not-zip";
|
|
378
|
+
} /** A ZIP archive could not be decompressed. */ | {
|
|
379
|
+
code: "unreadable-zip";
|
|
380
|
+
} /** A board failed schema validation. `boardId` is set when known. */ | {
|
|
381
|
+
code: "invalid-board";
|
|
382
|
+
boardId?: string;
|
|
383
|
+
issues: readonly OBFIssue[];
|
|
384
|
+
} /** A manifest failed schema validation. */ | {
|
|
385
|
+
code: "invalid-manifest";
|
|
386
|
+
issues: readonly OBFIssue[];
|
|
387
|
+
} /** The archive has no `manifest.json`. */ | {
|
|
388
|
+
code: "missing-manifest";
|
|
389
|
+
} /** A board the manifest declares is absent from the archive. */ | {
|
|
390
|
+
code: "missing-board";
|
|
391
|
+
boardId: string;
|
|
392
|
+
path: string;
|
|
393
|
+
} /** A board's `id` disagrees with the id the manifest declares for it. */ | {
|
|
394
|
+
code: "board-id-mismatch";
|
|
395
|
+
path: string;
|
|
396
|
+
declaredId: string;
|
|
397
|
+
actualId: string;
|
|
398
|
+
} /** `rootBoardId` matches none of the supplied boards. */ | {
|
|
399
|
+
code: "unknown-root";
|
|
400
|
+
rootBoardId: string;
|
|
401
|
+
} /** Two supplied boards share the same `id`. */ | {
|
|
402
|
+
code: "duplicate-board";
|
|
403
|
+
boardId: string;
|
|
404
|
+
} /** A board declares a media `path` with no matching resource. */ | {
|
|
405
|
+
code: "missing-resource";
|
|
406
|
+
kind: "image" | "sound";
|
|
407
|
+
mediaId: string;
|
|
408
|
+
path: string;
|
|
409
|
+
} /** Two boards declare the same media id with different paths. */ | {
|
|
410
|
+
code: "conflicting-paths";
|
|
411
|
+
kind: "image" | "sound";
|
|
412
|
+
mediaId: string;
|
|
413
|
+
paths: [string, string];
|
|
414
|
+
} /** A supplied resource would overwrite a generated board or the manifest. */ | {
|
|
415
|
+
code: "path-collision";
|
|
416
|
+
path: string;
|
|
417
|
+
} /** The archive could not be compressed. */ | {
|
|
418
|
+
code: "zip-failed";
|
|
419
|
+
} /** An internal invariant was violated — a bug in this library; please report. */ | {
|
|
420
|
+
code: "internal";
|
|
421
|
+
detail: string;
|
|
422
|
+
};
|
|
423
|
+
/** Every `code` an {@link OBFError} can carry. */
|
|
424
|
+
type OBFErrorCode = OBFErrorInfo["code"];
|
|
425
|
+
/**
|
|
426
|
+
* The single error type thrown by `@shayc/open-board-format`.
|
|
427
|
+
*
|
|
428
|
+
* Branch on {@link OBFError.info} (a discriminated {@link OBFErrorInfo}) rather
|
|
429
|
+
* than parsing {@link OBFError.message}. Any underlying error — a `JSON.parse`
|
|
430
|
+
* failure, a `ZodError`, or an fflate error — is on the standard `error.cause`.
|
|
431
|
+
*/
|
|
432
|
+
declare class OBFError extends Error {
|
|
433
|
+
/** Structured, discriminated description of the failure. */
|
|
434
|
+
readonly info: OBFErrorInfo;
|
|
435
|
+
constructor(info: OBFErrorInfo, options?: {
|
|
436
|
+
cause?: unknown;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
357
440
|
//#region src/obf.d.ts
|
|
358
441
|
/**
|
|
359
442
|
* Parse a JSON string into a validated OBF board.
|
|
@@ -364,7 +447,8 @@ type OBFManifest = z.infer<typeof OBFManifestSchema>;
|
|
|
364
447
|
* @param json - The JSON string to parse.
|
|
365
448
|
* @returns The validated board object.
|
|
366
449
|
*
|
|
367
|
-
* @throws {
|
|
450
|
+
* @throws {@link OBFError} with `info.code` `"not-json"` if the JSON is
|
|
451
|
+
* malformed, or `"invalid-board"` if it fails schema validation.
|
|
368
452
|
*/
|
|
369
453
|
declare function parseOBF(json: string): OBFBoard;
|
|
370
454
|
/**
|
|
@@ -376,7 +460,8 @@ declare function parseOBF(json: string): OBFBoard;
|
|
|
376
460
|
* @param file - A `File` handle pointing to an `.obf` file.
|
|
377
461
|
* @returns The validated board object.
|
|
378
462
|
*
|
|
379
|
-
* @throws {
|
|
463
|
+
* @throws {@link OBFError} with `info.code` `"not-json"` if the file content is
|
|
464
|
+
* malformed, or `"invalid-board"` if it fails schema validation.
|
|
380
465
|
*/
|
|
381
466
|
declare function loadOBF(file: File): Promise<OBFBoard>;
|
|
382
467
|
/**
|
|
@@ -385,7 +470,8 @@ declare function loadOBF(file: File): Promise<OBFBoard>;
|
|
|
385
470
|
* @param data - The value to validate.
|
|
386
471
|
* @returns The validated board object.
|
|
387
472
|
*
|
|
388
|
-
* @throws {
|
|
473
|
+
* @throws {@link OBFError} with `info.code` `"invalid-board"` if the value fails
|
|
474
|
+
* schema validation. `info.issues` holds the underlying Zod issues.
|
|
389
475
|
*/
|
|
390
476
|
declare function validateOBF(data: unknown): OBFBoard;
|
|
391
477
|
/**
|
|
@@ -426,7 +512,8 @@ interface ParsedOBZ {
|
|
|
426
512
|
* @param file - A `File` handle pointing to an `.obz` archive.
|
|
427
513
|
* @returns The parsed manifest, boards, root board, and binary resources.
|
|
428
514
|
*
|
|
429
|
-
* @throws {
|
|
515
|
+
* @throws {@link OBFError} — the same failures as {@link extractOBZ}, which
|
|
516
|
+
* this delegates to.
|
|
430
517
|
*/
|
|
431
518
|
declare function loadOBZ(file: File): Promise<ParsedOBZ>;
|
|
432
519
|
/**
|
|
@@ -437,9 +524,10 @@ declare function loadOBZ(file: File): Promise<ParsedOBZ>;
|
|
|
437
524
|
* the resolved root board, and a map of file paths to their
|
|
438
525
|
* binary content.
|
|
439
526
|
*
|
|
440
|
-
* @throws {
|
|
441
|
-
*
|
|
442
|
-
*
|
|
527
|
+
* @throws {@link OBFError}; branch on `info.code`: `"not-zip"`,
|
|
528
|
+
* `"unreadable-zip"`, `"missing-manifest"`, `"not-json"` or
|
|
529
|
+
* `"invalid-manifest"` (bad manifest), `"missing-board"`,
|
|
530
|
+
* `"board-id-mismatch"`, or `"invalid-board"` (a board fails validation).
|
|
443
531
|
*/
|
|
444
532
|
declare function extractOBZ(archive: ArrayBuffer): Promise<ParsedOBZ>;
|
|
445
533
|
/**
|
|
@@ -449,7 +537,8 @@ declare function extractOBZ(archive: ArrayBuffer): Promise<ParsedOBZ>;
|
|
|
449
537
|
* @param json - A JSON string representing the manifest.
|
|
450
538
|
* @returns The validated manifest object.
|
|
451
539
|
*
|
|
452
|
-
* @throws {
|
|
540
|
+
* @throws {@link OBFError} with `info.code` `"not-json"` if the JSON is
|
|
541
|
+
* malformed, or `"invalid-manifest"` if it fails schema validation.
|
|
453
542
|
*/
|
|
454
543
|
declare function parseManifest(json: string): OBFManifest;
|
|
455
544
|
/**
|
|
@@ -458,17 +547,19 @@ declare function parseManifest(json: string): OBFManifest;
|
|
|
458
547
|
* A manifest is generated automatically from the supplied boards,
|
|
459
548
|
* using the `rootBoardId` to designate the entry-point board.
|
|
460
549
|
*
|
|
550
|
+
* Every failure is an {@link OBFError}; branch on `info.code`.
|
|
551
|
+
*
|
|
461
552
|
* @param boards - The boards to include in the archive.
|
|
462
553
|
* @param rootBoardId - The ID of the board that serves as the archive's entry point.
|
|
463
554
|
* @param resources - Optional map of file paths to binary content (images, sounds, etc.).
|
|
464
555
|
* @returns A `Blob` containing the compressed OBZ archive.
|
|
465
556
|
*
|
|
466
|
-
* @throws {
|
|
467
|
-
* @throws {
|
|
468
|
-
* @throws {
|
|
469
|
-
* @throws {
|
|
470
|
-
* @throws {
|
|
471
|
-
* @throws {
|
|
557
|
+
* @throws {@link OBFError} `"unknown-root"` if `rootBoardId` does not match any of the supplied boards.
|
|
558
|
+
* @throws {@link OBFError} `"duplicate-board"` if two supplied boards share the same ID.
|
|
559
|
+
* @throws {@link OBFError} `"invalid-board"` if a supplied board fails schema validation.
|
|
560
|
+
* @throws {@link OBFError} `"conflicting-paths"` if two boards declare the same media ID with conflicting paths.
|
|
561
|
+
* @throws {@link OBFError} `"missing-resource"` if a board declares an image or sound `path` with no matching entry in `resources`.
|
|
562
|
+
* @throws {@link OBFError} `"path-collision"` if a `resources` entry would overwrite the generated `manifest.json` or a board file.
|
|
472
563
|
*/
|
|
473
564
|
declare function createOBZ(boards: OBFBoard[], rootBoardId: string, resources?: Map<string, Uint8Array | ArrayBuffer>): Promise<Blob>;
|
|
474
565
|
//#endregion
|
|
@@ -508,8 +599,9 @@ type LoadedBoard = {
|
|
|
508
599
|
* @param input - A `File` handle or `ArrayBuffer` holding `.obf` or `.obz` content.
|
|
509
600
|
* @returns A discriminated union tagged by `format`.
|
|
510
601
|
*
|
|
511
|
-
* @throws {
|
|
512
|
-
*
|
|
602
|
+
* @throws {@link OBFError} — the OBZ failures of {@link extractOBZ} when the
|
|
603
|
+
* input is an archive, or the OBF failures of {@link parseOBF} otherwise.
|
|
604
|
+
* Branch on `error.info.code`.
|
|
513
605
|
*/
|
|
514
606
|
declare function loadBoard(input: File | ArrayBuffer): Promise<LoadedBoard>;
|
|
515
607
|
//#endregion
|
|
@@ -523,7 +615,8 @@ declare function loadBoard(input: File | ArrayBuffer): Promise<LoadedBoard>;
|
|
|
523
615
|
* @param archive - The ZIP archive as an `ArrayBuffer`.
|
|
524
616
|
* @returns A map of file paths to their decompressed content.
|
|
525
617
|
*
|
|
526
|
-
* @throws {
|
|
618
|
+
* @throws {@link OBFError} with `info.code` `"unreadable-zip"` if the archive is
|
|
619
|
+
* corrupt or cannot be decompressed.
|
|
527
620
|
*/
|
|
528
621
|
declare function unzip(archive: ArrayBuffer): Promise<Map<string, Uint8Array>>;
|
|
529
622
|
/**
|
|
@@ -536,7 +629,8 @@ declare function unzip(archive: ArrayBuffer): Promise<Map<string, Uint8Array>>;
|
|
|
536
629
|
* @param entries - A map of file paths to their content bytes.
|
|
537
630
|
* @returns The compressed archive as a `Uint8Array`.
|
|
538
631
|
*
|
|
539
|
-
* @throws {
|
|
632
|
+
* @throws {@link OBFError} with `info.code` `"zip-failed"` if fflate fails to
|
|
633
|
+
* compress an entry.
|
|
540
634
|
*/
|
|
541
635
|
declare function zip(entries: Map<string, Uint8Array | ArrayBuffer>): Promise<Uint8Array>;
|
|
542
636
|
/**
|
|
@@ -548,5 +642,5 @@ declare function zip(entries: Map<string, Uint8Array | ArrayBuffer>): Promise<Ui
|
|
|
548
642
|
*/
|
|
549
643
|
declare function isZip(archive: ArrayBuffer): boolean;
|
|
550
644
|
//#endregion
|
|
551
|
-
export { type LoadedBoard, type OBFBoard, OBFBoardSchema, type OBFButton, type OBFButtonAction, OBFButtonActionSchema, OBFButtonSchema, type OBFFormatVersion, OBFFormatVersionSchema, type OBFGrid, OBFGridSchema, type OBFID, OBFIDSchema, type OBFImage, OBFImageSchema, type OBFLicense, OBFLicenseSchema, type OBFLoadBoard, OBFLoadBoardSchema, type OBFLocaleCode, OBFLocaleCodeSchema, type OBFLocalizedStrings, OBFLocalizedStringsSchema, type OBFManifest, OBFManifestSchema, type OBFMedia, OBFMediaSchema, type OBFSound, OBFSoundSchema, type OBFSpecialtyAction, OBFSpecialtyActionSchema, type OBFSpellingAction, OBFSpellingActionSchema, type OBFStrings, OBFStringsSchema, type OBFSymbolInfo, OBFSymbolInfoSchema, type ParsedOBZ, createOBZ, extractOBZ, isZip, loadBoard, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
|
645
|
+
export { type LoadedBoard, type OBFBoard, OBFBoardSchema, type OBFButton, type OBFButtonAction, OBFButtonActionSchema, OBFButtonSchema, OBFError, type OBFErrorCode, type OBFErrorInfo, type OBFFormatVersion, OBFFormatVersionSchema, type OBFGrid, OBFGridSchema, type OBFID, OBFIDSchema, type OBFImage, OBFImageSchema, type OBFIssue, type OBFLicense, OBFLicenseSchema, type OBFLoadBoard, OBFLoadBoardSchema, type OBFLocaleCode, OBFLocaleCodeSchema, type OBFLocalizedStrings, OBFLocalizedStringsSchema, type OBFManifest, OBFManifestSchema, type OBFMedia, OBFMediaSchema, type OBFSound, OBFSoundSchema, type OBFSpecialtyAction, OBFSpecialtyActionSchema, type OBFSpellingAction, OBFSpellingActionSchema, type OBFStrings, OBFStringsSchema, type OBFSymbolInfo, OBFSymbolInfoSchema, type ParsedOBZ, createOBZ, extractOBZ, isZip, loadBoard, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
|
552
646
|
//# sourceMappingURL=index.d.mts.map
|