@twin.org/data-core 0.0.3-next.2 → 0.0.3-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,8 @@
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { Is, StringHelper } from "@twin.org/core";
4
4
  import { FetchHelper, HttpMethod } from "@twin.org/web";
5
- import Ajv from "ajv/dist/2020.js";
5
+ import Ajv2019 from "ajv/dist/2019.js";
6
+ import Ajv2020 from "ajv/dist/2020.js";
6
7
  import formatsPlugin from "ajv-formats";
7
8
  import { DataTypeHandlerFactory } from "../factories/dataTypeHandlerFactory.js";
8
9
  /**
@@ -10,9 +11,13 @@ import { DataTypeHandlerFactory } from "../factories/dataTypeHandlerFactory.js";
10
11
  */
11
12
  export class JsonSchemaHelper {
12
13
  /**
13
- * The schema version.
14
+ * The schema version 2020 (default).
14
15
  */
15
16
  static SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
17
+ /**
18
+ * The schema version 2019.
19
+ */
20
+ static SCHEMA_VERSION_2019 = "https://json-schema.org/draft/2019-09/schema";
16
21
  /**
17
22
  * The class name.
18
23
  * @internal
@@ -26,7 +31,7 @@ export class JsonSchemaHelper {
26
31
  * @returns Result containing errors if there are any.
27
32
  */
28
33
  static async validate(schema, data, additionalTypes) {
29
- const ajv = new Ajv.Ajv2020({
34
+ const params = {
30
35
  allowUnionTypes: true,
31
36
  // Disable strict tuples as it causes issues with the schema validation when
32
37
  // you have an array with fixed elements e.g. myType: [string, ...string[]]
@@ -55,7 +60,11 @@ export class JsonSchemaHelper {
55
60
  return {};
56
61
  }
57
62
  }
58
- });
63
+ };
64
+ let ajv = new Ajv2020.Ajv2020(params);
65
+ if (schema.$schema === JsonSchemaHelper.SCHEMA_VERSION_2019) {
66
+ ajv = new Ajv2019.Ajv2019({ strict: false, ...params });
67
+ }
59
68
  formatsPlugin.default(ajv);
60
69
  // Add the additional types provided by the user
61
70
  if (Is.objectValue(additionalTypes)) {
@@ -1 +1 @@
1
- {"version":3,"file":"jsonSchemaHelper.js","sourceRoot":"","sources":["../../../src/utils/jsonSchemaHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, StringHelper } from \"@twin.org/core\";\nimport type { IEntitySchema } from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { FetchHelper, HttpMethod } from \"@twin.org/web\";\nimport Ajv from \"ajv/dist/2020.js\";\nimport formatsPlugin from \"ajv-formats\";\nimport { DataTypeHandlerFactory } from \"../factories/dataTypeHandlerFactory.js\";\nimport type { IJsonSchema } from \"../models/IJsonSchema.js\";\nimport type { ISchemaValidationError } from \"../models/ISchemaValidationError.js\";\nimport type { ISchemaValidationResult } from \"../models/ISchemaValidationResult.js\";\n\n/**\n * A helper for JSON schemas.\n */\nexport class JsonSchemaHelper {\n\t/**\n\t * The schema version.\n\t */\n\tpublic static readonly SCHEMA_VERSION = \"https://json-schema.org/draft/2020-12/schema\";\n\n\t/**\n\t * The class name.\n\t * @internal\n\t */\n\tpublic static readonly CLASS_NAME = nameof<JsonSchemaHelper>();\n\n\t/**\n\t * Validates data against the schema.\n\t * @param schema The schema to validate the data with.\n\t * @param data The data to be validated.\n\t * @param additionalTypes Additional types to add for reference, not already in DataTypeHandlerFactory.\n\t * @returns Result containing errors if there are any.\n\t */\n\tpublic static async validate<T = unknown>(\n\t\tschema: IJsonSchema,\n\t\tdata: T,\n\t\tadditionalTypes?: { [id: string]: IJsonSchema }\n\t): Promise<ISchemaValidationResult> {\n\t\tconst ajv = new Ajv.Ajv2020({\n\t\t\tallowUnionTypes: true,\n\t\t\t// Disable strict tuples as it causes issues with the schema validation when\n\t\t\t// you have an array with fixed elements e.g. myType: [string, ...string[]]\n\t\t\t// https://github.com/ajv-validator/ajv/issues/1417\n\t\t\tstrictTuples: false,\n\t\t\tloadSchema: async uri => {\n\t\t\t\tconst subTypeHandler = DataTypeHandlerFactory.getIfExists(uri);\n\t\t\t\tconst jsonSchemaMethod = subTypeHandler?.jsonSchema?.bind(subTypeHandler);\n\t\t\t\tif (Is.function(jsonSchemaMethod)) {\n\t\t\t\t\tconst subSchema = await jsonSchemaMethod();\n\t\t\t\t\tif (Is.object<IJsonSchema>(subSchema)) {\n\t\t\t\t\t\treturn subSchema;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t// We don't have the type in our local data types, so we try to fetch it from the web\n\t\t\t\t\tconst result = await FetchHelper.fetchJson<never, IJsonSchema>(\n\t\t\t\t\t\tJsonSchemaHelper.CLASS_NAME,\n\t\t\t\t\t\turi,\n\t\t\t\t\t\tHttpMethod.GET,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Cache for an hour\n\t\t\t\t\t\t\tcacheTtlMs: 3600000\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\treturn result;\n\t\t\t\t} catch {\n\t\t\t\t\t// Failed to load remotely so return an empty object\n\t\t\t\t\t// so the schema validation doesn't completely fail\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tformatsPlugin.default(ajv);\n\n\t\t// Add the additional types provided by the user\n\t\tif (Is.objectValue(additionalTypes)) {\n\t\t\tfor (const key in additionalTypes) {\n\t\t\t\tajv.addSchema(additionalTypes[key], key);\n\t\t\t}\n\t\t}\n\n\t\tconst compiled = await ajv.compileAsync(schema);\n\t\tconst result = compiled(data);\n\n\t\tconst output: ISchemaValidationResult = {\n\t\t\tresult\n\t\t};\n\n\t\tif (!output.result) {\n\t\t\toutput.error = compiled.errors as ISchemaValidationError;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\t/**\n\t * Get the property type from a schema.\n\t * @param schema The schema to extract the types from.\n\t * @param propertyName The name of the property to get the type for.\n\t * @returns The types of the property.\n\t */\n\tpublic static getPropertyType(schema: IJsonSchema, propertyName: string): string | undefined {\n\t\tif (schema.type === \"object\" && Is.objectValue(schema.properties)) {\n\t\t\tconst propertySchema = schema.properties[propertyName];\n\t\t\tif (Is.object<IJsonSchema>(propertySchema)) {\n\t\t\t\tif (Is.stringValue(propertySchema.$ref)) {\n\t\t\t\t\treturn propertySchema.$ref;\n\t\t\t\t}\n\t\t\t\treturn propertySchema.type as string;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Convert an entity schema to JSON schema e.g https://example.com/schemas/.\n\t * @param entitySchema The entity schema to convert.\n\t * @param baseDomain The base domain for local schemas e.g. https://example.com/\n\t * @returns The JSON schema for the entity.\n\t */\n\tpublic static entitySchemaToJsonSchema(\n\t\tentitySchema: IEntitySchema | undefined,\n\t\tbaseDomain?: string\n\t): IJsonSchema {\n\t\tlet domain = StringHelper.trimTrailingSlashes(baseDomain ?? \"\");\n\t\tif (domain.length > 0) {\n\t\t\tdomain += \"/\";\n\t\t}\n\n\t\tconst properties: {\n\t\t\t[key: string]: IJsonSchema;\n\t\t} = {};\n\n\t\tconst required: string[] = [];\n\n\t\tif (Is.arrayValue(entitySchema?.properties)) {\n\t\t\tfor (const propertySchema of entitySchema.properties) {\n\t\t\t\tconst jsonPropertySchema: IJsonSchema = {\n\t\t\t\t\ttype: propertySchema.type,\n\t\t\t\t\tdescription: propertySchema.description,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t\t\texamples: propertySchema.examples as unknown as any\n\t\t\t\t};\n\n\t\t\t\tif (Is.stringValue(propertySchema.itemType) && propertySchema.type === \"array\") {\n\t\t\t\t\tif (propertySchema.itemType === \"object\") {\n\t\t\t\t\t\tjsonPropertySchema.items = {\n\t\t\t\t\t\t\t$ref: propertySchema.itemTypeRef?.startsWith(\"http\")\n\t\t\t\t\t\t\t\t? propertySchema.itemTypeRef\n\t\t\t\t\t\t\t\t: `${domain}${propertySchema.itemTypeRef}`\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjsonPropertySchema.items = {\n\t\t\t\t\t\t\ttype: propertySchema.itemType\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t} else if (propertySchema.type === \"object\") {\n\t\t\t\t\tdelete jsonPropertySchema.type;\n\t\t\t\t\tjsonPropertySchema.$ref = propertySchema.itemTypeRef?.startsWith(\"http\")\n\t\t\t\t\t\t? propertySchema.itemTypeRef\n\t\t\t\t\t\t: `${domain}${propertySchema.itemTypeRef}`;\n\t\t\t\t}\n\n\t\t\t\tproperties[propertySchema.property] = jsonPropertySchema;\n\n\t\t\t\tif (!propertySchema.optional) {\n\t\t\t\t\trequired.push(propertySchema.property);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\t$schema: JsonSchemaHelper.SCHEMA_VERSION,\n\t\t\t$id: `${domain}${entitySchema?.type}`,\n\t\t\ttitle: entitySchema?.type,\n\t\t\ttype: entitySchema ? \"object\" : \"null\",\n\t\t\tdescription: entitySchema?.options?.description,\n\t\t\trequired,\n\t\t\tproperties,\n\t\t\tadditionalProperties: false\n\t\t};\n\t}\n}\n"]}
1
+ {"version":3,"file":"jsonSchemaHelper.js","sourceRoot":"","sources":["../../../src/utils/jsonSchemaHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,OAAO,MAAM,kBAAkB,CAAC;AACvC,OAAO,OAAO,MAAM,kBAAkB,CAAC;AACvC,OAAO,aAAa,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAKhF;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAC5B;;OAEG;IACI,MAAM,CAAU,cAAc,GAAG,8CAA8C,CAAC;IAEvF;;OAEG;IACI,MAAM,CAAU,mBAAmB,GAAG,8CAA8C,CAAC;IAE5F;;;OAGG;IACI,MAAM,CAAU,UAAU,sBAA8B;IAE/D;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAC3B,MAAmB,EACnB,IAAO,EACP,eAA+C;QAE/C,MAAM,MAAM,GAAG;YACd,eAAe,EAAE,IAAI;YACrB,4EAA4E;YAC5E,2EAA2E;YAC3E,mDAAmD;YACnD,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,KAAK,EAAE,GAAW,EAAE,EAAE;gBACjC,MAAM,cAAc,GAAG,sBAAsB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBAC/D,MAAM,gBAAgB,GAAG,cAAc,EAAE,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;gBAC1E,IAAI,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;oBACnC,MAAM,SAAS,GAAG,MAAM,gBAAgB,EAAE,CAAC;oBAC3C,IAAI,EAAE,CAAC,MAAM,CAAc,SAAS,CAAC,EAAE,CAAC;wBACvC,OAAO,SAAS,CAAC;oBAClB,CAAC;gBACF,CAAC;gBAED,IAAI,CAAC;oBACJ,qFAAqF;oBACrF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CACzC,gBAAgB,CAAC,UAAU,EAC3B,GAAG,EACH,UAAU,CAAC,GAAG,EACd,SAAS,EACT;wBACC,oBAAoB;wBACpB,UAAU,EAAE,OAAO;qBACnB,CACD,CAAC;oBACF,OAAO,MAAM,CAAC;gBACf,CAAC;gBAAC,MAAM,CAAC;oBACR,oDAAoD;oBACpD,mDAAmD;oBACnD,OAAO,EAAE,CAAC;gBACX,CAAC;YACF,CAAC;SACD,CAAC;QAEF,IAAI,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,OAAO,KAAK,gBAAgB,CAAC,mBAAmB,EAAE,CAAC;YAC7D,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAE3B,gDAAgD;QAChD,IAAI,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;YACrC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBACnC,GAAG,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YAC1C,CAAC;QACF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAE9B,MAAM,MAAM,GAA4B;YACvC,MAAM;SACN,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAgC,CAAC;QAC1D,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,eAAe,CAAC,MAAmB,EAAE,YAAoB;QACtE,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YACnE,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YACvD,IAAI,EAAE,CAAC,MAAM,CAAc,cAAc,CAAC,EAAE,CAAC;gBAC5C,IAAI,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzC,OAAO,cAAc,CAAC,IAAI,CAAC;gBAC5B,CAAC;gBACD,OAAO,cAAc,CAAC,IAAc,CAAC;YACtC,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,wBAAwB,CACrC,YAAuC,EACvC,UAAmB;QAEnB,IAAI,MAAM,GAAG,YAAY,CAAC,mBAAmB,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,CAAC;QACf,CAAC;QAED,MAAM,UAAU,GAEZ,EAAE,CAAC;QAEP,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;YAC7C,KAAK,MAAM,cAAc,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;gBACtD,MAAM,kBAAkB,GAAgB;oBACvC,IAAI,EAAE,cAAc,CAAC,IAAI;oBACzB,WAAW,EAAE,cAAc,CAAC,WAAW;oBACvC,8DAA8D;oBAC9D,QAAQ,EAAE,cAAc,CAAC,QAA0B;iBACnD,CAAC;gBAEF,IAAI,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,cAAc,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAChF,IAAI,cAAc,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;wBAC1C,kBAAkB,CAAC,KAAK,GAAG;4BAC1B,IAAI,EAAE,cAAc,CAAC,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC;gCACnD,CAAC,CAAC,cAAc,CAAC,WAAW;gCAC5B,CAAC,CAAC,GAAG,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE;yBAC3C,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACP,kBAAkB,CAAC,KAAK,GAAG;4BAC1B,IAAI,EAAE,cAAc,CAAC,QAAQ;yBAC7B,CAAC;oBACH,CAAC;gBACF,CAAC;qBAAM,IAAI,cAAc,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC7C,OAAO,kBAAkB,CAAC,IAAI,CAAC;oBAC/B,kBAAkB,CAAC,IAAI,GAAG,cAAc,CAAC,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC;wBACvE,CAAC,CAAC,cAAc,CAAC,WAAW;wBAC5B,CAAC,CAAC,GAAG,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;gBAC7C,CAAC;gBAED,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,kBAAkB,CAAC;gBAEzD,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;oBAC9B,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;gBACxC,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO;YACN,OAAO,EAAE,gBAAgB,CAAC,cAAc;YACxC,GAAG,EAAE,GAAG,MAAM,GAAG,YAAY,EAAE,IAAI,EAAE;YACrC,KAAK,EAAE,YAAY,EAAE,IAAI;YACzB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;YACtC,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW;YAC/C,QAAQ;YACR,UAAU;YACV,oBAAoB,EAAE,KAAK;SAC3B,CAAC;IACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, StringHelper } from \"@twin.org/core\";\nimport type { IEntitySchema } from \"@twin.org/entity\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { FetchHelper, HttpMethod } from \"@twin.org/web\";\nimport Ajv2019 from \"ajv/dist/2019.js\";\nimport Ajv2020 from \"ajv/dist/2020.js\";\nimport formatsPlugin from \"ajv-formats\";\nimport { DataTypeHandlerFactory } from \"../factories/dataTypeHandlerFactory.js\";\nimport type { IJsonSchema } from \"../models/IJsonSchema.js\";\nimport type { ISchemaValidationError } from \"../models/ISchemaValidationError.js\";\nimport type { ISchemaValidationResult } from \"../models/ISchemaValidationResult.js\";\n\n/**\n * A helper for JSON schemas.\n */\nexport class JsonSchemaHelper {\n\t/**\n\t * The schema version 2020 (default).\n\t */\n\tpublic static readonly SCHEMA_VERSION = \"https://json-schema.org/draft/2020-12/schema\";\n\n\t/**\n\t * The schema version 2019.\n\t */\n\tpublic static readonly SCHEMA_VERSION_2019 = \"https://json-schema.org/draft/2019-09/schema\";\n\n\t/**\n\t * The class name.\n\t * @internal\n\t */\n\tpublic static readonly CLASS_NAME = nameof<JsonSchemaHelper>();\n\n\t/**\n\t * Validates data against the schema.\n\t * @param schema The schema to validate the data with.\n\t * @param data The data to be validated.\n\t * @param additionalTypes Additional types to add for reference, not already in DataTypeHandlerFactory.\n\t * @returns Result containing errors if there are any.\n\t */\n\tpublic static async validate<T = unknown>(\n\t\tschema: IJsonSchema,\n\t\tdata: T,\n\t\tadditionalTypes?: { [id: string]: IJsonSchema }\n\t): Promise<ISchemaValidationResult> {\n\t\tconst params = {\n\t\t\tallowUnionTypes: true,\n\t\t\t// Disable strict tuples as it causes issues with the schema validation when\n\t\t\t// you have an array with fixed elements e.g. myType: [string, ...string[]]\n\t\t\t// https://github.com/ajv-validator/ajv/issues/1417\n\t\t\tstrictTuples: false,\n\t\t\tloadSchema: async (uri: string) => {\n\t\t\t\tconst subTypeHandler = DataTypeHandlerFactory.getIfExists(uri);\n\t\t\t\tconst jsonSchemaMethod = subTypeHandler?.jsonSchema?.bind(subTypeHandler);\n\t\t\t\tif (Is.function(jsonSchemaMethod)) {\n\t\t\t\t\tconst subSchema = await jsonSchemaMethod();\n\t\t\t\t\tif (Is.object<IJsonSchema>(subSchema)) {\n\t\t\t\t\t\treturn subSchema;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t// We don't have the type in our local data types, so we try to fetch it from the web\n\t\t\t\t\tconst result = await FetchHelper.fetchJson<never, IJsonSchema>(\n\t\t\t\t\t\tJsonSchemaHelper.CLASS_NAME,\n\t\t\t\t\t\turi,\n\t\t\t\t\t\tHttpMethod.GET,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Cache for an hour\n\t\t\t\t\t\t\tcacheTtlMs: 3600000\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\treturn result;\n\t\t\t\t} catch {\n\t\t\t\t\t// Failed to load remotely so return an empty object\n\t\t\t\t\t// so the schema validation doesn't completely fail\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tlet ajv = new Ajv2020.Ajv2020(params);\n\t\tif (schema.$schema === JsonSchemaHelper.SCHEMA_VERSION_2019) {\n\t\t\tajv = new Ajv2019.Ajv2019({ strict: false, ...params });\n\t\t}\n\n\t\tformatsPlugin.default(ajv);\n\n\t\t// Add the additional types provided by the user\n\t\tif (Is.objectValue(additionalTypes)) {\n\t\t\tfor (const key in additionalTypes) {\n\t\t\t\tajv.addSchema(additionalTypes[key], key);\n\t\t\t}\n\t\t}\n\n\t\tconst compiled = await ajv.compileAsync(schema);\n\t\tconst result = compiled(data);\n\n\t\tconst output: ISchemaValidationResult = {\n\t\t\tresult\n\t\t};\n\n\t\tif (!output.result) {\n\t\t\toutput.error = compiled.errors as ISchemaValidationError;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\t/**\n\t * Get the property type from a schema.\n\t * @param schema The schema to extract the types from.\n\t * @param propertyName The name of the property to get the type for.\n\t * @returns The types of the property.\n\t */\n\tpublic static getPropertyType(schema: IJsonSchema, propertyName: string): string | undefined {\n\t\tif (schema.type === \"object\" && Is.objectValue(schema.properties)) {\n\t\t\tconst propertySchema = schema.properties[propertyName];\n\t\t\tif (Is.object<IJsonSchema>(propertySchema)) {\n\t\t\t\tif (Is.stringValue(propertySchema.$ref)) {\n\t\t\t\t\treturn propertySchema.$ref;\n\t\t\t\t}\n\t\t\t\treturn propertySchema.type as string;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Convert an entity schema to JSON schema e.g https://example.com/schemas/.\n\t * @param entitySchema The entity schema to convert.\n\t * @param baseDomain The base domain for local schemas e.g. https://example.com/\n\t * @returns The JSON schema for the entity.\n\t */\n\tpublic static entitySchemaToJsonSchema(\n\t\tentitySchema: IEntitySchema | undefined,\n\t\tbaseDomain?: string\n\t): IJsonSchema {\n\t\tlet domain = StringHelper.trimTrailingSlashes(baseDomain ?? \"\");\n\t\tif (domain.length > 0) {\n\t\t\tdomain += \"/\";\n\t\t}\n\n\t\tconst properties: {\n\t\t\t[key: string]: IJsonSchema;\n\t\t} = {};\n\n\t\tconst required: string[] = [];\n\n\t\tif (Is.arrayValue(entitySchema?.properties)) {\n\t\t\tfor (const propertySchema of entitySchema.properties) {\n\t\t\t\tconst jsonPropertySchema: IJsonSchema = {\n\t\t\t\t\ttype: propertySchema.type,\n\t\t\t\t\tdescription: propertySchema.description,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t\t\texamples: propertySchema.examples as unknown as any\n\t\t\t\t};\n\n\t\t\t\tif (Is.stringValue(propertySchema.itemType) && propertySchema.type === \"array\") {\n\t\t\t\t\tif (propertySchema.itemType === \"object\") {\n\t\t\t\t\t\tjsonPropertySchema.items = {\n\t\t\t\t\t\t\t$ref: propertySchema.itemTypeRef?.startsWith(\"http\")\n\t\t\t\t\t\t\t\t? propertySchema.itemTypeRef\n\t\t\t\t\t\t\t\t: `${domain}${propertySchema.itemTypeRef}`\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjsonPropertySchema.items = {\n\t\t\t\t\t\t\ttype: propertySchema.itemType\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t} else if (propertySchema.type === \"object\") {\n\t\t\t\t\tdelete jsonPropertySchema.type;\n\t\t\t\t\tjsonPropertySchema.$ref = propertySchema.itemTypeRef?.startsWith(\"http\")\n\t\t\t\t\t\t? propertySchema.itemTypeRef\n\t\t\t\t\t\t: `${domain}${propertySchema.itemTypeRef}`;\n\t\t\t\t}\n\n\t\t\t\tproperties[propertySchema.property] = jsonPropertySchema;\n\n\t\t\t\tif (!propertySchema.optional) {\n\t\t\t\t\trequired.push(propertySchema.property);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\t$schema: JsonSchemaHelper.SCHEMA_VERSION,\n\t\t\t$id: `${domain}${entitySchema?.type}`,\n\t\t\ttitle: entitySchema?.type,\n\t\t\ttype: entitySchema ? \"object\" : \"null\",\n\t\t\tdescription: entitySchema?.options?.description,\n\t\t\trequired,\n\t\t\tproperties,\n\t\t\tadditionalProperties: false\n\t\t};\n\t}\n}\n"]}
@@ -6,9 +6,13 @@ import type { ISchemaValidationResult } from "../models/ISchemaValidationResult.
6
6
  */
7
7
  export declare class JsonSchemaHelper {
8
8
  /**
9
- * The schema version.
9
+ * The schema version 2020 (default).
10
10
  */
11
11
  static readonly SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
12
+ /**
13
+ * The schema version 2019.
14
+ */
15
+ static readonly SCHEMA_VERSION_2019 = "https://json-schema.org/draft/2019-09/schema";
12
16
  /**
13
17
  * Validates data against the schema.
14
18
  * @param schema The schema to validate the data with.
package/docs/changelog.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @twin.org/data-core - Changelog
2
2
 
3
+ ## [0.0.3-next.4](https://github.com/twinfoundation/data/compare/data-core-v0.0.3-next.3...data-core-v0.0.3-next.4) (2026-01-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * support JSON Schema 2019 ([#31](https://github.com/twinfoundation/data/issues/31)) ([f798f72](https://github.com/twinfoundation/data/commit/f798f721c998cf50b8ba2318bec574069aad02ae))
9
+
10
+ ## [0.0.3-next.3](https://github.com/twinfoundation/data/compare/data-core-v0.0.3-next.2...data-core-v0.0.3-next.3) (2026-01-05)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * **data-core:** Synchronize repo versions
16
+
3
17
  ## [0.0.3-next.2](https://github.com/twinfoundation/data/compare/data-core-v0.0.3-next.1...data-core-v0.0.3-next.2) (2025-11-24)
4
18
 
5
19
 
@@ -18,7 +18,15 @@ A helper for JSON schemas.
18
18
 
19
19
  > `readonly` `static` **SCHEMA\_VERSION**: `"https://json-schema.org/draft/2020-12/schema"` = `"https://json-schema.org/draft/2020-12/schema"`
20
20
 
21
- The schema version.
21
+ The schema version 2020 (default).
22
+
23
+ ***
24
+
25
+ ### SCHEMA\_VERSION\_2019
26
+
27
+ > `readonly` `static` **SCHEMA\_VERSION\_2019**: `"https://json-schema.org/draft/2019-09/schema"` = `"https://json-schema.org/draft/2019-09/schema"`
28
+
29
+ The schema version 2019.
22
30
 
23
31
  ## Methods
24
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/data-core",
3
- "version": "0.0.3-next.2",
3
+ "version": "0.0.3-next.4",
4
4
  "description": "Definitions and helpers for using with data and schemas",
5
5
  "repository": {
6
6
  "type": "git",