@yoyoboot/yoyo-admin-schema 10.5.5

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.
Files changed (49) hide show
  1. package/dist/action-contract.d.ts +50 -0
  2. package/dist/action-contract.d.ts.map +1 -0
  3. package/dist/action-contract.js +185 -0
  4. package/dist/action-registry.d.ts +12 -0
  5. package/dist/action-registry.d.ts.map +1 -0
  6. package/dist/action-registry.js +23 -0
  7. package/dist/compiler.d.ts +17 -0
  8. package/dist/compiler.d.ts.map +1 -0
  9. package/dist/compiler.js +1300 -0
  10. package/dist/contract-drift.d.ts +18 -0
  11. package/dist/contract-drift.d.ts.map +1 -0
  12. package/dist/contract-drift.js +32 -0
  13. package/dist/diagnostics.d.ts +19 -0
  14. package/dist/diagnostics.d.ts.map +1 -0
  15. package/dist/diagnostics.js +8 -0
  16. package/dist/extension-registry.d.ts +13 -0
  17. package/dist/extension-registry.d.ts.map +1 -0
  18. package/dist/extension-registry.js +23 -0
  19. package/dist/field-registry.d.ts +12 -0
  20. package/dist/field-registry.d.ts.map +1 -0
  21. package/dist/field-registry.js +30 -0
  22. package/dist/generated/action-contract/action-contract-keys.d.ts +3 -0
  23. package/dist/generated/action-contract/action-contract-keys.d.ts.map +1 -0
  24. package/dist/generated/action-contract/action-contract-keys.js +50 -0
  25. package/dist/generated/action-contract/index.d.ts +534 -0
  26. package/dist/generated/action-contract/index.d.ts.map +1 -0
  27. package/dist/generated/action-contract/index.js +579 -0
  28. package/dist/generated-validation-contract.d.ts +36 -0
  29. package/dist/generated-validation-contract.d.ts.map +1 -0
  30. package/dist/generated-validation-contract.js +124 -0
  31. package/dist/index.d.ts +15 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +14 -0
  34. package/dist/page-contract-linking.d.ts +36 -0
  35. package/dist/page-contract-linking.d.ts.map +1 -0
  36. package/dist/page-contract-linking.js +78 -0
  37. package/dist/project-check.d.ts +34 -0
  38. package/dist/project-check.d.ts.map +1 -0
  39. package/dist/project-check.js +151 -0
  40. package/dist/registry-diagnostics.d.ts +15 -0
  41. package/dist/registry-diagnostics.d.ts.map +1 -0
  42. package/dist/registry-diagnostics.js +41 -0
  43. package/dist/renderer-registry.d.ts +22 -0
  44. package/dist/renderer-registry.d.ts.map +1 -0
  45. package/dist/renderer-registry.js +33 -0
  46. package/dist/types.d.ts +290 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +49 -0
  49. package/package.json +30 -0
