@themeparks/typelib 1.0.4 → 1.1.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/README.md CHANGED
@@ -6,6 +6,10 @@ TypeScript definition system for ThemeParks.wiki
6
6
 
7
7
  `@themeparks/typelib` is a TypeScript types package that generates types from JSON schemas and provides runtime type validation. It is designed for the internal ThemeParks.wiki systems and generating client libraries. You likely do not want to interact with this library directly.
8
8
 
9
+ ## Requirements
10
+
11
+ - Node.js >= 24.0.0
12
+
9
13
  ## Installation
10
14
 
11
15
  ```bash
@@ -17,30 +21,32 @@ npm install @themeparks/typelib
17
21
  ### Using pre-built types
18
22
 
19
23
  ```typescript
20
- import { V1RootResponse, registerTypeSchema, getTypeSchema } from '@themeparks/typelib';
24
+ import { Entity, LiveData, EntitySchedule } from '@themeparks/typelib';
21
25
 
22
26
  // Use generated types
23
- const response: V1RootResponse = {
24
- success: true,
25
- message: "API is running",
26
- version: "1.0.0"
27
+ const entity: Entity = {
28
+ id: 'park-123',
29
+ name: 'Example Park',
30
+ entityType: 'PARK',
31
+ timezone: 'America/New_York',
27
32
  };
28
33
 
29
- // Access runtime schemas
30
- const schema = getTypeSchema('V1RootResponse');
31
- console.log(schema); // Returns the JSON schema for validation
34
+ const liveData: LiveData = {
35
+ id: 'attraction-456',
36
+ status: 'OPERATING',
37
+ };
32
38
  ```
33
39
 
34
- ### Generating types from custom schemas
40
+ ### Enums and conversion functions
35
41
 
36
42
  ```typescript
37
- import { generateTypes } from '@themeparks/typelib/generate';
38
- import { resolve } from 'path';
43
+ import { EntityTypeEnum, StringToEntityType } from '@themeparks/typelib';
39
44
 
40
- await generateTypes({
41
- schemaDirs: [resolve('./typesrc')],
42
- outputDir: './src/types'
43
- });
45
+ // Native TypeScript enums
46
+ const type = EntityTypeEnum.ATTRACTION;
47
+
48
+ // Convert strings to enum values
49
+ const parsed = StringToEntityType('attraction');
44
50
  ```
45
51
 
46
52
  ### Runtime Schema Registry
@@ -55,38 +61,69 @@ Retrieve a registered schema.
55
61
  import { registerTypeSchema, getTypeSchema } from '@themeparks/typelib';
56
62
 
57
63
  // Schemas are automatically registered when importing types
58
- import { V1RootResponse } from '@themeparks/typelib';
64
+ import { Entity } from '@themeparks/typelib';
59
65
 
60
66
  // Access the schema at runtime
61
- const schema = getTypeSchema('V1RootResponse');
67
+ const schema = getTypeSchema('Entity');
68
+ ```
69
+
70
+ ### Deterministic Object Hashing
71
+
72
+ ```typescript
73
+ import { hashObject } from '@themeparks/typelib/hash';
74
+
75
+ // Returns a 64-character hex SHA-256 hash
76
+ const hash = hashObject({ name: 'Example', id: 123 });
77
+
78
+ // Deterministic — key order doesn't matter
79
+ hashObject({ b: 2, a: 1 }) === hashObject({ a: 1, b: 2 }); // true
80
+ ```
81
+
82
+ ### Generating types from schemas
83
+
84
+ ```typescript
85
+ import { generateTypes } from '@themeparks/typelib/generate';
86
+ import { resolve } from 'path';
87
+
88
+ await generateTypes({
89
+ schemaDirs: [resolve('./typesrc')],
90
+ outputDir: './src/types'
91
+ });
62
92
  ```
63
93
 
64
94
  ## Schema Format
65
95
 
66
- Schemas follow JSON Schema Draft 7 specification:
96
+ Schemas follow JSON Schema Draft 7 specification. Each file defines top-level types as properties:
67
97
 
68
98
  ```json
69
99
  {
70
100
  "$schema": "http://json-schema.org/draft-07/schema#",
71
- "title": "MyTypes",
101
+ "title": "Entities",
72
102
  "type": "object",
73
103
  "properties": {
74
- "MyType": {
104
+ "LanguageCode": {
105
+ "type": "string",
106
+ "enum": ["en", "en-gb", "de", "fr", "es", "ja", "ko", "zh"],
107
+ "description": "Supported language codes for ThemeParks.wiki"
108
+ },
109
+ "Entity": {
75
110
  "type": "object",
76
- "required": ["id", "name"],
111
+ "required": ["id", "name", "entityType", "timezone"],
77
112
  "properties": {
78
113
  "id": {
79
114
  "type": "string",
80
- "description": "Unique identifier"
115
+ "description": "Unique identifier for this entity"
81
116
  },
82
117
  "name": {
83
- "type": "string",
84
- "description": "Display name"
118
+ "description": "Entity name",
119
+ "$ref": "#/properties/LocalisedString"
85
120
  },
86
- "status": {
121
+ "entityType": {
122
+ "$ref": "#/properties/EntityType"
123
+ },
124
+ "timezone": {
87
125
  "type": "string",
88
- "enum": ["active", "inactive", "pending"],
89
- "description": "Status of the item"
126
+ "description": "Timezone of this entity (IANA)"
90
127
  }
91
128
  }
92
129
  }
@@ -94,38 +131,29 @@ Schemas follow JSON Schema Draft 7 specification:
94
131
  }
95
132
  ```
96
133
 
134
+ Types can reference each other within the same file using `$ref`, and the generator resolves cross-file references automatically.
135
+
97
136
  ## Generated Output
98
137
 
99
138
  The generator creates:
100
- - **Type definitions** - TypeScript interfaces and types
101
- - **Enum types** - Native TypeScript enums with conversion functions
102
- - **Runtime registration** - Automatic schema registration
103
- - **Re-export index** - Convenient imports from a single file
139
+ - **Type definitions** TypeScript interfaces and types
140
+ - **Enum types** Native TypeScript enums with `StringTo*` conversion functions
141
+ - **Runtime registration** Automatic schema registration via `registerTypeSchema`
142
+ - **Re-export index** Convenient imports from a single file
104
143
 
105
- Example generated output:
106
- ```typescript
107
- // Generated: my-types.types.ts
108
-
109
- export interface MyType {
110
- /** Unique identifier */
111
- id: string;
112
- /** Display name */
113
- name: string;
114
- /** Status of the item */
115
- status?: 'active' | 'inactive' | 'pending';
116
- }
144
+ ## Package Exports
117
145
 
118
- export enum StatusEnum {
119
- "active" = 'active',
120
- "inactive" = 'inactive',
121
- "pending" = 'pending'
122
- }
146
+ - `@themeparks/typelib` Types, enums, and schema registry functions
147
+ - `@themeparks/typelib/generate` — Type generation from JSON schemas
148
+ - `@themeparks/typelib/hash` Deterministic SHA-256 object hashing
123
149
 
124
- // Runtime schema registration
125
- registerTypeSchema("MyType", { /* schema */ });
126
- ```
150
+ ## Publishing
127
151
 
128
- ## Package Exports
152
+ Builds and publishes to npm automatically via `prepublishOnly`:
153
+
154
+ ```bash
155
+ # Bump version in package.json, then:
156
+ npm publish --access public
157
+ ```
129
158
 
130
- - `@themeparks/typelib` - Main package with types and registry functions
131
- - `@themeparks/typelib/generate` - Type generation functionality
159
+ This runs `npm run build` (which regenerates types from schemas, then compiles TypeScript) before publishing. The published package includes `dist/`, `typesrc/`, and `src/`.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hash.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hash.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,207 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { hashObject } from '../hash.js';
3
+ // ── Helper ─────────────────────────────────────────────────────────────────
4
+ /** Assert that two values hash the same */
5
+ function same(a, b) {
6
+ expect(hashObject(a)).toBe(hashObject(b));
7
+ }
8
+ /** Assert that two values hash differently */
9
+ function different(a, b) {
10
+ expect(hashObject(a)).not.toBe(hashObject(b));
11
+ }
12
+ /** Assert that hashObject throws a TypeError with a message matching the pattern */
13
+ function throwsTypeError(value, pattern) {
14
+ expect(() => hashObject(value)).toThrow(TypeError);
15
+ expect(() => hashObject(value)).toThrow(pattern);
16
+ }
17
+ // ── Output format ──────────────────────────────────────────────────────────
18
+ describe('output format', () => {
19
+ it('returns a 64-character lowercase hex string', () => {
20
+ expect(hashObject({ x: 1 })).toMatch(/^[0-9a-f]{64}$/);
21
+ });
22
+ it('is deterministic — same input always produces same output', () => {
23
+ const obj = { a: 1, b: [2, 3], c: { d: 'hello' } };
24
+ expect(hashObject(obj)).toBe(hashObject(obj));
25
+ expect(hashObject(obj)).toBe(hashObject({ a: 1, b: [2, 3], c: { d: 'hello' } }));
26
+ });
27
+ });
28
+ // ── Supported types ────────────────────────────────────────────────────────
29
+ describe('supported scalar types', () => {
30
+ it('hashes null', () => {
31
+ const h = hashObject(null);
32
+ expect(h).toMatch(/^[0-9a-f]{64}$/);
33
+ });
34
+ it('hashes strings', () => {
35
+ different('hello', 'world');
36
+ same('hello', 'hello');
37
+ });
38
+ it('hashes numbers', () => {
39
+ different(1, 2);
40
+ same(42, 42);
41
+ });
42
+ it('hashes booleans', () => {
43
+ different(true, false);
44
+ same(true, true);
45
+ });
46
+ it('differentiates between types with similar string forms', () => {
47
+ different(null, 'null');
48
+ different(0, false);
49
+ different(0, '0');
50
+ different(false, 'false');
51
+ different(1, '1');
52
+ different(null, ''); // null is fine; just checking null != ''
53
+ });
54
+ it('differentiates NaN from null — NaN throws', () => {
55
+ expect(() => hashObject(NaN)).toThrow(TypeError);
56
+ // null should still work fine
57
+ expect(() => hashObject(null)).not.toThrow();
58
+ });
59
+ });
60
+ describe('supported object types', () => {
61
+ it('hashes empty object', () => {
62
+ const h = hashObject({});
63
+ expect(h).toMatch(/^[0-9a-f]{64}$/);
64
+ });
65
+ it('hashes empty array', () => {
66
+ different([], {});
67
+ expect(hashObject([])).toMatch(/^[0-9a-f]{64}$/);
68
+ });
69
+ it('hashes plain objects', () => {
70
+ same({ a: 1 }, { a: 1 });
71
+ different({ a: 1 }, { a: 2 });
72
+ different({ a: 1 }, { b: 1 });
73
+ });
74
+ it('hashes arrays (order is preserved)', () => {
75
+ same([1, 2, 3], [1, 2, 3]);
76
+ different([1, 2, 3], [3, 2, 1]);
77
+ });
78
+ it('hashes nested objects', () => {
79
+ same({ a: { b: { c: 42 } } }, { a: { b: { c: 42 } } });
80
+ different({ a: { b: 1 } }, { a: { b: 2 } });
81
+ });
82
+ it('hashes mixed nested structures', () => {
83
+ same({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] });
84
+ different({ a: [1, { b: 2 }] }, { a: [1, { b: 3 }] });
85
+ });
86
+ it('allows the same object reference in sibling array positions', () => {
87
+ const shared = { x: 1 };
88
+ expect(() => hashObject([shared, shared])).not.toThrow();
89
+ same([shared, shared], [{ x: 1 }, { x: 1 }]);
90
+ });
91
+ });
92
+ // ── Key ordering ───────────────────────────────────────────────────────────
93
+ describe('key ordering', () => {
94
+ it('produces the same hash regardless of key insertion order (top level)', () => {
95
+ same({ a: 1, b: 2 }, { b: 2, a: 1 });
96
+ });
97
+ it('produces the same hash for deeply nested objects with different key order', () => {
98
+ same({ x: { c: 3, a: 1, b: 2 } }, { x: { a: 1, b: 2, c: 3 } });
99
+ });
100
+ it('produces the same hash for objects inside arrays with different key order', () => {
101
+ same([{ z: 26, a: 1 }, { m: 13, b: 2 }], [{ a: 1, z: 26 }, { b: 2, m: 13 }]);
102
+ });
103
+ it('distinguishes objects with the same keys but different values', () => {
104
+ different({ a: 1, b: 2 }, { a: 2, b: 1 });
105
+ });
106
+ });
107
+ // ── Unsupported types — must throw TypeError ───────────────────────────────
108
+ describe('unsupported types throw TypeError', () => {
109
+ it('throws on undefined (top-level)', () => {
110
+ throwsTypeError(undefined, /undefined/);
111
+ });
112
+ it('throws on undefined inside an object value', () => {
113
+ throwsTypeError({ a: undefined }, /undefined/);
114
+ });
115
+ it('throws on undefined inside a nested object', () => {
116
+ throwsTypeError({ a: { b: undefined } }, /undefined/);
117
+ });
118
+ it('throws on undefined inside an array', () => {
119
+ throwsTypeError([1, undefined, 3], /undefined/);
120
+ });
121
+ it('throws on Date', () => {
122
+ throwsTypeError(new Date(), /Date/);
123
+ });
124
+ it('throws on Date inside an object', () => {
125
+ throwsTypeError({ created: new Date() }, /Date/);
126
+ });
127
+ it('throws on Map', () => {
128
+ throwsTypeError(new Map([['a', 1]]), /Map/);
129
+ });
130
+ it('throws on Map inside an object', () => {
131
+ throwsTypeError({ data: new Map() }, /Map/);
132
+ });
133
+ it('throws on Set', () => {
134
+ throwsTypeError(new Set([1, 2, 3]), /Set/);
135
+ });
136
+ it('throws on Set inside an object', () => {
137
+ throwsTypeError({ tags: new Set(['a', 'b']) }, /Set/);
138
+ });
139
+ it('throws on Buffer', () => {
140
+ throwsTypeError(Buffer.from('hello'), /TypedArray|Buffer/);
141
+ });
142
+ it('throws on Uint8Array', () => {
143
+ throwsTypeError(new Uint8Array([1, 2, 3]), /TypedArray|Buffer/);
144
+ });
145
+ it('throws on Int32Array', () => {
146
+ throwsTypeError(new Int32Array([1, 2, 3]), /TypedArray|Buffer/);
147
+ });
148
+ it('throws on Float64Array', () => {
149
+ throwsTypeError(new Float64Array([1.0, 2.0]), /TypedArray|Buffer/);
150
+ });
151
+ it('throws on function', () => {
152
+ throwsTypeError(() => 42, /function/);
153
+ });
154
+ it('throws on function inside an object', () => {
155
+ throwsTypeError({ fn: () => { } }, /function/);
156
+ });
157
+ it('throws on BigInt', () => {
158
+ throwsTypeError(BigInt(9007199254740993), /BigInt/);
159
+ });
160
+ it('throws on BigInt inside an object', () => {
161
+ throwsTypeError({ id: BigInt(1) }, /BigInt/);
162
+ });
163
+ it('throws on Symbol', () => {
164
+ throwsTypeError(Symbol('test'), /Symbol/);
165
+ });
166
+ it('throws on circular reference (object)', () => {
167
+ const obj = { a: 1 };
168
+ obj.self = obj;
169
+ throwsTypeError(obj, /circular/);
170
+ });
171
+ it('throws on circular reference (array)', () => {
172
+ const arr = [1, 2];
173
+ arr.push(arr);
174
+ throwsTypeError(arr, /circular/);
175
+ });
176
+ it('throws on circular reference (nested)', () => {
177
+ const inner = { x: 1 };
178
+ const outer = { a: inner, b: inner }; // shared ref is fine (not circular)
179
+ outer.a.parent = outer; // this makes it circular
180
+ throwsTypeError(outer, /circular/);
181
+ });
182
+ it('does NOT throw for shared (non-circular) references', () => {
183
+ // The same object appearing in two sibling branches is fine — it is not circular
184
+ const shared = { x: 1 };
185
+ expect(() => hashObject({ a: shared, b: shared })).not.toThrow();
186
+ // And both branches must hash the same
187
+ same({ a: shared, b: shared }, { a: { x: 1 }, b: { x: 1 } });
188
+ });
189
+ it('throws on NaN', () => {
190
+ throwsTypeError(NaN, /non-finite/);
191
+ });
192
+ it('throws on Infinity', () => {
193
+ throwsTypeError(Infinity, /non-finite/);
194
+ });
195
+ it('throws on -Infinity', () => {
196
+ throwsTypeError(-Infinity, /non-finite/);
197
+ });
198
+ it('throws on NaN inside an object', () => {
199
+ throwsTypeError({ value: NaN }, /non-finite/);
200
+ });
201
+ it('throws on Symbol inside an object', () => {
202
+ throwsTypeError({ s: Symbol('test') }, /Symbol/);
203
+ });
204
+ it('throws on Buffer inside an object', () => {
205
+ throwsTypeError({ data: Buffer.from('hello') }, /TypedArray|Buffer/);
206
+ });
207
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"generate_types.d.ts","sourceRoot":"","sources":["../src/generate_types.ts"],"names":[],"mappings":"AAwXA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAChC,UAAgC,EAChC,SAA8B,EAC9B,kBAA0C,EAC7C;;;;CAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrB"}
1
+ {"version":3,"file":"generate_types.d.ts","sourceRoot":"","sources":["../src/generate_types.ts"],"names":[],"mappings":"AA8XA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAChC,UAAgC,EAChC,SAA8B,EAC9B,kBAA0C,EAC7C;;;;CAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrB"}
@@ -66,7 +66,10 @@ function resolveReference(ref, schema, registry, tracker) {
66
66
  // If still not found, check registry
67
67
  const registryType = registry[segment];
68
68
  if (registryType) {
69
- tracker.imports.set(registryType.sourceFile, segment);
69
+ if (!tracker.imports.has(registryType.sourceFile)) {
70
+ tracker.imports.set(registryType.sourceFile, new Set());
71
+ }
72
+ tracker.imports.get(registryType.sourceFile).add(segment);
70
73
  tracker.referencePath.pop();
71
74
  return { typeName: segment, sourceFile: registryType.sourceFile };
72
75
  }
@@ -145,7 +148,10 @@ function getTypeFromSchema(schema, rootSchema, registry, tracker, typeName) {
145
148
  if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
146
149
  valueType = getTypeFromSchema(schema.additionalProperties, rootSchema, registry, tracker);
147
150
  }
148
- const recordType = `Record<${keyType}, ${valueType}>`;
151
+ // Use Partial for propertyNames constraints to allow partial translations
152
+ const recordType = schema.propertyNames?.$ref
153
+ ? `Partial<Record<${keyType}, ${valueType}>>`
154
+ : `Record<${keyType}, ${valueType}>`;
149
155
  if (typeName) {
150
156
  return `extends ${recordType} {}`;
151
157
  }
@@ -288,7 +294,7 @@ async function generateTypeFile(schemaPath, registry, outputDir, typeRegistryImp
288
294
  const currentFile = schemaPath.split(/[\/\\]/).pop().replace('.json', '');
289
295
  const imports = Array.from(tracker.imports.entries())
290
296
  .filter(([file]) => file !== currentFile)
291
- .map(([file, typeName]) => `import { ${typeName} } from './${file}.types.js';`);
297
+ .map(([file, typeNames]) => `import { ${Array.from(typeNames).join(', ')} } from './${file}.types.js';`);
292
298
  if (imports.length > 0) {
293
299
  // Replace header with imports and re-add header once
294
300
  output = output.replace(FILE_HEADER, FILE_HEADER + imports.join('\n') + '\n\n');
@@ -361,6 +367,6 @@ export async function generateTypes({ schemaDirs = DEFAULT_SCHEMA_DIRS, outputDi
361
367
  }
362
368
  catch (err) {
363
369
  error('Error generating types:', err);
364
- process.exit(1);
370
+ throw err;
365
371
  }
366
372
  }
package/dist/hash.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Deterministic object hashing using node:crypto SHA-256.
3
+ *
4
+ * ## Supported types
5
+ * Plain objects, arrays, strings, numbers, booleans, null.
6
+ *
7
+ * ## Unsupported types (throws TypeError)
8
+ * undefined, Date, Map, Set, TypedArrays (Buffer, Uint8Array, etc.),
9
+ * Function, BigInt, Symbol, and circular references.
10
+ *
11
+ * Unsupported types throw rather than silently producing a hash that
12
+ * diverges from the previous object-hash implementation.
13
+ *
14
+ * ## Key ordering
15
+ * Object keys are sorted recursively before serialisation, so
16
+ * `{ b: 2, a: 1 }` and `{ a: 1, b: 2 }` produce the same hash.
17
+ *
18
+ * ## Array ordering
19
+ * Array element order is preserved — `[1, 2]` and `[2, 1]` hash differently.
20
+ */
21
+ /**
22
+ * Return a deterministic 64-char hex SHA-256 hash of the given value.
23
+ *
24
+ * Throws TypeError if the value contains any unsupported type.
25
+ */
26
+ export declare function hashObject(value: unknown): string;
27
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoGH;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKjD"}
package/dist/hash.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Deterministic object hashing using node:crypto SHA-256.
3
+ *
4
+ * ## Supported types
5
+ * Plain objects, arrays, strings, numbers, booleans, null.
6
+ *
7
+ * ## Unsupported types (throws TypeError)
8
+ * undefined, Date, Map, Set, TypedArrays (Buffer, Uint8Array, etc.),
9
+ * Function, BigInt, Symbol, and circular references.
10
+ *
11
+ * Unsupported types throw rather than silently producing a hash that
12
+ * diverges from the previous object-hash implementation.
13
+ *
14
+ * ## Key ordering
15
+ * Object keys are sorted recursively before serialisation, so
16
+ * `{ b: 2, a: 1 }` and `{ a: 1, b: 2 }` produce the same hash.
17
+ *
18
+ * ## Array ordering
19
+ * Array element order is preserved — `[1, 2]` and `[2, 1]` hash differently.
20
+ */
21
+ import { createHash } from 'node:crypto';
22
+ /**
23
+ * Recursively normalise a value for hashing:
24
+ * - Sort object keys alphabetically
25
+ * - Throw TypeError on any unsupported type
26
+ * - Track seen objects to detect circular references
27
+ */
28
+ function normalise(value, seen) {
29
+ // null is fine
30
+ if (value === null)
31
+ return null;
32
+ // Primitives
33
+ if (typeof value === 'string')
34
+ return value;
35
+ if (typeof value === 'number') {
36
+ if (!Number.isFinite(value)) {
37
+ throw new TypeError(`hashObject: non-finite number is not supported (got: ${value})`);
38
+ }
39
+ return value;
40
+ }
41
+ if (typeof value === 'boolean')
42
+ return value;
43
+ // Explicitly unsupported primitives
44
+ if (value === undefined) {
45
+ throw new TypeError('hashObject: undefined is not supported — use null or omit the key instead');
46
+ }
47
+ if (typeof value === 'bigint') {
48
+ throw new TypeError(`hashObject: BigInt is not supported — convert to string or number first (got: ${value}n)`);
49
+ }
50
+ if (typeof value === 'symbol') {
51
+ throw new TypeError(`hashObject: Symbol is not supported (got: ${String(value)})`);
52
+ }
53
+ if (typeof value === 'function') {
54
+ throw new TypeError(`hashObject: function is not supported (got: ${value.name || 'anonymous'})`);
55
+ }
56
+ // Object types
57
+ if (typeof value === 'object') {
58
+ // Circular reference detection
59
+ if (seen.has(value)) {
60
+ throw new TypeError('hashObject: circular reference detected');
61
+ }
62
+ // Explicitly unsupported object types — check before Array/plain-object
63
+ if (value instanceof Date) {
64
+ throw new TypeError(`hashObject: Date is not supported — convert to ISO string first (got: ${value.toISOString()})`);
65
+ }
66
+ if (value instanceof Map) {
67
+ throw new TypeError('hashObject: Map is not supported — convert to a plain object first');
68
+ }
69
+ if (value instanceof Set) {
70
+ throw new TypeError('hashObject: Set is not supported — convert to an Array first');
71
+ }
72
+ if (ArrayBuffer.isView(value)) {
73
+ throw new TypeError(`hashObject: TypedArray/Buffer is not supported — convert to a plain array or base64 string first (got: ${value.constructor.name})`);
74
+ }
75
+ seen.add(value);
76
+ let result;
77
+ if (Array.isArray(value)) {
78
+ result = value.map((item) => normalise(item, seen));
79
+ }
80
+ else {
81
+ // Plain object — sort keys for determinism
82
+ const sorted = {};
83
+ for (const key of Object.keys(value).sort()) {
84
+ sorted[key] = normalise(value[key], seen);
85
+ }
86
+ result = sorted;
87
+ }
88
+ seen.delete(value); // allow the same object to appear in sibling branches
89
+ return result;
90
+ }
91
+ // Should be unreachable, but TypeScript needs the exhaustive case
92
+ throw new TypeError(`hashObject: unsupported type: ${typeof value}`);
93
+ }
94
+ /**
95
+ * Return a deterministic 64-char hex SHA-256 hash of the given value.
96
+ *
97
+ * Throws TypeError if the value contains any unsupported type.
98
+ */
99
+ export function hashObject(value) {
100
+ const normalised = normalise(value, new Set());
101
+ return createHash('sha256')
102
+ .update(JSON.stringify(normalised))
103
+ .digest('hex');
104
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export { registerTypeSchema, getTypeSchema } from './type_register.js';
2
2
  export * from './types/index.js';
3
- export { generateTypes } from './generate_types.js';
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,3 @@
1
1
  // Main stub. Expose type register functions and all generated types.
2
2
  export { registerTypeSchema, getTypeSchema } from './type_register.js';
3
3
  export * from './types/index.js';
4
- // export the generate types function, for use in build scripts or other projects
5
- export { generateTypes } from './generate_types.js';
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Register an OpenAPI schema for runtime type checking
3
- * @param name The type name (e.g. "DestinationsResponse")
4
- * @param schema The OpenAPI schema definition
2
+ * Register a JSON schema for runtime type checking
3
+ * @param name The type name (e.g. "Entity")
4
+ * @param schema The JSON schema definition
5
5
  */
6
6
  export declare function registerTypeSchema(name: string, schema: any): void;
7
7
  /**
8
- * Retrieve the OpenAPI schema for a registered type
8
+ * Retrieve the JSON schema for a registered type
9
9
  * @param name The type name
10
- * @returns The OpenAPI schema or undefined if not found
10
+ * @returns The JSON schema or undefined if not found
11
11
  */
12
12
  export declare function getTypeSchema(name: string): any | undefined;
13
13
  //# sourceMappingURL=type_register.d.ts.map
@@ -1,17 +1,17 @@
1
- // Store OpenAPI schemas for runtime type information
1
+ // Store JSON schemas for runtime type information
2
2
  const typeMetadataRegistry = new Map();
3
3
  /**
4
- * Register an OpenAPI schema for runtime type checking
5
- * @param name The type name (e.g. "DestinationsResponse")
6
- * @param schema The OpenAPI schema definition
4
+ * Register a JSON schema for runtime type checking
5
+ * @param name The type name (e.g. "Entity")
6
+ * @param schema The JSON schema definition
7
7
  */
8
8
  export function registerTypeSchema(name, schema) {
9
9
  typeMetadataRegistry.set(name, schema);
10
10
  }
11
11
  /**
12
- * Retrieve the OpenAPI schema for a registered type
12
+ * Retrieve the JSON schema for a registered type
13
13
  * @param name The type name
14
- * @returns The OpenAPI schema or undefined if not found
14
+ * @returns The JSON schema or undefined if not found
15
15
  */
16
16
  export function getTypeSchema(name) {
17
17
  return typeMetadataRegistry.get(name);
@@ -15,7 +15,7 @@ export declare enum LanguageCodeEnum {
15
15
  export type LanguageCode = keyof typeof LanguageCodeEnum;
16
16
  export declare function StringToLanguageCode(value: string): LanguageCodeEnum;
17
17
  /** A string with multiple language translations */
18
- export interface MultilangString extends Record<LanguageCode, string> {
18
+ export interface MultilangString extends Partial<Record<LanguageCode, string>> {
19
19
  }
20
20
  /** A string that may be localised or a simple string */
21
21
  export type LocalisedString = MultilangString | string;