๐Ÿ“„ src/models/schemaGenerator.ts
D-OPEN SOVEREIGN

Documentation & Insights

V2 ORM code generator.

Consumes the enriched schema JSONs from `@the_library/db_schemas/generated-src`
(frozen numeric IDs) plus the compiled `versions.json` layout, and emits one
TypeScript model file per schema with:
  - generated column-bag interfaces (<Name>SlimCols / <Name>ExtendedCols /
    <Name>ProvCols) whose TypedArray widths derive from the schema `bits`
  - the model class (prop getters/setters dispatching AnyPatch, relation
    helpers, mergeSlim / mergeExtended / mergeProv, Load / LoadAsync)
  - a <Name>Instance wrapper when `multipleInstances` is enabled
  - the SYMMETRIC write side: a <Name>Input record type plus
    serialize<Name>{Slim,Extended,Prov}(records) free functions (and static
    aliases on the class), emitted from the SAME colName/propBits/base-col/
    default-instance/relation/instance logic as the merge* readers, so writer
    and reader are provably the inverse of one another (see the round-trip
    contract in test/serializer.test.ts).
All schemas, keyed by model name (for relation target resolution).
Fully-merged current layout from versions.json.
Column name: pluralize string props ("description" -> "descriptions"), keep others.
Method base for in-relations: "inTags" -> "Tag", "authorOf" -> "AuthorOf".
TS type of a prop getter: enums use the generated enum type.
propName -> propId (provenance column mapping)
Synchronous LRU read โ€” throws on miss (use LoadAsync to re-hydrate).
On-demand hydration from the worker after LRU eviction (SPEC ยง2.5).
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library โ€” The World Library
4 * 
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 * 
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15/**
16 * V2 ORM code generator.
17 *
18 * Consumes the enriched schema JSONs from `@the_library/db_schemas/generated-src`
19 * (frozen numeric IDs) plus the compiled `versions.json` layout, and emits one
20 * TypeScript model file per schema with:
21 *   - generated column-bag interfaces (<Name>SlimCols / <Name>ExtendedCols /
22 *     <Name>ProvCols) whose TypedArray widths derive from the schema `bits`
23 *   - the model class (prop getters/setters dispatching AnyPatch, relation
24 *     helpers, mergeSlim / mergeExtended / mergeProv, Load / LoadAsync)
25 *   - a <Name>Instance wrapper when `multipleInstances` is enabled
26 *   - the SYMMETRIC write side: a <Name>Input record type plus
27 *     serialize<Name>{Slim,Extended,Prov}(records) free functions (and static
28 *     aliases on the class), emitted from the SAME colName/propBits/base-col/
29 *     default-instance/relation/instance logic as the merge* readers, so writer
30 *     and reader are provably the inverse of one another (see the round-trip
31 *     contract in test/serializer.test.ts).
32 */
33import { relationColName } from "./registry.ts";
34import type { ModelDefinition, SchemaProp, SchemaInstanceField, SchemaRelation } from "./registry.ts";
35import type { VersionLayout } from "./binary.ts";
36
37export interface GeneratorContext {
38    /** All schemas, keyed by model name (for relation target resolution). */
39    schemas: Map<string, ModelDefinition>;
40    /** Fully-merged current layout from versions.json. */
41    layout: VersionLayout;
42}
43
44// โ”€โ”€โ”€ Naming helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
45
46const capitalize = (name: string) => name.charAt(0).toUpperCase() + name.slice(1)
47
48const toSingular = (name: string) => name.endsWith('s') ? name.slice(0, -1) : name
49
50/** Column name: pluralize string props ("description" -> "descriptions"), keep others. */
51const colName = (name: string, type: string) =>
52    type === 'string' && !name.endsWith('s') ? `${name}s` : name
53
54/** Method base for in-relations: "inTags" -> "Tag", "authorOf" -> "AuthorOf". */
55const inMethodBase = (name: string) => {
56    const stripped = (name.startsWith('in') && name[2] === name[2]?.toUpperCase())
57        ? name.slice(2)
58        : capitalize(name);
59    return toSingular(stripped);
60}
61
62// โ”€โ”€โ”€ Type helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
63
64const typedArrayFor = (bits: number): string =>
65    bits <= 8 ? 'Uint8Array' : bits <= 16 ? 'Uint16Array' : 'Uint32Array'
66
67const propBits = (prop: SchemaProp, layout: VersionLayout): number => {
68    const fromLayout = layout.props[prop.propId]?.bits;
69    if (typeof fromLayout === 'number' && fromLayout > 0) return fromLayout;
70    if (prop.type === 'boolean') return 1;
71    return prop.bits ?? 32;
72}
73
74const instanceFieldBits = (field: SchemaInstanceField, index: number, objectId: number, layout: VersionLayout): number => {
75    const propId = (layout as any).instanceFields?.[objectId]?.[index];
76    const fromLayout = typeof propId === 'number' ? layout.props[propId]?.bits : undefined;
77    if (typeof fromLayout === 'number' && fromLayout > 0) return fromLayout;
78    if (field.type === 'boolean') return 1;
79    return field.bits ?? 32;
80}
81
82/** TS type of a prop getter: enums use the generated enum type. */
83const tsType = (p: { type: string, enum?: string }): string => {
84    switch (p.type) {
85        case 'string': return 'string';
86        case 'boolean': return 'boolean';
87        case 'enum': return p.enum ?? 'number';
88        default: return 'number';
89    }
90}
91
92const usedEnums = (schema: ModelDefinition): string[] => {
93    const names = new Set<string>();
94    for (const p of schema.props) if (p.type === 'enum' && p.enum) names.add(p.enum);
95    for (const f of schema.instanceFields) if (f.type === 'enum' && f.enum) names.add(f.enum);
96    return Array.from(names).sort();
97}
98
99// โ”€โ”€โ”€ Cols interfaces โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
100
101const slimColsSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
102    const lines: string[] = [
103        `export interface ${schema.name}SlimCols {`,
104        `    ids: Uint16Array;`,
105        `    titles: (string | null)[];`,
106        `    originalLangs: (string | null)[];`,
107        `    previewTxIds: (string | null)[];`,
108        `    ageGradeBands: Uint8Array;`,
109        `    safes: Uint8Array;`,
110        `    deleteds: Uint8Array;`,
111    ];
112    for (const prop of schema.props.filter(p => p.source === 'slim')) {
113        const name = colName(prop.name, prop.type);
114        if (prop.type === 'string') {
115            lines.push(`    ${name}: (string | null)[];`);
116        } else {
117            lines.push(`    ${name}: ${typedArrayFor(propBits(prop, ctx.layout))};`);
118        }
119    }
120    if (schema.multipleInstances) {
121        lines.push(`    // Default instance inlined (slim tier carries one default instance per record):`);
122        for (let i = 0; i < schema.instanceFields.length; i++) {
123            const field = schema.instanceFields[i];
124            const capName = colName(capitalize(field.name), field.type);
125            if (field.type === 'string') {
126                lines.push(`    default${capName}: (string | null)[];`);
127            } else if (field.type === 'boolean') {
128                lines.push(`    default${capName}: Uint8Array;`);
129            } else {
130                lines.push(`    default${capName}: ${typedArrayFor(instanceFieldBits(field, i, schema.objectId, ctx.layout))};`);
131            }
132        }
133    }
134    lines.push(`}`);
135    return lines.join('\n');
136}
137
138const extendedColsSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
139    const lines: string[] = [
140        `export interface ${schema.name}ExtendedCols {`,
141        `    ids: Uint16Array;`,
142        `    descriptions: (string | null)[];`,
143    ];
144    for (const prop of schema.props.filter(p => p.source === 'extended')) {
145        const name = colName(prop.name, prop.type);
146        if (prop.type === 'string') {
147            lines.push(`    ${name}: (string | null)[];`);
148        } else {
149            lines.push(`    ${name}: ${typedArrayFor(propBits(prop, ctx.layout))};`);
150        }
151    }
152    const hasRelations = schema.relations.filter(r => r.direction === 'has');
153    if (hasRelations.length > 0) {
154        lines.push(`    // Structured relation tails (parsed from the sidecar tail, not columnar):`);
155        for (const rel of hasRelations) {
156            lines.push(`    ${relationColName(rel)}?: number[][];`);
157        }
158    }
159    if (schema.multipleInstances) {
160        lines.push(`    instances?: InstanceData[][];`);
161    }
162    lines.push(`}`);
163    return lines.join('\n');
164}
165
166const provColsSource = (schema: ModelDefinition): string => `export interface ${schema.name}ProvCols {
167    ids: Uint16Array;
168    fields: Record<string, Uint8Array>;
169}`
170
171// โ”€โ”€โ”€ Input & Serializer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
172
173const inputInterfaceSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
174    const lines: string[] = [
175        `export interface ${schema.name}Input {`,
176        `    id: number;`,
177        `    title: string | null;`,
178        `    description: string | null;`,
179        `    originalLang: string | null;`,
180        `    previewTxId: string | null;`,
181        `    ageGradeBand: number;`,
182        `    safe: boolean;`,
183        `    deleted: boolean;`,
184    ];
185    for (const prop of schema.props) {
186        if (prop.type === 'string') {
187            lines.push(`    ${prop.name}: string | null;`);
188        } else if (prop.type === 'boolean') {
189            lines.push(`    ${prop.name}: boolean;`);
190        } else {
191            lines.push(`    ${prop.name}: ${tsType(prop)};`);
192        }
193    }
194    const hasRelations = schema.relations.filter(r => r.direction === 'has');
195    for (const rel of hasRelations) {
196        lines.push(`    ${relationColName(rel)}: number[];`);
197    }
198    if (schema.multipleInstances) {
199        for (const field of schema.instanceFields) {
200            const cap = capitalize(field.name);
201            const ts = field.type === 'string' ? 'string | null' : field.type === 'boolean' ? 'boolean' : 'number';
202            lines.push(`    default${cap}: ${ts};`);
203        }
204        lines.push(`    instances: InstanceData[];`);
205    }
206    lines.push(`}`);
207    return lines.join('\n');
208}
209
210const serializeSlimSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
211    return `export function serialize${schema.name}Slim(records: ${schema.name}Input[]): ArrayBuffer {
212    return commonSerializeSlim(schema, records, currentLayout());
213}`;
214}
215
216const serializeExtendedSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
217    return `export function serialize${schema.name}Extended(records: ${schema.name}Input[]): ArrayBuffer {
218    return commonSerializeExtended(schema, records, currentLayout());
219}`;
220}
221
222const serializeProvSource = (schema: ModelDefinition): string => {
223    return `export function serialize${schema.name}Prov(records: ${schema.name}Input[]): ArrayBuffer {
224    return commonSerializeProv(schema, records);
225}`;
226}
227
228// โ”€โ”€โ”€ Relations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
229
230const invertedRelationId = (rel: SchemaRelation, ctx: GeneratorContext): number | undefined => {
231    const target = ctx.schemas.get(rel.targetType);
232    return target?.relations.find(r => r.name === rel.inverted)?.relationId;
233}
234
235const hasRelationSource = (schema: ModelDefinition, rel: SchemaRelation, _ctx: GeneratorContext): string => {
236    const single = capitalize(toSingular(rel.name));
237    // The has side IS the canonical side: dispatchRelation emits the single
238    // EditRelation patch; Orm.patch() propagates in-memory to the loaded
239    // inverse record generically. No second patch, no propagate flag.
240    return `
241    get ${rel.name}Ids(): Array<uid> {
242        return Array.from(this._has.get(${rel.relationId})!.values());
243    }
244
245    get ${rel.name}(): Array<${rel.targetType}> {
246        return this.${rel.name}Ids.map(id => ${rel.targetType}.Load(id));
247    }
248
249    has${single}(id: uid): boolean {
250        return this._has.get(${rel.relationId})!.has(id);
251    }
252
253    add${single}(id: uid) {
254        if (this.has${single}(id)) return;
255        this.dispatchRelation(${rel.relationId}, PatchAction.Add, id);
256    }
257
258    remove${single}(id: uid) {
259        if (!this.has${single}(id)) return;
260        this.dispatchRelation(${rel.relationId}, PatchAction.Remove, id);
261    }`;
262}
263
264const inRelationSource = (schema: ModelDefinition, rel: SchemaRelation, ctx: GeneratorContext): string => {
265    const getterBase = rel.name.startsWith('in') ? rel.name : `in${capitalize(rel.name)}`;
266    const single = inMethodBase(rel.name);
267    const invId = invertedRelationId(rel, ctx);
268
269    const getters = `
270    get ${getterBase}Ids(): Array<uid> {
271        return Array.from(this._in.get(${rel.relationId})!.values());
272    }
273
274    get ${getterBase}(): Array<${rel.targetType}> {
275        return this.${getterBase}Ids.map(id => ${rel.targetType}.Load(id));
276    }
277
278    isIn${single}(id: uid): boolean {
279        return this._in.get(${rel.relationId})!.has(id);
280    }`;
281
282    // No inverse declared: this side is the only representation of the edge,
283    // so it dispatches its own patch.
284    if (invId === undefined) {
285        return `${getters}
286
287    addInto${single}(id: uid) {
288        if (this.isIn${single}(id)) return;
289        this.dispatchRelation(${rel.relationId}, PatchAction.Add, id);
290    }
291
292    removeFrom${single}(id: uid) {
293        if (!this.isIn${single}(id)) return;
294        this.dispatchRelation(${rel.relationId}, PatchAction.Remove, id);
295    }`;
296    }
297
298    // Inverse exists: the canonical patch is expressed on the has side
299    // (${rel.targetType}.${rel.inverted}), regardless of which side the user
300    // called. Own _in is updated directly (idempotent) so the edge is
301    // coherent even when the target record is not loaded.
302    const edit = (method: string, guard: string, action: 'Add' | 'Remove') => `
303    ${method}(id: uid) {
304        if (${guard}) return;
305        const op: EditRelationObj = {
306            operation: PatchOperation.EditRelation,
307            objectType: ${rel.targetType}.type,
308            objectId: id,
309            langId: 0,
310            relationId: ${invId}, // canonical: ${rel.targetType}.${rel.inverted}
311            action: PatchAction.${action},
312            targetId: this.id,
313        };
314        if (hasRecord(${rel.targetType}.type, id)) {
315            (getRecord(${rel.targetType}.type, id) as ${rel.targetType}).patch(op);
316        }
317        this.applyRelationRaw('in', ${rel.relationId}, PatchAction.${action}, id);
318        PatchInstance.addPatch(op);
319    }`;
320
321    return `${getters}
322${edit(`addInto${single}`, `this.isIn${single}(id)`, 'Add')}
323${edit(`removeFrom${single}`, `!this.isIn${single}(id)`, 'Remove')}`;
324}
325
326// โ”€โ”€โ”€ Props โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
327
328const propAccessorsSource = (prop: SchemaProp): string => {
329    const type = tsType(prop);
330    const cap = capitalize(prop.name);
331    switch (prop.type) {
332        case 'string': {
333            const rt = prop.nullable ? 'string | null' : 'string';
334            const val = prop.nullable ? 'value ?? ""' : 'value';
335            return `
336    get ${prop.name}(): ${rt} { return this.getProp(${prop.propId})${prop.nullable ? ' ?? null' : ''}; }
337    set ${prop.name}(value: ${rt}) { this.dispatchString(${prop.propId}, ${val}); }
338    get${cap}(lang: Lang): ${rt} { return this.getProp(${prop.propId}, lang)${prop.nullable ? ' ?? null' : ''}; }
339    set${cap}(value: ${rt}, lang: Lang) { this.dispatchString(${prop.propId}, ${val}, lang); }`;
340        }
341        case 'boolean':
342            return `
343    get ${prop.name}(): boolean { return this.getProp(${prop.propId}) ?? false; }
344    set ${prop.name}(value: boolean) { this.dispatchNumber(${prop.propId}, value ? 1 : 0); }
345    get${cap}(lang: Lang): boolean { return this.getProp(${prop.propId}, lang) ?? false; }
346    set${cap}(value: boolean, lang: Lang) { this.dispatchNumber(${prop.propId}, value ? 1 : 0, lang); }`;
347        default: // number | enum
348            return `
349    get ${prop.name}(): ${type} { return this.getProp(${prop.propId}) ?? 0; }
350    set ${prop.name}(value: ${type}) { this.dispatchNumber(${prop.propId}, value); }
351    get${cap}(lang: Lang): ${type} { return this.getProp(${prop.propId}, lang) ?? 0; }
352    set${cap}(value: ${type}, lang: Lang) { this.dispatchNumber(${prop.propId}, value, lang); }`;
353    }
354}
355
356// โ”€โ”€โ”€ Merge methods โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
357
358// Generated methods for merging sidecars have been removed and implemented in Orm.
359
360// โ”€โ”€โ”€ Instance wrapper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
361
362const instanceWrapperSource = (schema: ModelDefinition): string => {
363    const accessors: string[] = [];
364    const instStrats = Array.isArray(schema.instanceUidStrategy) ? schema.instanceUidStrategy : (schema.instanceUidStrategy ? [schema.instanceUidStrategy] : []);
365    const isInstStandardsFirst = instStrats.length > 0 && instStrats[0] !== 'native-only';
366    let instUIDMethods = '';
367    
368    const idFields = schema.instanceFields?.filter(f => (f as any).identityComponent) || [];
369
370    if (idFields.length > 0) {
371        instUIDMethods += `
372    derivedSfcuid(sourceSfcuid: string): string {
373        const parts = [
374            ${idFields.map(f => `this.${f.name} ?? ''`).join(',\n            ')}
375        ];
376        return \`DL:C:DERIVED:\${sourceSfcuid}:\${parts.map(p => encodeURIComponent(String(p))).join(':')}\`;
377    }`;
378    }
379
380    if (isInstStandardsFirst || idFields.length > 0) {
381        let sfcReturns = '';
382        let aliases = '';
383        if (isInstStandardsFirst) {
384            const stds = instStrats.filter(s => s !== 'native-only');
385            for (const std of stds) {
386                const pk = schema.standardPks?.[std] ?? std;
387                instUIDMethods += `
388    // โ”€โ”€ Inherited from ${std} standard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
389    get ${std}Normalized(): string | null {
390        return this.${pk} ? ${std.toUpperCase()}Adapter.normalize(this.${pk}) : null;
391    }
392    validate${capitalize(std)}(): ReturnType<typeof ${std.toUpperCase()}Adapter.validate> {
393        return ${std.toUpperCase()}Adapter.validate(this.${pk});
394    }`;
395            }
396            
397            sfcReturns = stds.map(std => {
398                const pk = schema.standardPks?.[std] ?? std;
399                return `if (this.${pk}) return \`${std}:\${${std.toUpperCase()}Adapter.normalize(this.${pk})}\`;`;
400            }).join('\n        ');
401
402            aliases = stds.map(std => {
403                const pk = schema.standardPks?.[std] ?? std;
404                return `this.${pk} ? \`${std}:\${${std.toUpperCase()}Adapter.normalize(this.${pk})}\` : null,`;
405            }).join('\n            ');
406        }
407
408        const fallback = idFields.length > 0
409            ? `return this.derivedSfcuid(this.parent.toPID());`
410            : `return this.parent.toPID();`;
411
412        instUIDMethods += `
413    sfcuid(): string {
414        ${sfcReturns}
415        ${fallback}
416    }`;
417
418        instUIDMethods += `
419    get identityCluster(): RecordIdentityCluster {
420        const primary = this.sfcuid();
421        const allAliases = [
422            ${aliases}
423            this.parent.toPID()
424        ].filter(Boolean) as string[];
425        return {
426            sfcuid: primary,
427            native: this.parent.toPID(),
428            aliases: allAliases.filter(a => a !== primary),
429            wireId: this.parent.id,
430        };
431    }`;
432    }
433
434    schema.instanceFields.forEach((field, fieldIndex) => {
435        const type = tsType(field);
436        const nullable = field.type === 'string' && field.nullable;
437        const returnType = field.type === 'string' ? (nullable ? 'string | null' : 'string') : type;
438        const fallback = field.type === 'string'
439            ? (nullable ? 'null' : `""`)
440            : field.type === 'boolean' ? 'false' : '0';
441        accessors.push(`
442    get ${field.name}(): ${returnType} { return (this.data['${field.name}'] as ${returnType}) ?? ${fallback}; }
443    set ${field.name}(val: ${returnType}) {
444        this.data['${field.name}'] = val;
445        const op: ${field.type === 'string' ? 'EditInstanceFieldStringObj' : 'EditInstanceFieldNumberObj'} = {
446            operation: PatchOperation.${field.type === 'string' ? 'EditInstanceFieldString' : 'EditInstanceFieldNumber'},
447            objectType: ${schema.name}.type,
448            objectId: this.parent.id,
449            langId: LANG_ID_ENCODE[this.lang] ?? 0,
450            instanceIndex: this.index,
451            propertyId: ${field.propId}, // ${field.name} โ€” instanceField global propId
452            value: ${field.type === 'string' ? `val ?? ""` : field.type === 'boolean' ? 'val ? 1 : 0' : 'val'},
453        };
454        this.parent.patch(op);
455        PatchInstance.addPatch(op);
456    }`);
457    });
458
459    return `
460export class ${schema.name}Instance {
461    constructor(
462        public readonly parent: ${schema.name},
463        public readonly lang: string,
464        public readonly index: number,
465        private data: InstanceData,
466    ) {}
467
468    get isDefault(): boolean { return this.data['isDefault'] === true; }
469${accessors.join('\n')}
470${instUIDMethods}
471
472    // High-ease parent delegates
473    get description(): string {
474        return this.parent.getDescription(this.lang) ?? "";
475    }
476    set description(val: string) {
477        this.parent.setDescription(val, this.lang);
478    }
479}`;
480}
481
482const generateLoadInstance = (schema: ModelDefinition): string => {
483    return `
484    static async LoadInstance(pkValue: string, lang: Lang): Promise<{ record: ${schema.name}; instanceIndex?: number } | null> {
485        const crosswalk = (globalThis as any).ExternalIdCrosswalk?.instance;
486        if (!crosswalk) throw new Error("ExternalIdCrosswalk not initialized โ€” bootup incomplete");
487        await crosswalk.whenReady();
488
489        // 1. Mode DERIVED
490        if (pkValue.startsWith('DL:C:DERIVED:')) {
491            const result = await crosswalk.resolveDerived(pkValue, lang);
492            if (!result) return null;
493            const record = await ${schema.name}.LoadAsync(result.wireId);
494            return record ? { record, instanceIndex: result.instanceIndex } : null;
495        }
496
497        // 2. Mode N (Parent Work)
498        if (pkValue.startsWith('DL:C:')) {
499            const parsed = ${schema.name}.fromPID(pkValue);
500            if (parsed) {
501                const record = await ${schema.name}.LoadAsync(parsed.wireId);
502                return record ? { record } : null;
503            }
504            return null;
505        }
506
507        // 3. Mode S (Standards)
508        const colon = pkValue.indexOf(':');
509        if (colon === -1) return null;
510        const standardId = pkValue.slice(0, colon);
511        const value = pkValue.slice(colon + 1);
512        const result = await crosswalk.resolveForInstance(standardId, value, lang);
513        if (!result) return null;
514        const record = await ${schema.name}.LoadAsync(result.wireId);
515        return record ? { record, instanceIndex: result.instanceIndex } : null;
516    }`;
517}
518
519export const generateModelSource = (schema: ModelDefinition, ctx: GeneratorContext): string => {
520    const NAME = schema.name;
521
522    const targets = Array.from(new Set(
523        schema.relations
524            .map(r => r.targetType)
525            .filter(t => t !== NAME && ctx.schemas.has(t))
526    )).sort();
527
528    const enums = usedEnums(schema);
529
530    const propIdEntries: string[] = [
531        `title: 0`, `description: 1`, `originalLang: 2`, `ageGradeBand: 3`, `previewTxId: 4`, `safe: 5`, `deleted: 6`, `lifecycleStatus: 7`,
532        ...schema.props.map(p => `${p.name}: ${p.propId}`),
533        ...schema.instanceFields.map(f => `${f.name}: ${f.propId}`),
534    ];
535
536    const imports: string[] = [
537        `// AUTO-GENERATED by @the_library/db_schemas schemaGenerator โ€” run \`pnpm generate\` to rebuild.`,
538        `import { Orm, Config } from "../orm.ts";`,
539        `import { Registry, type ModelDefinition } from "../registry.ts";`,
540        `import { PatchOperation, PatchAction, LANG_ID_ENCODE, type EditInstanceFieldNumberObj, type EditInstanceFieldStringObj, type EditRelationObj } from "../binary.ts";`,
541        `import { PatchInstance } from "../patcher.ts";`,
542        `import { addRecord, getRecord, hasRecord } from "../db.ts";`,
543        `import type { uid, Lang, CommonOrmJson, SerializedCommonOrmJson, InstanceData, RecordIdentityCluster } from "../interfaces.ts";`,
544        `import { WorkerCommands, SidecarType } from "../enums.ts";`,
545        `import { ColumnType, type SidecarInput, type SidecarRelationInput, instanceSpecFromFields } from "../enriched-index.ts";`,
546        `import { encodePID, decodePID } from "../pid.ts";`,
547        `import { schemas } from "@the_library/db_schemas/schemas";`,
548        `import { commonSerializeSlim, commonSerializeExtended, commonSerializeProv } from "../serializer.ts";`,
549        `import { currentLayout } from "../versions.ts";`,
550    ];
551    if (enums.length > 0) {
552        imports.push(`import { ${enums.join(', ')} } from "@the_library/db_schemas/enums";`);
553    }
554    for (const target of targets) {
555        imports.push(`import { ${target} } from "./${target}.ts";`);
556    }
557
558    const parentStrats = Array.isArray(schema.uidStrategy) ? schema.uidStrategy : (schema.uidStrategy ? [schema.uidStrategy] : []);
559    const instStrats = Array.isArray(schema.instanceUidStrategy) ? schema.instanceUidStrategy : (schema.instanceUidStrategy ? [schema.instanceUidStrategy] : []);
560    const allStds = new Set([...parentStrats, ...instStrats].filter(s => s !== 'native-only'));
561    for (const std of allStds) {
562        imports.push(`import { ${std.toUpperCase()}Adapter } from "@the_library/standard-adapters/${std}";`);
563    }
564
565    const schemaBlock = `const schema = schemas["${schema.name}"] as ModelDefinition;`;
566
567    const relationBlocks = schema.relations.map(rel =>
568        rel.direction === 'has'
569            ? hasRelationSource(schema, rel, ctx)
570            : inRelationSource(schema, rel, ctx)
571    );
572
573    const propBlocks = schema.props.map(propAccessorsSource);
574
575    const instancesApi = !schema.multipleInstances ? '' : `
576    getInstances(lang: Lang = Config.userLang): ${NAME}Instance[] {
577        return this.getInstancesData(lang).map((data, i) => new ${NAME}Instance(this, lang, i, data));
578    }
579
580    getInstance(index: number, lang: Lang = Config.userLang): ${NAME}Instance | undefined {
581        return this.getInstances(lang)[index];
582    }
583
584    getDefaultInstance(lang: Lang = Config.userLang): ${NAME}Instance | undefined {
585        const instances = this.getInstances(lang);
586        return instances.find(i => i.isDefault) ?? instances[0];
587    }`;
588
589    const isStandardsFirst = parentStrats.length > 0 && parentStrats[0] !== 'native-only';
590    let classUIDMethods = `
591    // โ”€โ”€ UID methods โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
592    toPID(): string { 
593        const scope = (this.id & 0x80000000) ? 'U' : 'C';
594        return \`DL:\${scope}:${schema.typePrefix}:\${encodePID('${schema.typePrefix}', this.id)}\`; 
595    }
596    toNativePID(): string { return this.toPID(); }
597
598    static fromPID(pid: string): { wireId: number } | null {
599        try {
600            if (!pid.startsWith('DL:')) return decodePID('${schema.typePrefix}', pid);
601            const parts = pid.split(':');
602            if (parts.length >= 4 && parts[2] === '${schema.typePrefix}') {
603                return decodePID('${schema.typePrefix}', parts[3]);
604            }
605            return null;
606        } catch {
607            return null;
608        }
609    }`;
610
611    if (isStandardsFirst) {
612        const stds = parentStrats.filter(s => s !== 'native-only');
613        for (const std of stds) {
614            const pk = schema.standardPks?.[std] ?? std;
615            classUIDMethods += `
616    // โ”€โ”€ Inherited from ${std} standard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
617    get ${std}Normalized(): string | null {
618        return this.${pk} ? ${std.toUpperCase()}Adapter.normalize(this.${pk}) : null;
619    }
620    validate${capitalize(std)}(): ReturnType<typeof ${std.toUpperCase()}Adapter.validate> {
621        return ${std.toUpperCase()}Adapter.validate(this.${pk});
622    }`;
623        }
624        
625        const fallback = 'this.toNativePID()';
626        const sfcReturns = stds.map(std => {
627            const pk = schema.standardPks?.[std] ?? std;
628            return `if (this.${pk}) return \`${std}:\${${std.toUpperCase()}Adapter.normalize(this.${pk})}\`;`;
629        }).join('\n        ');
630
631        classUIDMethods += `
632    sfcuid(): string {
633        ${sfcReturns}
634        return ${fallback};
635    }`;
636
637        const aliases = stds.map(std => {
638            const pk = schema.standardPks?.[std] ?? std;
639            return `this.${pk} ? \`${std}:\${${std.toUpperCase()}Adapter.normalize(this.${pk})}\` : null,`;
640        }).join('\n            ');
641
642        classUIDMethods += `
643    get identityCluster(): RecordIdentityCluster {
644        const primary = this.sfcuid();
645        const allAliases = [
646            ${aliases}
647            this.toNativePID()
648        ].filter(Boolean) as string[];
649        return {
650            sfcuid: primary,
651            native: this.toNativePID(),
652            aliases: allAliases.filter(a => a !== primary),
653            wireId: this.id,
654        };
655    }`;
656    }
657
658    return `${imports.join('\n')}
659
660${schemaBlock}
661
662Registry.register(schema);
663
664/** propName -> propId (provenance column mapping) */
665export const ${NAME.toUpperCase()}_PROP_IDS: Record<string, number> = { ${propIdEntries.join(', ')} };
666
667${slimColsSource(schema, ctx)}
668
669${extendedColsSource(schema, ctx)}
670
671${provColsSource(schema)}
672
673${inputInterfaceSource(schema, ctx)}
674${serializeSlimSource(schema, ctx)}
675${serializeExtendedSource(schema, ctx)}
676${serializeProvSource(schema)}
677
678export class ${NAME} extends Orm {
679    static readonly type: number = ${schema.objectId};
680    static readonly UID_STRATEGY = '${isStandardsFirst ? 'standards-first' : 'native-only'}' as const;
681    static readonly STANDARD_PRIORITY = ${isStandardsFirst ? JSON.stringify(parentStrats) : '[]'} as const;
682
683    static readonly serializeSlim = serialize${NAME}Slim;
684    static readonly serializeExtended = serialize${NAME}Extended;
685    static readonly serializeProv = serialize${NAME}Prov;
686
687    constructor(json: CommonOrmJson | SerializedCommonOrmJson, creatorUsername?: string) {
688        super(${NAME}.type, json, creatorUsername);
689    }
690
691    /** Synchronous LRU read โ€” throws on miss (use LoadAsync to re-hydrate). */
692    static Load(id: uid, creatorUsername?: string): ${NAME} {
693        return getRecord(${NAME}.type, id, creatorUsername) as ${NAME};
694    }
695
696    static Has(id: uid, creatorUsername?: string): boolean {
697        return hasRecord(${NAME}.type, id, creatorUsername);
698    }
699
700    /** On-demand hydration from the worker after LRU eviction (SPEC ยง2.5). */
701    static async LoadAsync(id: uid, creatorUsername?: string): Promise<${NAME}> {
702        if (hasRecord(${NAME}.type, id, creatorUsername)) {
703            return getRecord(${NAME}.type, id, creatorUsername) as ${NAME};
704        }
705        const thread = PatchInstance.threadInstance;
706        if (!thread) throw new Error("worker thread not initialized โ€” call PatchInstance.initThread first");
707        const res = await thread.sendRequest({
708            cmd: WorkerCommands.GetRecord,
709            payload: { objectType: ${NAME}.type, id }
710        }) as Array<{ lang: Lang, slim?: ${NAME}SlimCols, extended?: ${NAME}ExtendedCols, prov?: ${NAME}ProvCols }> | null;
711        if (!res || res.length === 0) throw new Error(\`${NAME} \${id} not found in worker storage\`);
712        const record = New${NAME}WithId(id, creatorUsername);
713        for (const data of res) {
714            if (data.slim) record.mergeSlim(data.slim, 0, data.lang);
715            if (data.extended) record.mergeExtended(data.extended, 0, data.lang);
716            if (data.prov) record.mergeProv(data.prov, 0, data.lang);
717        }
718        return record;
719    }
720${propBlocks.join('\n')}
721${relationBlocks.join('\n')}
722${instancesApi}
723${classUIDMethods}
724
725    // Merge methods are now implemented in Orm using the schema definition.
726${schema.multipleInstances ? generateLoadInstance(schema) : ''}
727}
728${schema.multipleInstances ? instanceWrapperSource(schema) : ''}
729
730export const New${NAME}WithId = (id: uid, creatorUsername?: string): ${NAME} => {
731    if (hasRecord(${NAME}.type, id, creatorUsername)) {
732        return getRecord(${NAME}.type, id, creatorUsername) as ${NAME};
733    }
734    const record = new ${NAME}(${NAME}.createNewRequest(id), creatorUsername);
735    addRecord(${NAME}.type, record, creatorUsername);
736    return record;
737}
738`;
739}
740
741export const generateIndexSource = (schemas: ModelDefinition[]): string => {
742    const lines = [
743        `// AUTO-GENERATED by @the_library/public/models schemaGenerator โ€” run \`pnpm generate\` to rebuild.`,
744        ...schemas.map(s => `export * from "./${s.name}.ts";`),
745        ``,
746        ...schemas.map(s => `import { ${s.name} } from "./${s.name}.ts";`),
747        `export type ModelClass = { new(json: any, creatorUsername?: string): any; type: number; LoadAsync(id: number, creatorUsername?: string): Promise<any> };`,
748        `export const ModelTypes: Record<number, ModelClass> = {`,
749        ...schemas.map(s => `    [${s.objectId}]: ${s.name},`),
750        `};`
751    ];
752    return lines.join('\n') + '\n';
753}
754
755export const generateCrosswalkWorkerSource = (schemas: ModelDefinition[]): string => {
756    const maps: string[] = [];
757    const extractors: string[] = [];
758
759    // Mode DERIVED map
760    maps.push(`const derivedToInstance = new Map<string, { wireId: number; instanceIndex: number }>();`);
761
762    const entityStds = new Set<string>();
763    const instanceStds = new Set<string>();
764
765    for (const schema of schemas) {
766        const parentStrats = Array.isArray(schema.uidStrategy) ? schema.uidStrategy : (schema.uidStrategy ? [schema.uidStrategy] : []);
767        for (const std of parentStrats) if (std !== 'native-only') entityStds.add(std);
768
769        if (schema.multipleInstances) {
770            const instStrats = Array.isArray(schema.instanceUidStrategy) ? schema.instanceUidStrategy : (schema.instanceUidStrategy ? [schema.instanceUidStrategy] : []);
771            for (const std of instStrats) if (std !== 'native-only') instanceStds.add(std);
772        }
773    }
774
775    for (const std of entityStds) maps.push(`const ${std}ToEntity = new Map<string, { wireId: number }>();`);
776    for (const std of instanceStds) maps.push(`const ${std}ToInstance = new Map<string, { wireId: number; instanceIndex: number }>();`);
777
778    for (const schema of schemas) {
779        const parentStrats = Array.isArray(schema.uidStrategy) ? schema.uidStrategy : (schema.uidStrategy ? [schema.uidStrategy] : []);
780        const instStrats = schema.multipleInstances ? (Array.isArray(schema.instanceUidStrategy) ? schema.instanceUidStrategy : (schema.instanceUidStrategy ? [schema.instanceUidStrategy] : [])) : [];
781        const prefix = schema.typePrefix || 'XX';
782        const idFields = schema.instanceFields?.filter(f => (f as any).identityComponent) || [];
783
784        if (parentStrats.length === 0 && instStrats.length === 0 && idFields.length === 0) continue;
785
786        extractors.push(`
787        if (set.objectType === ${schema.objectId}) {
788            const slim = set.slim;
789            const ext = set.extended;
790            
791            // Read parent standards arrays
792            ${parentStrats.filter(s => s !== 'native-only').map(std => {
793                const pk = schema.standardPks?.[std] ?? std;
794                const colNameStr = colName(pk, 'string');
795                return `const ${std}Col = slim?.columns.get('${colNameStr}') || ext?.columns.get('${colNameStr}');`;
796            }).join('\n            ')}
797
798            for (const [id, rowIndex] of set.idIndex) {
799                // Populate parent standards
800                ${parentStrats.filter(s => s !== 'native-only').map(std => `
801                if (${std}Col && ${std}Col[rowIndex]) {
802                    ${std}ToEntity.set(\`\${lang}:\${${std}Col[rowIndex]}\`, { wireId: id });
803                }
804                `).join('')}
805
806                ${schema.multipleInstances ? `
807                const instances = ext?.instances?.[rowIndex];
808                if (instances) {
809                    // 1. Gather all source PIDs for this record
810                    const sourcePIDs: string[] = [\`\${encodePID('${prefix}', id)}\`];
811                    
812                    ${parentStrats.filter(s => s !== 'native-only').map(std => `
813                    if (${std}Col && ${std}Col[rowIndex]) sourcePIDs.push(\`${std}:\${${std}Col[rowIndex]}\`);
814                    `).join('')}
815
816                    // Add instance-level Mode S
817                    for (let i = 0; i < instances.length; i++) {
818                        ${instStrats.filter(s => s !== 'native-only').map(std => `
819                        if (instances[i]['${std}']) sourcePIDs.push(\`${std}:\${instances[i]['${std}']}\`);
820                        `).join('')}
821                    }
822
823                    // 2. Emit mappings
824                    for (let i = 0; i < instances.length; i++) {
825                        ${instStrats.filter(s => s !== 'native-only').map(std => `
826                        const ${std}Val = instances[i]['${std}'];
827                        if (${std}Val) {
828                            ${std}ToInstance.set(\`\${lang}:\${${std}Val}\`, { wireId: id, instanceIndex: i });
829                        }`).join('\n                        ')}
830
831                        ${idFields.length > 0 ? `
832                        const identityParts = [${idFields.map(f => `instances[i]['${f.name}']`).join(', ')}];
833                        if (identityParts.every(p => p != null && p !== "")) {
834                            const disc = identityParts.map(p => encodeURIComponent(String(p))).join(':');
835                            for (const src of sourcePIDs) {
836                                const sfcuid = \`DL:C:DERIVED:\${src}:\${disc}\`;
837                                derivedToInstance.set(\`\${lang}:\${sfcuid}\`, { wireId: id, instanceIndex: i });
838                            }
839                        }` : ''}
840                    }
841                }
842                ` : ''}
843            }
844        }`);
845    }
846
847    return `// AUTO-GENERATED by @the_library/public/models schemaGenerator
848import type { Lang } from "../../interfaces.ts";
849import type { SidecarSet } from "../../worker/libs/initialLoad.ts";
850import { encodePID } from "../../pid.ts";
851
852${maps.join('\n')}
853
854export function processSidecarSets(sets: SidecarSet[]) {
855    for (const set of sets) {
856        const lang = set.lang;
857${extractors.join('\n')}
858    }
859}
860
861self.onmessage = async (e: MessageEvent) => {
862    const { cmd, payload, msgId } = e.data;
863    
864    if (cmd === 'init_crosswalk' || cmd === 'load_lang_crosswalk') {
865        const sets = payload.sidecars as SidecarSet[];
866        processSidecarSets(sets);
867        self.postMessage({ msgId, status: 'registered' });
868    } else if (cmd === 'resolve_derived') {
869        const { sfcuid, lang } = payload;
870        const result = derivedToInstance.get(\`\${lang}:\${sfcuid}\`);
871        self.postMessage({ msgId, result: result || null });
872    } else if (cmd === 'resolve_standard') {
873        const { standardId, value, lang } = payload;
874        const key = \`\${lang}:\${value}\`;
875        let result: any = null;
876        switch (standardId) {
877            ${Array.from(entityStds).map(std => `case '${std}': result = ${std}ToEntity.get(key) || null; break;`).join('\n            ')}
878            ${Array.from(instanceStds).map(std => `case '${std}': result = ${std}ToInstance.get(key) || null; break;`).join('\n            ')}
879        }
880        self.postMessage({ msgId, result });
881    }
882};
883`;
884}
885
886export const generateCrosswalkClientSource = (): string => {
887    return `// AUTO-GENERATED by @the_library/public/models schemaGenerator
888import type { Lang } from "../interfaces.ts";
889import type { SidecarSet } from "../worker/libs/initialLoad.ts";
890
891export class ExternalIdCrosswalk {
892    static instance: ExternalIdCrosswalk | null = null;
893    private worker: Worker;
894    private readyPromise: Promise<void>;
895    private resolveReady!: () => void;
896    private rejectReady!: (e: Error) => void;
897    public ready = false;
898    private nextMsgId = 1;
899    private pending = new Map<number, (res: any) => void>();
900
901    constructor(workerPath = '/ExternalIdCrosswalkWorker.js') {
902        this.worker = new Worker(workerPath, { type: 'module' });
903        this.readyPromise = new Promise((resolve, reject) => { 
904            this.resolveReady = resolve; 
905            this.rejectReady = reject; 
906        });
907        ExternalIdCrosswalk.instance = this;
908        (globalThis as any).ExternalIdCrosswalk = ExternalIdCrosswalk;
909
910        this.worker.onmessage = (e) => {
911            const { msgId, result, status } = e.data;
912            if (msgId && this.pending.has(msgId)) {
913                this.pending.get(msgId)!(result ?? status);
914                this.pending.delete(msgId);
915            }
916        };
917        this.worker.onerror = (err) => {
918            console.error("[ExternalIdCrosswalk] Worker error:", err);
919            // If it crashes before ready, reject the ready promise
920            if (!this.ready) {
921                this.rejectReady(new Error("ExternalIdCrosswalk worker crashed during initialization: " + err.message));
922            }
923        };
924        this.worker.onmessageerror = (err) => {
925            console.error("[ExternalIdCrosswalk] Worker message error:", err);
926        };
927    }
928
929    async whenReady() {
930        return this.readyPromise;
931    }
932
933    private request(cmd: string, payload: any): Promise<any> {
934        return new Promise(resolve => {
935            const msgId = this.nextMsgId++;
936            this.pending.set(msgId, resolve);
937            this.worker.postMessage({ cmd, payload, msgId });
938        });
939    }
940
941    async init(sidecars: SidecarSet[]) {
942        await this.request('init_crosswalk', { sidecars });
943        this.ready = true;
944        this.resolveReady();
945    }
946
947    async loadLang(sidecars: SidecarSet[]) {
948        await this.request('load_lang_crosswalk', { sidecars });
949    }
950
951    async resolveDerived(sfcuid: string, lang: Lang) {
952        return this.request('resolve_derived', { sfcuid, lang });
953    }
954
955    async resolveForInstance(standardId: string, value: string, lang: Lang) {
956        return this.request('resolve_standard', { standardId, value, lang });
957    }
958}
959`;
960}
961
962