@@ -0,0 +1,124 @@
1
+ export const YOYO_GENERATED_VALIDATION_CONTRACT_SCHEMA_VERSION = '1.0.0';
2
+ export const YOYO_GENERATED_FORM_MODES = ['create', 'update'];
3
+ export function linkYoyoGeneratedValidationContract(catalog, operationId, formMode) {
4
+ const contract = catalog.contracts.find((candidate) => candidate.operationId === operationId && candidate.formMode === formMode);
5
+ if (!contract)
6
+ return undefined;
7
+ return {
8
+ ...contract,
9
+ catalogSchemaVersion: catalog.schemaVersion,
10
+ catalogGeneratorVersion: catalog.generatorVersion,
11
+ catalogSourceHash: catalog.sourceHash,
12
+ };
13
+ }
14
+ export function isYoyoGeneratedValidationContractSnapshotCompatible(linked, catalog) {
15
+ const current = linkYoyoGeneratedValidationContract(catalog, linked.operationId, linked.formMode);
16
+ return Boolean(current)
17
+ && current.contractKey === linked.contractKey
18
+ && current.inputPath === linked.inputPath
19
+ && sameStringArray(current.fieldPaths, linked.fieldPaths)
20
+ && current.schemaRef === linked.schemaRef
21
+ && current.schemaVersion === linked.schemaVersion
22
+ && current.generatorVersion === linked.generatorVersion
23
+ && current.sourceHash === linked.sourceHash
24
+ && linked.catalogSchemaVersion === catalog.schemaVersion
25
+ && linked.catalogGeneratorVersion === catalog.generatorVersion
26
+ && linked.catalogSourceHash === catalog.sourceHash;
27
+ }
28
+ export function diagnoseYoyoGeneratedValidationContractSnapshot(linked, catalog, executableContractKeys, path = 'validationContract') {
29
+ if (!linked || !catalog) {
30
+ return [{
31
+ code: 'YVAL003',
32
+ path,
33
+ severity: 'blocking',
34
+ guidance: 'Bind the current generated validation contract catalog before executing a form mutation.',
35
+ message: 'The generated validation contract snapshot is unavailable.',
36
+ }];
37
+ }
38
+ if (!isYoyoGeneratedValidationContractSnapshotCompatible(linked, catalog)) {
39
+ return [{
40
+ code: 'YVAL004',
41
+ path,
42
+ severity: 'blocking',
43
+ guidance: 'Regenerate and bind the validation contract catalog from the same OpenAPI snapshot used to compile the page.',
44
+ message: `Generated validation contract '${linked.contractKey}' is stale or incompatible with the current catalog snapshot.`,
45
+ }];
46
+ }
47
+ if (!executableContractKeys?.includes(linked.contractKey)) {
48
+ return [{
49
+ code: 'YVAL006',
50
+ path,
51
+ severity: 'blocking',
52
+ guidance: 'Register the executable generated validator that matches the compiled validation contract key.',
53
+ message: `Generated validation contract '${linked.contractKey}' has no executable validator.`,
54
+ }];
55
+ }
56
+ return [];
57
+ }
58
+ export function validateYoyoGeneratedValidationContractCatalog(catalog) {
59
+ const diagnostics = [];
60
+ if (!catalog.schemaVersion || !catalog.generatorVersion || !catalog.sourceHash) {
61
+ diagnostics.push({
62
+ code: 'YVAL001',
63
+ path: 'validationContracts',
64
+ severity: 'blocking',
65
+ guidance: 'Regenerate the OpenAPI validation contract catalog with complete provenance.',
66
+ message: 'Generated validation contract catalog provenance is incomplete.',
67
+ });
68
+ }
69
+ if (catalog.schemaVersion !== YOYO_GENERATED_VALIDATION_CONTRACT_SCHEMA_VERSION) {
70
+ diagnostics.push({
71
+ code: 'YVAL001',
72
+ path: 'validationContracts.schemaVersion',
73
+ severity: 'blocking',
74
+ guidance: `Regenerate the validation contract catalog with schemaVersion ${YOYO_GENERATED_VALIDATION_CONTRACT_SCHEMA_VERSION}.`,
75
+ message: `Generated validation contract catalog schemaVersion '${catalog.schemaVersion}' is incompatible.`,
76
+ });
77
+ }
78
+ const keys = new Set();
79
+ const operationModes = new Set();
80
+ for (const [index, contract] of catalog.contracts.entries()) {
81
+ const path = `validationContracts.contracts[${index}]`;
82
+ const operationMode = `${contract.operationId}:${contract.formMode}`;
83
+ if (keys.has(contract.contractKey) || operationModes.has(operationMode)) {
84
+ diagnostics.push({
85
+ code: 'YVAL002',
86
+ path: `${path}.contractKey`,
87
+ severity: 'blocking',
88
+ guidance: 'Generate exactly one validation branch for each operation and form mode.',
89
+ message: `Generated validation contract '${contract.contractKey}' duplicates an operation/form-mode branch.`,
90
+ });
91
+ }
92
+ keys.add(contract.contractKey);
93
+ operationModes.add(operationMode);
94
+ const fieldPaths = contract.fieldPaths;
95
+ if (!contract.contractKey || !contract.operationId || typeof contract.inputPath !== 'string' || !contract.schemaRef
96
+ || !contract.sourceHash || !contract.generatorVersion || !contract.schemaVersion
97
+ || !Array.isArray(fieldPaths)
98
+ || !fieldPaths.every((fieldPath) => typeof fieldPath === 'string')
99
+ || !YOYO_GENERATED_FORM_MODES.includes(contract.formMode)) {
100
+ diagnostics.push({
101
+ code: 'YVAL001',
102
+ path,
103
+ severity: 'blocking',
104
+ guidance: 'Regenerate the OpenAPI validation contract branch with complete supported metadata.',
105
+ message: `Generated validation contract '${contract.contractKey || '<unknown>'}' is incomplete or unsupported.`,
106
+ });
107
+ }
108
+ if (contract.schemaVersion !== catalog.schemaVersion
109
+ || contract.sourceHash !== catalog.sourceHash
110
+ || contract.generatorVersion !== catalog.generatorVersion) {
111
+ diagnostics.push({
112
+ code: 'YVAL004',
113
+ path,
114
+ severity: 'blocking',
115
+ guidance: 'Regenerate the validation contract catalog and all branch validators from the same OpenAPI snapshot.',
116
+ message: `Generated validation contract '${contract.contractKey}' provenance drifted from its catalog.`,
117
+ });
118
+ }
119
+ }
120
+ return diagnostics;
121
+ }
122
+ function sameStringArray(left, right) {
123
+ return left.length === right.length && left.every((value, index) => value === right[index]);
124
+ }
@@ -0,0 +1,15 @@
1
+ export * from './compiler.js';
2
+ export * from './diagnostics.js';
3
+ export * from './action-registry.js';
4
+ export * from './action-contract.js';
5
+ export * from './generated-validation-contract.js';
6
+ export * from './generated/action-contract/index.js';
7
+ export * from './contract-drift.js';
8
+ export * from './extension-registry.js';
9
+ export * from './field-registry.js';
10
+ export * from './project-check.js';
11
+ export * from './page-contract-linking.js';
12
+ export * from './renderer-registry.js';
13
+ export * from './registry-diagnostics.js';
14
+ export * from './types.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,2BAA2B,CAAA;AACzC,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export * from './compiler.js';
2
+ export * from './diagnostics.js';
3
+ export * from './action-registry.js';
4
+ export * from './action-contract.js';
5
+ export * from './generated-validation-contract.js';
6
+ export * from './generated/action-contract/index.js';
7
+ export * from './contract-drift.js';
8
+ export * from './extension-registry.js';
9
+ export * from './field-registry.js';
10
+ export * from './project-check.js';
11
+ export * from './page-contract-linking.js';
12
+ export * from './renderer-registry.js';
13
+ export * from './registry-diagnostics.js';
14
+ export * from './types.js';
@@ -0,0 +1,36 @@
1
+ import { type YoyoActionContractCapabilityRegistry, type YoyoActionContractCatalog } from './action-contract.js';
2
+ import { type YoyoGeneratedValidationContractCatalog } from './generated-validation-contract.js';
3
+ import type { YoyoPageIR } from './types.js';
4
+ /**
5
+ * Serializable shape of the runtime adapter facts needed for static linking.
6
+ * The adapter itself remains owned by the host application.
7
+ */
8
+ export interface YoyoDatasourceOperationContract {
9
+ readonly id: string;
10
+ readonly operations: readonly string[];
11
+ }
12
+ export interface YoyoPageContractLinkingInput {
13
+ readonly page: YoyoPageIR;
14
+ readonly datasource: YoyoDatasourceOperationContract;
15
+ readonly actionContracts: YoyoActionContractCatalog;
16
+ readonly validationContracts: YoyoGeneratedValidationContractCatalog;
17
+ readonly validationContractExecutables: readonly string[];
18
+ readonly capabilities?: YoyoActionContractCapabilityRegistry;
19
+ }
20
+ export interface YoyoPageContractLinkingResult {
21
+ readonly pageKey: string;
22
+ readonly datasource: YoyoDatasourceOperationContract;
23
+ readonly fields: readonly string[];
24
+ readonly actionKeys: readonly string[];
25
+ readonly sourceFingerprint: {
26
+ readonly actionCatalog: string;
27
+ readonly validationCatalog: string;
28
+ };
29
+ }
30
+ /**
31
+ * Verifies the host-provided datasource operation surface against compiled IR
32
+ * and current generated snapshots. This is pure linking: it never invokes a
33
+ * datasource, renders a page, or receives transport/DTO values.
34
+ */
35
+ export declare function linkYoyoPageContract(input: YoyoPageContractLinkingInput): YoyoPageContractLinkingResult;
36
+ //# sourceMappingURL=page-contract-linking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-contract-linking.d.ts","sourceRoot":"","sources":["../src/page-contract-linking.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oCAAoC,EACzC,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAGL,KAAK,sCAAsC,EAC5C,MAAM,oCAAoC,CAAA;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;GAGG;AACH,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,+BAA+B,CAAA;IACpD,QAAQ,CAAC,eAAe,EAAE,yBAAyB,CAAA;IACnD,QAAQ,CAAC,mBAAmB,EAAE,sCAAsC,CAAA;IACpE,QAAQ,CAAC,6BAA6B,EAAE,SAAS,MAAM,EAAE,CAAA;IACzD,QAAQ,CAAC,YAAY,CAAC,EAAE,oCAAoC,CAAA;CAC7D;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,+BAA+B,CAAA;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,QAAQ,CAAC,iBAAiB,EAAE;QAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;QAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;KACnC,CAAA;CACF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,4BAA4B,GAAG,6BAA6B,CAmFvG"}
@@ -0,0 +1,78 @@
1
+ import { diagnoseYoyoActionContractSnapshot, operationForYoyoActionKind, validateYoyoActionContractCatalog, } from './action-contract.js';
2
+ import { diagnoseYoyoGeneratedValidationContractSnapshot, validateYoyoGeneratedValidationContractCatalog, } from './generated-validation-contract.js';
3
+ import { YoyoSchemaValidationError } from './diagnostics.js';
4
+ /**
5
+ * Verifies the host-provided datasource operation surface against compiled IR
6
+ * and current generated snapshots. This is pure linking: it never invokes a
7
+ * datasource, renders a page, or receives transport/DTO values.
8
+ */
9
+ export function linkYoyoPageContract(input) {
10
+ const { page, datasource } = input;
11
+ const diagnostics = [
12
+ ...validateYoyoActionContractCatalog(input.actionContracts, input.capabilities),
13
+ ...validateYoyoGeneratedValidationContractCatalog(input.validationContracts),
14
+ ];
15
+ if (datasource.id !== page.query.datasource) {
16
+ diagnostics.push(linkDiagnostic('YLNK001', page, 'query.datasource', `Compiled datasource '${page.query.datasource}' does not match adapter '${datasource.id}'.`, 'Bind the production typed datasource that declares the compiled datasource id.'));
17
+ }
18
+ if (!datasource.operations.includes('query')) {
19
+ diagnostics.push(linkDiagnostic('YLNK002', page, 'datasource.operations', 'The typed datasource is missing its required query operation.', 'Expose query from the production typed datasource adapter.'));
20
+ }
21
+ for (const [index, action] of page.actions.entries()) {
22
+ const path = `actions[${index}]`;
23
+ const datasourceOperation = datasourceOperationForYoyoActionKind(action.kind);
24
+ if (datasourceOperation && !datasource.operations.includes(datasourceOperation)) {
25
+ diagnostics.push(linkDiagnostic('YLNK003', page, path, `Action '${action.key}' requires unavailable datasource operation '${datasourceOperation}'.`, 'Expose the required operation from the production typed datasource adapter.'));
26
+ }
27
+ if (!operationForYoyoActionKind(action.kind))
28
+ continue;
29
+ if (action.contract?.permission
30
+ && input.capabilities?.permissions
31
+ && !input.capabilities.permissions.includes(action.contract.permission)) {
32
+ diagnostics.push(linkDiagnostic('YLNK004', page, `${path}.permission`, `Action '${action.key}' references unknown permission '${action.contract.permission}'.`, 'Register the permission in the capability catalog before linking the action contract.'));
33
+ }
34
+ diagnostics.push(...diagnoseYoyoActionContractSnapshot(action.contract, input.actionContracts, `${path}.contractKey`));
35
+ if ((action.kind === 'create' || action.kind === 'edit') && action.contract?.validationOperationId) {
36
+ diagnostics.push(...diagnoseYoyoGeneratedValidationContractSnapshot(action.validation, input.validationContracts, input.validationContractExecutables, `${path}.contractKey`));
37
+ }
38
+ }
39
+ if (diagnostics.length > 0) {
40
+ throw new YoyoSchemaValidationError(diagnostics);
41
+ }
42
+ return {
43
+ pageKey: page.pageKey,
44
+ datasource: {
45
+ id: datasource.id,
46
+ operations: [...datasource.operations],
47
+ },
48
+ fields: [
49
+ ...page.query.fields.map((field) => field.key),
50
+ ...(page.table?.columns.map((column) => column.key) ?? []),
51
+ ...(page.form?.fields.map((field) => field.key) ?? []),
52
+ ],
53
+ actionKeys: page.actions.map((action) => action.key),
54
+ sourceFingerprint: {
55
+ actionCatalog: input.actionContracts.sourceHash,
56
+ validationCatalog: input.validationContracts.sourceHash,
57
+ },
58
+ };
59
+ }
60
+ function datasourceOperationForYoyoActionKind(kind) {
61
+ switch (kind) {
62
+ case 'create': return 'create';
63
+ case 'edit': return 'update';
64
+ case 'delete': return 'remove';
65
+ case 'disable': return 'setEnabled';
66
+ default: return undefined;
67
+ }
68
+ }
69
+ function linkDiagnostic(code, page, path, message, guidance) {
70
+ return {
71
+ code,
72
+ path,
73
+ severity: 'blocking',
74
+ source: page.source,
75
+ guidance,
76
+ message,
77
+ };
78
+ }
@@ -0,0 +1,34 @@
1
+ import { type YoyoCompileResult } from './compiler.js';
2
+ import { type YoyoContractDriftInput } from './contract-drift.js';
3
+ import type { YoyoSchemaCompileContext, YoyoSourceLocation } from './types.js';
4
+ export interface YoyoProjectSchemaEntry {
5
+ source: Required<Pick<YoyoSourceLocation, 'path' | 'line' | 'column'>>;
6
+ schema: unknown;
7
+ context: Omit<YoyoSchemaCompileContext, 'source'>;
8
+ drift: YoyoContractDriftInput;
9
+ /**
10
+ * Raw source text of the schema file, supplied by the host that discovered it.
11
+ * admin-schema is host-independent and never touches the filesystem itself;
12
+ * when omitted, source boundary scanning is skipped for this entry.
13
+ */
14
+ sourceText?: string;
15
+ }
16
+ export interface YoyoProjectSchemaException {
17
+ sourcePath: string;
18
+ owner: string;
19
+ reason: string;
20
+ exitCondition: string;
21
+ }
22
+ export interface YoyoProjectSchemaCheckInput {
23
+ discoveredSources: readonly string[];
24
+ schemas: readonly YoyoProjectSchemaEntry[];
25
+ exceptions: readonly YoyoProjectSchemaException[];
26
+ }
27
+ export interface YoyoProjectSchemaCheckResult {
28
+ compiled: readonly YoyoCompileResult[];
29
+ handoff: {
30
+ schemaSources: readonly string[];
31
+ };
32
+ }
33
+ export declare function checkYoyoSchemaProject(input: YoyoProjectSchemaCheckInput): YoyoProjectSchemaCheckResult;
34
+ //# sourceMappingURL=project-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-check.d.ts","sourceRoot":"","sources":["../src/project-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAE5F,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE9E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAA;IACtE,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,QAAQ,CAAC,CAAA;IACjD,KAAK,EAAE,sBAAsB,CAAA;IAC7B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,OAAO,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC1C,UAAU,EAAE,SAAS,0BAA0B,EAAE,CAAA;CAClD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;IACtC,OAAO,EAAE;QACP,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;KACjC,CAAA;CACF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,4BAA4B,CA4FvG"}
@@ -0,0 +1,151 @@
1
+ import { compileYoyoPageSchema } from './compiler.js';
2
+ import { diagnoseYoyoContractDrift } from './contract-drift.js';
3
+ import { YoyoSchemaValidationError } from './diagnostics.js';
4
+ export function checkYoyoSchemaProject(input) {
5
+ const diagnostics = [];
6
+ const discoveredSources = uniqueSorted(input.discoveredSources);
7
+ const schemaEntries = new Map();
8
+ const exceptions = new Map();
9
+ for (const entry of input.schemas) {
10
+ if (schemaEntries.has(entry.source.path)) {
11
+ diagnostics.push(projectDiagnostic('YPRJ001', entry.source.path, 'A project schema source may only have one registered entry.', entry.source));
12
+ continue;
13
+ }
14
+ schemaEntries.set(entry.source.path, entry);
15
+ }
16
+ for (const exception of input.exceptions) {
17
+ if (exceptions.has(exception.sourcePath)) {
18
+ diagnostics.push(projectDiagnostic('YPRJ001', exception.sourcePath, 'A project schema source may only have one exception.', { path: exception.sourcePath, line: 1, column: 1 }));
19
+ continue;
20
+ }
21
+ exceptions.set(exception.sourcePath, exception);
22
+ if (!exception.owner.trim() || !exception.reason.trim() || !exception.exitCondition.trim()) {
23
+ diagnostics.push(projectDiagnostic('YPRJ003', exception.sourcePath, 'Schema exceptions require owner, reason, and exit condition.', { path: exception.sourcePath, line: 1, column: 1 }));
24
+ }
25
+ }
26
+ for (const [sourcePath, entry] of schemaEntries) {
27
+ if (!discoveredSources.includes(sourcePath)) {
28
+ diagnostics.push(projectDiagnostic('YPRJ001', sourcePath, 'Registered schema source was not discovered by the project scan.', entry.source));
29
+ }
30
+ if (exceptions.has(sourcePath)) {
31
+ diagnostics.push(projectDiagnostic('YPRJ004', sourcePath, 'A source cannot be both compiled and excepted.', entry.source));
32
+ }
33
+ diagnostics.push(...scanBoundaryChecks(sourcePath, entry.sourceText ?? '', entry.source));
34
+ }
35
+ for (const [sourcePath, exception] of exceptions) {
36
+ if (!discoveredSources.includes(sourcePath)) {
37
+ diagnostics.push(projectDiagnostic('YPRJ001', sourcePath, 'Registered schema exception was not discovered by the project scan.', { path: sourcePath, line: 1, column: 1 }));
38
+ }
39
+ if (!exception.owner.trim() || !exception.reason.trim() || !exception.exitCondition.trim()) {
40
+ continue;
41
+ }
42
+ }
43
+ for (const sourcePath of discoveredSources) {
44
+ if (!schemaEntries.has(sourcePath) && !exceptions.has(sourcePath)) {
45
+ diagnostics.push(projectDiagnostic('YPRJ002', sourcePath, 'Discovered schema source is neither compiled nor covered by a complete exception.', { path: sourcePath, line: 1, column: 1 }));
46
+ }
47
+ }
48
+ const compiled = [];
49
+ for (const entry of schemaEntries.values()) {
50
+ try {
51
+ const result = compileYoyoPageSchema(entry.schema, {
52
+ mode: 'project',
53
+ context: {
54
+ ...entry.context,
55
+ source: entry.source,
56
+ },
57
+ });
58
+ compiled.push(result);
59
+ }
60
+ catch (error) {
61
+ if (error instanceof YoyoSchemaValidationError) {
62
+ diagnostics.push(...error.diagnostics);
63
+ continue;
64
+ }
65
+ throw error;
66
+ }
67
+ for (const drift of diagnoseYoyoContractDrift(entry.drift)) {
68
+ diagnostics.push({
69
+ code: drift.code,
70
+ path: drift.path,
71
+ severity: 'blocking',
72
+ source: entry.source,
73
+ guidance: 'Update the Schema, datasource binding, catalog, DTO mapping, or registry so the project contract is exact.',
74
+ message: drift.message,
75
+ });
76
+ }
77
+ }
78
+ if (diagnostics.length > 0) {
79
+ throw new YoyoSchemaValidationError(diagnostics);
80
+ }
81
+ return {
82
+ compiled,
83
+ handoff: {
84
+ schemaSources: [...schemaEntries.keys()].sort(),
85
+ },
86
+ };
87
+ }
88
+ function projectDiagnostic(code, path, message, source) {
89
+ return {
90
+ code,
91
+ path,
92
+ severity: 'blocking',
93
+ source,
94
+ guidance: 'Register and compile the source, or add a complete temporary exception with an owner and exit condition.',
95
+ message,
96
+ };
97
+ }
98
+ function scanBoundaryChecks(sourcePath, source, location) {
99
+ const normalized = sourcePath.replaceAll('\\', '/');
100
+ const diagnostics = [];
101
+ for (const rule of boundaryRules) {
102
+ const match = source.match(rule.pattern);
103
+ if (!match || match.index === undefined)
104
+ continue;
105
+ const sourceLocation = sourceLocationFromOffset(source, match.index);
106
+ diagnostics.push({
107
+ code: 'YPRJ005',
108
+ path: normalized,
109
+ severity: 'blocking',
110
+ source: {
111
+ path: location.path,
112
+ line: sourceLocation.line,
113
+ column: sourceLocation.column,
114
+ },
115
+ guidance: rule.guidance,
116
+ message: `Source boundary rule '${rule.label}' was violated in '${normalized}'. ${rule.message}`,
117
+ });
118
+ }
119
+ return diagnostics;
120
+ }
121
+ function sourceLocationFromOffset(source, offset) {
122
+ const clampedOffset = Math.max(0, Math.min(offset, source.length));
123
+ const normalized = source.slice(0, clampedOffset).replace(/\r\n/g, '\n');
124
+ const lines = normalized.split('\n');
125
+ const line = lines.length;
126
+ const column = (lines.at(-1)?.length ?? 0) + 1;
127
+ return { line, column };
128
+ }
129
+ const boundaryRules = [
130
+ {
131
+ label: 'handwritten-validation-validator',
132
+ pattern: /\bimport\b\s+(?:type\s+)?[^\n;]*?\bfrom\s+["'][^"']*\bzod\b[^"']*["']|\brequire\(\s*["'][^"']*\bzod\b[^"']*["']\s*\)/i,
133
+ message: 'Handwritten validation validator was referenced. Validation must come from generated validation contracts.',
134
+ guidance: 'Move rule definitions to backend validation contracts and compile the generated validation contract.',
135
+ },
136
+ {
137
+ label: 'tanstack-validation-callback',
138
+ pattern: /@tanstack\/(?:vue-form|form-core)|createYoyoTanstackForm|mapZodErrorToFieldErrors/i,
139
+ message: 'A TanStack/zod callback flow was referenced in the feature boundary.',
140
+ guidance: 'Render with schema primitives and do not introduce feature-local form validation callbacks.',
141
+ },
142
+ {
143
+ label: 'handwritten-boundary-callback',
144
+ pattern: /\b(?:interface|type)\s+\w*Dto\b|\bfunction\s+validate[A-Z]\w*\b|\bonValidate\b|\bvalidationPort\s*:\s*\{|\bthen\(\s*\(?\s*[\w$]+\s*=>/i,
145
+ message: 'A free validation/callback path was detected. Use compiled validation contracts and the page host seam only.',
146
+ guidance: 'Drop custom callback wiring and bind to the canonical `validation` contract reference.',
147
+ },
148
+ ];
149
+ function uniqueSorted(values) {
150
+ return [...new Set(values)].sort();
151
+ }
@@ -0,0 +1,15 @@
1
+ export interface YoyoRegistryGraphEntry {
2
+ key: string;
3
+ dependencies?: readonly string[];
4
+ slot?: string;
5
+ }
6
+ export interface YoyoRegistryGraphInput {
7
+ entries: readonly YoyoRegistryGraphEntry[];
8
+ }
9
+ export interface YoyoRegistryDiagnostic {
10
+ code: 'YREG002' | 'YREG003' | 'YREG004' | 'YREG005';
11
+ path: string;
12
+ message: string;
13
+ }
14
+ export declare function validateYoyoRegistryGraph(input: YoyoRegistryGraphInput): readonly YoyoRegistryDiagnostic[];
15
+ //# sourceMappingURL=registry-diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-diagnostics.d.ts","sourceRoot":"","sources":["../src/registry-diagnostics.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,SAAS,sBAAsB,EAAE,CAAA;CAC3C;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,SAAS,sBAAsB,EAAE,CAuC1G"}
@@ -0,0 +1,41 @@
1
+ import { YOYO_EXTENSION_SLOTS } from './extension-registry.js';
2
+ export function validateYoyoRegistryGraph(input) {
3
+ const diagnostics = [];
4
+ const entries = new Map();
5
+ for (const [index, entry] of input.entries.entries()) {
6
+ if (entries.has(entry.key)) {
7
+ diagnostics.push({ code: 'YREG002', path: `entries[${index}].key`, message: `Registry key '${entry.key}' is duplicated.` });
8
+ continue;
9
+ }
10
+ entries.set(entry.key, entry);
11
+ if (entry.slot && !YOYO_EXTENSION_SLOTS.includes(entry.slot)) {
12
+ diagnostics.push({ code: 'YREG005', path: `entries[${index}].slot`, message: `Extension slot '${entry.slot}' is invalid.` });
13
+ }
14
+ }
15
+ for (const [index, entry] of input.entries.entries()) {
16
+ for (const [dependencyIndex, dependency] of (entry.dependencies ?? []).entries()) {
17
+ if (!entries.has(dependency))
18
+ diagnostics.push({ code: 'YREG003', path: `entries[${index}].dependencies[${dependencyIndex}]`, message: `Registry dependency '${dependency}' is unknown.` });
19
+ }
20
+ }
21
+ const visiting = new Set();
22
+ const visited = new Set();
23
+ const reportCycle = (key) => {
24
+ if (visiting.has(key)) {
25
+ diagnostics.push({ code: 'YREG004', path: `entries.${key}`, message: `Registry dependency cycle includes '${key}'.` });
26
+ return;
27
+ }
28
+ if (visited.has(key))
29
+ return;
30
+ visiting.add(key);
31
+ for (const dependency of entries.get(key)?.dependencies ?? []) {
32
+ if (entries.has(dependency))
33
+ reportCycle(dependency);
34
+ }
35
+ visiting.delete(key);
36
+ visited.add(key);
37
+ };
38
+ for (const key of entries.keys())
39
+ reportCycle(key);
40
+ return diagnostics;
41
+ }
@@ -0,0 +1,22 @@
1
+ import { type YoyoGridPresentation, type YoyoRendererInputKind, type YoyoRendererSlot } from './types.js';
2
+ export interface YoyoRendererDefinition<TInput = unknown> {
3
+ key: string;
4
+ input?: TInput;
5
+ inputKind?: YoyoRendererInputKind;
6
+ presentations?: readonly YoyoGridPresentation[];
7
+ slots?: readonly YoyoRendererSlot[];
8
+ }
9
+ export interface YoyoResolvedRendererDefinition<TInput = unknown> extends YoyoRendererDefinition<TInput> {
10
+ inputKind: YoyoRendererInputKind;
11
+ presentations: readonly YoyoGridPresentation[];
12
+ slots: readonly YoyoRendererSlot[];
13
+ }
14
+ export interface YoyoRendererRegistry {
15
+ has(key: string): boolean;
16
+ keys(): readonly string[];
17
+ resolve(key: string): YoyoResolvedRendererDefinition;
18
+ }
19
+ export declare const yoyoV1RendererDefinitions: readonly YoyoRendererDefinition[];
20
+ export declare const yoyoV1RendererRegistry: YoyoRendererRegistry;
21
+ export declare function createYoyoRendererRegistry(definitions: readonly YoyoRendererDefinition[]): YoyoRendererRegistry;
22
+ //# sourceMappingURL=renderer-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-registry.d.ts","sourceRoot":"","sources":["../src/renderer-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,sBAAsB,CAAC,MAAM,GAAG,OAAO;IACtD,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC,aAAa,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC/C,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CACpC;AAED,MAAM,WAAW,8BAA8B,CAAC,MAAM,GAAG,OAAO,CAAE,SAAQ,sBAAsB,CAAC,MAAM,CAAC;IACtG,SAAS,EAAE,qBAAqB,CAAA;IAChC,aAAa,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC9C,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAA;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IACzB,IAAI,IAAI,SAAS,MAAM,EAAE,CAAA;IACzB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,8BAA8B,CAAA;CACrD;AAED,eAAO,MAAM,yBAAyB,EAAE,SAAS,sBAAsB,EAKpE,CAAA;AACH,eAAO,MAAM,sBAAsB,sBAAwD,CAAA;AAE3F,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,SAAS,sBAAsB,EAAE,GAAG,oBAAoB,CAsB/G"}
@@ -0,0 +1,33 @@
1
+ import { YOYO_GRID_PRESENTATIONS, YOYO_RENDERERS, } from './types.js';
2
+ export const yoyoV1RendererDefinitions = YOYO_RENDERERS.map((key) => ({
3
+ key,
4
+ inputKind: 'cell-value',
5
+ presentations: YOYO_GRID_PRESENTATIONS,
6
+ slots: ['cell'],
7
+ }));
8
+ export const yoyoV1RendererRegistry = createYoyoRendererRegistry(yoyoV1RendererDefinitions);
9
+ export function createYoyoRendererRegistry(definitions) {
10
+ const entries = new Map();
11
+ for (const definition of definitions) {
12
+ if (!definition.key.trim())
13
+ throw new Error('Renderer registry does not accept an empty key.');
14
+ if (entries.has(definition.key))
15
+ throw new Error(`Renderer '${definition.key}' is already registered.`);
16
+ entries.set(definition.key, {
17
+ ...definition,
18
+ inputKind: definition.inputKind ?? 'cell-value',
19
+ presentations: definition.presentations ?? YOYO_GRID_PRESENTATIONS,
20
+ slots: definition.slots ?? ['cell'],
21
+ });
22
+ }
23
+ return {
24
+ has: (key) => entries.has(key),
25
+ keys: () => [...entries.keys()].sort(),
26
+ resolve: (key) => {
27
+ const definition = entries.get(key);
28
+ if (!definition)
29
+ throw new Error(`Renderer '${key}' is not registered.`);
30
+ return definition;
31
+ },
32
+ };
33
+ }