// The platform's products: which one registers each platform resource, and // what backs the resources that read a table or view. Both are generated // (scripts/platform-preset/generate.ts) from each product's dictionaries and // scripts at the versions the showcase server pins: GSF 8.15.29 ('genesis'), // auth 8.15.5, genesis-notify 8.15.16, reporting 8.15.3, file-server 8.15.9 // and reconciliation 8.15.4. import type { MockServerConfig, QueryDef, RequestReplyDef, ResourceItem, Row, TableDef, TypedRequestReply, ViewDef, ViewRowSources, } from '../types.ts'; import platformBacking from './platform/backing.json' with { type: 'json' }; import platformResources from './platform/resources.json' with { type: 'json' }; /** * A platform product an app may include beside GSF and auth, by its GSF * product name: Create's Notifications, Reporting, Document Management and * Reconciliation. */ export type PlatformProduct = 'genesis-notify' | 'reporting' | 'file-server' | 'reconciliation'; export const PLATFORM_PRODUCTS: readonly PlatformProduct[] = [ 'genesis-notify', 'reporting', 'file-server', 'reconciliation', ]; // In every app. const ALWAYS = ['genesis', 'auth']; // backing.json's entries: the engine's definitions, plus where they come from. interface Provenance { product: string; // The script and line that declares it. script: string; // The tables it reads, through its view if it has one. reads: string[]; // Script blocks the mock doesn't model (row filters, permissioning, a // typed request server's Kotlin reply). unmodelled?: string[]; } interface Backing { tables: Record; views: Record< string, Omit & { dictionary: string; derivedTypes?: Record; } >; queries: Record; requestReplies: Record; // An ALIASED_ENUM's keys by TABLE.FIELD (its values are the labels), and a // RAW field's format by field: what GSF's dictionary reports beside the // FieldDefs. aliasedEnums: Record; fieldFormats: Record; // The products' typed request servers, by name: their META as GSF builds // it from the pinned jars, and their classes' fields. typedRequestReplies: Record; } const backing = platformBacking as unknown as Backing; type AttributedResource = ResourceItem & { PRODUCT: string }; const attributed = platformResources as AttributedResource[]; const productByKey = new Map( attributed.map((resource) => [ `${resource.RESOURCE_TYPE}:${resource.RESOURCE_NAME}`, resource.PRODUCT, ]), ); // The products a preset serves: GSF, auth and the chosen ones (all of them // when none are given). export function includedProducts(products?: readonly string[]): Set { for (const product of products ?? []) { if (!PLATFORM_PRODUCTS.includes(product as PlatformProduct)) { throw new Error( `Unknown platform product '${product}' (one of ${PLATFORM_PRODUCTS.join(', ')}; GSF and auth are always in)`, ); } } return new Set([...ALWAYS, ...(products ?? PLATFORM_PRODUCTS)]); } // The product that registers a platform resource, if it is one. export function productOf(type: ResourceItem['RESOURCE_TYPE'], name: string): string | undefined { return productByKey.get(`${type}:${name}`); } // The platform resources the included products register, as the real server // lists them. export function productResources(included: Set): ResourceItem[] { return attributed .filter((resource) => included.has(resource.PRODUCT)) .map(({ RESOURCE_NAME, RESOURCE_TYPE }) => ({ RESOURCE_NAME, RESOURCE_TYPE })); } // Keeps the entries of a preset's own record whose resource an included // product registers (or that isn't a platform resource at all). export function ofIncluded( record: Record, type: ResourceItem['RESOURCE_TYPE'], included: Set, ): Record { return Object.fromEntries( Object.entries(record).filter(([name]) => { const product = productOf(type, name); return product === undefined || included.has(product); }), ); } // 'genesis-notify@8.15.16' -> 'genesis-notify'. const productOfOrigin = (origin: string) => origin.slice(0, origin.lastIndexOf('@')); // The products' derived view fields, from each view dictionary's // derivedField { withInput(...) { ... } } in the product's config jar. GSF's // TIMESTAMP `shr 22` is epoch millis, which the mock's TIMESTAMP already is. type Derive = (row: Row, sources: ViewRowSources) => unknown; const input = (sources: ViewRowSources, table: string, field: string) => sources.tables[table]?.[field] ?? null; const DERIVED: Record> = { // genesis-notify-view-dictionary.kts:117: timestamp!!.shr(22) DYNAMIC_RULE_VIEW: { DATETIME: (_row, s) => input(s, 'DYNAMIC_RULE', 'TIMESTAMP') }, // reporting-view-dictionary.kts:42-64 GENERATED_REPORT_VIEW: { LAST_UPDATED_AT: (_row, s) => input(s, 'GENERATED_REPORT', 'TIMESTAMP'), FILE_NAME: (_row, s) => { const name = input(s, 'GENERATED_REPORT', 'FILE_NAME'); // Status.GENERATED, whose label the column holds. if (input(s, 'GENERATED_REPORT', 'STATUS') !== 'Generated') return name; const configName = input(s, 'REPORT_CONFIG', 'FILE_NAME'); const format = input(s, 'REPORT_CONFIG', 'OUTPUT_FORMAT'); return ( name ?? (configName !== null && format !== null ? `${configName}.${String(format).toLowerCase()}` : null) ); }, }, // reconciliation-view-dictionary.kts:30-38: the start, else the timestamp. RECONCILIATION_VIEW: { DATETIME: (_row, s) => { const start = input(s, 'RECONCILIATION', 'START_TIMESTAMP'); return start === null || start === 0 ? input(s, 'RECONCILIATION', 'TIMESTAMP') : start; }, }, // :59-63: the comment's timestamp. RECONCILIATION_RECORD_RESULT_VIEW: { DATETIME: (_row, s) => input(s, 'RECONCILIATION_RECORD_RESULT_COMMENT', 'TIMESTAMP'), }, }; // A derived field the list above doesn't know is declared, and null. const unmodelledValue = () => null; // A platform table as its product's dictionary declares it (fields, key, // indexes, sequence, audit), a fresh copy, for the preset to fill. export function platformTable(name: string): TableDef { const { dictionary: _dictionary, ...table } = structuredClone(backing.tables[name]); return table; } export const isPlatformTable = (name: string) => Object.hasOwn(backing.tables, name); // An ALIASED_ENUM column's keys, in its labels' order; undefined for any // other column. export const aliasedEnumKeys = (table: string, field: string): string[] | undefined => backing.aliasedEnums[`${table}.${field}`]; // A platform RAW field's format (BASE_64, UTF_8), if it has one. export const fieldFormat = (field: string): string | undefined => backing.fieldFormats[field]; // A product's typed request server (requestReply), a fresh copy, if // `name` is one. export const typedRequestReply = (name: string): TypedRequestReply | undefined => Object.hasOwn(backing.typedRequestReplies, name) ? structuredClone(backing.typedRequestReplies[name]) : undefined; const AUDIT_SUFFIX = '_AUDIT'; /** * The generated backing for the included products, a fresh copy each call: * their queries and request servers over a table or view, those tables * (empty, or holding the product's own seed rows) and their views, read or * not, as the real dictionary holds them all. A table in `presetTables` is * the caller's, and isn't repeated here. */ export function productBacking( included: Set, presetTables: Record, ): Pick { // Every table of an included product, which its event handlers write; a // query or request server reads some. An audit table is the engine's to // derive from its table's `audit`. const tables = new Set( Object.entries(backing.tables) .filter(([, table]) => included.has(productOfOrigin(table.dictionary))) .map(([name]) => name), ); // A table the app has: a preset or product one, or the audit table the // engine derives from an audited one. const definitionOf = (name: string) => presetTables[name] ?? (tables.has(name) ? backing.tables[name] : undefined); const hasTable = (name: string) => definitionOf(name) !== undefined || (name.endsWith(AUDIT_SUFFIX) && Boolean(definitionOf(name.slice(0, -AUDIT_SUFFIX.length))?.audit)); const views = new Set( Object.entries(backing.views) .filter( ([, view]) => included.has(productOfOrigin(view.dictionary)) && [view.base, ...(view.joins ?? []).map(({ table }) => table)].every(hasTable), ) .map(([name]) => name), ); const pick = (entries: Record) => Object.fromEntries( Object.entries(entries).flatMap(([name, entry]) => { const { product, script: _script, reads: _reads, unmodelled: _unmodelled, ...definition } = structuredClone(entry); if (!included.has(product)) return []; const { source } = definition as { source?: string }; // A source the app can't build is never served, should a product // read another's view or table. if (source && source in backing.views) { const view = backing.views[source]; if (![view.base, ...(view.joins ?? []).map(({ table }) => table)].every(hasTable)) { return []; } views.add(source); } else if (source && !hasTable(source)) { return []; } return [[name, definition]]; }), ); const queries = pick(backing.queries) as Record; const requestReplies = pick(backing.requestReplies) as Record; return { tables: Object.fromEntries( [...tables] .filter((name) => !(name in presetTables)) .map((name) => [name, platformTable(name)]), ), views: Object.fromEntries( [...views].map((name) => { const { dictionary: _dictionary, derivedTypes, ...view } = structuredClone(backing.views[name]); const derived = derivedTypes && { derivedTypes: derivedTypes as ViewDef['derivedTypes'], derived: Object.fromEntries( Object.keys(derivedTypes).map((field) => [ field, DERIVED[name]?.[field] ?? unmodelledValue, ]), ), }; return [name, { ...view, ...derived }]; }), ), queries, requestReplies, }; }