From c028ca9f95e9a4704c7fa8d06ba6bef860ad16da Mon Sep 17 00:00:00 2001 From: justin-medeiros Date: Fri, 20 Feb 2026 15:34:03 -0500 Subject: [PATCH 01/14] Fix validator throwing incorrect errors --- .../heritage-additional-properties.test.ts | 119 ++++++++++++++++++ xlr/converters/src/ts-to-xlr.ts | 30 +++-- xlr/utils/src/__tests__/ts-helpers.test.ts | 81 +++++++++++- xlr/utils/src/ts-helpers.ts | 25 +++- 4 files changed, 240 insertions(+), 15 deletions(-) create mode 100644 xlr/converters/src/__tests__/heritage-additional-properties.test.ts diff --git a/xlr/converters/src/__tests__/heritage-additional-properties.test.ts b/xlr/converters/src/__tests__/heritage-additional-properties.test.ts new file mode 100644 index 00000000..d2d52c92 --- /dev/null +++ b/xlr/converters/src/__tests__/heritage-additional-properties.test.ts @@ -0,0 +1,119 @@ +import { test, expect } from "vitest"; +import { setupTestEnv } from "@player-tools/test-utils"; +import { TsConverter } from ".."; + +/** + * Tests for handleHeritageClauses: when an interface extends a base that has + * no index signature, but the current interface declares [key: string]: unknown, + * the merged XLR must include the current interface's additionalProperties + * (so the merged type allows extra keys). + */ +test("merged type includes current interface additionalProperties when extending base without index signature", () => { + const sc = ` + interface NavigationBaseState { + state_type: string; + onStart?: string; + } + + export interface NavigationFlowEndState extends NavigationBaseState { + outcome: string; + [key: string]: unknown; + } + `; + + const { sf, tc } = setupTestEnv(sc); + const converter = new TsConverter(tc); + const types = converter.convertSourceFile(sf).data.types; + + const endStateType = types.find( + (t: { name?: string }) => t.name === "NavigationFlowEndState", + ); + expect(endStateType).toBeDefined(); + expect( + (endStateType as { additionalProperties?: unknown }).additionalProperties, + ).not.toBe(false); + expect( + ( + (endStateType as { additionalProperties?: { type?: string } }) + .additionalProperties as { type?: string } + )?.type, + ).toBe("unknown"); +}); + +test("merged type includes additionalProperties when only extended type has index signature", () => { + const sc = ` + interface Base { + required: string; + } + + interface WithIndex { + [key: string]: unknown; + } + + export interface Child extends Base, WithIndex { + extra: number; + } + `; + + const { sf, tc } = setupTestEnv(sc); + const converter = new TsConverter(tc); + const types = converter.convertSourceFile(sf).data.types; + + const childType = types.find((t: { name?: string }) => t.name === "Child"); + expect(childType).toBeDefined(); + const additionalProperties = (childType as { additionalProperties?: unknown }) + .additionalProperties; + expect(additionalProperties).not.toBe(false); + expect((additionalProperties as { type?: string })?.type).toBe("unknown"); +}); + +test("merged type has no additionalProperties when no interface has index signature", () => { + const sc = ` + interface Base { + required: string; + } + + export interface Child extends Base { + extra: number; + } + `; + + const { sf, tc } = setupTestEnv(sc); + const converter = new TsConverter(tc); + const types = converter.convertSourceFile(sf).data.types; + + const childType = types.find((t: { name?: string }) => t.name === "Child"); + expect(childType).toBeDefined(); + const additionalProperties = (childType as { additionalProperties?: unknown }) + .additionalProperties; + expect(additionalProperties).toBe(false); +}); + +test("merged type combines additionalProperties from current and extended when both have index signatures", () => { + const sc = ` + interface Base { + required: string; + [key: string]: unknown; + } + + export interface Child extends Base { + extra: number; + [key: string]: unknown; + } + `; + + const { sf, tc } = setupTestEnv(sc); + const converter = new TsConverter(tc); + const types = converter.convertSourceFile(sf).data.types; + + const childType = types.find((t: { name?: string }) => t.name === "Child"); + expect(childType).toBeDefined(); + const additionalProperties = (childType as { additionalProperties?: unknown }) + .additionalProperties; + expect(additionalProperties).not.toBe(false); + // When both have index sigs we merge (e.g. or of both); extra keys are allowed + const ap = additionalProperties as { type?: string; or?: unknown[] }; + expect( + ap.type === "unknown" || (ap.type === "or" && Array.isArray(ap.or)), + ).toBe(true); +}); diff --git a/xlr/converters/src/ts-to-xlr.ts b/xlr/converters/src/ts-to-xlr.ts index 47cfb9ea..7926d531 100644 --- a/xlr/converters/src/ts-to-xlr.ts +++ b/xlr/converters/src/ts-to-xlr.ts @@ -915,17 +915,29 @@ export class TsConverter { } }); }); - // Resolve Additional Properties + // Resolve Additional Properties: preserve index signature from current + // interface (baseObject) and merge with any from extended types. let additionalProperties: NodeType | false = false; - if (baseObject.additionalProperties === false) { - if (additionalPropertiesCollector.length === 1) { - additionalProperties = additionalPropertiesCollector[0]; - } else if (additionalPropertiesCollector.length >= 1) { - additionalProperties = { - type: "or", - or: additionalPropertiesCollector, - }; + if (baseObject.additionalProperties) { + if (additionalPropertiesCollector.length === 0) { + additionalProperties = baseObject.additionalProperties; + } else { + additionalPropertiesCollector.push(baseObject.additionalProperties); + additionalProperties = + additionalPropertiesCollector.length === 1 + ? additionalPropertiesCollector[0] + : { + type: "or", + or: additionalPropertiesCollector, + }; } + } else if (additionalPropertiesCollector.length === 1) { + additionalProperties = additionalPropertiesCollector[0]; + } else if (additionalPropertiesCollector.length >= 1) { + additionalProperties = { + type: "or", + or: additionalPropertiesCollector, + }; } return { diff --git a/xlr/utils/src/__tests__/ts-helpers.test.ts b/xlr/utils/src/__tests__/ts-helpers.test.ts index 95b3cdd4..1c6a4c0f 100644 --- a/xlr/utils/src/__tests__/ts-helpers.test.ts +++ b/xlr/utils/src/__tests__/ts-helpers.test.ts @@ -1,6 +1,6 @@ import { test, expect, describe } from "vitest"; import * as ts from "typescript"; -import { NodeType } from "@player-tools/xlr"; +import { NodeType, ObjectType } from "@player-tools/xlr"; import { tsStripOptionalType, @@ -8,6 +8,7 @@ import { applyPickOrOmitToNodeType, getStringLiteralsFromUnion, applyPartialOrRequiredToNodeType, + fillInGenerics, } from "../ts-helpers"; test("tsStripOptionalType", () => { @@ -178,3 +179,81 @@ describe("applyPartialOrRequiredToNodeType", () => { expect(result).toStrictEqual(modifiedObject); }); }); + +describe("fillInGenerics", () => { + test("scopes refs inside object with genericTokens to inner generic, not top-level", () => { + // Simulates Schema.DataType nested under Flow: + // inner object has T = unknown; we pass top-level map T → Asset. + // Refs inside the inner object (e.g. default: ref "T") must resolve to unknown. + const assetLikeType: NodeType = { + type: "object", + properties: { + id: { required: true, node: { type: "string" } }, + type: { required: true, node: { type: "string" } }, + }, + additionalProperties: false, + }; + const topLevelGenerics = new Map([["T", assetLikeType]]); + + const dataTypeLike: NodeType = { + type: "object", + name: "DataType", + properties: { + type: { + required: true, + node: { type: "string" }, + }, + default: { + required: false, + node: { type: "ref", ref: "T" }, + }, + }, + additionalProperties: false, + genericTokens: [ + { symbol: "T", constraints: undefined, default: { type: "unknown" } }, + ], + } as NodeType; + + const result = fillInGenerics(dataTypeLike, topLevelGenerics); + + expect(result.type).toBe("object"); + const resultObj = result as ObjectType; + expect(resultObj.properties.default.node).toBeDefined(); + // Resolved from inner scope (DataType's T = unknown), not top-level (Asset) + expect((resultObj.properties.default.node as { type?: string }).type).toBe( + "unknown", + ); + }); + + test("uses passed-in generic map when object has no genericTokens", () => { + const assetLikeType: NodeType = { + type: "object", + properties: { + id: { required: true, node: { type: "string" } }, + }, + additionalProperties: false, + }; + const generics = new Map([["T", assetLikeType]]); + + // No genericTokens on this object (e.g. Flow level): passed-in map is used, + // so ref "T" resolves to Asset — Flow still works as T = Asset. + const nodeWithRefT: NodeType = { + type: "object", + properties: { + value: { + required: true, + node: { type: "ref", ref: "T" }, + }, + }, + additionalProperties: false, + }; + + const result = fillInGenerics(nodeWithRefT, generics); + + const resultObj = result as ObjectType; + expect(resultObj.properties.value.node).toBeDefined(); + expect((resultObj.properties.value.node as { type?: string }).type).toBe( + "object", + ); + }); +}); diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index 793032a2..e29ca8d0 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -228,12 +228,27 @@ export function fillInGenerics( } if (xlrNode.type === "object") { + // When an object has its own genericTokens, scope refs inside it to those + // tokens (e.g. DataType's T) so they don't incorrectly resolve to an outer + // type's generics (e.g. Flow's T = Asset). + let scopeGenerics = localGenerics; + if (isGenericNamedType(xlrNode) && xlrNode.genericTokens) { + scopeGenerics = new Map(localGenerics); + xlrNode.genericTokens.forEach((token) => { + const genericValue = (token.default ?? token.constraints) as NodeType; + scopeGenerics.set( + token.symbol, + fillInGenerics(genericValue, scopeGenerics), + ); + }); + } + const newProperties: { [name: string]: ObjectProperty } = {}; Object.getOwnPropertyNames(xlrNode.properties).forEach((propName) => { const prop = xlrNode.properties[propName]; newProperties[propName] = { required: prop.required, - node: fillInGenerics(prop.node, localGenerics), + node: fillInGenerics(prop.node, scopeGenerics), }; }); @@ -246,20 +261,20 @@ export function fillInGenerics( return { ...token, constraints: token.constraints - ? fillInGenerics(token.constraints, localGenerics) + ? fillInGenerics(token.constraints, scopeGenerics) : undefined, default: token.default - ? fillInGenerics(token.default, localGenerics) + ? fillInGenerics(token.default, scopeGenerics) : undefined, }; }), } : {}), extends: xlrNode.extends - ? (fillInGenerics(xlrNode.extends, localGenerics) as RefNode) + ? (fillInGenerics(xlrNode.extends, scopeGenerics) as RefNode) : undefined, additionalProperties: xlrNode.additionalProperties - ? fillInGenerics(xlrNode.additionalProperties, localGenerics) + ? fillInGenerics(xlrNode.additionalProperties, scopeGenerics) : false, }; } From 36aaff5f1e6f127f8e49adee51893f1b2d8ed4ec Mon Sep 17 00:00:00 2001 From: justin-medeiros Date: Fri, 20 Feb 2026 16:23:56 -0500 Subject: [PATCH 02/14] Test --- .../__snapshots__/player.test.ts.snap | 64 ++++++-- xlr/utils/src/__tests__/ts-helpers.test.ts | 154 +++++++++--------- xlr/utils/src/ts-helpers.ts | 32 ++-- 3 files changed, 141 insertions(+), 109 deletions(-) diff --git a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap index cfb0c488..b65998e8 100644 --- a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap +++ b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap @@ -1831,7 +1831,9 @@ If the expression is a composite, the last expression executed is the return val "name": "NavigationFlowState", "or": [ { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "A state representing a view", "genericTokens": undefined, "name": "NavigationFlowViewState", @@ -1978,7 +1980,9 @@ If the expression is a composite, the last expression executed is the return val "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "An END state of the flow.", "genericTokens": undefined, "name": "NavigationFlowEndState", @@ -2347,7 +2351,9 @@ The return value determines the transition to take", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "External Flow states represent states in the FSM that can't be resolved internally in Player. The flow will wait for the embedded application to manage moving to the next state via a transition", "genericTokens": undefined, @@ -2643,7 +2649,9 @@ The flow will wait for the embedded application to manage moving to the next sta "name": "NavigationFlowState", "or": [ { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "A state representing a view", "name": "NavigationFlowViewState", "properties": { @@ -2781,7 +2789,9 @@ The flow will wait for the embedded application to manage moving to the next sta "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "An END state of the flow.", "name": "NavigationFlowEndState", "properties": { @@ -3138,7 +3148,9 @@ The return value determines the transition to take", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "External Flow states represent states in the FSM that can't be resolved internally in Player. The flow will wait for the embedded application to manage moving to the next state via a transition", "name": "NavigationFlowExternalState", @@ -3657,7 +3669,9 @@ So this explicity says there should never be an exp prop on a state node that"s "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "A state representing a view", "genericTokens": undefined, "name": "NavigationFlowViewState", @@ -3799,7 +3813,9 @@ So this explicity says there should never be an exp prop on a state node that"s "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "An END state of the flow.", "genericTokens": undefined, "name": "NavigationFlowEndState", @@ -4038,7 +4054,9 @@ The return value determines the transition to take", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "External Flow states represent states in the FSM that can't be resolved internally in Player. The flow will wait for the embedded application to manage moving to the next state via a transition", "genericTokens": undefined, @@ -4302,7 +4320,9 @@ The flow will wait for the embedded application to manage moving to the next sta "name": "NavigationFlowState", "or": [ { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "A state representing a view", "name": "NavigationFlowViewState", "properties": { @@ -4440,7 +4460,9 @@ The flow will wait for the embedded application to manage moving to the next sta "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "An END state of the flow.", "name": "NavigationFlowEndState", "properties": { @@ -4797,7 +4819,9 @@ The return value determines the transition to take", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "External Flow states represent states in the FSM that can't be resolved internally in Player. The flow will wait for the embedded application to manage moving to the next state via a transition", "name": "NavigationFlowExternalState", @@ -4944,7 +4968,9 @@ The flow will wait for the embedded application to manage moving to the next sta }, "endState": { "node": { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "The outcome describes _how_ the flow ended (forwards, backwards, etc)", "genericTokens": undefined, "name": "NavigationFlowEndState", @@ -5507,7 +5533,9 @@ This will be used to lookup the proper handler", "name": "NavigationFlowState", "or": [ { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "A state representing a view", "name": "NavigationFlowViewState", "properties": { @@ -5645,7 +5673,9 @@ This will be used to lookup the proper handler", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "An END state of the flow.", "name": "NavigationFlowEndState", "properties": { @@ -6002,7 +6032,9 @@ The return value determines the transition to take", "type": "object", }, { - "additionalProperties": false, + "additionalProperties": { + "type": "unknown", + }, "description": "External Flow states represent states in the FSM that can't be resolved internally in Player. The flow will wait for the embedded application to manage moving to the next state via a transition", "name": "NavigationFlowExternalState", diff --git a/xlr/utils/src/__tests__/ts-helpers.test.ts b/xlr/utils/src/__tests__/ts-helpers.test.ts index 1c6a4c0f..59c55192 100644 --- a/xlr/utils/src/__tests__/ts-helpers.test.ts +++ b/xlr/utils/src/__tests__/ts-helpers.test.ts @@ -180,80 +180,80 @@ describe("applyPartialOrRequiredToNodeType", () => { }); }); -describe("fillInGenerics", () => { - test("scopes refs inside object with genericTokens to inner generic, not top-level", () => { - // Simulates Schema.DataType nested under Flow: - // inner object has T = unknown; we pass top-level map T → Asset. - // Refs inside the inner object (e.g. default: ref "T") must resolve to unknown. - const assetLikeType: NodeType = { - type: "object", - properties: { - id: { required: true, node: { type: "string" } }, - type: { required: true, node: { type: "string" } }, - }, - additionalProperties: false, - }; - const topLevelGenerics = new Map([["T", assetLikeType]]); - - const dataTypeLike: NodeType = { - type: "object", - name: "DataType", - properties: { - type: { - required: true, - node: { type: "string" }, - }, - default: { - required: false, - node: { type: "ref", ref: "T" }, - }, - }, - additionalProperties: false, - genericTokens: [ - { symbol: "T", constraints: undefined, default: { type: "unknown" } }, - ], - } as NodeType; - - const result = fillInGenerics(dataTypeLike, topLevelGenerics); - - expect(result.type).toBe("object"); - const resultObj = result as ObjectType; - expect(resultObj.properties.default.node).toBeDefined(); - // Resolved from inner scope (DataType's T = unknown), not top-level (Asset) - expect((resultObj.properties.default.node as { type?: string }).type).toBe( - "unknown", - ); - }); - - test("uses passed-in generic map when object has no genericTokens", () => { - const assetLikeType: NodeType = { - type: "object", - properties: { - id: { required: true, node: { type: "string" } }, - }, - additionalProperties: false, - }; - const generics = new Map([["T", assetLikeType]]); - - // No genericTokens on this object (e.g. Flow level): passed-in map is used, - // so ref "T" resolves to Asset — Flow still works as T = Asset. - const nodeWithRefT: NodeType = { - type: "object", - properties: { - value: { - required: true, - node: { type: "ref", ref: "T" }, - }, - }, - additionalProperties: false, - }; - - const result = fillInGenerics(nodeWithRefT, generics); - - const resultObj = result as ObjectType; - expect(resultObj.properties.value.node).toBeDefined(); - expect((resultObj.properties.value.node as { type?: string }).type).toBe( - "object", - ); - }); -}); +// describe("fillInGenerics", () => { +// test("scopes refs inside object with genericTokens to inner generic, not top-level", () => { +// // Simulates Schema.DataType nested under Flow: +// // inner object has T = unknown; we pass top-level map T → Asset. +// // Refs inside the inner object (e.g. default: ref "T") must resolve to unknown. +// const assetLikeType: NodeType = { +// type: "object", +// properties: { +// id: { required: true, node: { type: "string" } }, +// type: { required: true, node: { type: "string" } }, +// }, +// additionalProperties: false, +// }; +// const topLevelGenerics = new Map([["T", assetLikeType]]); + +// const dataTypeLike: NodeType = { +// type: "object", +// name: "DataType", +// properties: { +// type: { +// required: true, +// node: { type: "string" }, +// }, +// default: { +// required: false, +// node: { type: "ref", ref: "T" }, +// }, +// }, +// additionalProperties: false, +// genericTokens: [ +// { symbol: "T", constraints: undefined, default: { type: "unknown" } }, +// ], +// } as NodeType; + +// const result = fillInGenerics(dataTypeLike, topLevelGenerics); + +// expect(result.type).toBe("object"); +// const resultObj = result as ObjectType; +// expect(resultObj.properties.default.node).toBeDefined(); +// // Resolved from inner scope (DataType's T = unknown), not top-level (Asset) +// expect((resultObj.properties.default.node as { type?: string }).type).toBe( +// "unknown", +// ); +// }); + +// test("uses passed-in generic map when object has no genericTokens", () => { +// const assetLikeType: NodeType = { +// type: "object", +// properties: { +// id: { required: true, node: { type: "string" } }, +// }, +// additionalProperties: false, +// }; +// const generics = new Map([["T", assetLikeType]]); + +// // No genericTokens on this object (e.g. Flow level): passed-in map is used, +// // so ref "T" resolves to Asset — Flow still works as T = Asset. +// const nodeWithRefT: NodeType = { +// type: "object", +// properties: { +// value: { +// required: true, +// node: { type: "ref", ref: "T" }, +// }, +// }, +// additionalProperties: false, +// }; + +// const result = fillInGenerics(nodeWithRefT, generics); + +// const resultObj = result as ObjectType; +// expect(resultObj.properties.value.node).toBeDefined(); +// expect((resultObj.properties.value.node as { type?: string }).type).toBe( +// "object", +// ); +// }); +// }); diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index e29ca8d0..03c8f0dc 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -231,24 +231,24 @@ export function fillInGenerics( // When an object has its own genericTokens, scope refs inside it to those // tokens (e.g. DataType's T) so they don't incorrectly resolve to an outer // type's generics (e.g. Flow's T = Asset). - let scopeGenerics = localGenerics; - if (isGenericNamedType(xlrNode) && xlrNode.genericTokens) { - scopeGenerics = new Map(localGenerics); - xlrNode.genericTokens.forEach((token) => { - const genericValue = (token.default ?? token.constraints) as NodeType; - scopeGenerics.set( - token.symbol, - fillInGenerics(genericValue, scopeGenerics), - ); - }); - } + // let scopeGenerics = localGenerics; + // if (isGenericNamedType(xlrNode) && xlrNode.genericTokens) { + // scopeGenerics = new Map(localGenerics); + // xlrNode.genericTokens.forEach((token) => { + // const genericValue = (token.default ?? token.constraints) as NodeType; + // scopeGenerics.set( + // token.symbol, + // fillInGenerics(genericValue, scopeGenerics), + // ); + // }); + // } const newProperties: { [name: string]: ObjectProperty } = {}; Object.getOwnPropertyNames(xlrNode.properties).forEach((propName) => { const prop = xlrNode.properties[propName]; newProperties[propName] = { required: prop.required, - node: fillInGenerics(prop.node, scopeGenerics), + node: fillInGenerics(prop.node, localGenerics), }; }); @@ -261,20 +261,20 @@ export function fillInGenerics( return { ...token, constraints: token.constraints - ? fillInGenerics(token.constraints, scopeGenerics) + ? fillInGenerics(token.constraints, localGenerics) : undefined, default: token.default - ? fillInGenerics(token.default, scopeGenerics) + ? fillInGenerics(token.default, localGenerics) : undefined, }; }), } : {}), extends: xlrNode.extends - ? (fillInGenerics(xlrNode.extends, scopeGenerics) as RefNode) + ? (fillInGenerics(xlrNode.extends, localGenerics) as RefNode) : undefined, additionalProperties: xlrNode.additionalProperties - ? fillInGenerics(xlrNode.additionalProperties, scopeGenerics) + ? fillInGenerics(xlrNode.additionalProperties, localGenerics) : false, }; } From 44c47e7b5290cc160230a8625ef7f6ac4000e7f4 Mon Sep 17 00:00:00 2001 From: justin-medeiros Date: Fri, 20 Feb 2026 17:19:54 -0500 Subject: [PATCH 03/14] Revert scopeGenerics --- xlr/utils/src/__tests__/ts-helpers.test.ts | 3 +-- xlr/utils/src/ts-helpers.ts | 15 --------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/xlr/utils/src/__tests__/ts-helpers.test.ts b/xlr/utils/src/__tests__/ts-helpers.test.ts index 59c55192..6008dc35 100644 --- a/xlr/utils/src/__tests__/ts-helpers.test.ts +++ b/xlr/utils/src/__tests__/ts-helpers.test.ts @@ -1,6 +1,6 @@ import { test, expect, describe } from "vitest"; import * as ts from "typescript"; -import { NodeType, ObjectType } from "@player-tools/xlr"; +import type { NodeType } from "@player-tools/xlr"; import { tsStripOptionalType, @@ -8,7 +8,6 @@ import { applyPickOrOmitToNodeType, getStringLiteralsFromUnion, applyPartialOrRequiredToNodeType, - fillInGenerics, } from "../ts-helpers"; test("tsStripOptionalType", () => { diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index 03c8f0dc..793032a2 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -228,21 +228,6 @@ export function fillInGenerics( } if (xlrNode.type === "object") { - // When an object has its own genericTokens, scope refs inside it to those - // tokens (e.g. DataType's T) so they don't incorrectly resolve to an outer - // type's generics (e.g. Flow's T = Asset). - // let scopeGenerics = localGenerics; - // if (isGenericNamedType(xlrNode) && xlrNode.genericTokens) { - // scopeGenerics = new Map(localGenerics); - // xlrNode.genericTokens.forEach((token) => { - // const genericValue = (token.default ?? token.constraints) as NodeType; - // scopeGenerics.set( - // token.symbol, - // fillInGenerics(genericValue, scopeGenerics), - // ); - // }); - // } - const newProperties: { [name: string]: ObjectProperty } = {}; Object.getOwnPropertyNames(xlrNode.properties).forEach((propName) => { const prop = xlrNode.properties[propName]; From 1b66fd3f634528bfc79fc3ed1477bc5dbb40d27e Mon Sep 17 00:00:00 2001 From: justin-medeiros Date: Fri, 20 Feb 2026 17:22:09 -0500 Subject: [PATCH 04/14] Remove commented out test --- xlr/utils/src/__tests__/ts-helpers.test.ts | 78 ---------------------- 1 file changed, 78 deletions(-) diff --git a/xlr/utils/src/__tests__/ts-helpers.test.ts b/xlr/utils/src/__tests__/ts-helpers.test.ts index 6008dc35..cced2ae6 100644 --- a/xlr/utils/src/__tests__/ts-helpers.test.ts +++ b/xlr/utils/src/__tests__/ts-helpers.test.ts @@ -178,81 +178,3 @@ describe("applyPartialOrRequiredToNodeType", () => { expect(result).toStrictEqual(modifiedObject); }); }); - -// describe("fillInGenerics", () => { -// test("scopes refs inside object with genericTokens to inner generic, not top-level", () => { -// // Simulates Schema.DataType nested under Flow: -// // inner object has T = unknown; we pass top-level map T → Asset. -// // Refs inside the inner object (e.g. default: ref "T") must resolve to unknown. -// const assetLikeType: NodeType = { -// type: "object", -// properties: { -// id: { required: true, node: { type: "string" } }, -// type: { required: true, node: { type: "string" } }, -// }, -// additionalProperties: false, -// }; -// const topLevelGenerics = new Map([["T", assetLikeType]]); - -// const dataTypeLike: NodeType = { -// type: "object", -// name: "DataType", -// properties: { -// type: { -// required: true, -// node: { type: "string" }, -// }, -// default: { -// required: false, -// node: { type: "ref", ref: "T" }, -// }, -// }, -// additionalProperties: false, -// genericTokens: [ -// { symbol: "T", constraints: undefined, default: { type: "unknown" } }, -// ], -// } as NodeType; - -// const result = fillInGenerics(dataTypeLike, topLevelGenerics); - -// expect(result.type).toBe("object"); -// const resultObj = result as ObjectType; -// expect(resultObj.properties.default.node).toBeDefined(); -// // Resolved from inner scope (DataType's T = unknown), not top-level (Asset) -// expect((resultObj.properties.default.node as { type?: string }).type).toBe( -// "unknown", -// ); -// }); - -// test("uses passed-in generic map when object has no genericTokens", () => { -// const assetLikeType: NodeType = { -// type: "object", -// properties: { -// id: { required: true, node: { type: "string" } }, -// }, -// additionalProperties: false, -// }; -// const generics = new Map([["T", assetLikeType]]); - -// // No genericTokens on this object (e.g. Flow level): passed-in map is used, -// // so ref "T" resolves to Asset — Flow still works as T = Asset. -// const nodeWithRefT: NodeType = { -// type: "object", -// properties: { -// value: { -// required: true, -// node: { type: "ref", ref: "T" }, -// }, -// }, -// additionalProperties: false, -// }; - -// const result = fillInGenerics(nodeWithRefT, generics); - -// const resultObj = result as ObjectType; -// expect(resultObj.properties.value.node).toBeDefined(); -// expect((resultObj.properties.value.node as { type?: string }).type).toBe( -// "object", -// ); -// }); -// }); From 3d4f71bf41954ea5895539ed620d34f0ad45af03 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Wed, 25 Feb 2026 17:00:41 -0800 Subject: [PATCH 05/14] Fix issue with local generics not being honored during SDK recall of type leading to improper generic resolution --- package.json | 1 + pnpm-lock.yaml | 122 +++++++++++++++++++++++------- xlr/sdk/src/__tests__/sdk.test.ts | 20 ++++- xlr/sdk/src/sdk.ts | 2 +- xlr/utils/src/ts-helpers.ts | 86 +++++++++++++++++---- 5 files changed, 186 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 71848cbd..82591a79 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@typescript-eslint/parser": "^5.62.0", "@typescript/vfs": "^1.4.0", "@vitest/coverage-v8": "^2.1.3", + "@vitest/ui": "^2.1.3", "@vscode/vsce": "^2.23.0", "all-contributors-cli": "^6.20.0", "auto": "^10.37.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b5c1a96..5f7035bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,7 +119,7 @@ importers: version: 9.3.4 '@testing-library/jest-dom': specifier: ^6.4.1 - version: 6.4.6(vitest@2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1)) + version: 6.4.6(vitest@2.1.8) '@testing-library/react': specifier: ^14.2.1 version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -179,7 +179,10 @@ importers: version: 1.5.3(typescript@5.5.4) '@vitest/coverage-v8': specifier: ^2.1.3 - version: 2.1.8(vitest@2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1)) + version: 2.1.8(vitest@2.1.8) + '@vitest/ui': + specifier: ^2.1.3 + version: 2.1.9(vitest@2.1.8) '@vscode/vsce': specifier: ^2.23.0 version: 2.27.0 @@ -440,7 +443,7 @@ importers: version: 8.3.2 vitest: specifier: ^2.1.3 - version: 2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1) + version: 2.1.8(@types/node@18.19.34)(@vitest/ui@2.1.9)(happy-dom@13.10.1)(terser@5.31.1) vscode-languageserver: specifier: ^6.1.1 version: 6.1.1 @@ -3150,6 +3153,9 @@ packages: '@player-ui/types@0.7.2-next.4': resolution: {integrity: sha512-wvKlmu+ZJSVT5fs/vHHwpv4KhgZmqinJBvHhttU4LHzqrEauaRcS/DVCItABXt6ABxhIakPInnxQtH1xCwdDyQ==, tarball: https://registry.npmjs.org/@player-ui/types/-/types-0.7.2-next.4.tgz} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==, tarball: https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==, tarball: https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz} @@ -4016,6 +4022,9 @@ packages: '@vitest/pretty-format@2.1.8': resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz} + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz} + '@vitest/runner@2.1.8': resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz} @@ -4025,9 +4034,17 @@ packages: '@vitest/spy@2.1.8': resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz} + '@vitest/ui@2.1.9': + resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==, tarball: https://registry.npmjs.org/@vitest/ui/-/ui-2.1.9.tgz} + peerDependencies: + vitest: 2.1.9 + '@vitest/utils@2.1.8': resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz} + '@vscode/vsce-sign-alpine-arm64@2.0.2': resolution: {integrity: sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==, tarball: https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz} cpu: [arm64] @@ -6088,6 +6105,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==, tarball: https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz} + figgy-pudding@3.5.2: resolution: {integrity: sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==, tarball: https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz} deprecated: This module is no longer supported. @@ -6196,8 +6216,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, tarball: https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz} engines: {node: '>=16'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, tarball: https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, tarball: https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz} flipper-common@0.212.0: resolution: {integrity: sha512-5ZuVn/pCrIa10UuASra8dNwdvNkYuV23m2F4J8k4E+EIfp/s2+woOTkMC1C+JhRDmLYZmLQUtmmD87qMUB8hFw==, tarball: https://registry.npmjs.org/flipper-common/-/flipper-common-0.212.0.tgz} @@ -6494,15 +6514,16 @@ packages: glob@10.4.1: resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==, tarball: https://registry.npmjs.org/glob/-/glob-10.4.1.tgz} engines: {node: '>=16 || 14 >=14.18'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@3.2.11: resolution: {integrity: sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==, tarball: https://registry.npmjs.org/glob/-/glob-3.2.11.tgz} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, tarball: https://registry.npmjs.org/glob/-/glob-7.2.3.tgz} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-modules@1.0.0: resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==, tarball: https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz} @@ -7926,6 +7947,10 @@ packages: resolution: {integrity: sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==, tarball: https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz} deprecated: This package is no longer supported. + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, tarball: https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, tarball: https://registry.npmjs.org/ms/-/ms-2.0.0.tgz} @@ -9760,6 +9785,10 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==, tarball: https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==, tarball: https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz} + engines: {node: '>=18'} + slash@1.0.0: resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==, tarball: https://registry.npmjs.org/slash/-/slash-1.0.0.tgz} engines: {node: '>=0.10.0'} @@ -10135,6 +10164,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, tarball: https://registry.npmjs.org/tar/-/tar-6.2.1.tgz} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me temp@0.8.3: resolution: {integrity: sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==, tarball: https://registry.npmjs.org/temp/-/temp-0.8.3.tgz} @@ -10287,6 +10317,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, tarball: https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==, tarball: https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, tarball: https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz} @@ -11352,8 +11386,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -11402,8 +11436,8 @@ snapshots: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-bucket-endpoint': 3.587.0 @@ -11460,11 +11494,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.596.0': + '@aws-sdk/client-sso-oidc@3.596.0(@aws-sdk/client-sts@3.596.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -11503,6 +11537,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso@3.592.0': @@ -11548,11 +11583,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)': + '@aws-sdk/client-sts@3.596.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -11591,7 +11626,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.592.0': @@ -11625,7 +11659,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/credential-provider-env': 3.587.0 '@aws-sdk/credential-provider-http': 3.596.0 '@aws-sdk/credential-provider-process': 3.587.0 @@ -11683,7 +11717,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.596.0)': dependencies: - '@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0) + '@aws-sdk/client-sts': 3.596.0 '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.1 '@smithy/types': 3.1.0 @@ -11799,7 +11833,7 @@ snapshots: '@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.596.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.596.0 + '@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.1 '@smithy/shared-ini-file-loader': 3.1.1 @@ -15290,6 +15324,8 @@ snapshots: dependencies: '@babel/runtime': 7.15.4 + '@polka/url@1.0.0-next.29': {} + '@popperjs/core@2.11.8': {} '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.3)(react@18.3.1)': @@ -15796,7 +15832,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.6(vitest@2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1))': + '@testing-library/jest-dom@6.4.6(vitest@2.1.8)': dependencies: '@adobe/css-tools': 4.4.0 '@babel/runtime': 7.24.7 @@ -15807,7 +15843,7 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 optionalDependencies: - vitest: 2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1) + vitest: 2.1.8(@types/node@18.19.34)(@vitest/ui@2.1.9)(happy-dom@13.10.1)(terser@5.31.1) '@testing-library/react-hooks@8.0.1(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -16306,7 +16342,7 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' - '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1))': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -16320,7 +16356,7 @@ snapshots: std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1) + vitest: 2.1.8(@types/node@18.19.34)(@vitest/ui@2.1.9)(happy-dom@13.10.1)(terser@5.31.1) transitivePeerDependencies: - supports-color @@ -16343,6 +16379,10 @@ snapshots: dependencies: tinyrainbow: 1.2.0 + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/runner@2.1.8': dependencies: '@vitest/utils': 2.1.8 @@ -16358,12 +16398,29 @@ snapshots: dependencies: tinyspy: 3.0.2 + '@vitest/ui@2.1.9(vitest@2.1.8)': + dependencies: + '@vitest/utils': 2.1.9 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 1.1.2 + sirv: 3.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@18.19.34)(@vitest/ui@2.1.9)(happy-dom@13.10.1)(terser@5.31.1) + '@vitest/utils@2.1.8': dependencies: '@vitest/pretty-format': 2.1.8 loupe: 3.1.2 tinyrainbow: 1.2.0 + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + '@vscode/vsce-sign-alpine-arm64@2.0.2': optional: true @@ -19070,6 +19127,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + figgy-pudding@3.5.2: {} figures@2.0.0: @@ -19202,10 +19261,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.1: {} + flatted@3.3.3: {} flipper-common@0.212.0: dependencies: @@ -21307,6 +21366,8 @@ snapshots: rimraf: 2.7.1 run-queue: 1.0.3 + mrmime@2.0.1: {} + ms@2.0.0: {} ms@2.1.2: {} @@ -23465,6 +23526,12 @@ snapshots: simple-concat: 1.0.1 optional: true + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + slash@1.0.0: {} slash@2.0.0: {} @@ -24053,6 +24120,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + tr46@0.0.3: {} tr46@1.0.1: @@ -24515,7 +24584,7 @@ snapshots: fsevents: 2.3.3 terser: 5.31.1 - vitest@2.1.8(@types/node@18.19.34)(happy-dom@13.10.1)(terser@5.31.1): + vitest@2.1.8(@types/node@18.19.34)(@vitest/ui@2.1.9)(happy-dom@13.10.1)(terser@5.31.1): dependencies: '@vitest/expect': 2.1.8 '@vitest/mocker': 2.1.8(vite@5.3.0(@types/node@18.19.34)(terser@5.31.1)) @@ -24539,6 +24608,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 18.19.34 + '@vitest/ui': 2.1.9(vitest@2.1.8) happy-dom: 13.10.1 transitivePeerDependencies: - less diff --git a/xlr/sdk/src/__tests__/sdk.test.ts b/xlr/sdk/src/__tests__/sdk.test.ts index 4b7d1307..427bfd07 100644 --- a/xlr/sdk/src/__tests__/sdk.test.ts +++ b/xlr/sdk/src/__tests__/sdk.test.ts @@ -1,5 +1,10 @@ import { test, expect, describe } from "vitest"; -import type { NamedType, TransformFunction, OrType } from "@player-tools/xlr"; +import type { + NamedType, + TransformFunction, + OrType, + ObjectType, +} from "@player-tools/xlr"; import { parseTree } from "jsonc-parser"; import { Types, @@ -70,9 +75,20 @@ describe("Object Recall", () => { const sdk = new XLRSDK(); sdk.loadDefinitionsFromModule(Types); sdk.loadDefinitionsFromModule(ReferenceAssetsWebPluginManifest); - expect(sdk.getType("InputAsset")).toMatchSnapshot(); }); + + test("Test", () => { + const sdk = new XLRSDK(); + sdk.loadDefinitionsFromModule(Types); + sdk.loadDefinitionsFromModule(ReferenceAssetsWebPluginManifest); + const Flow = sdk.getType("Flow") as ObjectType; + const Schema = Flow.properties["schema"].node as ObjectType; + const Node = Schema.additionalProperties as ObjectType; + const DataType = Node.additionalProperties as ObjectType; + const df = DataType.properties["default"].node; + expect(df.type).toBe("unknown"); + }); }); describe("Validation", () => { diff --git a/xlr/sdk/src/sdk.ts b/xlr/sdk/src/sdk.ts index c47a6f0b..8e089a00 100644 --- a/xlr/sdk/src/sdk.ts +++ b/xlr/sdk/src/sdk.ts @@ -311,7 +311,7 @@ export class XLRSDK { * - filing in any remaining generics with their default value */ private resolveType(type: NamedType, optimize = true): NamedType { - const resolvedObject = fillInGenerics(type); + const resolvedObject = fillInGenerics(type, new Map(), true); let transformMap: TransformFunctionMap = { object: [(objectNode: ObjectType) => { diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index 793032a2..a0d1ac80 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -178,12 +178,22 @@ export function buildTemplateRegex( export function fillInGenerics( xlrNode: NodeType, generics?: Map, + preferLocalGenerics = false, ): NodeType { // Need to make sure not to set generics in passed in map to avoid using generics outside of tree let localGenerics: Map; if (generics) { localGenerics = new Map(generics); + if (preferLocalGenerics && isGenericNodeType(xlrNode)) { + xlrNode.genericTokens?.forEach((token) => { + const genericValue = (token.default ?? token.constraints) as NodeType; + localGenerics.set( + token.symbol, + fillInGenerics(genericValue, localGenerics, preferLocalGenerics), + ); + }); + } } else { localGenerics = new Map(); if (isGenericNodeType(xlrNode)) { @@ -191,7 +201,7 @@ export function fillInGenerics( const genericValue = (token.default ?? token.constraints) as NodeType; localGenerics.set( token.symbol, - fillInGenerics(genericValue, localGenerics), + fillInGenerics(genericValue, localGenerics, preferLocalGenerics), ); }); } @@ -204,7 +214,7 @@ export function fillInGenerics( ...(xlrNode.genericArguments ? { genericArguments: xlrNode.genericArguments.map((ga) => - fillInGenerics(ga, localGenerics), + fillInGenerics(ga, localGenerics, preferLocalGenerics), ), } : {}), @@ -220,7 +230,7 @@ export function fillInGenerics( ...(xlrNode.genericArguments ? { genericArguments: xlrNode.genericArguments.map((ga) => - fillInGenerics(ga, localGenerics), + fillInGenerics(ga, localGenerics, preferLocalGenerics), ), } : {}), @@ -233,7 +243,7 @@ export function fillInGenerics( const prop = xlrNode.properties[propName]; newProperties[propName] = { required: prop.required, - node: fillInGenerics(prop.node, localGenerics), + node: fillInGenerics(prop.node, localGenerics, preferLocalGenerics), }; }); @@ -246,20 +256,36 @@ export function fillInGenerics( return { ...token, constraints: token.constraints - ? fillInGenerics(token.constraints, localGenerics) + ? fillInGenerics( + token.constraints, + localGenerics, + preferLocalGenerics, + ) : undefined, default: token.default - ? fillInGenerics(token.default, localGenerics) + ? fillInGenerics( + token.default, + localGenerics, + preferLocalGenerics, + ) : undefined, }; }), } : {}), extends: xlrNode.extends - ? (fillInGenerics(xlrNode.extends, localGenerics) as RefNode) + ? (fillInGenerics( + xlrNode.extends, + localGenerics, + preferLocalGenerics, + ) as RefNode) : undefined, additionalProperties: xlrNode.additionalProperties - ? fillInGenerics(xlrNode.additionalProperties, localGenerics) + ? fillInGenerics( + xlrNode.additionalProperties, + localGenerics, + preferLocalGenerics, + ) : false, }; } @@ -267,7 +293,11 @@ export function fillInGenerics( if (xlrNode.type === "array") { return { ...xlrNode, - elementType: fillInGenerics(xlrNode.elementType, localGenerics), + elementType: fillInGenerics( + xlrNode.elementType, + localGenerics, + preferLocalGenerics, + ), }; } else if (xlrNode.type === "or" || xlrNode.type === "and") { let pointer; @@ -280,25 +310,49 @@ export function fillInGenerics( return { ...xlrNode, [xlrNode.type]: pointer.map((prop) => { - return fillInGenerics(prop, localGenerics); + return fillInGenerics(prop, localGenerics, preferLocalGenerics); }), }; } else if (xlrNode.type === "record") { return { ...xlrNode, - keyType: fillInGenerics(xlrNode.keyType, localGenerics), - valueType: fillInGenerics(xlrNode.valueType, localGenerics), + keyType: fillInGenerics( + xlrNode.keyType, + localGenerics, + preferLocalGenerics, + ), + valueType: fillInGenerics( + xlrNode.valueType, + localGenerics, + preferLocalGenerics, + ), }; } else if (xlrNode.type === "conditional") { const filledInConditional = { ...xlrNode, check: { - left: fillInGenerics(xlrNode.check.left, localGenerics), - right: fillInGenerics(xlrNode.check.right, localGenerics), + left: fillInGenerics( + xlrNode.check.left, + localGenerics, + preferLocalGenerics, + ), + right: fillInGenerics( + xlrNode.check.right, + localGenerics, + preferLocalGenerics, + ), }, value: { - true: fillInGenerics(xlrNode.value.true, localGenerics), - false: fillInGenerics(xlrNode.value.false, localGenerics), + true: fillInGenerics( + xlrNode.value.true, + localGenerics, + preferLocalGenerics, + ), + false: fillInGenerics( + xlrNode.value.false, + localGenerics, + preferLocalGenerics, + ), }, }; From 1bbf94c752e03868b2ef635daae17b560d130434 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Thu, 26 Feb 2026 22:58:31 -0800 Subject: [PATCH 06/14] Fix issue where top level variable exports that were in the custom primitives list were fully evaluated --- cli/src/utils/xlr/consts.ts | 2 ++ xlr/converters/src/ts-to-xlr.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/cli/src/utils/xlr/consts.ts b/cli/src/utils/xlr/consts.ts index e728cdce..d4dc03f8 100644 --- a/cli/src/utils/xlr/consts.ts +++ b/cli/src/utils/xlr/consts.ts @@ -6,6 +6,8 @@ export const customPrimitives = [ "AssetWrapper", "Schema.DataType", "ExpressionHandler", + "FormatType", + "ValidatorFunction", ]; export enum Mode { diff --git a/xlr/converters/src/ts-to-xlr.ts b/xlr/converters/src/ts-to-xlr.ts index 7926d531..526b9b13 100644 --- a/xlr/converters/src/ts-to-xlr.ts +++ b/xlr/converters/src/ts-to-xlr.ts @@ -211,6 +211,13 @@ export class TsConverter { if (variable.initializer) { let resultingNode; if ( + variable.type && + ts.isTypeReferenceNode(variable.type) && + ts.isIdentifier(variable.type.typeName) && + this.context.customPrimitives.includes(variable.type.typeName.text) + ) { + resultingNode = this.makeBasicRefNode(variable.type); + } else if ( ts.isCallExpression(variable.initializer) || ts.isArrowFunction(variable.initializer) ) { From 0f964629c7e8e1ccff246906b87d507ef394e16b Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Fri, 27 Feb 2026 14:38:07 -0800 Subject: [PATCH 07/14] Support types exported under namespaces as part of type export --- .../__snapshots__/player.test.ts.snap | 1146 +++++++++++++++++ xlr/converters/src/ts-to-xlr.ts | 13 + xlr/utils/src/ts-helpers.ts | 9 + 3 files changed, 1168 insertions(+) diff --git a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap index b65998e8..f333bd8f 100644 --- a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap +++ b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap @@ -6874,5 +6874,1151 @@ This will be used to lookup the proper handler", "title": "Flow", "type": "object", }, + { + "additionalProperties": { + "additionalProperties": { + "additionalProperties": { + "type": "unknown", + }, + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "constraints": { + "type": "any", + }, + "default": { + "type": "unknown", + }, + "symbol": "T", + }, + ], + "name": "DataType", + "properties": { + "default": { + "node": { + "description": "A default value for this property. +Any reads for this property will result in this default value being written to the model.", + "ref": "T", + "title": "DataType.default", + "type": "ref", + }, + "required": false, + }, + "format": { + "node": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a specific data format to use. +If none is specified, will fallback to that of the base type", + "name": "Formatting.Reference", + "properties": { + "type": { + "node": { + "description": "The name of the formatter (and de-formatter) to use", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "DataType.format", + "type": "object", + }, + "required": false, + }, + "isArray": { + "node": { + "description": "The referenced object represents an array rather than an object", + "title": "DataType.isArray", + "type": "boolean", + }, + "required": false, + }, + "type": { + "node": { + "description": "The reference of the base type to use", + "title": "DataType.type", + "type": "string", + }, + "required": true, + }, + "validation": { + "node": { + "description": "Any additional validations that are associated with this property +These will add to any base validations associated with the "type"", + "elementType": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a validation object", + "name": "Validation.Reference", + "properties": { + "dataTarget": { + "node": { + "description": "Each validation is passed the value of the data to run it's validation against. +By default, this is the value stored in the data-model (deformatted). +In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option", + "or": [ + { + "const": "formatted", + "type": "string", + }, + { + "const": "deformatted", + "type": "string", + }, + ], + "title": "Reference.dataTarget", + "type": "or", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "Reference", + "type": "object", + }, + "title": "DataType.validation", + "type": "array", + }, + "required": false, + }, + }, + "title": "DataType", + "type": "object", + }, + "description": "A Node describes a specific object in the tree", + "name": "Node", + "properties": {}, + "title": "Node", + "type": "object", + }, + "description": "The authored schema object in the JSON payload", + "genericTokens": undefined, + "name": "Schema", + "properties": { + "ROOT": { + "node": { + "additionalProperties": { + "additionalProperties": { + "type": "unknown", + }, + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "constraints": { + "type": "any", + }, + "default": { + "type": "unknown", + }, + "symbol": "T", + }, + ], + "name": "DataType", + "properties": { + "default": { + "node": { + "description": "A default value for this property. +Any reads for this property will result in this default value being written to the model.", + "ref": "T", + "title": "DataType.default", + "type": "ref", + }, + "required": false, + }, + "format": { + "node": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a specific data format to use. +If none is specified, will fallback to that of the base type", + "name": "Formatting.Reference", + "properties": { + "type": { + "node": { + "description": "The name of the formatter (and de-formatter) to use", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "DataType.format", + "type": "object", + }, + "required": false, + }, + "isArray": { + "node": { + "description": "The referenced object represents an array rather than an object", + "title": "DataType.isArray", + "type": "boolean", + }, + "required": false, + }, + "type": { + "node": { + "description": "The reference of the base type to use", + "title": "DataType.type", + "type": "string", + }, + "required": true, + }, + "validation": { + "node": { + "description": "Any additional validations that are associated with this property +These will add to any base validations associated with the "type"", + "elementType": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a validation object", + "name": "Validation.Reference", + "properties": { + "dataTarget": { + "node": { + "description": "Each validation is passed the value of the data to run it's validation against. +By default, this is the value stored in the data-model (deformatted). +In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option", + "or": [ + { + "const": "formatted", + "type": "string", + }, + { + "const": "deformatted", + "type": "string", + }, + ], + "title": "Reference.dataTarget", + "type": "or", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "Reference", + "type": "object", + }, + "title": "DataType.validation", + "type": "array", + }, + "required": false, + }, + }, + "title": "DataType", + "type": "object", + }, + "description": "The ROOT object is the top level object to use", + "name": "Node", + "properties": {}, + "title": "Schema.ROOT", + "type": "object", + }, + "required": true, + }, + }, + "source": undefined, + "title": "Schema", + "type": "object", + }, + { + "additionalProperties": { + "additionalProperties": { + "type": "unknown", + }, + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "constraints": { + "type": "any", + }, + "default": { + "type": "unknown", + }, + "symbol": "T", + }, + ], + "name": "DataType", + "properties": { + "default": { + "node": { + "description": "A default value for this property. +Any reads for this property will result in this default value being written to the model.", + "ref": "T", + "title": "DataType.default", + "type": "ref", + }, + "required": false, + }, + "format": { + "node": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a specific data format to use. +If none is specified, will fallback to that of the base type", + "name": "Formatting.Reference", + "properties": { + "type": { + "node": { + "description": "The name of the formatter (and de-formatter) to use", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "DataType.format", + "type": "object", + }, + "required": false, + }, + "isArray": { + "node": { + "description": "The referenced object represents an array rather than an object", + "title": "DataType.isArray", + "type": "boolean", + }, + "required": false, + }, + "type": { + "node": { + "description": "The reference of the base type to use", + "title": "DataType.type", + "type": "string", + }, + "required": true, + }, + "validation": { + "node": { + "description": "Any additional validations that are associated with this property +These will add to any base validations associated with the "type"", + "elementType": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a validation object", + "name": "Validation.Reference", + "properties": { + "dataTarget": { + "node": { + "description": "Each validation is passed the value of the data to run it's validation against. +By default, this is the value stored in the data-model (deformatted). +In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option", + "or": [ + { + "const": "formatted", + "type": "string", + }, + { + "const": "deformatted", + "type": "string", + }, + ], + "title": "Reference.dataTarget", + "type": "or", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "Reference", + "type": "object", + }, + "title": "DataType.validation", + "type": "array", + }, + "required": false, + }, + }, + "title": "DataType", + "type": "object", + }, + "description": "A Node describes a specific object in the tree", + "genericTokens": undefined, + "name": "Node", + "properties": {}, + "source": undefined, + "title": "Node", + "type": "object", + }, + { + "additionalProperties": { + "type": "unknown", + }, + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "constraints": { + "type": "any", + }, + "default": { + "type": "unknown", + }, + "symbol": "T", + }, + ], + "name": "DataType", + "properties": { + "default": { + "node": { + "description": "A default value for this property. +Any reads for this property will result in this default value being written to the model.", + "ref": "T", + "title": "DataType.default", + "type": "ref", + }, + "required": false, + }, + "format": { + "node": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a specific data format to use. +If none is specified, will fallback to that of the base type", + "name": "Formatting.Reference", + "properties": { + "type": { + "node": { + "description": "The name of the formatter (and de-formatter) to use", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "DataType.format", + "type": "object", + }, + "required": false, + }, + "isArray": { + "node": { + "description": "The referenced object represents an array rather than an object", + "title": "DataType.isArray", + "type": "boolean", + }, + "required": false, + }, + "type": { + "node": { + "description": "The reference of the base type to use", + "title": "DataType.type", + "type": "string", + }, + "required": true, + }, + "validation": { + "node": { + "description": "Any additional validations that are associated with this property +These will add to any base validations associated with the "type"", + "elementType": { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a validation object", + "name": "Validation.Reference", + "properties": { + "dataTarget": { + "node": { + "description": "Each validation is passed the value of the data to run it's validation against. +By default, this is the value stored in the data-model (deformatted). +In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option", + "or": [ + { + "const": "formatted", + "type": "string", + }, + { + "const": "deformatted", + "type": "string", + }, + ], + "title": "Reference.dataTarget", + "type": "or", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "title": "Reference", + "type": "object", + }, + "title": "DataType.validation", + "type": "array", + }, + "required": false, + }, + }, + "source": undefined, + "title": "DataType", + "type": "object", + }, + { + "additionalProperties": false, + "description": "Helper to compliment \`Schema.DataType\` to provide a way to export a reference to a data type instead of the whole object", + "genericTokens": undefined, + "name": "DataTypeRef", + "properties": { + "type": { + "node": { + "description": "Name of the type in Player Core", + "title": "DataTypeRef.type", + "type": "string", + }, + "required": true, + }, + }, + "source": undefined, + "title": "DataTypeRef", + "type": "object", + }, + { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a specific formatter", + "genericTokens": undefined, + "name": "Reference", + "properties": { + "type": { + "node": { + "description": "The name of the formatter (and de-formatter) to use", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "source": undefined, + "title": "Reference", + "type": "object", + }, + { + "description": "How serious are you about this error? +Warning validations are reserved for errors that could be ignored by the user without consequence +Errors must be fixed before proceeding", + "genericTokens": undefined, + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "source": undefined, + "title": "Severity", + "type": "or", + }, + { + "description": "When to _first_ start caring about a validation of a data-val. + +load - only check once the first time the binding appears on screen +change - check anytime the data changes +navigation - check once the user attempts to navigate away from a view", + "genericTokens": undefined, + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "source": undefined, + "title": "Trigger", + "type": "or", + }, + { + "description": "Where the error/warning should be displayed. +- \`field\` is the default display target. This renders the error/warning directly underneath the field. +- \`section\` is used to display a message at a parent node that is designated as a "section" +- \`page\` a special section used to display a message at the top of the page.", + "genericTokens": undefined, + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "source": undefined, + "title": "DisplayTarget", + "type": "or", + }, + { + "additionalProperties": { + "type": "unknown", + }, + "description": "A reference to a validation object", + "genericTokens": undefined, + "name": "Reference", + "properties": { + "dataTarget": { + "node": { + "description": "Each validation is passed the value of the data to run it's validation against. +By default, this is the value stored in the data-model (deformatted). +In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option", + "or": [ + { + "const": "formatted", + "type": "string", + }, + { + "const": "deformatted", + "type": "string", + }, + ], + "title": "Reference.dataTarget", + "type": "or", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "source": undefined, + "title": "Reference", + "type": "object", + }, + { + "additionalProperties": { + "type": "unknown", + }, + "genericTokens": undefined, + "name": "CrossfieldReference", + "properties": { + "dataTarget": { + "node": { + "description": "Cross-field references and validation must run against the default (deformatted) value", + "title": "CrossfieldReference.dataTarget", + "type": "never", + }, + "required": false, + }, + "displayTarget": { + "node": { + "description": "Where the error should be displayed", + "name": "DisplayTarget", + "or": [ + { + "const": "page", + "type": "string", + }, + { + "const": "section", + "type": "string", + }, + { + "const": "field", + "type": "string", + }, + ], + "title": "Reference.displayTarget", + "type": "or", + }, + "required": false, + }, + "message": { + "node": { + "description": "An optional means of overriding the default message if the validation is triggered", + "title": "Reference.message", + "type": "string", + }, + "required": false, + }, + "ref": { + "node": { + "description": "The binding to associate this validation with", + "ref": "Binding", + "title": "CrossfieldReference.ref", + "type": "ref", + }, + "required": false, + }, + "severity": { + "node": { + "description": "An optional means of overriding the default severity of the validation if triggered", + "name": "Severity", + "or": [ + { + "const": "error", + "type": "string", + }, + { + "const": "warning", + "type": "string", + }, + ], + "title": "Reference.severity", + "type": "or", + }, + "required": false, + }, + "trigger": { + "node": { + "description": "When to run this particular validation", + "name": "Trigger", + "or": [ + { + "const": "navigation", + "type": "string", + }, + { + "const": "change", + "type": "string", + }, + { + "const": "load", + "type": "string", + }, + ], + "title": "Reference.trigger", + "type": "or", + }, + "required": false, + }, + "type": { + "node": { + "description": "The name of the referenced validation type +This will be used to lookup the proper handler", + "title": "Reference.type", + "type": "string", + }, + "required": true, + }, + }, + "source": undefined, + "title": "CrossfieldReference", + "type": "object", + }, ] `; diff --git a/xlr/converters/src/ts-to-xlr.ts b/xlr/converters/src/ts-to-xlr.ts index 526b9b13..d0bd11a7 100644 --- a/xlr/converters/src/ts-to-xlr.ts +++ b/xlr/converters/src/ts-to-xlr.ts @@ -41,6 +41,7 @@ import { applyExcludeToNodeType, isPrimitiveTypeNode, isTypeScriptLibType, + isExportedModuleDeclaration, } from "@player-tools/xlr-utils"; import { ConversionError } from "./types"; @@ -124,6 +125,18 @@ export class TsConverter { /** Converts all exported objects to a XLR representation */ public convertSourceFile(sourceFile: ts.SourceFile) { const declarations = sourceFile.statements.filter(isTopLevelNode); + const exportedModules = sourceFile.statements.filter((s) => + isExportedModuleDeclaration(s), + ); + + declarations.push( + ...exportedModules.flatMap((module) => { + if (module.body && ts.isModuleBlock(module.body)) { + return module.body.statements.filter(isTopLevelNode); + } + return []; + }), + ); const types = declarations .filter((declaration) => isExportedDeclaration(declaration)) diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index a0d1ac80..3c0fc559 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -31,6 +31,15 @@ export function isExportedDeclaration(node: ts.Statement): boolean { return false; } +/** + * Returns if the top level declaration is exported + */ +export function isExportedModuleDeclaration( + node: ts.Statement, +): node is ts.ModuleDeclaration { + return isExportedDeclaration(node) && ts.isModuleDeclaration(node); +} + /** * Returns if the node is exported from the source file */ From bcb9b51c6e1cfd262a994f94d61bc44362c1c115 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Fri, 27 Feb 2026 15:23:04 -0800 Subject: [PATCH 08/14] Fix issue with cross namespace collisions for type exports and variable names with plugin exports --- cli/src/commands/xlr/compile.ts | 4 +- cli/src/utils/xlr/visitors/plugin.ts | 12 ++++- .../__snapshots__/player.test.ts.snap | 52 +++++++++---------- xlr/converters/src/ts-to-xlr.ts | 33 ++++++++---- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/cli/src/commands/xlr/compile.ts b/cli/src/commands/xlr/compile.ts index d884dc4d..73bc8f48 100644 --- a/cli/src/commands/xlr/compile.ts +++ b/cli/src/commands/xlr/compile.ts @@ -155,7 +155,7 @@ export default class XLRCompile extends BaseCommand { const tsManifestFile = `${[...(capabilities.capabilities?.values() ?? [])] .flat(2) .map((capability) => { - return `const ${capability} = require("./${capability}.json")`; + return `const ${capability.replace(".", "_")} = require("./${capability}.json")`; }) .join("\n")} @@ -164,7 +164,7 @@ export default class XLRCompile extends BaseCommand { "capabilities": { ${[...(capabilities.capabilities?.entries() ?? [])] .map(([capabilityName, provides]) => { - return `"${capabilityName}":[${provides.join(",")}],`; + return `"${capabilityName}":[${provides.join(",").replaceAll(".", "_")}],`; }) .join("\n\t\t")} }, diff --git a/cli/src/utils/xlr/visitors/plugin.ts b/cli/src/utils/xlr/visitors/plugin.ts index 00acac44..6df081d0 100644 --- a/cli/src/utils/xlr/visitors/plugin.ts +++ b/cli/src/utils/xlr/visitors/plugin.ts @@ -112,6 +112,7 @@ function runPlayerPostProcessing( */ function generateXLR( node: ts.Node, + capabilityType: string, checker: ts.TypeChecker, converter: TsConverter, outputDirectory: string, @@ -126,7 +127,7 @@ function generateXLR( capabilityDescription, checker, ); - const capabilityName = capabilityDescription?.name ?? "error"; + const capabilityName = `${capabilityType}.${capabilityDescription?.name ?? "error"}`; fs.writeFileSync( path.join(outputDirectory, `${capabilityName}.json`), JSON.stringify(capabilityDescription, undefined, 4), @@ -194,7 +195,13 @@ export function pluginVisitor(args: VisitorProps): Manifest | undefined { if (ts.isTupleTypeNode(exportedCapabilities)) { const capabilityNames = exportedCapabilities.elements.map( (element) => - generateXLR(element, checker, converter, outputDirectory), + generateXLR( + element, + capabilityType, + checker, + converter, + outputDirectory, + ), ); provides.set(capabilityType, capabilityNames); @@ -204,6 +211,7 @@ export function pluginVisitor(args: VisitorProps): Manifest | undefined { ) { const capabilityName = generateXLR( exportedCapabilities, + capabilityType, checker, converter, outputDirectory, diff --git a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap index f333bd8f..ea75299a 100644 --- a/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap +++ b/xlr/converters/src/__tests__/__snapshots__/player.test.ts.snap @@ -5310,7 +5310,6 @@ Any reference to _index_ is replaced with the current iteration index.", "displayTarget": { "node": { "description": "Where the error should be displayed", - "genericTokens": undefined, "name": "DisplayTarget", "or": [ { @@ -5326,7 +5325,6 @@ Any reference to _index_ is replaced with the current iteration index.", "type": "string", }, ], - "source": undefined, "title": "Reference.displayTarget", "type": "or", }, @@ -5343,7 +5341,6 @@ Any reference to _index_ is replaced with the current iteration index.", "ref": { "node": { "description": "The binding to associate this validation with", - "genericArguments": undefined, "ref": "Binding", "title": "CrossfieldReference.ref", "type": "ref", @@ -5353,7 +5350,6 @@ Any reference to _index_ is replaced with the current iteration index.", "severity": { "node": { "description": "An optional means of overriding the default severity of the validation if triggered", - "genericTokens": undefined, "name": "Severity", "or": [ { @@ -5365,7 +5361,6 @@ Any reference to _index_ is replaced with the current iteration index.", "type": "string", }, ], - "source": undefined, "title": "Reference.severity", "type": "or", }, @@ -5374,7 +5369,6 @@ Any reference to _index_ is replaced with the current iteration index.", "trigger": { "node": { "description": "When to run this particular validation", - "genericTokens": undefined, "name": "Trigger", "or": [ { @@ -5390,7 +5384,6 @@ Any reference to _index_ is replaced with the current iteration index.", "type": "string", }, ], - "source": undefined, "title": "Reference.trigger", "type": "or", }, @@ -6463,10 +6456,8 @@ This will be used to lookup the proper handler", "type": "object", }, "description": "A Node describes a specific object in the tree", - "genericTokens": undefined, "name": "Node", "properties": {}, - "source": undefined, "title": "Node", "type": "object", }, @@ -6512,7 +6503,6 @@ Any reads for this property will result in this default value being written to t }, "description": "A reference to a specific data format to use. If none is specified, will fallback to that of the base type", - "genericTokens": undefined, "name": "Formatting.Reference", "properties": { "type": { @@ -6524,7 +6514,6 @@ If none is specified, will fallback to that of the base type", "required": true, }, }, - "source": undefined, "title": "DataType.format", "type": "object", }, @@ -6555,7 +6544,6 @@ These will add to any base validations associated with the "type"", "type": "unknown", }, "description": "A reference to a validation object", - "genericTokens": undefined, "name": "Validation.Reference", "properties": { "dataTarget": { @@ -6661,7 +6649,6 @@ This will be used to lookup the proper handler", "required": true, }, }, - "source": undefined, "title": "Reference", "type": "object", }, @@ -6671,15 +6658,12 @@ This will be used to lookup the proper handler", "required": false, }, }, - "source": undefined, "title": "DataType", "type": "object", }, "description": "The ROOT object is the top level object to use", - "genericTokens": undefined, "name": "Node", "properties": {}, - "source": undefined, "title": "Schema.ROOT", "type": "object", }, @@ -7070,14 +7054,16 @@ This will be used to lookup the proper handler", "type": "object", }, "description": "A Node describes a specific object in the tree", + "genericTokens": undefined, "name": "Node", "properties": {}, + "source": undefined, "title": "Node", "type": "object", }, "description": "The authored schema object in the JSON payload", "genericTokens": undefined, - "name": "Schema", + "name": "Schema.Schema", "properties": { "ROOT": { "node": { @@ -7116,6 +7102,7 @@ Any reads for this property will result in this default value being written to t }, "description": "A reference to a specific data format to use. If none is specified, will fallback to that of the base type", + "genericTokens": undefined, "name": "Formatting.Reference", "properties": { "type": { @@ -7127,6 +7114,7 @@ If none is specified, will fallback to that of the base type", "required": true, }, }, + "source": undefined, "title": "DataType.format", "type": "object", }, @@ -7157,6 +7145,7 @@ These will add to any base validations associated with the "type"", "type": "unknown", }, "description": "A reference to a validation object", + "genericTokens": undefined, "name": "Validation.Reference", "properties": { "dataTarget": { @@ -7182,6 +7171,7 @@ In the off chance you'd like this validator to run against the formatted value ( "displayTarget": { "node": { "description": "Where the error should be displayed", + "genericTokens": undefined, "name": "DisplayTarget", "or": [ { @@ -7197,6 +7187,7 @@ In the off chance you'd like this validator to run against the formatted value ( "type": "string", }, ], + "source": undefined, "title": "Reference.displayTarget", "type": "or", }, @@ -7213,6 +7204,7 @@ In the off chance you'd like this validator to run against the formatted value ( "severity": { "node": { "description": "An optional means of overriding the default severity of the validation if triggered", + "genericTokens": undefined, "name": "Severity", "or": [ { @@ -7224,6 +7216,7 @@ In the off chance you'd like this validator to run against the formatted value ( "type": "string", }, ], + "source": undefined, "title": "Reference.severity", "type": "or", }, @@ -7232,6 +7225,7 @@ In the off chance you'd like this validator to run against the formatted value ( "trigger": { "node": { "description": "When to run this particular validation", + "genericTokens": undefined, "name": "Trigger", "or": [ { @@ -7247,6 +7241,7 @@ In the off chance you'd like this validator to run against the formatted value ( "type": "string", }, ], + "source": undefined, "title": "Reference.trigger", "type": "or", }, @@ -7262,6 +7257,7 @@ This will be used to lookup the proper handler", "required": true, }, }, + "source": undefined, "title": "Reference", "type": "object", }, @@ -7271,12 +7267,15 @@ This will be used to lookup the proper handler", "required": false, }, }, + "source": undefined, "title": "DataType", "type": "object", }, "description": "The ROOT object is the top level object to use", + "genericTokens": undefined, "name": "Node", "properties": {}, + "source": undefined, "title": "Schema.ROOT", "type": "object", }, @@ -7483,7 +7482,7 @@ This will be used to lookup the proper handler", }, "description": "A Node describes a specific object in the tree", "genericTokens": undefined, - "name": "Node", + "name": "Schema.Node", "properties": {}, "source": undefined, "title": "Node", @@ -7505,7 +7504,7 @@ This will be used to lookup the proper handler", "symbol": "T", }, ], - "name": "DataType", + "name": "Schema.DataType", "properties": { "default": { "node": { @@ -7687,7 +7686,7 @@ This will be used to lookup the proper handler", "additionalProperties": false, "description": "Helper to compliment \`Schema.DataType\` to provide a way to export a reference to a data type instead of the whole object", "genericTokens": undefined, - "name": "DataTypeRef", + "name": "Language.DataTypeRef", "properties": { "type": { "node": { @@ -7708,7 +7707,7 @@ This will be used to lookup the proper handler", }, "description": "A reference to a specific formatter", "genericTokens": undefined, - "name": "Reference", + "name": "Formatting.Reference", "properties": { "type": { "node": { @@ -7728,7 +7727,7 @@ This will be used to lookup the proper handler", Warning validations are reserved for errors that could be ignored by the user without consequence Errors must be fixed before proceeding", "genericTokens": undefined, - "name": "Severity", + "name": "Validation.Severity", "or": [ { "const": "error", @@ -7750,7 +7749,7 @@ load - only check once the first time the binding appears on screen change - check anytime the data changes navigation - check once the user attempts to navigate away from a view", "genericTokens": undefined, - "name": "Trigger", + "name": "Validation.Trigger", "or": [ { "const": "navigation", @@ -7775,7 +7774,7 @@ navigation - check once the user attempts to navigate away from a view", - \`section\` is used to display a message at a parent node that is designated as a "section" - \`page\` a special section used to display a message at the top of the page.", "genericTokens": undefined, - "name": "DisplayTarget", + "name": "Validation.DisplayTarget", "or": [ { "const": "page", @@ -7800,7 +7799,7 @@ navigation - check once the user attempts to navigate away from a view", }, "description": "A reference to a validation object", "genericTokens": undefined, - "name": "Reference", + "name": "Validation.Reference", "properties": { "dataTarget": { "node": { @@ -7914,7 +7913,7 @@ This will be used to lookup the proper handler", "type": "unknown", }, "genericTokens": undefined, - "name": "CrossfieldReference", + "name": "Validation.CrossfieldReference", "properties": { "dataTarget": { "node": { @@ -7958,6 +7957,7 @@ This will be used to lookup the proper handler", "ref": { "node": { "description": "The binding to associate this validation with", + "genericArguments": undefined, "ref": "Binding", "title": "CrossfieldReference.ref", "type": "ref", diff --git a/xlr/converters/src/ts-to-xlr.ts b/xlr/converters/src/ts-to-xlr.ts index d0bd11a7..9281aa58 100644 --- a/xlr/converters/src/ts-to-xlr.ts +++ b/xlr/converters/src/ts-to-xlr.ts @@ -123,26 +123,41 @@ export class TsConverter { } /** Converts all exported objects to a XLR representation */ - public convertSourceFile(sourceFile: ts.SourceFile) { + public convertSourceFile(sourceFile: ts.SourceFile): { + data: { + version: number; + types: NonNullable[]; + }; + convertedTypes: string[]; + } { const declarations = sourceFile.statements.filter(isTopLevelNode); - const exportedModules = sourceFile.statements.filter((s) => - isExportedModuleDeclaration(s), - ); - declarations.push( - ...exportedModules.flatMap((module) => { + const namespacedTypes: NonNullable[] = sourceFile.statements + .filter((s) => isExportedModuleDeclaration(s)) + .flatMap((module) => { if (module.body && ts.isModuleBlock(module.body)) { - return module.body.statements.filter(isTopLevelNode); + const nameSpaceName = module.name.text; + return module.body.statements + .filter(isExportedDeclaration) + .filter(isTopLevelNode) + .map((statement) => { + const convertedNode = this.convertTopLevelNode(statement); + return { + ...convertedNode, + name: `${nameSpaceName}.${convertedNode.name}`, + } as NamedType; + }); } return []; - }), - ); + }); const types = declarations .filter((declaration) => isExportedDeclaration(declaration)) .map((statement) => this.convertTopLevelNode(statement) as NamedType) .filter((v: T): v is NonNullable => !!v); + types.push(...namespacedTypes); + return { data: { version: 1, types }, convertedTypes: types.map(({ name }) => name), From 2a9be366ea75e9861079c0a36f013bf6c3b3e54d Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Fri, 27 Feb 2026 16:00:20 -0800 Subject: [PATCH 09/14] Update reference XLRs & fix associated tests --- common/static-xlrs/src/core/xlr/Asset.json | 2 +- .../src/core/xlr/AssetBinding.json | 2 +- .../static-xlrs/src/core/xlr/AssetSwitch.json | 28 +- .../src/core/xlr/AssetWrapper.json | 2 +- .../src/core/xlr/AssetWrapperOrSwitch.json | 28 +- common/static-xlrs/src/core/xlr/Binding.json | 2 +- .../static-xlrs/src/core/xlr/BindingRef.json | 2 +- .../static-xlrs/src/core/xlr/DataModel.json | 2 +- .../src/core/xlr/DynamicSwitch.json | 11 +- .../static-xlrs/src/core/xlr/Expression.json | 2 +- .../src/core/xlr/ExpressionObject.json | 2 +- .../src/core/xlr/ExpressionRef.json | 2 +- common/static-xlrs/src/core/xlr/Flow.json | 1779 +++++++++++++---- .../static-xlrs/src/core/xlr/FlowResult.json | 12 +- .../src/core/xlr/Formatting.Reference.json | 19 + .../src/core/xlr/Formatting_Reference.json | 19 + .../src/core/xlr/Language.DataTypeRef.json | 17 + .../src/core/xlr/Language_DataTypeRef.json | 17 + .../static-xlrs/src/core/xlr/Navigation.json | 190 +- .../src/core/xlr/NavigationBaseState.json | 19 +- .../src/core/xlr/NavigationFlow.json | 188 +- .../core/xlr/NavigationFlowActionState.json | 8 +- .../xlr/NavigationFlowAsyncActionState.json | 128 ++ .../src/core/xlr/NavigationFlowEndState.json | 10 +- .../core/xlr/NavigationFlowExternalState.json | 12 +- .../src/core/xlr/NavigationFlowFlowState.json | 8 +- .../src/core/xlr/NavigationFlowState.json | 180 +- .../core/xlr/NavigationFlowTransition.json | 2 +- .../NavigationFlowTransitionableState.json | 21 +- .../src/core/xlr/NavigationFlowViewState.json | 12 +- .../src/core/xlr/Schema.ArrayType.json | 203 ++ .../src/core/xlr/Schema.DataType.json | 198 ++ .../src/core/xlr/Schema.DataTypes.json | 611 ++++++ .../static-xlrs/src/core/xlr/Schema.Node.json | 618 ++++++ .../src/core/xlr/Schema.RecordType.json | 203 ++ .../src/core/xlr/Schema.Schema.json | 1247 ++++++++++++ .../src/core/xlr/Schema_ArrayType.json | 203 ++ .../src/core/xlr/Schema_DataType.json | 198 ++ .../src/core/xlr/Schema_DataTypes.json | 611 ++++++ .../static-xlrs/src/core/xlr/Schema_Node.json | 618 ++++++ .../src/core/xlr/Schema_RecordType.json | 203 ++ .../src/core/xlr/Schema_Schema.json | 1247 ++++++++++++ .../src/core/xlr/StaticSwitch.json | 11 +- common/static-xlrs/src/core/xlr/Switch.json | 9 +- .../static-xlrs/src/core/xlr/SwitchCase.json | 5 +- .../static-xlrs/src/core/xlr/Templatable.json | 22 +- common/static-xlrs/src/core/xlr/Template.json | 20 +- .../xlr/Validation.CrossfieldReference.json | 127 ++ .../core/xlr/Validation.DisplayTarget.json | 20 + .../src/core/xlr/Validation.Reference.json | 129 ++ .../src/core/xlr/Validation.Severity.json | 16 + .../src/core/xlr/Validation.Trigger.json | 20 + .../xlr/Validation_CrossfieldReference.json | 127 ++ .../core/xlr/Validation_DisplayTarget.json | 20 + .../src/core/xlr/Validation_Reference.json | 129 ++ .../src/core/xlr/Validation_Severity.json | 16 + .../src/core/xlr/Validation_Trigger.json | 20 + common/static-xlrs/src/core/xlr/View.json | 47 +- common/static-xlrs/src/core/xlr/manifest.js | 131 +- common/static-xlrs/src/core/xlr/manifest.json | 28 +- .../static-xlrs/src/expression/xlr/ceil.json | 0 .../src/expression/xlr/concat.json | 0 .../src/expression/xlr/containsAny.json | 0 .../src/expression/xlr/findProperty.json | 0 .../src/expression/xlr/findPropertyIndex.json | 0 .../static-xlrs/src/expression/xlr/floor.json | 0 .../src/expression/xlr/isEmpty.json | 0 .../src/expression/xlr/isNotEmpty.json | 0 .../src/expression/xlr/length.json | 0 .../src/expression/xlr/lowerCase.json | 0 common/static-xlrs/src/index.d.ts | 2 + common/static-xlrs/src/index.js | 2 + .../src/types/xlr/DataTypes.BooleanType.json | 72 + .../types/xlr/DataTypes.CollectionType.json | 39 + .../src/types/xlr/DataTypes.DateType.json | 55 + .../types/xlr/DataTypes.IntegerNNType.json | 75 + .../types/xlr/DataTypes.IntegerPosType.json | 75 + .../src/types/xlr/DataTypes.IntegerType.json | 55 + .../src/types/xlr/DataTypes.PhoneType.json | 55 + .../src/types/xlr/DataTypes.StringType.json | 62 + .../src/types/xlr/Formatters.commaNumber.json | 28 + .../src/types/xlr/Formatters.currency.json | 44 + .../src/types/xlr/Formatters.date.json | 28 + .../src/types/xlr/Formatters.integer.json | 14 + .../src/types/xlr/Formatters.phone.json | 11 + .../src/types/xlr/Validators.collection.json | 6 + .../src/types/xlr/Validators.email.json | 6 + .../src/types/xlr/Validators.expression.json | 23 + .../src/types/xlr/Validators.integer.json | 6 + .../src/types/xlr/Validators.length.json | 49 + .../src/types/xlr/Validators.max.json | 22 + .../src/types/xlr/Validators.min.json | 22 + .../src/types/xlr/Validators.oneOf.json | 25 + .../src/types/xlr/Validators.phone.json | 6 + .../src/types/xlr/Validators.readonly.json | 6 + .../src/types/xlr/Validators.regex.json | 22 + .../src/types/xlr/Validators.required.json | 32 + .../src/types/xlr/Validators.string.json | 6 + .../src/types/xlr/Validators.zip.json | 6 + .../static-xlrs/src/types/xlr/manifest.d.ts | 3 + common/static-xlrs/src/types/xlr/manifest.js | 42 + .../static-xlrs/src/types/xlr/manifest.json | 51 + .../__snapshots__/service.test.ts.snap | 16 +- .../asset-wrapper-array-plugin.test.ts | 4 +- .../missing-asset-wrapper-plugin.test.ts | 4 +- .../__tests__/__snapshots__/sdk.test.ts.snap | 1408 ++++++++++++- xlr/sdk/src/__tests__/sdk.test.ts | 5 +- 107 files changed, 11413 insertions(+), 757 deletions(-) create mode 100644 common/static-xlrs/src/core/xlr/Formatting.Reference.json create mode 100644 common/static-xlrs/src/core/xlr/Formatting_Reference.json create mode 100644 common/static-xlrs/src/core/xlr/Language.DataTypeRef.json create mode 100644 common/static-xlrs/src/core/xlr/Language_DataTypeRef.json create mode 100644 common/static-xlrs/src/core/xlr/NavigationFlowAsyncActionState.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.ArrayType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.DataType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.DataTypes.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.Node.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.RecordType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema.Schema.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_ArrayType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_DataType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_DataTypes.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_Node.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_RecordType.json create mode 100644 common/static-xlrs/src/core/xlr/Schema_Schema.json create mode 100644 common/static-xlrs/src/core/xlr/Validation.CrossfieldReference.json create mode 100644 common/static-xlrs/src/core/xlr/Validation.DisplayTarget.json create mode 100644 common/static-xlrs/src/core/xlr/Validation.Reference.json create mode 100644 common/static-xlrs/src/core/xlr/Validation.Severity.json create mode 100644 common/static-xlrs/src/core/xlr/Validation.Trigger.json create mode 100644 common/static-xlrs/src/core/xlr/Validation_CrossfieldReference.json create mode 100644 common/static-xlrs/src/core/xlr/Validation_DisplayTarget.json create mode 100644 common/static-xlrs/src/core/xlr/Validation_Reference.json create mode 100644 common/static-xlrs/src/core/xlr/Validation_Severity.json create mode 100644 common/static-xlrs/src/core/xlr/Validation_Trigger.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/ceil.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/concat.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/containsAny.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/findProperty.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/findPropertyIndex.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/floor.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/isEmpty.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/isNotEmpty.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/length.json mode change 100755 => 100644 common/static-xlrs/src/expression/xlr/lowerCase.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.BooleanType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.CollectionType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.DateType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.IntegerNNType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.IntegerPosType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.IntegerType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.PhoneType.json create mode 100644 common/static-xlrs/src/types/xlr/DataTypes.StringType.json create mode 100644 common/static-xlrs/src/types/xlr/Formatters.commaNumber.json create mode 100644 common/static-xlrs/src/types/xlr/Formatters.currency.json create mode 100644 common/static-xlrs/src/types/xlr/Formatters.date.json create mode 100644 common/static-xlrs/src/types/xlr/Formatters.integer.json create mode 100644 common/static-xlrs/src/types/xlr/Formatters.phone.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.collection.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.email.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.expression.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.integer.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.length.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.max.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.min.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.oneOf.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.phone.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.readonly.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.regex.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.required.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.string.json create mode 100644 common/static-xlrs/src/types/xlr/Validators.zip.json create mode 100644 common/static-xlrs/src/types/xlr/manifest.d.ts create mode 100644 common/static-xlrs/src/types/xlr/manifest.js create mode 100644 common/static-xlrs/src/types/xlr/manifest.json diff --git a/common/static-xlrs/src/core/xlr/Asset.json b/common/static-xlrs/src/core/xlr/Asset.json index 2398e4d3..d5a4e36d 100644 --- a/common/static-xlrs/src/core/xlr/Asset.json +++ b/common/static-xlrs/src/core/xlr/Asset.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "Asset", "type": "object", - "source": "src/index.ts", "properties": { "id": { "required": true, diff --git a/common/static-xlrs/src/core/xlr/AssetBinding.json b/common/static-xlrs/src/core/xlr/AssetBinding.json index ba2dd31a..f4de5bc3 100644 --- a/common/static-xlrs/src/core/xlr/AssetBinding.json +++ b/common/static-xlrs/src/core/xlr/AssetBinding.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "AssetBinding", "type": "object", - "source": "src/index.ts", "properties": { "binding": { "required": true, diff --git a/common/static-xlrs/src/core/xlr/AssetSwitch.json b/common/static-xlrs/src/core/xlr/AssetSwitch.json index 3f0f4ba6..18f73f97 100644 --- a/common/static-xlrs/src/core/xlr/AssetSwitch.json +++ b/common/static-xlrs/src/core/xlr/AssetSwitch.json @@ -1,23 +1,23 @@ { - "name": "AssetSwitch", "source": "src/index.ts", + "name": "AssetSwitch", "type": "or", "or": [ { - "name": "StaticSwitch", - "type": "object", "source": "src/index.ts", + "name": "StaticSwitch", + "type": "object", "properties": { "staticSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -40,8 +40,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", @@ -101,20 +100,20 @@ ] }, { - "name": "DynamicSwitch", - "type": "object", "source": "src/index.ts", + "name": "DynamicSwitch", + "type": "object", "properties": { "dynamicSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -137,8 +136,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/AssetWrapper.json b/common/static-xlrs/src/core/xlr/AssetWrapper.json index 74be978f..7b6a206d 100644 --- a/common/static-xlrs/src/core/xlr/AssetWrapper.json +++ b/common/static-xlrs/src/core/xlr/AssetWrapper.json @@ -1,6 +1,6 @@ { - "name": "AssetWrapper", "source": "src/index.ts", + "name": "AssetWrapper", "type": "object", "properties": { "asset": { diff --git a/common/static-xlrs/src/core/xlr/AssetWrapperOrSwitch.json b/common/static-xlrs/src/core/xlr/AssetWrapperOrSwitch.json index c7fb328d..bd688c11 100644 --- a/common/static-xlrs/src/core/xlr/AssetWrapperOrSwitch.json +++ b/common/static-xlrs/src/core/xlr/AssetWrapperOrSwitch.json @@ -1,6 +1,6 @@ { - "name": "AssetWrapperOrSwitch", "source": "src/index.ts", + "name": "AssetWrapperOrSwitch", "type": "or", "or": [ { @@ -44,20 +44,20 @@ "type": "and", "and": [ { - "name": "StaticSwitch", - "type": "object", "source": "src/index.ts", + "name": "StaticSwitch", + "type": "object", "properties": { "staticSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -80,8 +80,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", @@ -168,20 +167,20 @@ "type": "and", "and": [ { - "name": "DynamicSwitch", - "type": "object", "source": "src/index.ts", + "name": "DynamicSwitch", + "type": "object", "properties": { "dynamicSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -204,8 +203,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/Binding.json b/common/static-xlrs/src/core/xlr/Binding.json index 025f5151..16f00533 100644 --- a/common/static-xlrs/src/core/xlr/Binding.json +++ b/common/static-xlrs/src/core/xlr/Binding.json @@ -1,6 +1,6 @@ { - "name": "Binding", "source": "src/index.ts", + "name": "Binding", "type": "string", "title": "Binding", "description": "Bindings describe locations in the data model." diff --git a/common/static-xlrs/src/core/xlr/BindingRef.json b/common/static-xlrs/src/core/xlr/BindingRef.json index d79e4ed3..8af7d73f 100644 --- a/common/static-xlrs/src/core/xlr/BindingRef.json +++ b/common/static-xlrs/src/core/xlr/BindingRef.json @@ -1,6 +1,6 @@ { - "name": "BindingRef", "source": "src/index.ts", + "name": "BindingRef", "type": "template", "format": "{{.*}}", "title": "BindingRef" diff --git a/common/static-xlrs/src/core/xlr/DataModel.json b/common/static-xlrs/src/core/xlr/DataModel.json index c545c9ae..6c124048 100644 --- a/common/static-xlrs/src/core/xlr/DataModel.json +++ b/common/static-xlrs/src/core/xlr/DataModel.json @@ -1,6 +1,6 @@ { - "name": "DataModel", "source": "src/index.ts", + "name": "DataModel", "type": "record", "keyType": { "type": "any" diff --git a/common/static-xlrs/src/core/xlr/DynamicSwitch.json b/common/static-xlrs/src/core/xlr/DynamicSwitch.json index 1b05ee90..2be5b9bc 100644 --- a/common/static-xlrs/src/core/xlr/DynamicSwitch.json +++ b/common/static-xlrs/src/core/xlr/DynamicSwitch.json @@ -1,18 +1,18 @@ { + "source": "src/index.ts", "name": "DynamicSwitch", "type": "object", - "source": "src/index.ts", "properties": { "dynamicSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -35,8 +35,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/Expression.json b/common/static-xlrs/src/core/xlr/Expression.json index 215a7bc5..f1892bbd 100644 --- a/common/static-xlrs/src/core/xlr/Expression.json +++ b/common/static-xlrs/src/core/xlr/Expression.json @@ -1,6 +1,6 @@ { - "name": "Expression", "source": "src/index.ts", + "name": "Expression", "type": "or", "or": [ { diff --git a/common/static-xlrs/src/core/xlr/ExpressionObject.json b/common/static-xlrs/src/core/xlr/ExpressionObject.json index b0ca2b73..79269013 100644 --- a/common/static-xlrs/src/core/xlr/ExpressionObject.json +++ b/common/static-xlrs/src/core/xlr/ExpressionObject.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, diff --git a/common/static-xlrs/src/core/xlr/ExpressionRef.json b/common/static-xlrs/src/core/xlr/ExpressionRef.json index 1c248559..2a339fd5 100644 --- a/common/static-xlrs/src/core/xlr/ExpressionRef.json +++ b/common/static-xlrs/src/core/xlr/ExpressionRef.json @@ -1,6 +1,6 @@ { - "name": "ExpressionRef", "source": "src/index.ts", + "name": "ExpressionRef", "type": "template", "format": "@[.*]@", "title": "ExpressionRef" diff --git a/common/static-xlrs/src/core/xlr/Flow.json b/common/static-xlrs/src/core/xlr/Flow.json index 963bb549..f0316130 100644 --- a/common/static-xlrs/src/core/xlr/Flow.json +++ b/common/static-xlrs/src/core/xlr/Flow.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "Flow", "type": "object", - "source": "src/index.ts", "properties": { "id": { "required": true, @@ -16,8 +16,8 @@ "node": { "type": "array", "elementType": { - "name": "View", "source": "src/index.ts", + "name": "View", "type": "conditional", "check": { "left": { @@ -45,7 +45,7 @@ "node": { "type": "array", "elementType": { - "name": "CrossfieldReference", + "name": "Validation.CrossfieldReference", "type": "object", "properties": { "type": { @@ -72,13 +72,11 @@ "or": [ { "type": "string", - "const": "error", - "title": "Severity" + "const": "error" }, { "type": "string", - "const": "warning", - "title": "Severity" + "const": "warning" } ], "title": "Reference.severity", @@ -93,18 +91,15 @@ "or": [ { "type": "string", - "const": "navigation", - "title": "Trigger" + "const": "navigation" }, { "type": "string", - "const": "change", - "title": "Trigger" + "const": "change" }, { "type": "string", - "const": "load", - "title": "Trigger" + "const": "load" } ], "title": "Reference.trigger", @@ -127,24 +122,40 @@ "or": [ { "type": "string", - "const": "page", - "title": "DisplayTarget" + "const": "page" }, { "type": "string", - "const": "section", - "title": "DisplayTarget" + "const": "section" }, { "type": "string", - "const": "field", - "title": "DisplayTarget" + "const": "field" } ], "title": "Reference.displayTarget", "description": "Where the error should be displayed" } }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + }, "ref": { "required": false, "node": { @@ -196,7 +207,7 @@ "schema": { "required": false, "node": { - "name": "Schema", + "name": "Schema.Schema", "type": "object", "properties": { "ROOT": { @@ -206,166 +217,989 @@ "type": "object", "properties": {}, "additionalProperties": { - "name": "DataType", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "DataType.type", - "description": "The reference of the base type to use" - } - }, - "isArray": { - "required": false, - "node": { - "type": "boolean", - "title": "DataType.isArray", - "description": "The referenced object represents an array rather than an object" - } - }, - "validation": { - "required": false, - "node": { - "type": "array", - "elementType": { - "name": "Reference", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "Reference.type", - "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" - } - }, - "message": { - "required": false, - "node": { - "type": "string", - "title": "Reference.message", - "description": "An optional means of overriding the default message if the validation is triggered" - } - }, - "severity": { - "required": false, - "node": { - "name": "Severity", - "type": "or", - "or": [ - { - "type": "string", - "const": "error", - "title": "Severity" - }, - { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { "type": "string", - "const": "warning", - "title": "Severity" + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" } - ], - "title": "Reference.severity", - "description": "An optional means of overriding the default severity of the validation if triggered" - } - }, - "trigger": { - "required": false, - "node": { - "name": "Trigger", - "type": "or", - "or": [ - { - "type": "string", - "const": "navigation", - "title": "Trigger" - }, - { - "type": "string", - "const": "change", - "title": "Trigger" - }, - { + }, + "message": { + "required": false, + "node": { "type": "string", - "const": "load", - "title": "Trigger" + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" } - ], - "title": "Reference.trigger", - "description": "When to run this particular validation" - } - }, - "dataTarget": { - "required": false, - "node": { - "type": "or", - "or": [ - { - "type": "string", - "const": "formatted", - "title": "Reference.dataTarget" - }, - { - "type": "string", - "const": "deformatted", - "title": "Reference.dataTarget" + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" } - ], - "title": "Reference.dataTarget", - "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" - } - }, - "displayTarget": { - "required": false, - "node": { - "name": "DisplayTarget", - "type": "or", - "or": [ - { - "type": "string", - "const": "page", - "title": "DisplayTarget" - }, - { - "type": "string", - "const": "section", - "title": "DisplayTarget" - }, - { - "type": "string", - "const": "field", - "title": "DisplayTarget" + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" } - ], - "title": "Reference.displayTarget", - "description": "Where the error should be displayed" + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } } - } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" }, - "additionalProperties": { + "default": { "type": "unknown" - }, - "title": "Reference", - "description": "A reference to a validation object" - }, - "title": "DataType.validation", - "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" - } + } + } + ] }, - "format": { - "required": false, - "node": { - "name": "Reference", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "Reference.type", - "description": "The name of the formatter (and de-formatter) to use" - } + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Schema.ROOT", + "description": "The ROOT object is the top level object to use" + } + } + }, + "additionalProperties": { + "name": "Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } } }, "additionalProperties": { @@ -383,230 +1217,235 @@ "title": "DataType.default", "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } } }, "additionalProperties": { "type": "unknown" }, - "title": "DataType", - "description": "Each prop in the object can have a specific DataType", - "genericTokens": [ - { - "symbol": "T", - "constraints": { - "type": "any" - }, - "default": { - "type": "unknown" - } - } - ] - }, - "title": "Schema.ROOT", - "description": "The ROOT object is the top level object to use" - } - } - }, - "additionalProperties": { - "name": "Node", - "type": "object", - "properties": {}, - "additionalProperties": { - "name": "DataType", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "DataType.type", - "description": "The reference of the base type to use" - } - }, - "isArray": { - "required": false, - "node": { - "type": "boolean", - "title": "DataType.isArray", - "description": "The referenced object represents an array rather than an object" - } + "title": "RecordType", + "description": "Determines if the Datatype is a record object" }, - "validation": { - "required": false, - "node": { - "type": "array", - "elementType": { - "name": "Reference", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "Reference.type", - "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" - } - }, - "message": { - "required": false, - "node": { - "type": "string", - "title": "Reference.message", - "description": "An optional means of overriding the default message if the validation is triggered" - } - }, - "severity": { - "required": false, - "node": { - "name": "Severity", - "type": "or", - "or": [ - { - "type": "string", - "const": "error", - "title": "Severity" - }, - { + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { "type": "string", - "const": "warning", - "title": "Severity" + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" } - ], - "title": "Reference.severity", - "description": "An optional means of overriding the default severity of the validation if triggered" - } - }, - "trigger": { - "required": false, - "node": { - "name": "Trigger", - "type": "or", - "or": [ - { - "type": "string", - "const": "navigation", - "title": "Trigger" - }, - { - "type": "string", - "const": "change", - "title": "Trigger" - }, - { + }, + "message": { + "required": false, + "node": { "type": "string", - "const": "load", - "title": "Trigger" + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" } - ], - "title": "Reference.trigger", - "description": "When to run this particular validation" + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } } }, - "dataTarget": { - "required": false, - "node": { - "type": "or", - "or": [ - { - "type": "string", - "const": "formatted", - "title": "Reference.dataTarget" - }, - { - "type": "string", - "const": "deformatted", - "title": "Reference.dataTarget" - } - ], - "title": "Reference.dataTarget", - "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" - } + "additionalProperties": { + "type": "unknown" }, - "displayTarget": { - "required": false, - "node": { - "name": "DisplayTarget", - "type": "or", - "or": [ - { - "type": "string", - "const": "page", - "title": "DisplayTarget" - }, - { - "type": "string", - "const": "section", - "title": "DisplayTarget" - }, - { - "type": "string", - "const": "field", - "title": "DisplayTarget" - } - ], - "title": "Reference.displayTarget", - "description": "Where the error should be displayed" - } - } - }, - "additionalProperties": { - "type": "unknown" - }, - "title": "Reference", - "description": "A reference to a validation object" + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } }, - "title": "DataType.validation", - "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" - } - }, - "format": { - "required": false, - "node": { - "name": "Reference", - "type": "object", - "properties": { - "type": { - "required": true, - "node": { - "type": "string", - "title": "Reference.type", - "description": "The name of the formatter (and de-formatter) to use" - } + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." } }, - "additionalProperties": { - "type": "unknown" + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } }, - "title": "DataType.format", - "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" - } - }, - "default": { - "required": false, - "node": { - "type": "ref", - "ref": "T", - "title": "DataType.default", - "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." - } - } - }, - "additionalProperties": { - "type": "unknown" - }, - "title": "DataType", - "description": "Each prop in the object can have a specific DataType", - "genericTokens": [ - { - "symbol": "T", - "constraints": { - "type": "any" + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } }, - "default": { + "additionalProperties": { "type": "unknown" - } + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" } - ] + ], + "title": "DataTypes" }, "title": "Node", "description": "A Node describes a specific object in the tree" @@ -618,8 +1457,8 @@ "data": { "required": false, "node": { - "name": "DataModel", "source": "src/index.ts", + "name": "DataModel", "type": "record", "keyType": { "type": "any" @@ -634,8 +1473,8 @@ "navigation": { "required": true, "node": { - "name": "Navigation", "source": "src/index.ts", + "name": "Navigation", "type": "and", "and": [ { @@ -664,9 +1503,9 @@ "type": "string" }, { + "source": "src/index.ts", "name": "NavigationFlow", "type": "object", - "source": "src/index.ts", "properties": { "startState": { "required": true, @@ -687,9 +1526,9 @@ "title": "NavigationFlow.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -721,9 +1560,9 @@ "title": "NavigationFlow.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -759,9 +1598,9 @@ "ref": "Expression" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -778,14 +1617,14 @@ "description": "An object with an expression in it" }, { - "name": "NavigationFlowState", "source": "src/index.ts", + "name": "NavigationFlowState", "type": "or", "or": [ { + "source": "src/index.ts", "name": "NavigationFlowViewState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -815,9 +1654,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -849,9 +1688,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -882,8 +1721,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -916,14 +1755,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowViewState", "description": "A state representing a view" }, { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -953,9 +1794,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -987,9 +1828,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1026,14 +1867,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowEndState", "description": "An END state of the flow." }, { + "source": "src/index.ts", "name": "NavigationFlowFlowState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -1063,9 +1906,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1097,9 +1940,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1130,8 +1973,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -1156,9 +1999,9 @@ "title": "NavigationFlowFlowState" }, { + "source": "src/index.ts", "name": "NavigationFlowActionState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -1188,9 +2031,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1222,9 +2065,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1257,8 +2100,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -1276,9 +2119,137 @@ "description": "Action states execute an expression to determine the next state to transition to" }, { - "name": "NavigationFlowExternalState", + "source": "src/index.ts", + "name": "NavigationFlowAsyncActionState", "type": "object", + "properties": { + "_comment": { + "required": false, + "node": { + "type": "string", + "title": "CommentBase._comment", + "description": "Add comments that will not be processing, but are useful for code explanation" + } + }, + "state_type": { + "required": true, + "node": { + "type": "string", + "const": "ASYNC_ACTION", + "title": "NavigationBaseState.state_type", + "description": "A property to determine the type of state this is" + } + }, + "onStart": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onStart" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onStart", + "description": "An optional expression to run when this view renders" + } + }, + "onEnd": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onEnd" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onEnd", + "description": "An optional expression to run before view transition" + } + }, + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "NavigationFlowAsyncActionState.exp", + "description": "An expression to execute.\nThe return value determines the transition to take" + } + }, + "transitions": { + "required": true, + "node": { + "source": "src/index.ts", + "name": "NavigationFlowTransition", + "type": "record", + "keyType": { + "type": "string" + }, + "valueType": { + "type": "string" + }, + "title": "NavigationFlowTransitionableState.transitions", + "description": "A mapping of transition-name to FlowState name" + } + }, + "await": { + "required": true, + "node": { + "type": "boolean", + "title": "NavigationFlowAsyncActionState.await", + "description": "Whether the expression(s) should be awaited before transitioning" + } + } + }, + "additionalProperties": false, + "title": "NavigationFlowAsyncActionState", + "description": "Action states execute an expression to determine the next state to transition to" + }, + { "source": "src/index.ts", + "name": "NavigationFlowExternalState", + "type": "object", "properties": { "_comment": { "required": false, @@ -1308,9 +2279,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1342,9 +2313,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -1375,8 +2346,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -1397,7 +2368,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowExternalState", "description": "External Flow states represent states in the FSM that can't be resolved internally in Player.\nThe flow will wait for the embedded application to manage moving to the next state via a transition" } diff --git a/common/static-xlrs/src/core/xlr/FlowResult.json b/common/static-xlrs/src/core/xlr/FlowResult.json index 0c3706b0..a1d3f6f9 100644 --- a/common/static-xlrs/src/core/xlr/FlowResult.json +++ b/common/static-xlrs/src/core/xlr/FlowResult.json @@ -1,14 +1,14 @@ { + "source": "src/index.ts", "name": "FlowResult", "type": "object", - "source": "src/index.ts", "properties": { "endState": { "required": true, "node": { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -38,9 +38,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -72,9 +72,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -111,7 +111,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "FlowResult.endState", "description": "The outcome describes _how_ the flow ended (forwards, backwards, etc)" } diff --git a/common/static-xlrs/src/core/xlr/Formatting.Reference.json b/common/static-xlrs/src/core/xlr/Formatting.Reference.json new file mode 100644 index 00000000..b0ba8676 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Formatting.Reference.json @@ -0,0 +1,19 @@ +{ + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a specific formatter" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Formatting_Reference.json b/common/static-xlrs/src/core/xlr/Formatting_Reference.json new file mode 100644 index 00000000..b0ba8676 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Formatting_Reference.json @@ -0,0 +1,19 @@ +{ + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a specific formatter" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Language.DataTypeRef.json b/common/static-xlrs/src/core/xlr/Language.DataTypeRef.json new file mode 100644 index 00000000..c7b04632 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Language.DataTypeRef.json @@ -0,0 +1,17 @@ +{ + "name": "Language.DataTypeRef", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataTypeRef.type", + "description": "Name of the type in Player Core" + } + } + }, + "additionalProperties": false, + "title": "DataTypeRef", + "description": "Helper to compliment `Schema.DataType` to provide a way to export a reference to a data type instead of the whole object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Language_DataTypeRef.json b/common/static-xlrs/src/core/xlr/Language_DataTypeRef.json new file mode 100644 index 00000000..c7b04632 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Language_DataTypeRef.json @@ -0,0 +1,17 @@ +{ + "name": "Language.DataTypeRef", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataTypeRef.type", + "description": "Name of the type in Player Core" + } + } + }, + "additionalProperties": false, + "title": "DataTypeRef", + "description": "Helper to compliment `Schema.DataType` to provide a way to export a reference to a data type instead of the whole object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Navigation.json b/common/static-xlrs/src/core/xlr/Navigation.json index f9cb37bc..5bfa13cc 100644 --- a/common/static-xlrs/src/core/xlr/Navigation.json +++ b/common/static-xlrs/src/core/xlr/Navigation.json @@ -1,6 +1,6 @@ { - "name": "Navigation", "source": "src/index.ts", + "name": "Navigation", "type": "and", "and": [ { @@ -29,9 +29,9 @@ "type": "string" }, { + "source": "src/index.ts", "name": "NavigationFlow", "type": "object", - "source": "src/index.ts", "properties": { "startState": { "required": true, @@ -52,9 +52,9 @@ "title": "NavigationFlow.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -86,9 +86,9 @@ "title": "NavigationFlow.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -124,9 +124,9 @@ "ref": "Expression" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -143,14 +143,14 @@ "description": "An object with an expression in it" }, { - "name": "NavigationFlowState", "source": "src/index.ts", + "name": "NavigationFlowState", "type": "or", "or": [ { + "source": "src/index.ts", "name": "NavigationFlowViewState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -180,9 +180,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -214,9 +214,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -247,8 +247,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -281,14 +281,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowViewState", "description": "A state representing a view" }, { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -318,9 +320,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -352,9 +354,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -391,14 +393,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowEndState", "description": "An END state of the flow." }, { + "source": "src/index.ts", "name": "NavigationFlowFlowState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -428,9 +432,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -462,9 +466,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -495,8 +499,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -521,9 +525,9 @@ "title": "NavigationFlowFlowState" }, { + "source": "src/index.ts", "name": "NavigationFlowActionState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -553,9 +557,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -587,9 +591,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -622,8 +626,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -641,9 +645,137 @@ "description": "Action states execute an expression to determine the next state to transition to" }, { - "name": "NavigationFlowExternalState", + "source": "src/index.ts", + "name": "NavigationFlowAsyncActionState", "type": "object", + "properties": { + "_comment": { + "required": false, + "node": { + "type": "string", + "title": "CommentBase._comment", + "description": "Add comments that will not be processing, but are useful for code explanation" + } + }, + "state_type": { + "required": true, + "node": { + "type": "string", + "const": "ASYNC_ACTION", + "title": "NavigationBaseState.state_type", + "description": "A property to determine the type of state this is" + } + }, + "onStart": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onStart" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onStart", + "description": "An optional expression to run when this view renders" + } + }, + "onEnd": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onEnd" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onEnd", + "description": "An optional expression to run before view transition" + } + }, + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "NavigationFlowAsyncActionState.exp", + "description": "An expression to execute.\nThe return value determines the transition to take" + } + }, + "transitions": { + "required": true, + "node": { + "source": "src/index.ts", + "name": "NavigationFlowTransition", + "type": "record", + "keyType": { + "type": "string" + }, + "valueType": { + "type": "string" + }, + "title": "NavigationFlowTransitionableState.transitions", + "description": "A mapping of transition-name to FlowState name" + } + }, + "await": { + "required": true, + "node": { + "type": "boolean", + "title": "NavigationFlowAsyncActionState.await", + "description": "Whether the expression(s) should be awaited before transitioning" + } + } + }, + "additionalProperties": false, + "title": "NavigationFlowAsyncActionState", + "description": "Action states execute an expression to determine the next state to transition to" + }, + { "source": "src/index.ts", + "name": "NavigationFlowExternalState", + "type": "object", "properties": { "_comment": { "required": false, @@ -673,9 +805,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -707,9 +839,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -740,8 +872,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -762,7 +894,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowExternalState", "description": "External Flow states represent states in the FSM that can't be resolved internally in Player.\nThe flow will wait for the embedded application to manage moving to the next state via a transition" } diff --git a/common/static-xlrs/src/core/xlr/NavigationBaseState.json b/common/static-xlrs/src/core/xlr/NavigationBaseState.json index afae752e..3ec3565c 100644 --- a/common/static-xlrs/src/core/xlr/NavigationBaseState.json +++ b/common/static-xlrs/src/core/xlr/NavigationBaseState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationBaseState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -98,8 +98,17 @@ "ref": "T" }, "right": { - "type": "string", - "const": "ACTION" + "type": "or", + "or": [ + { + "type": "string", + "const": "ACTION" + }, + { + "type": "string", + "const": "ASYNC_ACTION" + } + ] } }, "value": { diff --git a/common/static-xlrs/src/core/xlr/NavigationFlow.json b/common/static-xlrs/src/core/xlr/NavigationFlow.json index c1773648..83ec4dde 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlow.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlow.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlow", "type": "object", - "source": "src/index.ts", "properties": { "startState": { "required": true, @@ -22,9 +22,9 @@ "title": "NavigationFlow.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -56,9 +56,9 @@ "title": "NavigationFlow.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -94,9 +94,9 @@ "ref": "Expression" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -113,14 +113,14 @@ "description": "An object with an expression in it" }, { - "name": "NavigationFlowState", "source": "src/index.ts", + "name": "NavigationFlowState", "type": "or", "or": [ { + "source": "src/index.ts", "name": "NavigationFlowViewState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -150,9 +150,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -184,9 +184,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -217,8 +217,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -251,14 +251,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowViewState", "description": "A state representing a view" }, { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -288,9 +290,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -322,9 +324,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -361,14 +363,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowEndState", "description": "An END state of the flow." }, { + "source": "src/index.ts", "name": "NavigationFlowFlowState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -398,9 +402,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -432,9 +436,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -465,8 +469,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -491,9 +495,9 @@ "title": "NavigationFlowFlowState" }, { + "source": "src/index.ts", "name": "NavigationFlowActionState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -523,9 +527,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -557,9 +561,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -592,8 +596,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -611,9 +615,137 @@ "description": "Action states execute an expression to determine the next state to transition to" }, { - "name": "NavigationFlowExternalState", + "source": "src/index.ts", + "name": "NavigationFlowAsyncActionState", "type": "object", + "properties": { + "_comment": { + "required": false, + "node": { + "type": "string", + "title": "CommentBase._comment", + "description": "Add comments that will not be processing, but are useful for code explanation" + } + }, + "state_type": { + "required": true, + "node": { + "type": "string", + "const": "ASYNC_ACTION", + "title": "NavigationBaseState.state_type", + "description": "A property to determine the type of state this is" + } + }, + "onStart": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onStart" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onStart", + "description": "An optional expression to run when this view renders" + } + }, + "onEnd": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onEnd" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onEnd", + "description": "An optional expression to run before view transition" + } + }, + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "NavigationFlowAsyncActionState.exp", + "description": "An expression to execute.\nThe return value determines the transition to take" + } + }, + "transitions": { + "required": true, + "node": { + "source": "src/index.ts", + "name": "NavigationFlowTransition", + "type": "record", + "keyType": { + "type": "string" + }, + "valueType": { + "type": "string" + }, + "title": "NavigationFlowTransitionableState.transitions", + "description": "A mapping of transition-name to FlowState name" + } + }, + "await": { + "required": true, + "node": { + "type": "boolean", + "title": "NavigationFlowAsyncActionState.await", + "description": "Whether the expression(s) should be awaited before transitioning" + } + } + }, + "additionalProperties": false, + "title": "NavigationFlowAsyncActionState", + "description": "Action states execute an expression to determine the next state to transition to" + }, + { "source": "src/index.ts", + "name": "NavigationFlowExternalState", + "type": "object", "properties": { "_comment": { "required": false, @@ -643,9 +775,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -677,9 +809,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -710,8 +842,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -732,7 +864,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowExternalState", "description": "External Flow states represent states in the FSM that can't be resolved internally in Player.\nThe flow will wait for the embedded application to manage moving to the next state via a transition" } diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowActionState.json b/common/static-xlrs/src/core/xlr/NavigationFlowActionState.json index 3b35f38e..3d2ea614 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowActionState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowActionState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowActionState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -100,8 +100,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowAsyncActionState.json b/common/static-xlrs/src/core/xlr/NavigationFlowAsyncActionState.json new file mode 100644 index 00000000..ac18c13b --- /dev/null +++ b/common/static-xlrs/src/core/xlr/NavigationFlowAsyncActionState.json @@ -0,0 +1,128 @@ +{ + "source": "src/index.ts", + "name": "NavigationFlowAsyncActionState", + "type": "object", + "properties": { + "_comment": { + "required": false, + "node": { + "type": "string", + "title": "CommentBase._comment", + "description": "Add comments that will not be processing, but are useful for code explanation" + } + }, + "state_type": { + "required": true, + "node": { + "type": "string", + "const": "ASYNC_ACTION", + "title": "NavigationBaseState.state_type", + "description": "A property to determine the type of state this is" + } + }, + "onStart": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onStart" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onStart", + "description": "An optional expression to run when this view renders" + } + }, + "onEnd": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onEnd" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onEnd", + "description": "An optional expression to run before view transition" + } + }, + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "NavigationFlowAsyncActionState.exp", + "description": "An expression to execute.\nThe return value determines the transition to take" + } + }, + "transitions": { + "required": true, + "node": { + "source": "src/index.ts", + "name": "NavigationFlowTransition", + "type": "record", + "keyType": { + "type": "string" + }, + "valueType": { + "type": "string" + }, + "title": "NavigationFlowTransitionableState.transitions", + "description": "A mapping of transition-name to FlowState name" + } + }, + "await": { + "required": true, + "node": { + "type": "boolean", + "title": "NavigationFlowAsyncActionState.await", + "description": "Whether the expression(s) should be awaited before transitioning" + } + } + }, + "additionalProperties": false, + "title": "NavigationFlowAsyncActionState", + "description": "Action states execute an expression to determine the next state to transition to" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowEndState.json b/common/static-xlrs/src/core/xlr/NavigationFlowEndState.json index d0883fcb..7a834a9a 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowEndState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowEndState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -104,7 +104,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowEndState", "description": "An END state of the flow." } \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowExternalState.json b/common/static-xlrs/src/core/xlr/NavigationFlowExternalState.json index 57f503e5..2a51f8f0 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowExternalState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowExternalState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowExternalState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -98,8 +98,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -120,7 +120,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowExternalState", "description": "External Flow states represent states in the FSM that can't be resolved internally in Player.\nThe flow will wait for the embedded application to manage moving to the next state via a transition" } \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowFlowState.json b/common/static-xlrs/src/core/xlr/NavigationFlowFlowState.json index dcf62095..9f7abf57 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowFlowState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowFlowState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowFlowState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -98,8 +98,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowState.json b/common/static-xlrs/src/core/xlr/NavigationFlowState.json index 01070f22..b1fcf296 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowState.json @@ -1,12 +1,12 @@ { - "name": "NavigationFlowState", "source": "src/index.ts", + "name": "NavigationFlowState", "type": "or", "or": [ { + "source": "src/index.ts", "name": "NavigationFlowViewState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -36,9 +36,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -70,9 +70,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -103,8 +103,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -137,14 +137,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowViewState", "description": "A state representing a view" }, { + "source": "src/index.ts", "name": "NavigationFlowEndState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -174,9 +176,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -208,9 +210,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -247,14 +249,16 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowEndState", "description": "An END state of the flow." }, { + "source": "src/index.ts", "name": "NavigationFlowFlowState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -284,9 +288,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -318,9 +322,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -351,8 +355,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -377,9 +381,9 @@ "title": "NavigationFlowFlowState" }, { + "source": "src/index.ts", "name": "NavigationFlowActionState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -409,9 +413,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -443,9 +447,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -478,8 +482,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -497,9 +501,137 @@ "description": "Action states execute an expression to determine the next state to transition to" }, { - "name": "NavigationFlowExternalState", + "source": "src/index.ts", + "name": "NavigationFlowAsyncActionState", "type": "object", + "properties": { + "_comment": { + "required": false, + "node": { + "type": "string", + "title": "CommentBase._comment", + "description": "Add comments that will not be processing, but are useful for code explanation" + } + }, + "state_type": { + "required": true, + "node": { + "type": "string", + "const": "ASYNC_ACTION", + "title": "NavigationBaseState.state_type", + "description": "A property to determine the type of state this is" + } + }, + "onStart": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onStart" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onStart", + "description": "An optional expression to run when this view renders" + } + }, + "onEnd": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "ref", + "ref": "Expression", + "title": "NavigationBaseState.onEnd" + }, + { + "source": "src/index.ts", + "name": "ExpressionObject", + "type": "object", + "properties": { + "exp": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ExpressionObject.exp", + "description": "The expression to run" + } + } + }, + "additionalProperties": false, + "title": "ExpressionObject", + "description": "An object with an expression in it" + } + ], + "title": "NavigationBaseState.onEnd", + "description": "An optional expression to run before view transition" + } + }, + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "NavigationFlowAsyncActionState.exp", + "description": "An expression to execute.\nThe return value determines the transition to take" + } + }, + "transitions": { + "required": true, + "node": { + "source": "src/index.ts", + "name": "NavigationFlowTransition", + "type": "record", + "keyType": { + "type": "string" + }, + "valueType": { + "type": "string" + }, + "title": "NavigationFlowTransitionableState.transitions", + "description": "A mapping of transition-name to FlowState name" + } + }, + "await": { + "required": true, + "node": { + "type": "boolean", + "title": "NavigationFlowAsyncActionState.await", + "description": "Whether the expression(s) should be awaited before transitioning" + } + } + }, + "additionalProperties": false, + "title": "NavigationFlowAsyncActionState", + "description": "Action states execute an expression to determine the next state to transition to" + }, + { "source": "src/index.ts", + "name": "NavigationFlowExternalState", + "type": "object", "properties": { "_comment": { "required": false, @@ -529,9 +661,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -563,9 +695,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -596,8 +728,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -618,7 +750,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowExternalState", "description": "External Flow states represent states in the FSM that can't be resolved internally in Player.\nThe flow will wait for the embedded application to manage moving to the next state via a transition" } diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowTransition.json b/common/static-xlrs/src/core/xlr/NavigationFlowTransition.json index a93e3682..7470a21a 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowTransition.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowTransition.json @@ -1,6 +1,6 @@ { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowTransitionableState.json b/common/static-xlrs/src/core/xlr/NavigationFlowTransitionableState.json index 484899a4..daeb7125 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowTransitionableState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowTransitionableState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowTransitionableState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -98,8 +98,17 @@ "ref": "T" }, "right": { - "type": "string", - "const": "ACTION" + "type": "or", + "or": [ + { + "type": "string", + "const": "ACTION" + }, + { + "type": "string", + "const": "ASYNC_ACTION" + } + ] } }, "value": { @@ -118,8 +127,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" diff --git a/common/static-xlrs/src/core/xlr/NavigationFlowViewState.json b/common/static-xlrs/src/core/xlr/NavigationFlowViewState.json index cd381b29..9cec0c39 100644 --- a/common/static-xlrs/src/core/xlr/NavigationFlowViewState.json +++ b/common/static-xlrs/src/core/xlr/NavigationFlowViewState.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "NavigationFlowViewState", "type": "object", - "source": "src/index.ts", "properties": { "_comment": { "required": false, @@ -31,9 +31,9 @@ "title": "NavigationBaseState.onStart" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -65,9 +65,9 @@ "title": "NavigationBaseState.onEnd" }, { + "source": "src/index.ts", "name": "ExpressionObject", "type": "object", - "source": "src/index.ts", "properties": { "exp": { "required": false, @@ -98,8 +98,8 @@ "transitions": { "required": true, "node": { - "name": "NavigationFlowTransition", "source": "src/index.ts", + "name": "NavigationFlowTransition", "type": "record", "keyType": { "type": "string" @@ -132,7 +132,9 @@ } } }, - "additionalProperties": false, + "additionalProperties": { + "type": "unknown" + }, "title": "NavigationFlowViewState", "description": "A state representing a view" } \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.ArrayType.json b/common/static-xlrs/src/core/xlr/Schema.ArrayType.json new file mode 100644 index 00000000..f63a7b65 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.ArrayType.json @@ -0,0 +1,203 @@ +{ + "name": "Schema.ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.DataType.json b/common/static-xlrs/src/core/xlr/Schema.DataType.json new file mode 100644 index 00000000..9bce05da --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.DataType.json @@ -0,0 +1,198 @@ +{ + "name": "Schema.DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.DataTypes.json b/common/static-xlrs/src/core/xlr/Schema.DataTypes.json new file mode 100644 index 00000000..4e778f94 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.DataTypes.json @@ -0,0 +1,611 @@ +{ + "name": "Schema.DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.Node.json b/common/static-xlrs/src/core/xlr/Schema.Node.json new file mode 100644 index 00000000..3a80aa11 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.Node.json @@ -0,0 +1,618 @@ +{ + "name": "Schema.Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Node", + "description": "A Node describes a specific object in the tree" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.RecordType.json b/common/static-xlrs/src/core/xlr/Schema.RecordType.json new file mode 100644 index 00000000..76692f61 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.RecordType.json @@ -0,0 +1,203 @@ +{ + "name": "Schema.RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema.Schema.json b/common/static-xlrs/src/core/xlr/Schema.Schema.json new file mode 100644 index 00000000..78e060d1 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema.Schema.json @@ -0,0 +1,1247 @@ +{ + "name": "Schema.Schema", + "type": "object", + "properties": { + "ROOT": { + "required": true, + "node": { + "name": "Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Schema.ROOT", + "description": "The ROOT object is the top level object to use" + } + } + }, + "additionalProperties": { + "name": "Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Node", + "description": "A Node describes a specific object in the tree" + }, + "title": "Schema", + "description": "The authored schema object in the JSON payload" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_ArrayType.json b/common/static-xlrs/src/core/xlr/Schema_ArrayType.json new file mode 100644 index 00000000..f63a7b65 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_ArrayType.json @@ -0,0 +1,203 @@ +{ + "name": "Schema.ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_DataType.json b/common/static-xlrs/src/core/xlr/Schema_DataType.json new file mode 100644 index 00000000..9bce05da --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_DataType.json @@ -0,0 +1,198 @@ +{ + "name": "Schema.DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_DataTypes.json b/common/static-xlrs/src/core/xlr/Schema_DataTypes.json new file mode 100644 index 00000000..4e778f94 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_DataTypes.json @@ -0,0 +1,611 @@ +{ + "name": "Schema.DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_Node.json b/common/static-xlrs/src/core/xlr/Schema_Node.json new file mode 100644 index 00000000..3a80aa11 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_Node.json @@ -0,0 +1,618 @@ +{ + "name": "Schema.Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Node", + "description": "A Node describes a specific object in the tree" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_RecordType.json b/common/static-xlrs/src/core/xlr/Schema_RecordType.json new file mode 100644 index 00000000..76692f61 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_RecordType.json @@ -0,0 +1,203 @@ +{ + "name": "Schema.RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Schema_Schema.json b/common/static-xlrs/src/core/xlr/Schema_Schema.json new file mode 100644 index 00000000..78e060d1 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Schema_Schema.json @@ -0,0 +1,1247 @@ +{ + "name": "Schema.Schema", + "type": "object", + "properties": { + "ROOT": { + "required": true, + "node": { + "name": "Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Schema.ROOT", + "description": "The ROOT object is the top level object to use" + } + } + }, + "additionalProperties": { + "name": "Node", + "type": "object", + "properties": {}, + "additionalProperties": { + "name": "DataTypes", + "type": "or", + "or": [ + { + "name": "DataType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType", + "description": "Each prop in the object can have a specific DataType", + "genericTokens": [ + { + "symbol": "T", + "constraints": { + "type": "any" + }, + "default": { + "type": "unknown" + } + } + ] + }, + { + "name": "RecordType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isRecord": { + "required": true, + "node": { + "type": "boolean", + "title": "RecordType.isRecord", + "description": "boolean to define if its a record" + } + }, + "isArray": { + "required": false, + "node": { + "type": "never", + "title": "RecordType.isArray", + "description": "This property is mutually exclusive with RecordType and can not be used with ArrayType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "RecordType", + "description": "Determines if the Datatype is a record object" + }, + { + "name": "ArrayType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "DataType.type", + "description": "The reference of the base type to use" + } + }, + "validation": { + "required": false, + "node": { + "type": "array", + "elementType": { + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" + }, + "title": "DataType.validation", + "description": "Any additional validations that are associated with this property\nThese will add to any base validations associated with the \"type\"" + } + }, + "format": { + "required": false, + "node": { + "name": "Formatting.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the formatter (and de-formatter) to use" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "DataType.format", + "description": "A reference to a specific data format to use.\nIf none is specified, will fallback to that of the base type" + } + }, + "default": { + "required": false, + "node": { + "type": "ref", + "ref": "T", + "title": "DataType.default", + "description": "A default value for this property.\nAny reads for this property will result in this default value being written to the model." + } + }, + "isArray": { + "required": true, + "node": { + "type": "boolean", + "title": "ArrayType.isArray", + "description": "boolean to define if its an array" + } + }, + "isRecord": { + "required": false, + "node": { + "type": "never", + "title": "ArrayType.isRecord", + "description": "This property is mutually exclusive with ArrayType and can not be used with RecordType" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "ArrayType", + "description": "Determines if the DataType is an Array Object" + } + ], + "title": "DataTypes" + }, + "title": "Node", + "description": "A Node describes a specific object in the tree" + }, + "title": "Schema", + "description": "The authored schema object in the JSON payload" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/StaticSwitch.json b/common/static-xlrs/src/core/xlr/StaticSwitch.json index 3a8904f7..b7cd02fc 100644 --- a/common/static-xlrs/src/core/xlr/StaticSwitch.json +++ b/common/static-xlrs/src/core/xlr/StaticSwitch.json @@ -1,18 +1,18 @@ { + "source": "src/index.ts", "name": "StaticSwitch", "type": "object", - "source": "src/index.ts", "properties": { "staticSwitch": { "required": true, "node": { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -35,8 +35,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/Switch.json b/common/static-xlrs/src/core/xlr/Switch.json index 467f893b..34de8877 100644 --- a/common/static-xlrs/src/core/xlr/Switch.json +++ b/common/static-xlrs/src/core/xlr/Switch.json @@ -1,11 +1,11 @@ { - "name": "Switch", "source": "src/index.ts", + "name": "Switch", "type": "array", "elementType": { - "name": "SwitchCase", - "type": "object", "source": "src/index.ts", + "name": "SwitchCase", + "type": "object", "properties": { "asset": { "required": true, @@ -28,8 +28,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/SwitchCase.json b/common/static-xlrs/src/core/xlr/SwitchCase.json index 0c144fb5..8c7c7f8a 100644 --- a/common/static-xlrs/src/core/xlr/SwitchCase.json +++ b/common/static-xlrs/src/core/xlr/SwitchCase.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "SwitchCase", "type": "object", - "source": "src/index.ts", "properties": { "asset": { "required": true, @@ -24,8 +24,7 @@ }, { "type": "boolean", - "const": true, - "title": "SwitchCase.case" + "const": true } ], "title": "SwitchCase.case", diff --git a/common/static-xlrs/src/core/xlr/Templatable.json b/common/static-xlrs/src/core/xlr/Templatable.json index d7cf4bf6..7fad7b6c 100644 --- a/common/static-xlrs/src/core/xlr/Templatable.json +++ b/common/static-xlrs/src/core/xlr/Templatable.json @@ -1,16 +1,16 @@ { + "source": "src/index.ts", "name": "Templatable", "type": "object", - "source": "src/index.ts", "properties": { "template": { "required": false, "node": { "type": "array", "elementType": { + "source": "src/index.ts", "name": "Template", "type": "object", - "source": "src/index.ts", "properties": { "data": { "required": true, @@ -46,6 +46,24 @@ "title": "Template.output", "description": "A property on the parent object to store the new map under.\nIf it already exists, values are appended to the end." } + }, + "placement": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "prepend" + }, + { + "type": "string", + "const": "append" + } + ], + "title": "Template.placement", + "description": "Specifies the template placement in relation to existing elements" + } } }, "additionalProperties": false, diff --git a/common/static-xlrs/src/core/xlr/Template.json b/common/static-xlrs/src/core/xlr/Template.json index 934b2027..ff9952c4 100644 --- a/common/static-xlrs/src/core/xlr/Template.json +++ b/common/static-xlrs/src/core/xlr/Template.json @@ -1,7 +1,7 @@ { + "source": "src/index.ts", "name": "Template", "type": "object", - "source": "src/index.ts", "properties": { "data": { "required": true, @@ -37,6 +37,24 @@ "title": "Template.output", "description": "A property on the parent object to store the new map under.\nIf it already exists, values are appended to the end." } + }, + "placement": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "prepend" + }, + { + "type": "string", + "const": "append" + } + ], + "title": "Template.placement", + "description": "Specifies the template placement in relation to existing elements" + } } }, "additionalProperties": false, diff --git a/common/static-xlrs/src/core/xlr/Validation.CrossfieldReference.json b/common/static-xlrs/src/core/xlr/Validation.CrossfieldReference.json new file mode 100644 index 00000000..37189815 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation.CrossfieldReference.json @@ -0,0 +1,127 @@ +{ + "name": "Validation.CrossfieldReference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "never", + "title": "CrossfieldReference.dataTarget", + "description": "Cross-field references and validation must run against the default (deformatted) value" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + }, + "ref": { + "required": false, + "node": { + "type": "ref", + "ref": "Binding", + "title": "CrossfieldReference.ref", + "description": "The binding to associate this validation with" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "CrossfieldReference" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation.DisplayTarget.json b/common/static-xlrs/src/core/xlr/Validation.DisplayTarget.json new file mode 100644 index 00000000..03cf8aae --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation.DisplayTarget.json @@ -0,0 +1,20 @@ +{ + "name": "Validation.DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "DisplayTarget", + "description": "Where the error/warning should be displayed.\n- `field` is the default display target. This renders the error/warning directly underneath the field.\n- `section` is used to display a message at a parent node that is designated as a \"section\"\n- `page` a special section used to display a message at the top of the page." +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation.Reference.json b/common/static-xlrs/src/core/xlr/Validation.Reference.json new file mode 100644 index 00000000..5bb834e6 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation.Reference.json @@ -0,0 +1,129 @@ +{ + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation.Severity.json b/common/static-xlrs/src/core/xlr/Validation.Severity.json new file mode 100644 index 00000000..bbdee625 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation.Severity.json @@ -0,0 +1,16 @@ +{ + "name": "Validation.Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Severity", + "description": "How serious are you about this error?\nWarning validations are reserved for errors that could be ignored by the user without consequence\nErrors must be fixed before proceeding" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation.Trigger.json b/common/static-xlrs/src/core/xlr/Validation.Trigger.json new file mode 100644 index 00000000..40f15f97 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation.Trigger.json @@ -0,0 +1,20 @@ +{ + "name": "Validation.Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Trigger", + "description": "When to _first_ start caring about a validation of a data-val.\n\nload - only check once the first time the binding appears on screen\nchange - check anytime the data changes\nnavigation - check once the user attempts to navigate away from a view" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation_CrossfieldReference.json b/common/static-xlrs/src/core/xlr/Validation_CrossfieldReference.json new file mode 100644 index 00000000..37189815 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation_CrossfieldReference.json @@ -0,0 +1,127 @@ +{ + "name": "Validation.CrossfieldReference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "never", + "title": "CrossfieldReference.dataTarget", + "description": "Cross-field references and validation must run against the default (deformatted) value" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + }, + "ref": { + "required": false, + "node": { + "type": "ref", + "ref": "Binding", + "title": "CrossfieldReference.ref", + "description": "The binding to associate this validation with" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "CrossfieldReference" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation_DisplayTarget.json b/common/static-xlrs/src/core/xlr/Validation_DisplayTarget.json new file mode 100644 index 00000000..03cf8aae --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation_DisplayTarget.json @@ -0,0 +1,20 @@ +{ + "name": "Validation.DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "DisplayTarget", + "description": "Where the error/warning should be displayed.\n- `field` is the default display target. This renders the error/warning directly underneath the field.\n- `section` is used to display a message at a parent node that is designated as a \"section\"\n- `page` a special section used to display a message at the top of the page." +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation_Reference.json b/common/static-xlrs/src/core/xlr/Validation_Reference.json new file mode 100644 index 00000000..5bb834e6 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation_Reference.json @@ -0,0 +1,129 @@ +{ + "name": "Validation.Reference", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "title": "Reference.type", + "description": "The name of the referenced validation type\nThis will be used to lookup the proper handler" + } + }, + "message": { + "required": false, + "node": { + "type": "string", + "title": "Reference.message", + "description": "An optional means of overriding the default message if the validation is triggered" + } + }, + "severity": { + "required": false, + "node": { + "name": "Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Reference.severity", + "description": "An optional means of overriding the default severity of the validation if triggered" + } + }, + "trigger": { + "required": false, + "node": { + "name": "Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Reference.trigger", + "description": "When to run this particular validation" + } + }, + "dataTarget": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "string", + "const": "formatted" + }, + { + "type": "string", + "const": "deformatted" + } + ], + "title": "Reference.dataTarget", + "description": "Each validation is passed the value of the data to run it's validation against.\nBy default, this is the value stored in the data-model (deformatted).\nIn the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option" + } + }, + "displayTarget": { + "required": false, + "node": { + "name": "DisplayTarget", + "type": "or", + "or": [ + { + "type": "string", + "const": "page" + }, + { + "type": "string", + "const": "section" + }, + { + "type": "string", + "const": "field" + } + ], + "title": "Reference.displayTarget", + "description": "Where the error should be displayed" + } + }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + } + }, + "additionalProperties": { + "type": "unknown" + }, + "title": "Reference", + "description": "A reference to a validation object" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation_Severity.json b/common/static-xlrs/src/core/xlr/Validation_Severity.json new file mode 100644 index 00000000..bbdee625 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation_Severity.json @@ -0,0 +1,16 @@ +{ + "name": "Validation.Severity", + "type": "or", + "or": [ + { + "type": "string", + "const": "error" + }, + { + "type": "string", + "const": "warning" + } + ], + "title": "Severity", + "description": "How serious are you about this error?\nWarning validations are reserved for errors that could be ignored by the user without consequence\nErrors must be fixed before proceeding" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/Validation_Trigger.json b/common/static-xlrs/src/core/xlr/Validation_Trigger.json new file mode 100644 index 00000000..40f15f97 --- /dev/null +++ b/common/static-xlrs/src/core/xlr/Validation_Trigger.json @@ -0,0 +1,20 @@ +{ + "name": "Validation.Trigger", + "type": "or", + "or": [ + { + "type": "string", + "const": "navigation" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "load" + } + ], + "title": "Trigger", + "description": "When to _first_ start caring about a validation of a data-val.\n\nload - only check once the first time the binding appears on screen\nchange - check anytime the data changes\nnavigation - check once the user attempts to navigate away from a view" +} \ No newline at end of file diff --git a/common/static-xlrs/src/core/xlr/View.json b/common/static-xlrs/src/core/xlr/View.json index 5267003b..5d88c9db 100644 --- a/common/static-xlrs/src/core/xlr/View.json +++ b/common/static-xlrs/src/core/xlr/View.json @@ -1,6 +1,6 @@ { - "name": "View", "source": "src/index.ts", + "name": "View", "type": "conditional", "check": { "left": { @@ -28,7 +28,7 @@ "node": { "type": "array", "elementType": { - "name": "CrossfieldReference", + "name": "Validation.CrossfieldReference", "type": "object", "properties": { "type": { @@ -55,13 +55,11 @@ "or": [ { "type": "string", - "const": "error", - "title": "Severity" + "const": "error" }, { "type": "string", - "const": "warning", - "title": "Severity" + "const": "warning" } ], "title": "Reference.severity", @@ -76,18 +74,15 @@ "or": [ { "type": "string", - "const": "navigation", - "title": "Trigger" + "const": "navigation" }, { "type": "string", - "const": "change", - "title": "Trigger" + "const": "change" }, { "type": "string", - "const": "load", - "title": "Trigger" + "const": "load" } ], "title": "Reference.trigger", @@ -110,24 +105,40 @@ "or": [ { "type": "string", - "const": "page", - "title": "DisplayTarget" + "const": "page" }, { "type": "string", - "const": "section", - "title": "DisplayTarget" + "const": "section" }, { "type": "string", - "const": "field", - "title": "DisplayTarget" + "const": "field" } ], "title": "Reference.displayTarget", "description": "Where the error should be displayed" } }, + "blocking": { + "required": false, + "node": { + "type": "or", + "or": [ + { + "type": "boolean", + "title": "Reference.blocking" + }, + { + "type": "string", + "const": "once" + } + ], + "title": "Reference.blocking", + "description": "If the validation blocks navigation\ntrue/false - always/never block navigation\nonce - only block navigation if the validation has not been triggered before", + "default": "- true for errors, 'once' for warnings" + } + }, "ref": { "required": false, "node": { diff --git a/common/static-xlrs/src/core/xlr/manifest.js b/common/static-xlrs/src/core/xlr/manifest.js index 0a18eb17..7f0fbebf 100644 --- a/common/static-xlrs/src/core/xlr/manifest.js +++ b/common/static-xlrs/src/core/xlr/manifest.js @@ -1,78 +1,55 @@ -const Asset = require("./Asset.json"); -const AssetBinding = require("./AssetBinding.json"); -const SwitchCase = require("./SwitchCase.json"); -const Switch = require("./Switch.json"); -const AssetWrapper = require("./AssetWrapper.json"); -const AssetWrapperOrSwitch = require("./AssetWrapperOrSwitch.json"); -const AssetSwitch = require("./AssetSwitch.json"); -const StaticSwitch = require("./StaticSwitch.json"); -const DynamicSwitch = require("./DynamicSwitch.json"); -const Expression = require("./Expression.json"); -const ExpressionRef = require("./ExpressionRef.json"); -const Binding = require("./Binding.json"); -const BindingRef = require("./BindingRef.json"); -const DataModel = require("./DataModel.json"); -const Navigation = require("./Navigation.json"); -const ExpressionObject = require("./ExpressionObject.json"); -const NavigationFlow = require("./NavigationFlow.json"); -const NavigationFlowTransition = require("./NavigationFlowTransition.json"); -const NavigationBaseState = require("./NavigationBaseState.json"); -const NavigationFlowTransitionableState = require("./NavigationFlowTransitionableState.json"); -const NavigationFlowViewState = require("./NavigationFlowViewState.json"); -const NavigationFlowEndState = require("./NavigationFlowEndState.json"); -const NavigationFlowActionState = require("./NavigationFlowActionState.json"); -const NavigationFlowExternalState = require("./NavigationFlowExternalState.json"); -const NavigationFlowFlowState = require("./NavigationFlowFlowState.json"); -const NavigationFlowState = require("./NavigationFlowState.json"); -const FlowResult = require("./FlowResult.json"); -const Templatable = require("./Templatable.json"); -const Template = require("./Template.json"); -const View = require("./View.json"); -const Flow = require("./Flow.json"); +const Asset = require("./Asset.json") +const AssetBinding = require("./AssetBinding.json") +const SwitchCase = require("./SwitchCase.json") +const Switch = require("./Switch.json") +const AssetWrapper = require("./AssetWrapper.json") +const AssetWrapperOrSwitch = require("./AssetWrapperOrSwitch.json") +const AssetSwitch = require("./AssetSwitch.json") +const StaticSwitch = require("./StaticSwitch.json") +const DynamicSwitch = require("./DynamicSwitch.json") +const Expression = require("./Expression.json") +const ExpressionRef = require("./ExpressionRef.json") +const Binding = require("./Binding.json") +const BindingRef = require("./BindingRef.json") +const DataModel = require("./DataModel.json") +const Navigation = require("./Navigation.json") +const ExpressionObject = require("./ExpressionObject.json") +const NavigationFlow = require("./NavigationFlow.json") +const NavigationFlowTransition = require("./NavigationFlowTransition.json") +const NavigationBaseState = require("./NavigationBaseState.json") +const NavigationFlowTransitionableState = require("./NavigationFlowTransitionableState.json") +const NavigationFlowViewState = require("./NavigationFlowViewState.json") +const NavigationFlowEndState = require("./NavigationFlowEndState.json") +const NavigationFlowActionState = require("./NavigationFlowActionState.json") +const NavigationFlowAsyncActionState = require("./NavigationFlowAsyncActionState.json") +const NavigationFlowExternalState = require("./NavigationFlowExternalState.json") +const NavigationFlowFlowState = require("./NavigationFlowFlowState.json") +const NavigationFlowState = require("./NavigationFlowState.json") +const FlowResult = require("./FlowResult.json") +const Templatable = require("./Templatable.json") +const Template = require("./Template.json") +const View = require("./View.json") +const Flow = require("./Flow.json") +const Schema_Schema = require("./Schema.Schema.json") +const Schema_Node = require("./Schema.Node.json") +const Schema_DataTypes = require("./Schema.DataTypes.json") +const Schema_DataType = require("./Schema.DataType.json") +const Schema_RecordType = require("./Schema.RecordType.json") +const Schema_ArrayType = require("./Schema.ArrayType.json") +const Language_DataTypeRef = require("./Language.DataTypeRef.json") +const Formatting_Reference = require("./Formatting.Reference.json") +const Validation_Severity = require("./Validation.Severity.json") +const Validation_Trigger = require("./Validation.Trigger.json") +const Validation_DisplayTarget = require("./Validation.DisplayTarget.json") +const Validation_Reference = require("./Validation.Reference.json") +const Validation_CrossfieldReference = require("./Validation.CrossfieldReference.json") -module.exports = { - pluginName: "Types", - capabilities: { - Types: [ - Asset, - AssetBinding, - SwitchCase, - Switch, - AssetWrapper, - AssetWrapperOrSwitch, - AssetSwitch, - StaticSwitch, - DynamicSwitch, - Expression, - ExpressionRef, - Binding, - BindingRef, - DataModel, - Navigation, - ExpressionObject, - NavigationFlow, - NavigationFlowTransition, - NavigationBaseState, - NavigationFlowTransitionableState, - NavigationFlowViewState, - NavigationFlowEndState, - NavigationFlowActionState, - NavigationFlowExternalState, - NavigationFlowFlowState, - NavigationFlowState, - FlowResult, - Templatable, - Template, - View, - Flow, - ], - }, - customPrimitives: [ - "Expression", - "Asset", - "Binding", - "AssetWrapper", - "Schema.DataType", - "ExpressionHandler", - ], -}; + module.exports = { + "pluginName": "Types", + "capabilities": { + "Types":[Asset,AssetBinding,SwitchCase,Switch,AssetWrapper,AssetWrapperOrSwitch,AssetSwitch,StaticSwitch,DynamicSwitch,Expression,ExpressionRef,Binding,BindingRef,DataModel,Navigation,ExpressionObject,NavigationFlow,NavigationFlowTransition,NavigationBaseState,NavigationFlowTransitionableState,NavigationFlowViewState,NavigationFlowEndState,NavigationFlowActionState,NavigationFlowAsyncActionState,NavigationFlowExternalState,NavigationFlowFlowState,NavigationFlowState,FlowResult,Templatable,Template,View,Flow,Schema_Schema,Schema_Node,Schema_DataTypes,Schema_DataType,Schema_RecordType,Schema_ArrayType,Language_DataTypeRef,Formatting_Reference,Validation_Severity,Validation_Trigger,Validation_DisplayTarget,Validation_Reference,Validation_CrossfieldReference], + }, + "customPrimitives": [ + "Expression","Asset","Binding","AssetWrapper","Schema.DataType","ExpressionHandler","FormatType","ValidatorFunction" + ] + } diff --git a/common/static-xlrs/src/core/xlr/manifest.json b/common/static-xlrs/src/core/xlr/manifest.json index f3534f42..46122150 100644 --- a/common/static-xlrs/src/core/xlr/manifest.json +++ b/common/static-xlrs/src/core/xlr/manifest.json @@ -25,6 +25,7 @@ "NavigationFlowViewState", "NavigationFlowEndState", "NavigationFlowActionState", + "NavigationFlowAsyncActionState", "NavigationFlowExternalState", "NavigationFlowFlowState", "NavigationFlowState", @@ -32,7 +33,30 @@ "Templatable", "Template", "View", - "Flow" + "Flow", + "Schema.Schema", + "Schema.Node", + "Schema.DataTypes", + "Schema.DataType", + "Schema.RecordType", + "Schema.ArrayType", + "Language.DataTypeRef", + "Formatting.Reference", + "Validation.Severity", + "Validation.Trigger", + "Validation.DisplayTarget", + "Validation.Reference", + "Validation.CrossfieldReference" ] - } + }, + "customPrimitives": [ + "Expression", + "Asset", + "Binding", + "AssetWrapper", + "Schema.DataType", + "ExpressionHandler", + "FormatType", + "ValidatorFunction" + ] } \ No newline at end of file diff --git a/common/static-xlrs/src/expression/xlr/ceil.json b/common/static-xlrs/src/expression/xlr/ceil.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/concat.json b/common/static-xlrs/src/expression/xlr/concat.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/containsAny.json b/common/static-xlrs/src/expression/xlr/containsAny.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/findProperty.json b/common/static-xlrs/src/expression/xlr/findProperty.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/findPropertyIndex.json b/common/static-xlrs/src/expression/xlr/findPropertyIndex.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/floor.json b/common/static-xlrs/src/expression/xlr/floor.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/isEmpty.json b/common/static-xlrs/src/expression/xlr/isEmpty.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/isNotEmpty.json b/common/static-xlrs/src/expression/xlr/isNotEmpty.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/length.json b/common/static-xlrs/src/expression/xlr/length.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/expression/xlr/lowerCase.json b/common/static-xlrs/src/expression/xlr/lowerCase.json old mode 100755 new mode 100644 diff --git a/common/static-xlrs/src/index.d.ts b/common/static-xlrs/src/index.d.ts index 2232e8a8..f549fb37 100644 --- a/common/static-xlrs/src/index.d.ts +++ b/common/static-xlrs/src/index.d.ts @@ -1,7 +1,9 @@ import type { Types } from "./core/xlr/manifest"; import type { CommonExpressions } from "./expression/xlr/manifest"; +import type { CommonTypes } from "./types/xlr/manifest"; import type { ReferenceAssetsWebPluginManifest } from "./plugin/xlr/manifest"; export const Types: Types; export const CommonExpressions: CommonExpressions; +export const CommonTypes: CommonTypes; export const ReferenceAssetsWebPluginManifest: ReferenceAssetsWebPluginManifest; diff --git a/common/static-xlrs/src/index.js b/common/static-xlrs/src/index.js index 530e4492..799d7824 100644 --- a/common/static-xlrs/src/index.js +++ b/common/static-xlrs/src/index.js @@ -1,9 +1,11 @@ const Types = require("./core/xlr/manifest"); const CommonExpressions = require("./expression/xlr/manifest"); +const CommonTypes = require("./types/xlr/manifest"); const ReferenceAssetsWebPluginManifest = require("./plugin/xlr/manifest"); module.exports = { Types, CommonExpressions, + CommonTypes, ReferenceAssetsWebPluginManifest, }; diff --git a/common/static-xlrs/src/types/xlr/DataTypes.BooleanType.json b/common/static-xlrs/src/types/xlr/DataTypes.BooleanType.json new file mode 100644 index 00000000..856618ee --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.BooleanType.json @@ -0,0 +1,72 @@ +{ + "source": "/some/path/types.ts", + "name": "BooleanType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "BooleanType" + } + }, + "default": { + "required": true, + "node": { + "type": "boolean", + "const": false + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "oneOf" + } + }, + "message": { + "required": true, + "node": { + "type": "string", + "const": "Value must be true or false" + } + }, + "options": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "boolean", + "const": true + }, + { + "type": "boolean", + "const": false + } + ] + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.CollectionType.json b/common/static-xlrs/src/types/xlr/DataTypes.CollectionType.json new file mode 100644 index 00000000..40786a6f --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.CollectionType.json @@ -0,0 +1,39 @@ +{ + "source": "/some/path/types.ts", + "name": "CollectionType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "CollectionType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "collection" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.DateType.json b/common/static-xlrs/src/types/xlr/DataTypes.DateType.json new file mode 100644 index 00000000..4d3039a6 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.DateType.json @@ -0,0 +1,55 @@ +{ + "source": "/some/path/types.ts", + "name": "DateType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "DateType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "string" + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "date" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.IntegerNNType.json b/common/static-xlrs/src/types/xlr/DataTypes.IntegerNNType.json new file mode 100644 index 00000000..64285b63 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.IntegerNNType.json @@ -0,0 +1,75 @@ +{ + "source": "/some/path/types.ts", + "name": "IntegerNNType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "IntegerNNType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "min" + } + }, + "value": { + "required": true, + "node": { + "type": "number", + "const": 0 + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.IntegerPosType.json b/common/static-xlrs/src/types/xlr/DataTypes.IntegerPosType.json new file mode 100644 index 00000000..79ec8687 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.IntegerPosType.json @@ -0,0 +1,75 @@ +{ + "source": "/some/path/types.ts", + "name": "IntegerPosType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "IntegerPosType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "min" + } + }, + "value": { + "required": true, + "node": { + "type": "number", + "const": 1 + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.IntegerType.json b/common/static-xlrs/src/types/xlr/DataTypes.IntegerType.json new file mode 100644 index 00000000..32d846b4 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.IntegerType.json @@ -0,0 +1,55 @@ +{ + "source": "/some/path/types.ts", + "name": "IntegerType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "IntegerType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "integer" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.PhoneType.json b/common/static-xlrs/src/types/xlr/DataTypes.PhoneType.json new file mode 100644 index 00000000..86688187 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.PhoneType.json @@ -0,0 +1,55 @@ +{ + "source": "/some/path/types.ts", + "name": "PhoneType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "PhoneType" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "phone" + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "phone" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/DataTypes.StringType.json b/common/static-xlrs/src/types/xlr/DataTypes.StringType.json new file mode 100644 index 00000000..2b0e3e58 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/DataTypes.StringType.json @@ -0,0 +1,62 @@ +{ + "source": "/some/path/types.ts", + "name": "StringType", + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "StringType" + } + }, + "default": { + "required": true, + "node": { + "type": "string", + "const": "" + } + }, + "validation": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "any" + }, + "const": [ + { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "string" + } + } + }, + "additionalProperties": false + } + ] + } + }, + "format": { + "required": true, + "node": { + "type": "object", + "properties": { + "type": { + "required": true, + "node": { + "type": "string", + "const": "string" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Formatters.commaNumber.json b/common/static-xlrs/src/types/xlr/Formatters.commaNumber.json new file mode 100644 index 00000000..07b3e710 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Formatters.commaNumber.json @@ -0,0 +1,28 @@ +{ + "source": "/some/path/types.ts", + "name": "commaNumber", + "type": "ref", + "ref": "FormatType<\n number,\n string,\n {\n /** The number of decimal places to show */\n precision?: number;\n }\n>", + "genericArguments": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "precision": { + "required": false, + "node": { + "type": "number", + "title": "precision", + "description": "The number of decimal places to show" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Formatters.currency.json b/common/static-xlrs/src/types/xlr/Formatters.currency.json new file mode 100644 index 00000000..17509457 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Formatters.currency.json @@ -0,0 +1,44 @@ +{ + "source": "/some/path/types.ts", + "name": "currency", + "type": "ref", + "ref": "FormatType<\n number,\n string,\n {\n /** The symbol to use for currency */\n currencySymbol?: string;\n\n /** Use parenthesis instead of a - sign for negative values */\n useParensForNeg?: boolean;\n\n /** The number of decimal places to show */\n precision?: number;\n }\n>", + "genericArguments": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "currencySymbol": { + "required": false, + "node": { + "type": "string", + "title": "currencySymbol", + "description": "The symbol to use for currency" + } + }, + "useParensForNeg": { + "required": false, + "node": { + "type": "boolean", + "title": "useParensForNeg", + "description": "Use parenthesis instead of a - sign for negative values" + } + }, + "precision": { + "required": false, + "node": { + "type": "number", + "title": "precision", + "description": "The number of decimal places to show" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Formatters.date.json b/common/static-xlrs/src/types/xlr/Formatters.date.json new file mode 100644 index 00000000..44e12ac1 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Formatters.date.json @@ -0,0 +1,28 @@ +{ + "source": "/some/path/types.ts", + "name": "date", + "type": "ref", + "ref": "FormatType<\n string,\n string,\n {\n /** The mask to use to format the date */\n mask?: string;\n }\n>", + "genericArguments": [ + { + "type": "string" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "mask": { + "required": false, + "node": { + "type": "string", + "title": "mask", + "description": "The mask to use to format the date" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Formatters.integer.json b/common/static-xlrs/src/types/xlr/Formatters.integer.json new file mode 100644 index 00000000..e33df67e --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Formatters.integer.json @@ -0,0 +1,14 @@ +{ + "source": "/some/path/types.ts", + "name": "integer", + "type": "ref", + "ref": "FormatType", + "genericArguments": [ + { + "type": "number" + }, + { + "type": "string" + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Formatters.phone.json b/common/static-xlrs/src/types/xlr/Formatters.phone.json new file mode 100644 index 00000000..c3d706d1 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Formatters.phone.json @@ -0,0 +1,11 @@ +{ + "source": "/some/path/types.ts", + "name": "phone", + "type": "ref", + "ref": "FormatType", + "genericArguments": [ + { + "type": "string" + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.collection.json b/common/static-xlrs/src/types/xlr/Validators.collection.json new file mode 100644 index 00000000..0e14ec67 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.collection.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "collection", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.email.json b/common/static-xlrs/src/types/xlr/Validators.email.json new file mode 100644 index 00000000..53bf5524 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.email.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "email", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.expression.json b/common/static-xlrs/src/types/xlr/Validators.expression.json new file mode 100644 index 00000000..ec281dc8 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.expression.json @@ -0,0 +1,23 @@ +{ + "source": "/some/path/types.ts", + "name": "expression", + "type": "ref", + "ref": "ValidatorFunction<{\n /**\n * The expression to evaluate.\n * Falsy values indicate an invalid response\n */\n exp: Expression;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "exp": { + "required": true, + "node": { + "type": "ref", + "ref": "Expression", + "title": "exp", + "description": "The expression to evaluate.\nFalsy values indicate an invalid response" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.integer.json b/common/static-xlrs/src/types/xlr/Validators.integer.json new file mode 100644 index 00000000..8f3e7acb --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.integer.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "integer", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.length.json b/common/static-xlrs/src/types/xlr/Validators.length.json new file mode 100644 index 00000000..0a85e83e --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.length.json @@ -0,0 +1,49 @@ +{ + "source": "/some/path/types.ts", + "name": "length", + "type": "ref", + "ref": "ValidatorFunction<\n | {\n /** The minimum length to check against */\n min?: number;\n\n /** The maximum length to check against */\n max?: number;\n }\n | {\n /** The exact length to match against */\n exact: number;\n }\n>", + "genericArguments": [ + { + "type": "or", + "or": [ + { + "type": "object", + "properties": { + "min": { + "required": false, + "node": { + "type": "number", + "title": "min", + "description": "The minimum length to check against" + } + }, + "max": { + "required": false, + "node": { + "type": "number", + "title": "max", + "description": "The maximum length to check against" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "exact": { + "required": true, + "node": { + "type": "number", + "title": "exact", + "description": "The exact length to match against" + } + } + }, + "additionalProperties": false + } + ] + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.max.json b/common/static-xlrs/src/types/xlr/Validators.max.json new file mode 100644 index 00000000..6a7327da --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.max.json @@ -0,0 +1,22 @@ +{ + "source": "/some/path/types.ts", + "name": "max", + "type": "ref", + "ref": "ValidatorFunction<{\n /** The minimum value */\n value: number;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "value": { + "required": true, + "node": { + "type": "number", + "title": "value", + "description": "The minimum value" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.min.json b/common/static-xlrs/src/types/xlr/Validators.min.json new file mode 100644 index 00000000..a2743b80 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.min.json @@ -0,0 +1,22 @@ +{ + "source": "/some/path/types.ts", + "name": "min", + "type": "ref", + "ref": "ValidatorFunction<{\n /** The minimum value */\n value: number;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "value": { + "required": true, + "node": { + "type": "number", + "title": "value", + "description": "The minimum value" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.oneOf.json b/common/static-xlrs/src/types/xlr/Validators.oneOf.json new file mode 100644 index 00000000..57fbd9f0 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.oneOf.json @@ -0,0 +1,25 @@ +{ + "source": "/some/path/types.ts", + "name": "oneOf", + "type": "ref", + "ref": "ValidatorFunction<{\n /** The enum values that are acceptable */\n options: Array;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "options": { + "required": true, + "node": { + "type": "array", + "elementType": { + "type": "unknown" + }, + "title": "options", + "description": "The enum values that are acceptable" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.phone.json b/common/static-xlrs/src/types/xlr/Validators.phone.json new file mode 100644 index 00000000..30bee99b --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.phone.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "phone", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.readonly.json b/common/static-xlrs/src/types/xlr/Validators.readonly.json new file mode 100644 index 00000000..dc2981f5 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.readonly.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "readonly", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.regex.json b/common/static-xlrs/src/types/xlr/Validators.regex.json new file mode 100644 index 00000000..e75748be --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.regex.json @@ -0,0 +1,22 @@ +{ + "source": "/some/path/types.ts", + "name": "regex", + "type": "ref", + "ref": "ValidatorFunction<{\n /**\n * The regular expression to test: /pattern/\n * Can optionally include flags after the pattern: /pattern/flags\n */\n regex: string;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "regex": { + "required": true, + "node": { + "type": "string", + "title": "regex", + "description": "The regular expression to test: /pattern/\nCan optionally include flags after the pattern: /pattern/flags" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.required.json b/common/static-xlrs/src/types/xlr/Validators.required.json new file mode 100644 index 00000000..13ec55e3 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.required.json @@ -0,0 +1,32 @@ +{ + "source": "/some/path/types.ts", + "name": "required", + "type": "ref", + "ref": "ValidatorFunction<{\n /** An optional expression to limit the required check only if true */\n if?: Expression;\n\n /** An optional expression to limit the required check only if false */\n ifNot?: Expression;\n}>", + "genericArguments": [ + { + "type": "object", + "properties": { + "if": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "if", + "description": "An optional expression to limit the required check only if true" + } + }, + "ifNot": { + "required": false, + "node": { + "type": "ref", + "ref": "Expression", + "title": "ifNot", + "description": "An optional expression to limit the required check only if false" + } + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.string.json b/common/static-xlrs/src/types/xlr/Validators.string.json new file mode 100644 index 00000000..e1bb6d1c --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.string.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "string", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/Validators.zip.json b/common/static-xlrs/src/types/xlr/Validators.zip.json new file mode 100644 index 00000000..9519135d --- /dev/null +++ b/common/static-xlrs/src/types/xlr/Validators.zip.json @@ -0,0 +1,6 @@ +{ + "source": "/some/path/types.ts", + "name": "zip", + "type": "ref", + "ref": "ValidatorFunction" +} \ No newline at end of file diff --git a/common/static-xlrs/src/types/xlr/manifest.d.ts b/common/static-xlrs/src/types/xlr/manifest.d.ts new file mode 100644 index 00000000..b055bf04 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/manifest.d.ts @@ -0,0 +1,3 @@ +import type { TSManifest } from "@player-tools/xlr"; + +export type CommonTypes = TSManifest; diff --git a/common/static-xlrs/src/types/xlr/manifest.js b/common/static-xlrs/src/types/xlr/manifest.js new file mode 100644 index 00000000..26bdcb71 --- /dev/null +++ b/common/static-xlrs/src/types/xlr/manifest.js @@ -0,0 +1,42 @@ +const DataTypes_BooleanType = require("./DataTypes.BooleanType.json") +const DataTypes_IntegerType = require("./DataTypes.IntegerType.json") +const DataTypes_IntegerPosType = require("./DataTypes.IntegerPosType.json") +const DataTypes_IntegerNNType = require("./DataTypes.IntegerNNType.json") +const DataTypes_StringType = require("./DataTypes.StringType.json") +const DataTypes_CollectionType = require("./DataTypes.CollectionType.json") +const DataTypes_DateType = require("./DataTypes.DateType.json") +const DataTypes_PhoneType = require("./DataTypes.PhoneType.json") +const Formatters_commaNumber = require("./Formatters.commaNumber.json") +const Formatters_currency = require("./Formatters.currency.json") +const Formatters_date = require("./Formatters.date.json") +const Formatters_integer = require("./Formatters.integer.json") +const Formatters_phone = require("./Formatters.phone.json") +const Validators_collection = require("./Validators.collection.json") +const Validators_email = require("./Validators.email.json") +const Validators_expression = require("./Validators.expression.json") +const Validators_integer = require("./Validators.integer.json") +const Validators_length = require("./Validators.length.json") +const Validators_max = require("./Validators.max.json") +const Validators_min = require("./Validators.min.json") +const Validators_oneOf = require("./Validators.oneOf.json") +const Validators_phone = require("./Validators.phone.json") +const Validators_readonly = require("./Validators.readonly.json") +const Validators_regex = require("./Validators.regex.json") +const Validators_required = require("./Validators.required.json") +const Validators_string = require("./Validators.string.json") +const Validators_zip = require("./Validators.zip.json") + + module.exports = { + "pluginName": "CommonTypes", + "capabilities": { + "Assets":[], + "Views":[], + "Expressions":[], + "DataTypes":[DataTypes_BooleanType,DataTypes_IntegerType,DataTypes_IntegerPosType,DataTypes_IntegerNNType,DataTypes_StringType,DataTypes_CollectionType,DataTypes_DateType,DataTypes_PhoneType], + "Formatters":[Formatters_commaNumber,Formatters_currency,Formatters_date,Formatters_integer,Formatters_phone], + "Validators":[Validators_collection,Validators_email,Validators_expression,Validators_integer,Validators_length,Validators_max,Validators_min,Validators_oneOf,Validators_phone,Validators_readonly,Validators_regex,Validators_required,Validators_string,Validators_zip], + }, + "customPrimitives": [ + "Expression","Asset","Binding","AssetWrapper","Schema.DataType","ExpressionHandler","FormatType","ValidatorFunction" + ] + } diff --git a/common/static-xlrs/src/types/xlr/manifest.json b/common/static-xlrs/src/types/xlr/manifest.json new file mode 100644 index 00000000..5172d20e --- /dev/null +++ b/common/static-xlrs/src/types/xlr/manifest.json @@ -0,0 +1,51 @@ +{ + "pluginName": "CommonTypes", + "capabilities": { + "Assets": [], + "Views": [], + "Expressions": [], + "DataTypes": [ + "DataTypes.BooleanType", + "DataTypes.IntegerType", + "DataTypes.IntegerPosType", + "DataTypes.IntegerNNType", + "DataTypes.StringType", + "DataTypes.CollectionType", + "DataTypes.DateType", + "DataTypes.PhoneType" + ], + "Formatters": [ + "Formatters.commaNumber", + "Formatters.currency", + "Formatters.date", + "Formatters.integer", + "Formatters.phone" + ], + "Validators": [ + "Validators.collection", + "Validators.email", + "Validators.expression", + "Validators.integer", + "Validators.length", + "Validators.max", + "Validators.min", + "Validators.oneOf", + "Validators.phone", + "Validators.readonly", + "Validators.regex", + "Validators.required", + "Validators.string", + "Validators.zip" + ] + }, + "customPrimitives": [ + "Expression", + "Asset", + "Binding", + "AssetWrapper", + "Schema.DataType", + "ExpressionHandler", + "FormatType", + "ValidatorFunction" + ] +} \ No newline at end of file diff --git a/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap b/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap index 3a334d39..d84c578b 100644 --- a/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap +++ b/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap @@ -58,6 +58,10 @@ exports[`player language service > completion > basic value completions 1`] = ` "kind": 12, "label": "ACTION", }, + { + "kind": 12, + "label": "ASYNC_ACTION", + }, { "kind": 12, "label": "EXTERNAL", @@ -112,15 +116,15 @@ exports[`player language service > hover > basic hover docs 3`] = ` exports[`player language service > validation > throws AssetWrapper errors 1`] = ` [ { - "message": "Content Validation Error - type: Expected type "string" but got "null"", + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", "range": { "end": { - "character": 28, - "line": 20, + "character": 15, + "line": 21, }, "start": { - "character": 16, - "line": 20, + "character": 29, + "line": 19, }, }, "severity": 1, @@ -182,7 +186,7 @@ exports[`player language service > validation > throws AssetWrapper errors 1`] = "severity": 1, }, { - "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", + "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", "range": { "end": { "character": 16, diff --git a/language/json-language-service/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts b/language/json-language-service/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts index 406d9d31..07bedc7f 100644 --- a/language/json-language-service/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts +++ b/language/json-language-service/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts @@ -57,7 +57,7 @@ describe("asset-wrapper-array-plugin", () => { expect(diags?.map((d) => d.message)).toMatchInlineSnapshot(` [ "View Validation Error - value: Does not match any of the expected types for type: 'AssetWrapperOrSwitch'", - "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", + "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", "Implicit Array -> "collection" assets is not supported.", ] `); @@ -79,7 +79,7 @@ describe("asset-wrapper-array-plugin", () => { "severity": 1, }, { - "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", + "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", "range": { "end": { "character": 7, diff --git a/language/json-language-service/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts b/language/json-language-service/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts index a2136d3c..d2aeca46 100644 --- a/language/json-language-service/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts +++ b/language/json-language-service/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts @@ -60,7 +60,7 @@ describe("missing-asset-wrapper", () => { expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(` [ "View Validation Error - value: Does not match any of the expected types for type: 'AssetWrapperOrSwitch'", - "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", + "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", ] `); }); @@ -87,7 +87,7 @@ describe("missing-asset-wrapper", () => { "severity": 1, }, { - "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", + "message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal", "range": { "end": { "character": 7, diff --git a/xlr/sdk/src/__tests__/__snapshots__/sdk.test.ts.snap b/xlr/sdk/src/__tests__/__snapshots__/sdk.test.ts.snap index ca42a5c8..06b866ab 100644 --- a/xlr/sdk/src/__tests__/__snapshots__/sdk.test.ts.snap +++ b/xlr/sdk/src/__tests__/__snapshots__/sdk.test.ts.snap @@ -331,6 +331,7 @@ export type Navigation = { attributes?: { [key: string]: any; }; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -352,6 +353,7 @@ export type Navigation = { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -394,6 +396,30 @@ export type Navigation = { exp: Expression; /** A mapping of transition-name to FlowState name */ transitions: Record; + } | { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'ASYNC_ACTION'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ + onEnd?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** + * An expression to execute. + * The return value determines the transition to take + */ + exp: Expression; + /** A mapping of transition-name to FlowState name */ + transitions: Record; + /** Whether the expression(s) should be awaited before transitioning */ + await: boolean; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -414,6 +440,7 @@ export type Navigation = { transitions: Record; /** A reference for this external state */ ref: string; + [key: string]: unknown; }); }>; /** An object with an expression in it */ @@ -462,6 +489,7 @@ export interface NavigationFlow { attributes?: { [key: string]: any; }; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -483,6 +511,7 @@ export interface NavigationFlow { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -525,6 +554,30 @@ export interface NavigationFlow { exp: Expression; /** A mapping of transition-name to FlowState name */ transitions: Record; + } | { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'ASYNC_ACTION'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ + onEnd?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** + * An expression to execute. + * The return value determines the transition to take + */ + exp: Expression; + /** A mapping of transition-name to FlowState name */ + transitions: Record; + /** Whether the expression(s) should be awaited before transitioning */ + await: boolean; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -545,6 +598,7 @@ export interface NavigationFlow { transitions: Record; /** A reference for this external state */ ref: string; + [key: string]: unknown; }); } export type NavigationFlowTransition = Record; @@ -568,7 +622,7 @@ export interface NavigationBaseState { * TS gets really confused with both the ActionState and the onStart state both declaring the \`exp\` property * So this explicity says there should never be an exp prop on a state node that's not of type 'ACTION' */ - exp?: T extends 'ACTION' ? Expression : never; + exp?: T extends 'ACTION' | 'ASYNC_ACTION' ? Expression : never; } /** A generic state that can transition to another state */ export interface NavigationFlowTransitionableState { @@ -590,7 +644,7 @@ export interface NavigationFlowTransitionableState { * TS gets really confused with both the ActionState and the onStart state both declaring the \`exp\` property * So this explicity says there should never be an exp prop on a state node that's not of type 'ACTION' */ - exp?: T extends 'ACTION' ? Expression : never; + exp?: T extends 'ACTION' | 'ASYNC_ACTION' ? Expression : never; /** A mapping of transition-name to FlowState name */ transitions: Record; } @@ -619,6 +673,7 @@ export interface NavigationFlowViewState { attributes?: { [key: string]: any; }; + [key: string]: unknown; } /** An END state of the flow. */ export interface NavigationFlowEndState { @@ -642,6 +697,7 @@ export interface NavigationFlowEndState { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; } /** Action states execute an expression to determine the next state to transition to */ export interface NavigationFlowActionState { @@ -667,6 +723,32 @@ export interface NavigationFlowActionState { /** A mapping of transition-name to FlowState name */ transitions: Record; } +/** Action states execute an expression to determine the next state to transition to */ +export interface NavigationFlowAsyncActionState { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'ASYNC_ACTION'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ + onEnd?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** + * An expression to execute. + * The return value determines the transition to take + */ + exp: Expression; + /** A mapping of transition-name to FlowState name */ + transitions: Record; + /** Whether the expression(s) should be awaited before transitioning */ + await: boolean; +} /** * External Flow states represent states in the FSM that can't be resolved internally in Player. * The flow will wait for the embedded application to manage moving to the next state via a transition @@ -691,6 +773,7 @@ export interface NavigationFlowExternalState { transitions: Record; /** A reference for this external state */ ref: string; + [key: string]: unknown; } export interface NavigationFlowFlowState { /** Add comments that will not be processing, but are useful for code explanation */ @@ -737,6 +820,7 @@ export type NavigationFlowState = { attributes?: { [key: string]: any; }; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -758,6 +842,7 @@ export type NavigationFlowState = { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -800,6 +885,30 @@ export type NavigationFlowState = { exp: Expression; /** A mapping of transition-name to FlowState name */ transitions: Record; +} | { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'ASYNC_ACTION'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ + onEnd?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** + * An expression to execute. + * The return value determines the transition to take + */ + exp: Expression; + /** A mapping of transition-name to FlowState name */ + transitions: Record; + /** Whether the expression(s) should be awaited before transitioning */ + await: boolean; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -820,6 +929,7 @@ export type NavigationFlowState = { transitions: Record; /** A reference for this external state */ ref: string; + [key: string]: unknown; }; /** The data at the end of a flow */ export interface FlowResult { @@ -845,6 +955,7 @@ export interface FlowResult { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; }; /** The serialized data-model */ data?: any; @@ -867,6 +978,8 @@ export interface Templatable { * If it already exists, values are appended to the end. */ output: Key; + /** Specifies the template placement in relation to existing elements */ + placement?: 'prepend' | 'append'; }>; } /** A template describes a mapping from a data array -> array of objects */ @@ -885,6 +998,8 @@ export interface Template = unknown extends Asset ? T & { /** Each view can optionally supply a list of validations to run against a particular view */ @@ -904,6 +1019,12 @@ export type View = unknown extends Asset ? T & { dataTarget?: never; /** Where the error should be displayed */ displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; /** The binding to associate this validation with */ ref?: Binding; [key: string]: unknown; @@ -932,6 +1053,12 @@ export interface Flow { dataTarget?: never; /** Where the error should be displayed */ displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; /** The binding to associate this validation with */ ref?: Binding; [key: string]: unknown; @@ -947,8 +1074,6 @@ export interface Flow { [key: string]: { /** The reference of the base type to use */ type: string; - /** The referenced object represents an array rather than an object */ - isArray?: boolean; /** * Any additional validations that are associated with this property * These will add to any base validations associated with the "type" @@ -973,6 +1098,116 @@ export interface Flow { dataTarget?: 'formatted' | 'deformatted'; /** Where the error should be displayed */ displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; [key: string]: unknown; }>; /** @@ -989,6 +1224,10 @@ export interface Flow { * Any reads for this property will result in this default value being written to the model. */ default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; [key: string]: unknown; }; }; @@ -996,8 +1235,6 @@ export interface Flow { [key: string]: { /** The reference of the base type to use */ type: string; - /** The referenced object represents an array rather than an object */ - isArray?: boolean; /** * Any additional validations that are associated with this property * These will add to any base validations associated with the "type" @@ -1022,6 +1259,12 @@ export interface Flow { dataTarget?: 'formatted' | 'deformatted'; /** Where the error should be displayed */ displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; [key: string]: unknown; }>; /** @@ -1039,66 +1282,175 @@ export interface Flow { */ default?: T; [key: string]: unknown; - }; - }; - }; - /** Any initial data that the flow can use */ - data?: Record; - /** A state machine to drive a user through the experience */ - navigation: { - /** The name of the Flow to begin on */ - BEGIN: string; - } & Record; - /** An id corresponding to a view from the 'views' array */ - ref: string; - /** View meta-properties */ - attributes?: { - [key: string]: any; - }; - } | { - /** Add comments that will not be processing, but are useful for code explanation */ - _comment?: string; - /** A property to determine the type of state this is */ - state_type: 'END'; - /** An optional expression to run when this view renders */ - onStart?: Expression | { - /** The expression to run */ - exp?: Expression; - }; - /** An optional expression to run before view transition */ + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; + }; + }; + }; + /** Any initial data that the flow can use */ + data?: Record; + /** A state machine to drive a user through the experience */ + navigation: { + /** The name of the Flow to begin on */ + BEGIN: string; + } & Record; + /** An id corresponding to a view from the 'views' array */ + ref: string; + /** View meta-properties */ + attributes?: { + [key: string]: any; + }; + [key: string]: unknown; + } | { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'END'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ onEnd?: Expression | { /** The expression to run */ exp?: Expression; @@ -1109,6 +1461,7 @@ export interface Flow { * If this is a flow started from another flow, the outcome determines the flow transition */ outcome: string; + [key: string]: unknown; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -1151,6 +1504,30 @@ export interface Flow { exp: Expression; /** A mapping of transition-name to FlowState name */ transitions: Record; + } | { + /** Add comments that will not be processing, but are useful for code explanation */ + _comment?: string; + /** A property to determine the type of state this is */ + state_type: 'ASYNC_ACTION'; + /** An optional expression to run when this view renders */ + onStart?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** An optional expression to run before view transition */ + onEnd?: Expression | { + /** The expression to run */ + exp?: Expression; + }; + /** + * An expression to execute. + * The return value determines the transition to take + */ + exp: Expression; + /** A mapping of transition-name to FlowState name */ + transitions: Record; + /** Whether the expression(s) should be awaited before transitioning */ + await: boolean; } | { /** Add comments that will not be processing, but are useful for code explanation */ _comment?: string; @@ -1171,10 +1548,909 @@ export interface Flow { transitions: Record; /** A reference for this external state */ ref: string; + [key: string]: unknown; }); }>; [key: string]: unknown; } +/** The authored schema object in the JSON payload */ +export interface Schema.Schema { + /** The ROOT object is the top level object to use */ + ROOT: { + [key: string]: { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; + }; + }; + [key: string]: { + [key: string]: { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; + }; + }; +} +/** A Node describes a specific object in the tree */ +export interface Schema.Node { + [key: string]: { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; + } | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; + }; +} +export type Schema.DataTypes = { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; +} | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; +} | { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; +}; +/** Each prop in the object can have a specific DataType */ +export interface Schema.DataType { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + [key: string]: unknown; +} +/** Determines if the Datatype is a record object */ +export interface Schema.RecordType { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its a record */ + isRecord: boolean; + /** This property is mutually exclusive with RecordType and can not be used with ArrayType */ + isArray?: never; + [key: string]: unknown; +} +/** Determines if the DataType is an Array Object */ +export interface Schema.ArrayType { + /** The reference of the base type to use */ + type: string; + /** + * Any additional validations that are associated with this property + * These will add to any base validations associated with the "type" + */ + validation?: Array<{ + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; + }>; + /** + * A reference to a specific data format to use. + * If none is specified, will fallback to that of the base type + */ + format?: { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; + }; + /** + * A default value for this property. + * Any reads for this property will result in this default value being written to the model. + */ + default?: T; + /** boolean to define if its an array */ + isArray: boolean; + /** This property is mutually exclusive with ArrayType and can not be used with RecordType */ + isRecord?: never; + [key: string]: unknown; +} +/** Helper to compliment \`Schema.DataType\` to provide a way to export a reference to a data type instead of the whole object */ +export interface Language.DataTypeRef { + /** Name of the type in Player Core */ + type: string; +} +/** A reference to a specific formatter */ +export interface Formatting.Reference { + /** The name of the formatter (and de-formatter) to use */ + type: string; + [key: string]: unknown; +} +/** + * How serious are you about this error? + * Warning validations are reserved for errors that could be ignored by the user without consequence + * Errors must be fixed before proceeding +*/ +export type Validation.Severity = 'error' | 'warning'; +/** + * When to _first_ start caring about a validation of a data-val. + * + * load - only check once the first time the binding appears on screen + * change - check anytime the data changes + * navigation - check once the user attempts to navigate away from a view +*/ +export type Validation.Trigger = 'navigation' | 'change' | 'load'; +/** + * Where the error/warning should be displayed. + * - \`field\` is the default display target. This renders the error/warning directly underneath the field. + * - \`section\` is used to display a message at a parent node that is designated as a "section" + * - \`page\` a special section used to display a message at the top of the page. +*/ +export type Validation.DisplayTarget = 'page' | 'section' | 'field'; +/** A reference to a validation object */ +export interface Validation.Reference { + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** + * Each validation is passed the value of the data to run it's validation against. + * By default, this is the value stored in the data-model (deformatted). + * In the off chance you'd like this validator to run against the formatted value (the one the user sees), set this option + */ + dataTarget?: 'formatted' | 'deformatted'; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + [key: string]: unknown; +} +export interface Validation.CrossfieldReference { + /** + * The name of the referenced validation type + * This will be used to lookup the proper handler + */ + type: string; + /** An optional means of overriding the default message if the validation is triggered */ + message?: string; + /** An optional means of overriding the default severity of the validation if triggered */ + severity?: 'error' | 'warning'; + /** When to run this particular validation */ + trigger?: 'navigation' | 'change' | 'load'; + /** Cross-field references and validation must run against the default (deformatted) value */ + dataTarget?: never; + /** Where the error should be displayed */ + displayTarget?: 'page' | 'section' | 'field'; + /** + * If the validation blocks navigation + * true/false - always/never block navigation + * once - only block navigation if the validation has not been triggered before + */ + blocking?: boolean | 'once'; + /** The binding to associate this validation with */ + ref?: Binding; + [key: string]: unknown; +} /** * This is the most generic way of gathering data. The input is bound to a data model using the 'binding' property. * Players can get field type information from the 'schema' definition, thus to decide the input controls for visual rendering. diff --git a/xlr/sdk/src/__tests__/sdk.test.ts b/xlr/sdk/src/__tests__/sdk.test.ts index 427bfd07..6f6f4946 100644 --- a/xlr/sdk/src/__tests__/sdk.test.ts +++ b/xlr/sdk/src/__tests__/sdk.test.ts @@ -78,14 +78,15 @@ describe("Object Recall", () => { expect(sdk.getType("InputAsset")).toMatchSnapshot(); }); - test("Test", () => { + test("Test Correct Generic Cascading", () => { const sdk = new XLRSDK(); sdk.loadDefinitionsFromModule(Types); sdk.loadDefinitionsFromModule(ReferenceAssetsWebPluginManifest); const Flow = sdk.getType("Flow") as ObjectType; const Schema = Flow.properties["schema"].node as ObjectType; const Node = Schema.additionalProperties as ObjectType; - const DataType = Node.additionalProperties as ObjectType; + const DataTypes = Node.additionalProperties as OrType; + const DataType = DataTypes.or[0] as ObjectType; const df = DataType.properties["default"].node; expect(df.type).toBe("unknown"); }); From 57f838b92772ca5671cd0d5d73f4906ad93e935d Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Sat, 28 Feb 2026 00:30:41 -0800 Subject: [PATCH 10/14] Support Schema validation --- .../__snapshots__/service.test.ts.snap | 14 - .../src/__tests__/service.test.ts | 29 +- .../json-language-service/src/constants.ts | 2 + .../schema-validation-plugin.test.ts | 1555 +++++++++++++++++ .../src/plugins/schema-validation-plugin.ts | 443 +++++ .../src/plugins/xlr-plugin.ts | 23 +- language/json-language-service/src/utils.ts | 20 + 7 files changed, 2023 insertions(+), 63 deletions(-) create mode 100644 language/json-language-service/src/plugins/__tests__/schema-validation-plugin.test.ts create mode 100644 language/json-language-service/src/plugins/schema-validation-plugin.ts diff --git a/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap b/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap index d84c578b..1ef67fa8 100644 --- a/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap +++ b/language/json-language-service/src/__tests__/__snapshots__/service.test.ts.snap @@ -115,20 +115,6 @@ exports[`player language service > hover > basic hover docs 3`] = ` exports[`player language service > validation > throws AssetWrapper errors 1`] = ` [ - { - "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", - "range": { - "end": { - "character": 15, - "line": 21, - }, - "start": { - "character": 29, - "line": 19, - }, - }, - "severity": 1, - }, { "message": "Content Validation Error - missing: Property "BEGIN" missing from type "Navigation"", "range": { diff --git a/language/json-language-service/src/__tests__/service.test.ts b/language/json-language-service/src/__tests__/service.test.ts index 1700061b..4032a38a 100644 --- a/language/json-language-service/src/__tests__/service.test.ts +++ b/language/json-language-service/src/__tests__/service.test.ts @@ -71,34 +71,7 @@ describe("player language service", () => { "data": {}, "navigation": {}, "schema": { - "ROOT": { - "application": { - "type": null - }, - "foo": { - "type": "fooType" - } - }, - "fooType": { - "bar": { - "type": "barType", - "validation": [ - { - "type": "required", - "severity": "error" - } - ] - }, - "baz": { - "type": "bazType", - "validation": [ - { - "type": "required", - "message": "this is my own message" - } - ] - } - } + "ROOT": {} } } `, diff --git a/language/json-language-service/src/constants.ts b/language/json-language-service/src/constants.ts index 34f96575..4ce568dd 100644 --- a/language/json-language-service/src/constants.ts +++ b/language/json-language-service/src/constants.ts @@ -5,6 +5,7 @@ import type { PlayerLanguageServicePlugin } from "."; import { AssetWrapperArrayPlugin } from "./plugins/asset-wrapper-array-plugin"; import { SchemaInfoPlugin } from "./plugins/binding-schema-plugin"; import { XLRPlugin } from "./plugins/xlr-plugin"; +import { SchemaValidationPlugin } from "./plugins/schema-validation-plugin"; import { DuplicateIDPlugin } from "./plugins/duplicate-id-plugin"; import { MissingAssetWrapperPlugin } from "./plugins/missing-asset-wrapper-plugin"; import { NavStatePlugin } from "./plugins/nav-state-plugin"; @@ -20,6 +21,7 @@ export const PLUGINS: Array = [ new DuplicateIDPlugin(), new ViewNodePlugin(), new SchemaInfoPlugin(), + new SchemaValidationPlugin(), new AssetWrapperArrayPlugin(), new NavStatePlugin(), new MissingAssetWrapperPlugin(), diff --git a/language/json-language-service/src/plugins/__tests__/schema-validation-plugin.test.ts b/language/json-language-service/src/plugins/__tests__/schema-validation-plugin.test.ts new file mode 100644 index 00000000..f7dab3c6 --- /dev/null +++ b/language/json-language-service/src/plugins/__tests__/schema-validation-plugin.test.ts @@ -0,0 +1,1555 @@ +import { test, expect, describe, beforeEach } from "vitest"; +import { CommonTypes, Types } from "@player-tools/static-xlrs"; +import { PlayerLanguageService } from "../.."; +import { toTextDocument } from "../../utils"; + +describe("SchemaValidationPlugin", () => { + let service: PlayerLanguageService; + + beforeEach(async () => { + service = new PlayerLanguageService(); + await service.setAssetTypesFromModule([Types, CommonTypes]); + }); + + describe("schema structure validation", () => { + test("reports error when schema is missing ROOT", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + SomeType: { + foo: { type: "SomeType" }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - missing: Property "ROOT" missing from type "Schema.Schema"", + "range": { + "end": { + "character": 3, + "line": 12, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema must have a "ROOT" key.", + "range": { + "end": { + "character": 3, + "line": 12, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when DataType is missing type property", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + application: { + validation: [], + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 10, + }, + "start": { + "character": 21, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema.DataType must have a "type" property (reference to schema or XLR type).", + "range": { + "end": { + "character": 7, + "line": 10, + }, + "start": { + "character": 21, + "line": 8, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when type is not a string", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + application: { + type: 123, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 10, + }, + "start": { + "character": 21, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema "type" must be a string (schema type name or XLR type name).", + "range": { + "end": { + "character": 19, + "line": 9, + }, + "start": { + "character": 16, + "line": 9, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when isArray is not a boolean", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + items: { + type: "StringType", + isArray: "yes", + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Schema.DataType "isArray" must be a boolean.", + "range": { + "end": { + "character": 24, + "line": 10, + }, + "start": { + "character": 19, + "line": 10, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when validation is not an array", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "TypeA", + validation: "not-an-array", + }, + }, + TypeA: { + nested: { type: "StringType" }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 11, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when format is not an object", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "TypeA", + format: "not-an-object", + }, + }, + TypeA: { + nested: { type: "StringType" }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 11, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when isRecord is not a boolean", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + isRecord: "yes", + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Schema.DataType "isRecord" must be a boolean.", + "range": { + "end": { + "character": 25, + "line": 10, + }, + "start": { + "character": 20, + "line": 10, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when both isArray and isRecord are true", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + isArray: true, + isRecord: true, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Schema.DataType cannot have both "isArray" and "isRecord" true.", + "range": { + "end": { + "character": 7, + "line": 12, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + ] + `); + }); + }); + + describe("schema type reference validation", () => { + test("reports error for unknown type reference (not in schema, not in XLR)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + foo: { + type: "NonExistentXLRType", + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Unknown schema type "NonExistentXLRType". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.", + "range": { + "end": { + "character": 36, + "line": 9, + }, + "start": { + "character": 16, + "line": 9, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("accepts type reference to XLR-loaded type when that type is in the SDK", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + name: { type: "StringType" }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(`[]`); + }); + + test("reports errors for multiple unknown type references", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + a: { type: "UnknownA" }, + b: { type: "UnknownB" }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Unknown schema type "UnknownA". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.", + "range": { + "end": { + "character": 26, + "line": 9, + }, + "start": { + "character": 16, + "line": 9, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Unknown schema type "UnknownB". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.", + "range": { + "end": { + "character": 26, + "line": 12, + }, + "start": { + "character": 16, + "line": 12, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports no Schema DataType errors when DataType conforms to XLR (StringType)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + name: { + type: "StringType", + default: "", + validation: [], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(`[]`); + }); + + test("reports Schema DataType error when BooleanType payload has wrong type for property", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + flag: { + type: "BooleanType", + default: "not-a-boolean", + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Default value doesn't match the expected type of boolean for type BooleanType", + "range": { + "end": { + "character": 34, + "line": 10, + }, + "start": { + "character": 19, + "line": 10, + }, + }, + "severity": 1, + }, + ] + `); + }); + }); + + describe("flow without schema", () => { + test("does not add schema errors when schema is absent", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(`[]`); + }); + }); + + describe("validation object logic branches", () => { + test('reports error when validation entry is missing "type" property', async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [{}], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 16, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Validation object missing "type" property", + "range": { + "end": { + "character": 12, + "line": 11, + }, + "start": { + "character": 10, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when validation type is not a string", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [{ type: 123 }], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 18, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Validation type must be a string", + "range": { + "end": { + "character": 23, + "line": 12, + }, + "start": { + "character": 20, + "line": 12, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when validation type is not a registered validator", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [{ type: "NonExistentValidator" }], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Validation Function [object Object] is not a registered validator", + "range": { + "end": { + "character": 11, + "line": 13, + }, + "start": { + "character": 10, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when registered validator has invalid props (min expects number)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + score: { + type: "IntegerType", + validation: [{ type: "min", value: "not-a-number" }], + format: { type: "integer" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Expected type "number" but got "string"", + "range": { + "end": { + "character": 11, + "line": 14, + }, + "start": { + "character": 10, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when validation array element is not an object", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: ["not-an-object"], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 16, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema.DataType "validation" must be an object.", + "range": { + "end": { + "character": 9, + "line": 12, + }, + "start": { + "character": 22, + "line": 10, + }, + }, + "severity": 1, + }, + ] + `); + }); + }); + + describe("format object logic branches", () => { + test('reports error when format object is missing "type" property', async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [], + format: {}, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 12, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Format object missing "type" property", + "range": { + "end": { + "character": 20, + "line": 11, + }, + "start": { + "character": 18, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when format type is not a string", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [], + format: { type: 42 }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 7, + "line": 14, + }, + "start": { + "character": 15, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Format type must be a string", + "range": { + "end": { + "character": 20, + "line": 12, + }, + "start": { + "character": 18, + "line": 12, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when format type is not a registered formatter", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + field: { + type: "StringType", + validation: [], + format: { type: "NonExistentFormatter" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Formatter [object Object] is not a registered formatter", + "range": { + "end": { + "character": 9, + "line": 13, + }, + "start": { + "character": 18, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when formatter with 3 args has invalid props (date mask must be string)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + birthDate: { + type: "DateType", + validation: [], + format: { type: "date", mask: 123 }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Expected type "string" but got "number"", + "range": { + "end": { + "character": 9, + "line": 14, + }, + "start": { + "character": 18, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("accepts formatter with 3 args and valid props (date with mask string)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + birthDate: { + type: "DateType", + validation: [], + format: { type: "date", mask: "MM/DD/YYYY" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + }); + + describe("schema and flow structure branches", () => { + test("reports error when flow schema is not an object (string)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: "not-an-object", + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - type: Expected an object but got an "string"", + "range": { + "end": { + "character": 27, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Flow "schema" must be an object with at least a "ROOT" key.", + "range": { + "end": { + "character": 27, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when flow schema is not an object (array)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: [], + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - type: Expected an object but got an "array"", + "range": { + "end": { + "character": 14, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Flow "schema" must be an object with at least a "ROOT" key.", + "range": { + "end": { + "character": 14, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when schema node (ROOT) value is not an object", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: "not-an-object", + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - type: Expected an object but got an "string"", + "range": { + "end": { + "character": 27, + "line": 7, + }, + "start": { + "character": 12, + "line": 7, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema node "ROOT" must be an object.", + "range": { + "end": { + "character": 27, + "line": 7, + }, + "start": { + "character": 12, + "line": 7, + }, + }, + "severity": 1, + }, + ] + `); + }); + + test("reports error when schema property value is not an object (DataType)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + application: "must-be-DataType-object", + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'", + "range": { + "end": { + "character": 46, + "line": 8, + }, + "start": { + "character": 21, + "line": 8, + }, + }, + "severity": 1, + }, + { + "message": "Schema Validation Error: Schema property "application" must be an object (Schema.DataType) with a "type" field.", + "range": { + "end": { + "character": 46, + "line": 8, + }, + "start": { + "character": 21, + "line": 8, + }, + }, + "severity": 1, + }, + ] + `); + }); + }); + + describe("specific DataTypes from CommonTypes", () => { + test("accepts IntegerType with valid validation and format", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + count: { + type: "IntegerType", + validation: [{ type: "integer" }], + format: { type: "integer" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("accepts BooleanType with valid default and validation", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + flag: { + type: "BooleanType", + default: false, + validation: [ + { + type: "oneOf", + message: "Value must be true or false", + options: [true, false], + }, + ], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("accepts DateType with valid format", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + start: { + type: "DateType", + validation: [], + format: { type: "date" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("accepts formatter with 2 generic args (integer) without prop validation", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + num: { + type: "IntegerType", + validation: [], + format: { type: "integer" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + }); + + describe("specific Validators from CommonTypes", () => { + test("accepts required validator with no props", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + name: { + type: "StringType", + validation: [{ type: "required" }], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("accepts string validator (no props)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + name: { + type: "StringType", + validation: [{ type: "string" }], + format: { type: "string" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("accepts min validator with valid number prop", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + score: { + type: "IntegerType", + validation: [{ type: "min", value: 0 }], + format: { type: "integer" }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + }); + + describe("specific Formatters from CommonTypes", () => { + test("accepts currency formatter with valid optional props", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + price: { + type: "StringType", + validation: [], + format: { + type: "currency", + currencySymbol: "$", + precision: 2, + useParensForNeg: false, + }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + const schemaErrors = (diagnostics ?? []).filter((d) => + d.message.includes("Schema Validation Error:"), + ); + expect(schemaErrors).toHaveLength(0); + }); + + test("reports error when currency formatter has invalid prop type (precision must be number)", async () => { + const document = toTextDocument( + JSON.stringify( + { + id: "foo", + views: [], + navigation: { BEGIN: "FLOW1" }, + schema: { + ROOT: { + price: { + type: "StringType", + validation: [], + format: { + type: "currency", + precision: "two", + }, + }, + }, + }, + }, + null, + 2, + ), + ); + const diagnostics = await service.validateTextDocument(document); + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "message": "Schema Validation Error: Expected type "number" but got "string"", + "range": { + "end": { + "character": 9, + "line": 14, + }, + "start": { + "character": 18, + "line": 11, + }, + }, + "severity": 1, + }, + ] + `); + }); + }); +}); diff --git a/language/json-language-service/src/plugins/schema-validation-plugin.ts b/language/json-language-service/src/plugins/schema-validation-plugin.ts new file mode 100644 index 00000000..5951d093 --- /dev/null +++ b/language/json-language-service/src/plugins/schema-validation-plugin.ts @@ -0,0 +1,443 @@ +import { DiagnosticSeverity } from "vscode-languageserver-types"; +import type { PlayerLanguageService, PlayerLanguageServicePlugin } from ".."; +import type { ValidationContext, ASTVisitor } from "../types"; +import type { ContentASTNode, ObjectASTNode, StringASTNode } from "../parser"; +import { getProperty } from "../utils"; +import type { XLRSDK } from "@player-tools/xlr-sdk"; +import { NamedType, ObjectType, RefType } from "@player-tools/xlr"; +import { translateSeverity } from "./xlr-plugin"; + +function formatErrorMessage(message: string): string { + return `Schema Validation Error: ${message}`; +} + +/** + * Validate that all claimed validations are registered and pass the correct props + */ +function validateSchemaValidations( + validationNode: ObjectASTNode, + sdk: XLRSDK, + validationContext: ValidationContext, +) { + const claimedValidator = getProperty(validationNode, "type"); + if (!claimedValidator) { + validationContext.addViolation({ + node: validationNode, + message: formatErrorMessage('Validation object missing "type" property'), + severity: DiagnosticSeverity.Error, + }); + } else if (claimedValidator.valueNode?.type !== "string") { + validationContext.addViolation({ + node: claimedValidator.valueNode ?? validationNode, + message: formatErrorMessage("Validation type must be a string"), + severity: DiagnosticSeverity.Error, + }); + } else { + const validationXLR = sdk.getType(claimedValidator.valueNode.value, { + getRawType: true, + }) as NamedType; + if (!validationXLR) { + validationContext.addViolation({ + node: validationNode, + message: formatErrorMessage( + `Validation Function ${claimedValidator} is not a registered validator`, + ), + severity: DiagnosticSeverity.Error, + }); + } else { + const valRef = sdk.getType("Validation.Reference", { + getRawType: true, + }) as NamedType | undefined; + if (valRef) { + const validatorFunctionProps = + (validationXLR.genericArguments?.[0] as ObjectType)?.properties ?? {}; + + const valRefInstance = { + ...valRef, + properties: { + ...valRef.properties, + ...validatorFunctionProps, + }, + additionalProperties: false, + } as ObjectType; + + const validationIssues = sdk.validateByType( + valRefInstance, + validationNode.jsonNode, + ); + validationIssues.forEach((issue) => { + validationContext.addViolation({ + node: validationNode, + message: formatErrorMessage(issue.message), + severity: translateSeverity(issue.severity), + }); + }); + } else { + validationContext.addViolation({ + node: validationNode, + message: formatErrorMessage( + "Validation.Reference from @player-ui/types is not loaded into SDK", + ), + severity: DiagnosticSeverity.Error, + }); + } + } + } +} + +/** + * Validate that the format function is registered and passes the correct props + */ +function validateSchemaFormat( + formatNode: ObjectASTNode, + sdk: XLRSDK, + validationContext: ValidationContext, +) { + const claimedFormatter = getProperty(formatNode, "type"); + if (!claimedFormatter) { + validationContext.addViolation({ + node: formatNode, + message: formatErrorMessage('Format object missing "type" property'), + severity: DiagnosticSeverity.Error, + }); + } else if (claimedFormatter.valueNode?.type !== "string") { + validationContext.addViolation({ + node: claimedFormatter.valueNode ?? claimedFormatter, + message: formatErrorMessage("Format type must be a string"), + severity: DiagnosticSeverity.Error, + }); + } else { + const formatterXLR = sdk.getType(claimedFormatter.valueNode.value, { + getRawType: true, + }) as RefType; + if (!formatterXLR) { + validationContext.addViolation({ + node: formatNode, + message: formatErrorMessage( + `Formatter ${claimedFormatter} is not a registered formatter`, + ), + severity: DiagnosticSeverity.Error, + }); + } else if ( + formatterXLR.genericArguments && + formatterXLR.genericArguments.length === 3 + ) { + const otherArgsXLR = formatterXLR.genericArguments[2] as ObjectType; + const validationIssues = sdk.validateByType( + { + ...otherArgsXLR, + properties: { + ...otherArgsXLR.properties, + type: { + required: true, + node: { + type: "string", + const: claimedFormatter.valueNode.value, + }, + }, + }, + }, + formatNode.jsonNode, + ); + + validationIssues.forEach((issue) => { + validationContext.addViolation({ + node: formatNode, + message: formatErrorMessage(issue.message), + severity: translateSeverity(issue.severity), + }); + }); + } + } +} + +/** + * Collects all type names defined at the top level of the schema (ROOT and + * any custom type names). Used to validate that "type" references either + * point to a schema type or an XLR-loaded type. + */ +function getSchemaTypeNames(schemaObj: ObjectASTNode): Set { + const names = new Set(); + for (const prop of schemaObj.properties) { + const key = prop.keyNode?.value; + if (typeof key === "string") { + names.add(key); + } + } + return names; +} + +/** + * Validates that a Schema.DataType object has the proper structure + */ +function validateDataTypeStructure( + dataTypeNode: ObjectASTNode, + claimedDataType: NamedType, + sdk: XLRSDK, + validationContext: ValidationContext, +): void { + // Basic Structural Tests + const validationProp = getProperty(dataTypeNode, "validation"); + if (validationProp?.valueNode && validationProp.valueNode.type !== "array") { + validationContext.addViolation({ + node: validationProp.valueNode, + message: formatErrorMessage( + 'Schema.DataType "validation" must be an array.', + ), + severity: DiagnosticSeverity.Error, + }); + } else if (validationProp?.valueNode) { + validationProp.valueNode.children?.forEach((valRef) => { + if (valRef && valRef.type === "object") { + validateSchemaValidations(valRef, sdk, validationContext); + } else { + validationContext.addViolation({ + node: validationProp.valueNode ?? dataTypeNode, + message: formatErrorMessage( + 'Schema.DataType "validation" must be an object.', + ), + severity: DiagnosticSeverity.Error, + }); + } + }); + } + + const formatProp = getProperty(dataTypeNode, "format"); + if (formatProp?.valueNode?.type === "object") { + validateSchemaFormat(formatProp.valueNode, sdk, validationContext); + } else { + if (formatProp) { + validationContext.addViolation({ + node: formatProp?.valueNode ?? dataTypeNode, + message: formatErrorMessage( + 'Schema.DataType "format" must be an object.', + ), + severity: DiagnosticSeverity.Error, + }); + } + } + + // Check if default value conforms to the expected value + const defaultNode = claimedDataType.properties["default"]?.node; + const defaultProp = getProperty(dataTypeNode, "default"); + if ( + defaultNode && + defaultProp?.valueNode && + defaultProp.valueNode.type !== defaultNode.type + ) { + validationContext.addViolation({ + node: defaultProp.valueNode, + message: formatErrorMessage( + `Default value doesn't match the expected type of ${defaultNode.type} for type ${claimedDataType.name}`, + ), + severity: DiagnosticSeverity.Error, + }); + } + + // RecordType/ArrayType Checks + const isArrayProp = getProperty(dataTypeNode, "isArray"); + const isRecordProp = getProperty(dataTypeNode, "isRecord"); + if (isArrayProp?.valueNode && isArrayProp.valueNode.type !== "boolean") { + validationContext.addViolation({ + node: isArrayProp.valueNode, + message: formatErrorMessage( + 'Schema.DataType "isArray" must be a boolean.', + ), + severity: DiagnosticSeverity.Error, + }); + } + if (isRecordProp?.valueNode && isRecordProp.valueNode.type !== "boolean") { + validationContext.addViolation({ + node: isRecordProp.valueNode, + message: formatErrorMessage( + 'Schema.DataType "isRecord" must be a boolean.', + ), + severity: DiagnosticSeverity.Error, + }); + } + + if ( + isArrayProp?.valueNode && + isRecordProp?.valueNode && + (isArrayProp.valueNode as { value?: boolean }).value === true && + (isRecordProp.valueNode as { value?: boolean }).value === true + ) { + validationContext.addViolation({ + node: dataTypeNode, + message: formatErrorMessage( + 'Schema.DataType cannot have both "isArray" and "isRecord" true.', + ), + severity: DiagnosticSeverity.Error, + }); + } +} + +/** + * Validates a single schema node (e.g. ROOT or a custom type): each property + * must be an object with a "type" field (Schema.DataType), full structure + * validation, known type reference (schema or XLR), and when the type is an + * XLR type, validates the DataType object against the XLR definition via the SDK. + */ +function validateSchemaNode( + node: ObjectASTNode, + schemaTypeNames: Set, + sdk: XLRSDK, + validationContext: ValidationContext, +): void { + for (const prop of node.properties) { + const valueNode = prop.valueNode; + if (!(valueNode && valueNode.type === "object")) { + if (valueNode) { + validationContext.addViolation({ + node: valueNode, + message: formatErrorMessage( + `Schema property "${prop.keyNode.value}" must be an object (Schema.DataType) with a "type" field.`, + ), + severity: DiagnosticSeverity.Error, + }); + } + continue; + } + + const dataTypeNode = valueNode as ObjectASTNode; + const typeProp = getProperty(dataTypeNode, "type"); + if (!typeProp) { + validationContext.addViolation({ + node: valueNode, + message: formatErrorMessage( + 'Schema.DataType must have a "type" property (reference to schema or XLR type).', + ), + severity: DiagnosticSeverity.Error, + }); + continue; + } + + const typeValueNode = typeProp.valueNode; + if (!typeValueNode || typeValueNode.type !== "string") { + validationContext.addViolation({ + node: typeValueNode ?? typeProp, + message: formatErrorMessage( + 'Schema "type" must be a string (schema type name or XLR type name).', + ), + severity: DiagnosticSeverity.Error, + }); + continue; + } + + const typeName = (typeValueNode as StringASTNode).value; + const isSchemaType = schemaTypeNames.has(typeName); + const XLRType = sdk.getType(typeName); + + if (!isSchemaType && !XLRType) { + validationContext.addViolation({ + node: typeValueNode, + message: formatErrorMessage( + `Unknown schema type "${typeName}". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.`, + ), + severity: DiagnosticSeverity.Error, + }); + } else if (XLRType) { + /** Full DataType structure per @player-ui/types */ + validateDataTypeStructure( + dataTypeNode, + XLRType as NamedType, + sdk, + validationContext, + ); + } + } +} + +/** + * Validates the Flow's schema property: structure per Schema.Schema, + * type references, full DataType structure, and XLR shape when type is an XLR type. + */ +function validateFlowSchema( + contentNode: ContentASTNode, + sdk: XLRSDK, + validationContext: ValidationContext, +): void { + const schemaProp = getProperty(contentNode, "schema"); + if (!schemaProp?.valueNode) { + return; + } + + const schemaValue = schemaProp.valueNode; + if (schemaValue.type !== "object") { + validationContext.addViolation({ + node: schemaValue, + message: formatErrorMessage( + 'Flow "schema" must be an object with at least a "ROOT" key.', + ), + severity: DiagnosticSeverity.Error, + }); + return; + } + + const schemaObj = schemaValue as ObjectASTNode; + const hasRoot = schemaObj.properties.some((p) => p.keyNode.value === "ROOT"); + + if (!hasRoot) { + validationContext.addViolation({ + node: schemaValue, + message: formatErrorMessage('Schema must have a "ROOT" key.'), + severity: DiagnosticSeverity.Error, + }); + } + + const schemaTypeNames = getSchemaTypeNames(schemaObj); + + for (const prop of schemaObj.properties) { + const nodeValue = prop.valueNode; + if (!nodeValue || nodeValue.type !== "object") { + if (nodeValue) { + validationContext.addViolation({ + node: nodeValue, + message: formatErrorMessage( + `Schema node "${prop.keyNode.value}" must be an object.`, + ), + severity: DiagnosticSeverity.Error, + }); + } + continue; + } + + validateSchemaNode( + nodeValue as ObjectASTNode, + schemaTypeNames, + sdk, + validationContext, + ); + } +} + +/** + * Plugin that registers schema validation with the Player Language Service. + */ +export class SchemaValidationPlugin implements PlayerLanguageServicePlugin { + name = "schema-validation"; + + /** Resolved when CommonTypes have been loaded into the XLR SDK (once per plugin apply) */ + private commonTypesLoaded: Promise | null = null; + + apply(service: PlayerLanguageService): void { + service.hooks.validate.tap(this.name, async (_ctx, validationContext) => { + await this.commonTypesLoaded; + validationContext.useASTVisitor( + this.createValidationVisitor(service, validationContext), + ); + }); + } + + private createValidationVisitor( + service: PlayerLanguageService, + validationContext: ValidationContext, + ): ASTVisitor { + const sdk = service.XLRService.XLRSDK; + return { + ContentNode: (contentNode) => { + validateFlowSchema(contentNode, sdk, validationContext); + }, + }; + } +} diff --git a/language/json-language-service/src/plugins/xlr-plugin.ts b/language/json-language-service/src/plugins/xlr-plugin.ts index 329229a4..167cfb5c 100644 --- a/language/json-language-service/src/plugins/xlr-plugin.ts +++ b/language/json-language-service/src/plugins/xlr-plugin.ts @@ -14,7 +14,7 @@ import type { PlayerLanguageServicePlugin, ValidationContext, } from ".."; -import { mapFlowStateToType } from "../utils"; +import { findErrorNode, mapFlowStateToType } from "../utils"; import type { ASTNode, ObjectASTNode } from "../parser"; import type { EnhancedDocumentContextWithPosition } from "../types"; @@ -22,30 +22,11 @@ function isError(issue: ValidationMessage): boolean { return issue.severity === DiagnosticSeverity.Error; } -/** BFS search to find a JSONC node in children of some AST Node */ -const findErrorNode = (rootNode: ASTNode, nodeToFind: Node): ASTNode => { - const children: Array = [rootNode]; - - while (children.length > 0) { - const child = children.pop() as ASTNode; - if (child.jsonNode === nodeToFind) { - return child; - } - - if (child.children) { - children.push(...child.children); - } - } - - // if the node can't be found return the original - return rootNode; -}; - /** * Translates an SDK severity level to an LSP severity level * Relies on both levels having the values associated to the underlying levels */ -const translateSeverity = ( +export const translateSeverity = ( severity: ValidationSeverity, ): DiagnosticSeverity => { return severity as DiagnosticSeverity; diff --git a/language/json-language-service/src/utils.ts b/language/json-language-service/src/utils.ts index 9d33ac3f..df430854 100644 --- a/language/json-language-service/src/utils.ts +++ b/language/json-language-service/src/utils.ts @@ -7,6 +7,7 @@ import type { ObjectASTNode, PropertyASTNode, } from "./parser"; +import type { Node } from "jsonc-parser"; import type { ASTVisitor } from "./types"; export const typeToVisitorMap: Record = { @@ -141,3 +142,22 @@ export function mapFlowStateToType( return flowXLR; } + +/** BFS search to find a JSONC node in children of some AST Node */ +export const findErrorNode = (rootNode: ASTNode, nodeToFind: Node): ASTNode => { + const children: Array = [rootNode]; + + while (children.length > 0) { + const child = children.pop() as ASTNode; + if (child.jsonNode === nodeToFind) { + return child; + } + + if (child.children) { + children.push(...child.children); + } + } + + // if the node can't be found return the original + return rootNode; +}; From cf2786ea499473916cacdbc70b24d6892355171b Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Tue, 3 Mar 2026 11:54:17 -0800 Subject: [PATCH 11/14] Fix issue with double registering of DataTypes/Formatters/Validators types --- language/json-language-service/src/xlr/registry.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/language/json-language-service/src/xlr/registry.ts b/language/json-language-service/src/xlr/registry.ts index 761b7529..c51dab80 100644 --- a/language/json-language-service/src/xlr/registry.ts +++ b/language/json-language-service/src/xlr/registry.ts @@ -2,6 +2,8 @@ import type { Filters, TypeMetadata } from "@player-tools/xlr-sdk"; import { BasicXLRRegistry } from "@player-tools/xlr-sdk"; import type { NamedType, NodeType } from "@player-tools/xlr"; +const SINGLE_INSTANCE_CAPABILITIES = ["DataTypes", "Formatters", "Validators"]; + /** * Player specific implementation of a XLRs Registry */ @@ -47,7 +49,10 @@ export class PlayerXLRRegistry extends BasicXLRRegistry { registeredName = type.extends.genericArguments[0].const; } - if (this.registrationMap.has(registeredName)) { + if ( + this.registrationMap.has(registeredName) && + !SINGLE_INSTANCE_CAPABILITIES.includes(capability) + ) { const current = this.registrationMap.get(registeredName) as | string | string[]; From 4c5e119cf50656ed3a9c500f09a28a0947f63336 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Tue, 3 Mar 2026 12:38:49 -0800 Subject: [PATCH 12/14] Improve tests for xlr utils --- xlr/utils/src/__tests__/ts-helpers.test.ts | 715 ++++++++++++++++++++- xlr/utils/src/ts-helpers.ts | 7 +- 2 files changed, 712 insertions(+), 10 deletions(-) diff --git a/xlr/utils/src/__tests__/ts-helpers.test.ts b/xlr/utils/src/__tests__/ts-helpers.test.ts index cced2ae6..a9f5285e 100644 --- a/xlr/utils/src/__tests__/ts-helpers.test.ts +++ b/xlr/utils/src/__tests__/ts-helpers.test.ts @@ -1,14 +1,50 @@ import { test, expect, describe } from "vitest"; import * as ts from "typescript"; -import type { NodeType } from "@player-tools/xlr"; +import type { + NodeType, + NodeTypeWithGenerics, + ObjectNode, + OrType, +} from "@player-tools/xlr"; import { tsStripOptionalType, isExportedDeclaration, + isExportedModuleDeclaration, + isNodeExported, + getReferencedType, + isTypeScriptLibType, + buildTemplateRegex, + fillInGenerics, applyPickOrOmitToNodeType, getStringLiteralsFromUnion, applyPartialOrRequiredToNodeType, + applyExcludeToNodeType, } from "../ts-helpers"; +import { ScriptTarget } from "typescript"; + +/** Create a TypeChecker from a source string (single file). */ +function createChecker(source: string, fileName = "test.ts"): ts.TypeChecker { + const sourceFile = ts.createSourceFile( + fileName, + source, + ts.ScriptTarget.Latest, + true, + ); + const defaultHost = ts.createCompilerHost({}); + const host: ts.CompilerHost = { + ...defaultHost, + getSourceFile: (name) => + name === fileName + ? sourceFile + : defaultHost.getSourceFile(name, ScriptTarget.ESNext), + writeFile: () => {}, + getCurrentDirectory: () => "", + readFile: () => "", + }; + const program = ts.createProgram([fileName], {}, host); + return program.getTypeChecker(); +} test("tsStripOptionalType", () => { const input: ts.TypeNode = ts.factory.createKeywordTypeNode( @@ -21,6 +57,14 @@ test("tsStripOptionalType", () => { expect(actual).toEqual(expected); }); +test("tsStripOptionalType strips optional type", () => { + const inner = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + const optional = ts.factory.createOptionalTypeNode(inner); + const actual = tsStripOptionalType(optional); + expect(actual).toBe(inner); + expect(ts.isOptionalTypeNode(actual)).toBe(false); +}); + describe("isExportedDeclaration", () => { test("should return false for a non exported Statement", () => { const source = ts.createSourceFile( @@ -55,16 +99,451 @@ describe("isExportedDeclaration", () => { const result = isExportedDeclaration(node); expect(result).toBe(true); }); + + test("should return false for node without modifiers", () => { + const source = ts.createSourceFile( + "test.ts", + "const x = 1;", + ts.ScriptTarget.Latest, + true, + ); + const node = source.statements[0] as ts.Statement; + expect(ts.canHaveModifiers(node)).toBe(true); + const result = isExportedDeclaration(node); + expect(result).toBe(false); + }); }); -test("getStringLiteralsFromUnion", () => { - const input: ts.Node = ts.factory.createUnionTypeNode([ - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("foo")), - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("bar")), - ]); - const expected: Set = new Set(["foo", "bar"]); - const actual = getStringLiteralsFromUnion(input); - expect(actual).toEqual(expected); +describe("isExportedModuleDeclaration", () => { + test("returns true for exported module declaration", () => { + const source = ts.createSourceFile( + "test.ts", + "export module M { }", + ts.ScriptTarget.Latest, + true, + ); + const node = source.statements[0] as ts.Statement; + expect(isExportedModuleDeclaration(node)).toBe(true); + expect(ts.isModuleDeclaration(node)).toBe(true); + }); + + test("returns false for non-exported module declaration", () => { + const source = ts.createSourceFile( + "test.ts", + "module M { }", + ts.ScriptTarget.Latest, + true, + ); + const node = source.statements[0] as ts.Statement; + expect(isExportedModuleDeclaration(node)).toBe(false); + }); + + test("returns false for exported non-module declaration", () => { + const source = ts.createSourceFile( + "test.ts", + "export interface I { }", + ts.ScriptTarget.Latest, + true, + ); + const node = source.statements[0] as ts.Statement; + expect(isExportedModuleDeclaration(node)).toBe(false); + }); +}); + +describe("isNodeExported", () => { + test("returns true when node has Export modifier", () => { + const source = ts.createSourceFile( + "test.ts", + "export interface I { x: number; }", + ts.ScriptTarget.Latest, + true, + ); + const decl = source.statements[0] as ts.InterfaceDeclaration; + expect(isNodeExported(decl)).toBe(true); + }); + + test("returns false when node is nested and has no Export modifier", () => { + const source = ts.createSourceFile( + "test.ts", + "namespace N { interface I { x: number; } }", + ts.ScriptTarget.Latest, + true, + ); + const mod = source.statements[0] as ts.ModuleDeclaration; + const body = mod.body as ts.ModuleBlock; + const decl = body.statements[0] as ts.InterfaceDeclaration; + expect(isNodeExported(decl)).toBe(false); + }); + + test("returns true when parent is SourceFile (top-level)", () => { + const source = ts.createSourceFile( + "test.ts", + "type T = string;", + ts.ScriptTarget.Latest, + true, + ); + const decl = source.statements[0]; + expect(decl.parent?.kind).toBe(ts.SyntaxKind.SourceFile); + expect(isNodeExported(decl)).toBe(true); + }); +}); + +describe("getReferencedType", () => { + test("returns declaration and exported for interface reference", () => { + const source = `interface Foo { a: number } +type Bar = Foo;`; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[1] as ts.TypeAliasDeclaration; + const typeRef = typeAlias.type as ts.TypeReferenceNode; + const result = getReferencedType(typeRef, checker); + expect(result).toBeDefined(); + expect(result!.declaration.kind).toBe(ts.SyntaxKind.InterfaceDeclaration); + expect(ts.isInterfaceDeclaration(result!.declaration)).toBe(true); + expect(result!.exported).toBe(true); + }); + + test("returns declaration for type alias reference", () => { + const source = `type Foo = { a: number }; +type Bar = Foo;`; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[1] as ts.TypeAliasDeclaration; + const typeRef = typeAlias.type as ts.TypeReferenceNode; + const result = getReferencedType(typeRef, checker); + expect(result).toBeDefined(); + expect(result!.declaration.kind).toBe(ts.SyntaxKind.TypeAliasDeclaration); + expect(ts.isTypeAliasDeclaration(result!.declaration)).toBe(true); + }); + + test("returns undefined when reference is to class (not interface/type alias)", () => { + const source = `class C { } +type Bar = C;`; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[1] as ts.TypeAliasDeclaration; + const typeRef = typeAlias.type as ts.TypeReferenceNode; + const result = getReferencedType(typeRef, checker); + expect(result).toBeUndefined(); + }); +}); + +describe("isTypeScriptLibType", () => { + test("returns true for Promise (lib type)", () => { + const source = "type T = Promise;"; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[0] as ts.TypeAliasDeclaration; + const typeRef = typeAlias.type as ts.TypeReferenceNode; + expect(isTypeScriptLibType(typeRef, checker)).toBe(true); + }); + + test("returns false for user-defined type", () => { + const source = "interface Foo { } type T = Foo;"; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[1] as ts.TypeAliasDeclaration; + const typeRef = typeAlias.type as ts.TypeReferenceNode; + expect(isTypeScriptLibType(typeRef, checker)).toBe(false); + }); + + test("returns false when symbol is undefined", () => { + const source = "type T = NonExistent;"; + const checker = createChecker(source); + const typeRef = ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("NonExistent"), + ); + expect(isTypeScriptLibType(typeRef, checker)).toBe(false); + }); +}); + +describe("getStringLiteralsFromUnion", () => { + test("extracts string literals from union type node", () => { + const input: ts.Node = ts.factory.createUnionTypeNode([ + ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("foo")), + ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("bar")), + ]); + const expected: Set = new Set(["foo", "bar"]); + const actual = getStringLiteralsFromUnion(input); + expect(actual).toEqual(expected); + }); + + test("returns single string literal from LiteralTypeNode", () => { + const input = ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral("only"), + ); + expect(getStringLiteralsFromUnion(input)).toEqual(new Set(["only"])); + }); + + test("returns empty set for non-union non-literal node", () => { + const input = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + expect(getStringLiteralsFromUnion(input)).toEqual(new Set()); + }); + + test("union with non-string literal yields empty string in set", () => { + const input = ts.factory.createUnionTypeNode([ + ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("a")), + ts.factory.createLiteralTypeNode(ts.factory.createNumericLiteral(42)), + ]); + const actual = getStringLiteralsFromUnion(input); + expect(actual).toEqual(new Set(["a", ""])); + }); +}); + +describe("buildTemplateRegex", () => { + test("builds regex for template literal with string, number, boolean", () => { + const source = "type T = `pre${string}mid${number}suf`;"; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[0] as ts.TypeAliasDeclaration; + const templateNode = typeAlias.type as ts.TemplateLiteralTypeNode; + const regex = buildTemplateRegex(templateNode, checker); + expect(regex).toBe("pre.*mid[0-9]*suf"); + }); + + test("template with one string span", () => { + const source = "type T = `prefix-${string}-suffix`;"; + const checker = createChecker(source); + const sourceFile = ts.createSourceFile( + "test.ts", + source, + ts.ScriptTarget.Latest, + true, + ); + const typeAlias = sourceFile.statements[0] as ts.TypeAliasDeclaration; + const templateNode = typeAlias.type as ts.TemplateLiteralTypeNode; + const regex = buildTemplateRegex(templateNode, checker); + expect(regex).toBe("prefix-.*-suffix"); + }); +}); + +describe("fillInGenerics", () => { + test("returns primitive node unchanged when no generics", () => { + const node: NodeType = { type: "string" }; + expect(fillInGenerics(node)).toBe(node); + }); + + test("fills ref with value from generics map", () => { + const refNode: NodeType = { + type: "ref", + ref: "T", + }; + const generics = new Map([["T", { type: "string" }]]); + const result = fillInGenerics(refNode, generics); + expect(result).toEqual({ type: "string" }); + }); + + test("ref with genericArguments fills each argument", () => { + const refNode: NodeType = { + type: "ref", + ref: "Outer", + genericArguments: [{ type: "ref", ref: "T" }], + }; + const generics = new Map([["T", { type: "number" }]]); + const result = fillInGenerics(refNode, generics); + expect(result).toMatchObject({ + type: "ref", + ref: "Outer", + genericArguments: [{ type: "number" }], + }); + }); + + test("ref not in map returns ref with filled genericArguments", () => { + const refNode: NodeType = { + type: "ref", + ref: "Outer", + genericArguments: [{ type: "ref", ref: "T" }], + }; + const generics = new Map([["T", { type: "boolean" }]]); + const result = fillInGenerics(refNode, generics); + expect(result).toMatchObject({ + type: "ref", + ref: "Outer", + genericArguments: [{ type: "boolean" }], + }); + }); + + test("object properties are filled recursively", () => { + const obj: NodeType = { + type: "object", + properties: { + p: { required: true, node: { type: "ref", ref: "T" } }, + }, + additionalProperties: false, + }; + const generics = new Map([["T", { type: "string" }]]); + const result = fillInGenerics(obj, generics); + expect(result).toMatchObject({ + type: "object", + properties: { + p: { required: true, node: { type: "string" } }, + }, + }); + }); + + test("array elementType is filled", () => { + const arr: NodeType = { + type: "array", + elementType: { type: "ref", ref: "T" }, + }; + const generics = new Map([["T", { type: "number" }]]); + const result = fillInGenerics(arr, generics); + expect(result).toEqual({ + type: "array", + elementType: { type: "number" }, + }); + }); + + test("or type members are filled", () => { + const orNode: NodeType = { + type: "or", + or: [{ type: "ref", ref: "T" }, { type: "string" }], + }; + const generics = new Map([["T", { type: "number" }]]); + const result = fillInGenerics(orNode, generics); + expect(result).toMatchObject({ + type: "or", + or: [{ type: "number" }, { type: "string" }], + }); + }); + + test("and type members are filled", () => { + const andNode: NodeType = { + type: "and", + and: [ + { type: "object", properties: {}, additionalProperties: false }, + { type: "ref", ref: "T" }, + ], + }; + const generics = new Map([["T", { type: "null" }]]); + const result = fillInGenerics(andNode, generics); + expect(result.type).toBe("and"); + expect((result as { and: NodeType[] }).and).toHaveLength(2); + expect((result as { and: NodeType[] }).and[1]).toEqual({ type: "null" }); + }); + + test("record keyType and valueType are filled", () => { + const recordNode: NodeType = { + type: "record", + keyType: { type: "ref", ref: "K" }, + valueType: { type: "ref", ref: "V" }, + }; + const generics = new Map([ + ["K", { type: "string" }], + ["V", { type: "number" }], + ]); + const result = fillInGenerics(recordNode, generics); + expect(result).toEqual({ + type: "record", + keyType: { type: "string" }, + valueType: { type: "number" }, + }); + }); + + test("generic node without map builds defaults from genericTokens", () => { + const genericObj: NodeTypeWithGenerics = { + type: "object", + properties: {}, + additionalProperties: false, + genericTokens: [ + { + symbol: "T", + default: { type: "string" }, + constraints: undefined, + }, + ], + }; + const result = fillInGenerics(genericObj); + expect(result).toMatchObject({ type: "object" }); + }); + + test("conditional type with both sides non-ref resolves via resolveConditional", () => { + const conditionalNode: NodeType = { + type: "conditional", + check: { + left: { type: "string" }, + right: { type: "string" }, + }, + value: { + true: { type: "number" }, + false: { type: "boolean" }, + }, + }; + const result = fillInGenerics(conditionalNode); + expect(result).toEqual({ type: "number" }); + }); + + test("conditional type with ref in check returns unresolved conditional", () => { + const conditionalNode: NodeType = { + type: "conditional", + check: { + left: { type: "ref", ref: "T" }, + right: { type: "string" }, + }, + value: { + true: { type: "number" }, + false: { type: "boolean" }, + }, + }; + const result = fillInGenerics(conditionalNode); + expect(result).toMatchObject({ + type: "conditional", + check: { left: { type: "ref", ref: "T" }, right: { type: "string" } }, + value: { true: { type: "number" }, false: { type: "boolean" } }, + }); + }); + + test("object with genericTokens and extends fills constraints and extends", () => { + const objWithExtends: NodeTypeWithGenerics = { + type: "object", + properties: { p: { required: false, node: { type: "ref", ref: "T" } } }, + additionalProperties: false, + genericTokens: [ + { symbol: "T", default: { type: "string" }, constraints: undefined }, + ], + extends: { type: "ref", ref: "Base" }, + }; + const generics = new Map([ + ["T", { type: "number" }], + ["Base", { type: "object", properties: {}, additionalProperties: false }], + ]); + const result = fillInGenerics(objWithExtends, generics); + expect(result).toMatchObject({ + type: "object", + properties: { p: { node: { type: "number" } } }, + extends: { type: "object" }, + }); + }); }); describe("applyPickOrOmitToNodeType", () => { @@ -127,6 +606,131 @@ describe("applyPickOrOmitToNodeType", () => { expect(result).toStrictEqual(filteredObject); }); + + test("Pick - no matching properties returns undefined", () => { + const baseObject: NodeType = { + type: "object", + properties: { foo: { node: { type: "string" }, required: false } }, + additionalProperties: false, + }; + const result = applyPickOrOmitToNodeType( + baseObject, + "Pick", + new Set(["bar"]), + ); + expect(result).toBeUndefined(); + }); + + test("Omit - all properties with additionalProperties false returns undefined", () => { + const baseObject: NodeType = { + type: "object", + properties: { foo: { node: { type: "string" }, required: false } }, + additionalProperties: false, + }; + const result = applyPickOrOmitToNodeType( + baseObject, + "Omit", + new Set(["foo"]), + ); + expect(result).toBeUndefined(); + }); + + test("and type - applies to each member and returns and", () => { + const baseObject: NodeType = { + type: "and", + and: [ + { + type: "object", + properties: { + a: { node: { type: "string" }, required: false }, + b: { node: { type: "string" }, required: false }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + b: { node: { type: "string" }, required: false }, + c: { node: { type: "string" }, required: false }, + }, + additionalProperties: false, + }, + ], + }; + const result = applyPickOrOmitToNodeType( + baseObject, + "Pick", + new Set(["b"]), + ); + expect(result).toMatchObject({ type: "and" }); + const and = (result as { and: NodeType[] }).and; + expect(and).toHaveLength(2); + expect(and[0]).toMatchObject({ + type: "object", + properties: { b: { node: { type: "string" }, required: false } }, + }); + expect(and[1]).toMatchObject({ + type: "object", + properties: { b: { node: { type: "string" }, required: false } }, + }); + }); + + test("or type - applies to each member and returns or", () => { + const baseObject: NodeType = { + type: "or", + or: [ + { + type: "object", + properties: { + x: { node: { type: "string" }, required: false }, + y: { node: { type: "string" }, required: false }, + }, + additionalProperties: false, + }, + ], + }; + const result = applyPickOrOmitToNodeType( + baseObject, + "Pick", + new Set(["x"]), + ); + expect(result).toMatchObject({ + type: "object", + properties: { x: { node: { type: "string" }, required: false } }, + }); + }); + + test("or type with multiple members returns single or", () => { + const baseObject: NodeType = { + type: "or", + or: [ + { + type: "object", + properties: { a: { node: { type: "string" }, required: false } }, + additionalProperties: false, + }, + { + type: "object", + properties: { b: { node: { type: "string" }, required: false } }, + additionalProperties: false, + }, + ], + }; + const result = applyPickOrOmitToNodeType( + baseObject, + "Pick", + new Set(["a", "b"]), + ); + expect(result).toMatchObject({ type: "or", or: expect.any(Array) }); + expect((result as { or: NodeType[] }).or.length).toBe(2); + }); + + test("throws when applying Pick/Omit to non-object non-union non-intersection", () => { + const baseObject: NodeType = { type: "string" }; + expect(() => + applyPickOrOmitToNodeType(baseObject, "Pick", new Set(["x"])), + ).toThrow(/Can not apply Pick to type string/); + }); }); describe("applyPartialOrRequiredToNodeType", () => { @@ -177,4 +781,97 @@ describe("applyPartialOrRequiredToNodeType", () => { expect(result).toStrictEqual(modifiedObject); }); + + test("and type - applies modifier to each member", () => { + const baseObject: NodeType = { + type: "and", + and: [ + { + type: "object", + properties: { a: { node: { type: "string" }, required: false } }, + additionalProperties: false, + }, + ], + }; + const result = applyPartialOrRequiredToNodeType(baseObject, true); + expect(result).toMatchObject({ type: "and" }); + expect((result as { and: NodeType[] }).and[0]).toMatchObject({ + type: "object", + properties: { a: { required: true, node: { type: "string" } } }, + }); + }); + + test("or type - applies modifier to each member", () => { + const baseObject: NodeType = { + type: "or", + or: [ + { + type: "object", + properties: { a: { node: { type: "string" }, required: true } }, + additionalProperties: false, + }, + ], + }; + const result = applyPartialOrRequiredToNodeType(baseObject, false); + expect(result).toMatchObject({ type: "or" }); + expect((result as { or: NodeType[] }).or[0]).toMatchObject({ + type: "object", + properties: { a: { required: false, node: { type: "string" } } }, + }); + }); + + test("throws when applying Partial/Required to non-object non-union non-intersection", () => { + const baseObject: NodeType = { type: "number" }; + expect(() => applyPartialOrRequiredToNodeType(baseObject, false)).toThrow( + /Can not apply Partial to type number/, + ); + }); +}); + +describe("applyExcludeToNodeType", () => { + test("excludes single type from union", () => { + const baseObject: OrType = { + type: "or", + or: [{ type: "string" }, { type: "number" }, { type: "boolean" }], + }; + const result = applyExcludeToNodeType(baseObject, { type: "number" }); + expect(result).toMatchObject({ + type: "or", + or: [{ type: "string" }, { type: "boolean" }], + }); + }); + + test("excludes with filter union (or)", () => { + const baseObject: OrType = { + type: "or", + or: [{ type: "string" }, { type: "number" }, { type: "boolean" }], + }; + const filters: OrType = { + type: "or", + or: [{ type: "number" }, { type: "boolean" }], + }; + const result = applyExcludeToNodeType(baseObject, filters); + expect(result).toEqual({ type: "string" }); + }); + + test("single remaining member returns that member", () => { + const baseObject: OrType = { + type: "or", + or: [{ type: "string" }, { type: "number" }], + }; + const result = applyExcludeToNodeType(baseObject, { type: "number" }); + expect(result).toEqual({ type: "string" }); + }); + + test("multiple remaining members returns or", () => { + const baseObject: OrType = { + type: "or", + or: [{ type: "string" }, { type: "number" }, { type: "boolean" }], + }; + const result = applyExcludeToNodeType(baseObject, { type: "number" }); + expect(result).toMatchObject({ + type: "or", + or: [{ type: "string" }, { type: "boolean" }], + }); + }); }); diff --git a/xlr/utils/src/ts-helpers.ts b/xlr/utils/src/ts-helpers.ts index 3c0fc559..64ca136a 100644 --- a/xlr/utils/src/ts-helpers.ts +++ b/xlr/utils/src/ts-helpers.ts @@ -58,7 +58,12 @@ export function isNodeExported(node: ts.Node): boolean { export function getReferencedType( node: ts.TypeReferenceNode, typeChecker: ts.TypeChecker, -) { +): + | { + declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration; + exported: boolean; + } + | undefined { let symbol = typeChecker.getSymbolAtLocation(node.typeName); if ( From a6486bd09a5eb29cbab60870fb8df297a832b70c Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Thu, 5 Mar 2026 11:20:36 -0800 Subject: [PATCH 13/14] null safe access for default on schema node --- .../src/plugins/schema-validation-plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language/json-language-service/src/plugins/schema-validation-plugin.ts b/language/json-language-service/src/plugins/schema-validation-plugin.ts index 5951d093..1c1bf472 100644 --- a/language/json-language-service/src/plugins/schema-validation-plugin.ts +++ b/language/json-language-service/src/plugins/schema-validation-plugin.ts @@ -218,7 +218,7 @@ function validateDataTypeStructure( } // Check if default value conforms to the expected value - const defaultNode = claimedDataType.properties["default"]?.node; + const defaultNode = claimedDataType.properties?.["default"]?.node; const defaultProp = getProperty(dataTypeNode, "default"); if ( defaultNode && From f8e0bbf59b48fb560beb4874377ce4ebd2d5fab0 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Thu, 5 Mar 2026 11:26:34 -0800 Subject: [PATCH 14/14] Don't unpack escaped properties with quotes as that behavior was removed from XLR --- xlr/utils/src/__tests__/validation-helpers.test.ts | 4 ++-- xlr/utils/src/validation-helpers.ts | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/xlr/utils/src/__tests__/validation-helpers.test.ts b/xlr/utils/src/__tests__/validation-helpers.test.ts index 59f26697..e40f6150 100644 --- a/xlr/utils/src/__tests__/validation-helpers.test.ts +++ b/xlr/utils/src/__tests__/validation-helpers.test.ts @@ -135,7 +135,7 @@ describe("makePropertyMap tests", () => { const propertyMap = makePropertyMap(treeObject); - expect(propertyMap.get("'some-key'")?.value).toStrictEqual("value"); - expect(propertyMap.get("'some-otherkey'")?.value).toStrictEqual("value"); + expect(propertyMap.get("some-key")?.value).toStrictEqual("value"); + expect(propertyMap.get("some-otherkey")?.value).toStrictEqual("value"); }); }); diff --git a/xlr/utils/src/validation-helpers.ts b/xlr/utils/src/validation-helpers.ts index fe3e130b..f8745356 100644 --- a/xlr/utils/src/validation-helpers.ts +++ b/xlr/utils/src/validation-helpers.ts @@ -20,14 +20,8 @@ export interface PropertyNode { * Takes a property node and returns the underlying Key/Value Pairs */ export function propertyToTuple(node: Node): PropertyNode { - let key = node.children?.[0].value as string; - if (key.includes("-")) { - // XLR outputs all escaped properties with single quotes - key = `'${key}'`; - } - return { - key, + key: node.children?.[0].value as string, value: node.children?.[1] as Node, }; }