@twin.org/data-core 0.0.2-next.3 → 0.0.3-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/es/factories/dataTypeHandlerFactory.js +9 -0
  2. package/dist/es/factories/dataTypeHandlerFactory.js.map +1 -0
  3. package/dist/es/factories/identifierHandlerFactory.js +19 -0
  4. package/dist/es/factories/identifierHandlerFactory.js.map +1 -0
  5. package/dist/es/index.js +13 -0
  6. package/dist/es/index.js.map +1 -0
  7. package/dist/es/models/IDataTypeHandler.js +2 -0
  8. package/dist/es/models/IDataTypeHandler.js.map +1 -0
  9. package/dist/es/models/IIdentifierHandler.js +2 -0
  10. package/dist/es/models/IIdentifierHandler.js.map +1 -0
  11. package/dist/es/models/IJsonSchema.js +2 -0
  12. package/dist/es/models/IJsonSchema.js.map +1 -0
  13. package/dist/es/models/ISchemaValidationError.js +2 -0
  14. package/dist/es/models/ISchemaValidationError.js.map +1 -0
  15. package/dist/es/models/ISchemaValidationResult.js +2 -0
  16. package/dist/es/models/ISchemaValidationResult.js.map +1 -0
  17. package/dist/es/models/validationMode.js +25 -0
  18. package/dist/es/models/validationMode.js.map +1 -0
  19. package/dist/es/utils/dataTypeHelper.js +80 -0
  20. package/dist/es/utils/dataTypeHelper.js.map +1 -0
  21. package/dist/es/utils/jsonSchemaHelper.js +152 -0
  22. package/dist/es/utils/jsonSchemaHelper.js.map +1 -0
  23. package/dist/types/factories/dataTypeHandlerFactory.d.ts +1 -1
  24. package/dist/types/factories/identifierHandlerFactory.d.ts +1 -1
  25. package/dist/types/index.d.ts +10 -10
  26. package/dist/types/models/IDataTypeHandler.d.ts +1 -1
  27. package/dist/types/models/IJsonSchema.d.ts +2 -2
  28. package/dist/types/models/ISchemaValidationResult.d.ts +1 -1
  29. package/dist/types/utils/dataTypeHelper.d.ts +1 -1
  30. package/dist/types/utils/jsonSchemaHelper.d.ts +2 -2
  31. package/docs/changelog.md +31 -0
  32. package/docs/reference/classes/DataTypeHelper.md +1 -1
  33. package/docs/reference/classes/JsonSchemaHelper.md +3 -3
  34. package/docs/reference/interfaces/IDataTypeHandler.md +2 -2
  35. package/docs/reference/type-aliases/IJsonSchema.md +1 -1
  36. package/locales/en.json +0 -3
  37. package/package.json +21 -8
  38. package/dist/cjs/index.cjs +0 -282
  39. package/dist/esm/index.mjs +0 -276
@@ -0,0 +1,9 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Factory } from "@twin.org/core";
4
+ /**
5
+ * Factory for creating handlers for data types.
6
+ */
7
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8
+ export const DataTypeHandlerFactory = Factory.createFactory("data-type");
9
+ //# sourceMappingURL=dataTypeHandlerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataTypeHandlerFactory.js","sourceRoot":"","sources":["../../../src/factories/dataTypeHandlerFactory.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAmB,WAAW,CAAC,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Factory } from \"@twin.org/core\";\nimport type { IDataTypeHandler } from \"../models/IDataTypeHandler.js\";\n\n/**\n * Factory for creating handlers for data types.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const DataTypeHandlerFactory = Factory.createFactory<IDataTypeHandler>(\"data-type\");\n"]}
@@ -0,0 +1,19 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Factory, Urn } from "@twin.org/core";
4
+ /**
5
+ * Factory for creating handlers for identifiers.
6
+ */
7
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8
+ export const IdentifierHandlerFactory = Factory.createFactory("namespace", false, (names, uri) => {
9
+ Urn.guard("IdentifierHandlerFactory", "uri", uri);
10
+ const urn = Urn.fromValidString(uri);
11
+ const urnParts = urn.parts();
12
+ for (let i = urnParts.length - 1; i >= 0; i--) {
13
+ const wholeNamespace = urnParts.slice(i).join(":");
14
+ if (names.includes(wholeNamespace)) {
15
+ return wholeNamespace;
16
+ }
17
+ }
18
+ });
19
+ //# sourceMappingURL=identifierHandlerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifierHandlerFactory.js","sourceRoot":"","sources":["../../../src/factories/identifierHandlerFactory.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAI9C;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAC,aAAa,CAC5D,WAAW,EACX,KAAK,EACL,CAAC,KAAe,EAAE,GAAW,EAAE,EAAE;IAChC,GAAG,CAAC,KAAK,oCAAgD,GAAG,CAAC,CAAC;IAE9D,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACpC,OAAO,cAAc,CAAC;QACvB,CAAC;IACF,CAAC;AACF,CAAC,CACD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Factory, Urn } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IIdentifierHandler } from \"../models/IIdentifierHandler.js\";\n\n/**\n * Factory for creating handlers for identifiers.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const IdentifierHandlerFactory = Factory.createFactory<IIdentifierHandler>(\n\t\"namespace\",\n\tfalse,\n\t(names: string[], uri: string) => {\n\t\tUrn.guard(nameof(IdentifierHandlerFactory), nameof(uri), uri);\n\n\t\tconst urn = Urn.fromValidString(uri);\n\t\tconst urnParts = urn.parts();\n\n\t\tfor (let i = urnParts.length - 1; i >= 0; i--) {\n\t\t\tconst wholeNamespace = urnParts.slice(i).join(\":\");\n\t\t\tif (names.includes(wholeNamespace)) {\n\t\t\t\treturn wholeNamespace;\n\t\t\t}\n\t\t}\n\t}\n);\n"]}
@@ -0,0 +1,13 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./factories/dataTypeHandlerFactory.js";
4
+ export * from "./factories/identifierHandlerFactory.js";
5
+ export * from "./models/IDataTypeHandler.js";
6
+ export * from "./models/IIdentifierHandler.js";
7
+ export * from "./models/IJsonSchema.js";
8
+ export * from "./models/ISchemaValidationError.js";
9
+ export * from "./models/ISchemaValidationResult.js";
10
+ export * from "./models/validationMode.js";
11
+ export * from "./utils/dataTypeHelper.js";
12
+ export * from "./utils/jsonSchemaHelper.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AACxD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./factories/dataTypeHandlerFactory.js\";\nexport * from \"./factories/identifierHandlerFactory.js\";\nexport * from \"./models/IDataTypeHandler.js\";\nexport * from \"./models/IIdentifierHandler.js\";\nexport * from \"./models/IJsonSchema.js\";\nexport * from \"./models/ISchemaValidationError.js\";\nexport * from \"./models/ISchemaValidationResult.js\";\nexport * from \"./models/validationMode.js\";\nexport * from \"./utils/dataTypeHelper.js\";\nexport * from \"./utils/jsonSchemaHelper.js\";\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IDataTypeHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IDataTypeHandler.js","sourceRoot":"","sources":["../../../src/models/IDataTypeHandler.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IValidationFailure } from \"@twin.org/core\";\nimport type { IJsonSchema } from \"./IJsonSchema.js\";\n\n/**\n * Interface describing a type which can handle a specific data type.\n */\nexport interface IDataTypeHandler {\n\t/**\n\t * The context for the type.\n\t */\n\tcontext: string;\n\n\t/**\n\t * The type for the item.\n\t */\n\ttype: string;\n\n\t/**\n\t * The default value for the item to use when constructing a new object.\n\t */\n\tdefaultValue?: unknown;\n\n\t/**\n\t * Get the JSON schema for the data type.\n\t * @returns The JSON schema for the data type.\n\t */\n\tjsonSchema?(): Promise<IJsonSchema | undefined>;\n\n\t/**\n\t * A method for validating the data type.\n\t * @param propertyName The name of the property being validated.\n\t * @param value The value to validate.\n\t * @param failures List of failures to add to.\n\t * @param container The object which contains this one.\n\t * @returns True if the item is valid.\n\t */\n\tvalidate?(\n\t\tpropertyName: string,\n\t\tvalue: unknown,\n\t\tfailures: IValidationFailure[],\n\t\tcontainer?: unknown\n\t): Promise<boolean>;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IIdentifierHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IIdentifierHandler.js","sourceRoot":"","sources":["../../../src/models/IIdentifierHandler.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IValidationFailure } from \"@twin.org/core\";\n\n/**\n * Interface describing a type which can handle a specific urn namespace.\n */\nexport interface IIdentifierHandler {\n\t/**\n\t * The namespace for the identifier.\n\t */\n\tnamespace: string;\n\n\t/**\n\t * A method for validating the identifier.\n\t * @param propertyName The name of the property being validated.\n\t * @param value The value to validate.\n\t * @param failures List of failures to add to.\n\t * @returns True if the item is valid.\n\t */\n\tvalidate(propertyName: string, value: unknown, failures: IValidationFailure[]): boolean;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IJsonSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IJsonSchema.js","sourceRoot":"","sources":["../../../src/models/IJsonSchema.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type Ajv from \"ajv/dist/2020.js\";\n\n/**\n * Default schema type.\n */\nexport type IJsonSchema = Ajv.SchemaObject;\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ISchemaValidationError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISchemaValidationError.js","sourceRoot":"","sources":["../../../src/models/ISchemaValidationError.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ErrorObject } from \"ajv\";\n\n/**\n * Schema validation error.\n */\nexport type ISchemaValidationError = ErrorObject[];\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ISchemaValidationResult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISchemaValidationResult.js","sourceRoot":"","sources":["../../../src/models/ISchemaValidationResult.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ISchemaValidationError } from \"./ISchemaValidationError.js\";\n\n/**\n * Validation result.\n */\nexport interface ISchemaValidationResult {\n\t/**\n\t * The result.\n\t */\n\tresult: boolean;\n\n\t/**\n\t * The error.\n\t */\n\terror?: ISchemaValidationError;\n}\n"]}
@@ -0,0 +1,25 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ /**
4
+ * Validation modes for validating data types.
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/naming-convention
7
+ export const ValidationMode = {
8
+ /**
9
+ * Use the validation method of the data type.
10
+ */
11
+ Validate: "validate",
12
+ /**
13
+ * Use the JSON Schema methods of the data type.
14
+ */
15
+ JsonSchema: "json-schema",
16
+ /**
17
+ * Use either validation mode.
18
+ */
19
+ Either: "either",
20
+ /**
21
+ * Use both validation modes.
22
+ */
23
+ Both: "both"
24
+ };
25
+ //# sourceMappingURL=validationMode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validationMode.js","sourceRoot":"","sources":["../../../src/models/validationMode.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B;;OAEG;IACH,QAAQ,EAAE,UAAU;IAEpB;;OAEG;IACH,UAAU,EAAE,aAAa;IAEzB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,IAAI,EAAE,MAAM;CACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Validation modes for validating data types.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const ValidationMode = {\n\t/**\n\t * Use the validation method of the data type.\n\t */\n\tValidate: \"validate\",\n\n\t/**\n\t * Use the JSON Schema methods of the data type.\n\t */\n\tJsonSchema: \"json-schema\",\n\n\t/**\n\t * Use either validation mode.\n\t */\n\tEither: \"either\",\n\n\t/**\n\t * Use both validation modes.\n\t */\n\tBoth: \"both\"\n} as const;\n\n/**\n * Validation modes for validating data types.\n */\nexport type ValidationMode = (typeof ValidationMode)[keyof typeof ValidationMode];\n"]}
@@ -0,0 +1,80 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Is } from "@twin.org/core";
4
+ import { JsonSchemaHelper } from "./jsonSchemaHelper.js";
5
+ import { DataTypeHandlerFactory } from "../factories/dataTypeHandlerFactory.js";
6
+ import { ValidationMode } from "../models/validationMode.js";
7
+ /**
8
+ * Class to help with data types.
9
+ */
10
+ export class DataTypeHelper {
11
+ /**
12
+ * Validate a data type.
13
+ * @param propertyName The name of the property being validated to use in error messages.
14
+ * @param dataType The data type to validate.
15
+ * @param data The data to validate.
16
+ * @param validationFailures The list of validation failures to add to.
17
+ * @param options Optional options for validation.
18
+ * @param options.failOnMissingType If true, will fail validation if the data type is missing, defaults to false.
19
+ * @param options.validationMode The validation mode to use, defaults to either.
20
+ * @returns True if the data was valid.
21
+ */
22
+ static async validate(propertyName, dataType, data, validationFailures, options) {
23
+ let isValid = true;
24
+ if (Is.stringValue(dataType)) {
25
+ const handler = DataTypeHandlerFactory.getIfExists(dataType);
26
+ if (handler) {
27
+ const validationMode = options?.validationMode ?? ValidationMode.Either;
28
+ // If we have a validate function use that as it is more specific
29
+ // and will produce better error messages
30
+ let hasValidated = false;
31
+ const validateMethod = handler.validate?.bind(handler);
32
+ if ((validationMode === ValidationMode.Validate ||
33
+ validationMode === ValidationMode.Both ||
34
+ validationMode === ValidationMode.Either) &&
35
+ Is.function(validateMethod)) {
36
+ isValid = await validateMethod(propertyName, data, validationFailures);
37
+ hasValidated = true;
38
+ }
39
+ const jsonSchemaMethod = handler.jsonSchema?.bind(handler);
40
+ if ((validationMode === ValidationMode.JsonSchema ||
41
+ (validationMode === ValidationMode.Either && !hasValidated) ||
42
+ validationMode === ValidationMode.Both) &&
43
+ Is.function(jsonSchemaMethod)) {
44
+ // Otherwise use the JSON schema if there is one
45
+ const schema = await jsonSchemaMethod();
46
+ if (Is.object(schema)) {
47
+ const validationResult = await JsonSchemaHelper.validate(schema, data);
48
+ if (Is.arrayValue(validationResult.error)) {
49
+ validationFailures.push({
50
+ property: propertyName,
51
+ reason: "validation.schema.failedValidation",
52
+ properties: {
53
+ value: data,
54
+ schemaErrors: validationResult.error,
55
+ message: validationResult.error.map(e => e.message).join("\n")
56
+ }
57
+ });
58
+ }
59
+ if (!validationResult.result) {
60
+ isValid = false;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ else if (options?.failOnMissingType ?? false) {
66
+ // If we don't have a handler for a specific type and we are failing on missing type
67
+ validationFailures.push({
68
+ property: propertyName,
69
+ reason: "validation.schema.missingType",
70
+ properties: {
71
+ dataType
72
+ }
73
+ });
74
+ isValid = false;
75
+ }
76
+ }
77
+ return isValid;
78
+ }
79
+ }
80
+ //# sourceMappingURL=dataTypeHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataTypeHelper.js","sourceRoot":"","sources":["../../../src/utils/dataTypeHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, type IValidationFailure } from \"@twin.org/core\";\nimport { JsonSchemaHelper } from \"./jsonSchemaHelper.js\";\nimport { DataTypeHandlerFactory } from \"../factories/dataTypeHandlerFactory.js\";\nimport type { IJsonSchema } from \"../models/IJsonSchema.js\";\nimport { ValidationMode } from \"../models/validationMode.js\";\n\n/**\n * Class to help with data types.\n */\nexport class DataTypeHelper {\n\t/**\n\t * Validate a data type.\n\t * @param propertyName The name of the property being validated to use in error messages.\n\t * @param dataType The data type to validate.\n\t * @param data The data to validate.\n\t * @param validationFailures The list of validation failures to add to.\n\t * @param options Optional options for validation.\n\t * @param options.failOnMissingType If true, will fail validation if the data type is missing, defaults to false.\n\t * @param options.validationMode The validation mode to use, defaults to either.\n\t * @returns True if the data was valid.\n\t */\n\tpublic static async validate(\n\t\tpropertyName: string,\n\t\tdataType: string | undefined,\n\t\tdata: unknown,\n\t\tvalidationFailures: IValidationFailure[],\n\t\toptions?: {\n\t\t\tvalidationMode?: ValidationMode;\n\t\t\tfailOnMissingType?: boolean;\n\t\t}\n\t): Promise<boolean> {\n\t\tlet isValid = true;\n\n\t\tif (Is.stringValue(dataType)) {\n\t\t\tconst handler = DataTypeHandlerFactory.getIfExists(dataType);\n\n\t\t\tif (handler) {\n\t\t\t\tconst validationMode = options?.validationMode ?? ValidationMode.Either;\n\n\t\t\t\t// If we have a validate function use that as it is more specific\n\t\t\t\t// and will produce better error messages\n\t\t\t\tlet hasValidated = false;\n\t\t\t\tconst validateMethod = handler.validate?.bind(handler);\n\t\t\t\tif (\n\t\t\t\t\t(validationMode === ValidationMode.Validate ||\n\t\t\t\t\t\tvalidationMode === ValidationMode.Both ||\n\t\t\t\t\t\tvalidationMode === ValidationMode.Either) &&\n\t\t\t\t\tIs.function(validateMethod)\n\t\t\t\t) {\n\t\t\t\t\tisValid = await validateMethod(propertyName, data, validationFailures);\n\t\t\t\t\thasValidated = true;\n\t\t\t\t}\n\n\t\t\t\tconst jsonSchemaMethod = handler.jsonSchema?.bind(handler);\n\t\t\t\tif (\n\t\t\t\t\t(validationMode === ValidationMode.JsonSchema ||\n\t\t\t\t\t\t(validationMode === ValidationMode.Either && !hasValidated) ||\n\t\t\t\t\t\tvalidationMode === ValidationMode.Both) &&\n\t\t\t\t\tIs.function(jsonSchemaMethod)\n\t\t\t\t) {\n\t\t\t\t\t// Otherwise use the JSON schema if there is one\n\t\t\t\t\tconst schema = await jsonSchemaMethod();\n\n\t\t\t\t\tif (Is.object<IJsonSchema>(schema)) {\n\t\t\t\t\t\tconst validationResult = await JsonSchemaHelper.validate(schema, data);\n\t\t\t\t\t\tif (Is.arrayValue(validationResult.error)) {\n\t\t\t\t\t\t\tvalidationFailures.push({\n\t\t\t\t\t\t\t\tproperty: propertyName,\n\t\t\t\t\t\t\t\treason: \"validation.schema.failedValidation\",\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tvalue: data,\n\t\t\t\t\t\t\t\t\tschemaErrors: validationResult.error,\n\t\t\t\t\t\t\t\t\tmessage: validationResult.error.map(e => e.message).join(\"\\n\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!validationResult.result) {\n\t\t\t\t\t\t\tisValid = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (options?.failOnMissingType ?? false) {\n\t\t\t\t// If we don't have a handler for a specific type and we are failing on missing type\n\t\t\t\tvalidationFailures.push({\n\t\t\t\t\tproperty: propertyName,\n\t\t\t\t\treason: \"validation.schema.missingType\",\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tdataType\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tisValid = false;\n\t\t\t}\n\t\t}\n\n\t\treturn isValid;\n\t}\n}\n"]}
@@ -0,0 +1,152 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Is, StringHelper } from "@twin.org/core";
4
+ import { FetchHelper, HttpMethod } from "@twin.org/web";
5
+ import Ajv from "ajv/dist/2020.js";
6
+ import formatsPlugin from "ajv-formats";
7
+ import { DataTypeHandlerFactory } from "../factories/dataTypeHandlerFactory.js";
8
+ /**
9
+ * A helper for JSON schemas.
10
+ */
11
+ export class JsonSchemaHelper {
12
+ /**
13
+ * The schema version.
14
+ */
15
+ static SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
16
+ /**
17
+ * The class name.
18
+ * @internal
19
+ */
20
+ static CLASS_NAME = "JsonSchemaHelper";
21
+ /**
22
+ * Validates data against the schema.
23
+ * @param schema The schema to validate the data with.
24
+ * @param data The data to be validated.
25
+ * @param additionalTypes Additional types to add for reference, not already in DataTypeHandlerFactory.
26
+ * @returns Result containing errors if there are any.
27
+ */
28
+ static async validate(schema, data, additionalTypes) {
29
+ const ajv = new Ajv.Ajv2020({
30
+ allowUnionTypes: true,
31
+ // Disable strict tuples as it causes issues with the schema validation when
32
+ // you have an array with fixed elements e.g. myType: [string, ...string[]]
33
+ // https://github.com/ajv-validator/ajv/issues/1417
34
+ strictTuples: false,
35
+ loadSchema: async (uri) => {
36
+ const subTypeHandler = DataTypeHandlerFactory.getIfExists(uri);
37
+ const jsonSchemaMethod = subTypeHandler?.jsonSchema?.bind(subTypeHandler);
38
+ if (Is.function(jsonSchemaMethod)) {
39
+ const subSchema = await jsonSchemaMethod();
40
+ if (Is.object(subSchema)) {
41
+ return subSchema;
42
+ }
43
+ }
44
+ try {
45
+ // We don't have the type in our local data types, so we try to fetch it from the web
46
+ const result = await FetchHelper.fetchJson(JsonSchemaHelper.CLASS_NAME, uri, HttpMethod.GET, undefined, {
47
+ // Cache for an hour
48
+ cacheTtlMs: 3600000
49
+ });
50
+ return result;
51
+ }
52
+ catch {
53
+ // Failed to load remotely so return an empty object
54
+ // so the schema validation doesn't completely fail
55
+ return {};
56
+ }
57
+ }
58
+ });
59
+ formatsPlugin.default(ajv);
60
+ // Add the additional types provided by the user
61
+ if (Is.objectValue(additionalTypes)) {
62
+ for (const key in additionalTypes) {
63
+ ajv.addSchema(additionalTypes[key], key);
64
+ }
65
+ }
66
+ const compiled = await ajv.compileAsync(schema);
67
+ const result = compiled(data);
68
+ const output = {
69
+ result
70
+ };
71
+ if (!output.result) {
72
+ output.error = compiled.errors;
73
+ }
74
+ return output;
75
+ }
76
+ /**
77
+ * Get the property type from a schema.
78
+ * @param schema The schema to extract the types from.
79
+ * @param propertyName The name of the property to get the type for.
80
+ * @returns The types of the property.
81
+ */
82
+ static getPropertyType(schema, propertyName) {
83
+ if (schema.type === "object" && Is.objectValue(schema.properties)) {
84
+ const propertySchema = schema.properties[propertyName];
85
+ if (Is.object(propertySchema)) {
86
+ if (Is.stringValue(propertySchema.$ref)) {
87
+ return propertySchema.$ref;
88
+ }
89
+ return propertySchema.type;
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Convert an entity schema to JSON schema e.g https://example.com/schemas/.
95
+ * @param entitySchema The entity schema to convert.
96
+ * @param baseDomain The base domain for local schemas e.g. https://example.com/
97
+ * @returns The JSON schema for the entity.
98
+ */
99
+ static entitySchemaToJsonSchema(entitySchema, baseDomain) {
100
+ let domain = StringHelper.trimTrailingSlashes(baseDomain ?? "");
101
+ if (domain.length > 0) {
102
+ domain += "/";
103
+ }
104
+ const properties = {};
105
+ const required = [];
106
+ if (Is.arrayValue(entitySchema?.properties)) {
107
+ for (const propertySchema of entitySchema.properties) {
108
+ const jsonPropertySchema = {
109
+ type: propertySchema.type,
110
+ description: propertySchema.description,
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ examples: propertySchema.examples
113
+ };
114
+ if (Is.stringValue(propertySchema.itemType) && propertySchema.type === "array") {
115
+ if (propertySchema.itemType === "object") {
116
+ jsonPropertySchema.items = {
117
+ $ref: propertySchema.itemTypeRef?.startsWith("http")
118
+ ? propertySchema.itemTypeRef
119
+ : `${domain}${propertySchema.itemTypeRef}`
120
+ };
121
+ }
122
+ else {
123
+ jsonPropertySchema.items = {
124
+ type: propertySchema.itemType
125
+ };
126
+ }
127
+ }
128
+ else if (propertySchema.type === "object") {
129
+ delete jsonPropertySchema.type;
130
+ jsonPropertySchema.$ref = propertySchema.itemTypeRef?.startsWith("http")
131
+ ? propertySchema.itemTypeRef
132
+ : `${domain}${propertySchema.itemTypeRef}`;
133
+ }
134
+ properties[propertySchema.property] = jsonPropertySchema;
135
+ if (!propertySchema.optional) {
136
+ required.push(propertySchema.property);
137
+ }
138
+ }
139
+ }
140
+ return {
141
+ $schema: JsonSchemaHelper.SCHEMA_VERSION,
142
+ $id: `${domain}${entitySchema?.type}`,
143
+ title: entitySchema?.type,
144
+ type: entitySchema ? "object" : "null",
145
+ description: entitySchema?.options?.description,
146
+ required,
147
+ properties,
148
+ additionalProperties: false
149
+ };
150
+ }
151
+ }
152
+ //# sourceMappingURL=jsonSchemaHelper.js.map
@@ -0,0 +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,5 +1,5 @@
1
1
  import { Factory } from "@twin.org/core";
2
- import type { IDataTypeHandler } from "../models/IDataTypeHandler";
2
+ import type { IDataTypeHandler } from "../models/IDataTypeHandler.js";
3
3
  /**
4
4
  * Factory for creating handlers for data types.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  import { Factory } from "@twin.org/core";
2
- import type { IIdentifierHandler } from "../models/IIdentifierHandler";
2
+ import type { IIdentifierHandler } from "../models/IIdentifierHandler.js";
3
3
  /**
4
4
  * Factory for creating handlers for identifiers.
5
5
  */
@@ -1,10 +1,10 @@
1
- export * from "./factories/dataTypeHandlerFactory";
2
- export * from "./factories/identifierHandlerFactory";
3
- export * from "./models/IDataTypeHandler";
4
- export * from "./models/IIdentifierHandler";
5
- export * from "./models/IJsonSchema";
6
- export * from "./models/ISchemaValidationError";
7
- export * from "./models/ISchemaValidationResult";
8
- export * from "./models/validationMode";
9
- export * from "./utils/dataTypeHelper";
10
- export * from "./utils/jsonSchemaHelper";
1
+ export * from "./factories/dataTypeHandlerFactory.js";
2
+ export * from "./factories/identifierHandlerFactory.js";
3
+ export * from "./models/IDataTypeHandler.js";
4
+ export * from "./models/IIdentifierHandler.js";
5
+ export * from "./models/IJsonSchema.js";
6
+ export * from "./models/ISchemaValidationError.js";
7
+ export * from "./models/ISchemaValidationResult.js";
8
+ export * from "./models/validationMode.js";
9
+ export * from "./utils/dataTypeHelper.js";
10
+ export * from "./utils/jsonSchemaHelper.js";
@@ -1,5 +1,5 @@
1
1
  import type { IValidationFailure } from "@twin.org/core";
2
- import type { IJsonSchema } from "./IJsonSchema";
2
+ import type { IJsonSchema } from "./IJsonSchema.js";
3
3
  /**
4
4
  * Interface describing a type which can handle a specific data type.
5
5
  */
@@ -1,5 +1,5 @@
1
- import type { SchemaObject } from "ajv/dist/2020";
1
+ import type Ajv from "ajv/dist/2020.js";
2
2
  /**
3
3
  * Default schema type.
4
4
  */
5
- export type IJsonSchema = SchemaObject;
5
+ export type IJsonSchema = Ajv.SchemaObject;
@@ -1,4 +1,4 @@
1
- import type { ISchemaValidationError } from "./ISchemaValidationError";
1
+ import type { ISchemaValidationError } from "./ISchemaValidationError.js";
2
2
  /**
3
3
  * Validation result.
4
4
  */
@@ -1,5 +1,5 @@
1
1
  import { type IValidationFailure } from "@twin.org/core";
2
- import { ValidationMode } from "../models/validationMode";
2
+ import { ValidationMode } from "../models/validationMode.js";
3
3
  /**
4
4
  * Class to help with data types.
5
5
  */
@@ -1,6 +1,6 @@
1
1
  import type { IEntitySchema } from "@twin.org/entity";
2
- import type { IJsonSchema } from "../models/IJsonSchema";
3
- import type { ISchemaValidationResult } from "../models/ISchemaValidationResult";
2
+ import type { IJsonSchema } from "../models/IJsonSchema.js";
3
+ import type { ISchemaValidationResult } from "../models/ISchemaValidationResult.js";
4
4
  /**
5
5
  * A helper for JSON schemas.
6
6
  */
package/docs/changelog.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @twin.org/data-core - Changelog
2
2
 
3
+ ## [0.0.3-next.1](https://github.com/twinfoundation/data/compare/data-core-v0.0.3-next.0...data-core-v0.0.3-next.1) (2025-11-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * add context id features ([#25](https://github.com/twinfoundation/data/issues/25)) ([6592f2e](https://github.com/twinfoundation/data/commit/6592f2e4e59021cc42a079a4f46242758a54313d))
9
+ * add document cache access methods ([dbf1e36](https://github.com/twinfoundation/data/commit/dbf1e36d176c5f428f8c52628fb5a1ff7a6a174a))
10
+ * add fail on missing type option and both mode ([e8b9702](https://github.com/twinfoundation/data/commit/e8b97029a04b646497ff0e55b9610291e58ae92a))
11
+ * add validate-locales ([cf9b761](https://github.com/twinfoundation/data/commit/cf9b76160820fe0b13b4fe56ed241c1d5511b7c1))
12
+ * eslint migration to flat config ([b0db6e6](https://github.com/twinfoundation/data/commit/b0db6e69a90046fc60d29e4273fcdfee13c16088))
13
+ * expand Json LD Keyword ([70632d1](https://github.com/twinfoundation/data/commit/70632d1e11ad85cf3c57e118476b125a673f1681))
14
+ * update framework core ([c077b8c](https://github.com/twinfoundation/data/commit/c077b8c07e7ee66b5482254eab6f2a52cd911270))
15
+ * use fully qualified names for data type lookups ([b7b5c74](https://github.com/twinfoundation/data/commit/b7b5c746b0180a87baa976f6a7a76cedd53d8ff7))
16
+ * use shared store mechanism ([#3](https://github.com/twinfoundation/data/issues/3)) ([33eb221](https://github.com/twinfoundation/data/commit/33eb221ccec2b4a79549c06e9a04225009b93a46))
17
+ * use updated Is.function ([46a4715](https://github.com/twinfoundation/data/commit/46a4715f995aea34f2011138662fe003c9727d07))
18
+ * use updated JSON schema specs ([465223a](https://github.com/twinfoundation/data/commit/465223a9e9c24af546480ef084327a78fa366eaa))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * interface name ([6e49322](https://github.com/twinfoundation/data/commit/6e49322ec1797417220ec9e529bb124f4717f489))
24
+ * remove undici reference ([d77721e](https://github.com/twinfoundation/data/commit/d77721e21d23c7a6750c2f5cac8104851dfaa6d7))
25
+ * tests using context ([577b3bb](https://github.com/twinfoundation/data/commit/577b3bbb661eafbf6d3fd157133c106732e8eb3d))
26
+
27
+ ## [0.0.2-next.4](https://github.com/twinfoundation/data/compare/data-core-v0.0.2-next.3...data-core-v0.0.2-next.4) (2025-10-09)
28
+
29
+
30
+ ### Features
31
+
32
+ * add validate-locales ([cf9b761](https://github.com/twinfoundation/data/commit/cf9b76160820fe0b13b4fe56ed241c1d5511b7c1))
33
+
3
34
  ## [0.0.2-next.3](https://github.com/twinfoundation/data/compare/data-core-v0.0.2-next.2...data-core-v0.0.2-next.3) (2025-09-29)
4
35
 
5
36
 
@@ -32,7 +32,7 @@ The name of the property being validated to use in error messages.
32
32
 
33
33
  The data type to validate.
34
34
 
35
- `undefined` | `string`
35
+ `string` | `undefined`
36
36
 
37
37
  ##### data
38
38
 
@@ -62,7 +62,7 @@ Result containing errors if there are any.
62
62
 
63
63
  ### getPropertyType()
64
64
 
65
- > `static` **getPropertyType**(`schema`, `propertyName`): `undefined` \| `string`
65
+ > `static` **getPropertyType**(`schema`, `propertyName`): `string` \| `undefined`
66
66
 
67
67
  Get the property type from a schema.
68
68
 
@@ -82,7 +82,7 @@ The name of the property to get the type for.
82
82
 
83
83
  #### Returns
84
84
 
85
- `undefined` \| `string`
85
+ `string` \| `undefined`
86
86
 
87
87
  The types of the property.
88
88
 
@@ -100,7 +100,7 @@ Convert an entity schema to JSON schema e.g https://example.com/schemas/.
100
100
 
101
101
  The entity schema to convert.
102
102
 
103
- `undefined` | `IEntitySchema`\<`unknown`\>
103
+ `IEntitySchema`\<`unknown`\> | `undefined`
104
104
 
105
105
  ##### baseDomain?
106
106
 
@@ -30,13 +30,13 @@ The default value for the item to use when constructing a new object.
30
30
 
31
31
  ### jsonSchema()?
32
32
 
33
- > `optional` **jsonSchema**(): `Promise`\<`undefined` \| `SchemaObject`\>
33
+ > `optional` **jsonSchema**(): `Promise`\<`SchemaObject` \| `undefined`\>
34
34
 
35
35
  Get the JSON schema for the data type.
36
36
 
37
37
  #### Returns
38
38
 
39
- `Promise`\<`undefined` \| `SchemaObject`\>
39
+ `Promise`\<`SchemaObject` \| `undefined`\>
40
40
 
41
41
  The JSON schema for the data type.
42
42
 
@@ -1,5 +1,5 @@
1
1
  # Type Alias: IJsonSchema
2
2
 
3
- > **IJsonSchema** = `SchemaObject`
3
+ > **IJsonSchema** = `Ajv.SchemaObject`
4
4
 
5
5
  Default schema type.
package/locales/en.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "error": {
3
3
  "validation": {
4
- "properties": {
5
- "keyAlreadyExists": "The key already exists"
6
- },
7
4
  "schema": {
8
5
  "failedValidation": "The JSON schema failed validation, {message}",
9
6
  "missingType": "Failed to validate as there is no handler for type \"{dataType}\""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/data-core",
3
- "version": "0.0.2-next.3",
3
+ "version": "0.0.3-next.1",
4
4
  "description": "Definitions and helpers for using with data and schemas",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,21 +21,34 @@
21
21
  "ajv": "8.17.1",
22
22
  "ajv-formats": "3.0.1"
23
23
  },
24
- "main": "./dist/cjs/index.cjs",
25
- "module": "./dist/esm/index.mjs",
24
+ "main": "./dist/es/index.js",
26
25
  "types": "./dist/types/index.d.ts",
27
26
  "exports": {
28
27
  ".": {
29
28
  "types": "./dist/types/index.d.ts",
30
- "require": "./dist/cjs/index.cjs",
31
- "import": "./dist/esm/index.mjs"
29
+ "import": "./dist/es/index.js",
30
+ "default": "./dist/es/index.js"
32
31
  }
33
32
  },
34
33
  "files": [
35
- "dist/cjs",
36
- "dist/esm",
34
+ "dist/es",
37
35
  "dist/types",
38
36
  "locales",
39
37
  "docs"
40
- ]
38
+ ],
39
+ "keywords": [
40
+ "twin",
41
+ "trade",
42
+ "iota",
43
+ "framework",
44
+ "blockchain",
45
+ "data",
46
+ "core",
47
+ "foundation",
48
+ "utilities"
49
+ ],
50
+ "bugs": {
51
+ "url": "git+https://github.com/twinfoundation/data/issues"
52
+ },
53
+ "homepage": "https://twindev.org"
41
54
  }
@@ -1,282 +0,0 @@
1
- 'use strict';
2
-
3
- var core = require('@twin.org/core');
4
- var web = require('@twin.org/web');
5
- var Ajv = require('ajv/dist/2020.js');
6
- var addFormats = require('ajv-formats');
7
-
8
- // Copyright 2024 IOTA Stiftung.
9
- // SPDX-License-Identifier: Apache-2.0.
10
- /**
11
- * Factory for creating handlers for data types.
12
- */
13
- // eslint-disable-next-line @typescript-eslint/naming-convention
14
- const DataTypeHandlerFactory = core.Factory.createFactory("data-type");
15
-
16
- // Copyright 2024 IOTA Stiftung.
17
- // SPDX-License-Identifier: Apache-2.0.
18
- /**
19
- * Factory for creating handlers for identifiers.
20
- */
21
- // eslint-disable-next-line @typescript-eslint/naming-convention
22
- const IdentifierHandlerFactory = core.Factory.createFactory("namespace", false, (names, uri) => {
23
- core.Urn.guard("IdentifierHandlerFactory", "uri", uri);
24
- const urn = core.Urn.fromValidString(uri);
25
- const urnParts = urn.parts();
26
- for (let i = urnParts.length - 1; i >= 0; i--) {
27
- const wholeNamespace = urnParts.slice(i).join(":");
28
- if (names.includes(wholeNamespace)) {
29
- return wholeNamespace;
30
- }
31
- }
32
- });
33
-
34
- // Copyright 2024 IOTA Stiftung.
35
- // SPDX-License-Identifier: Apache-2.0.
36
- /**
37
- * Validation modes for validating data types.
38
- */
39
- // eslint-disable-next-line @typescript-eslint/naming-convention
40
- const ValidationMode = {
41
- /**
42
- * Use the validation method of the data type.
43
- */
44
- Validate: "validate",
45
- /**
46
- * Use the JSON Schema methods of the data type.
47
- */
48
- JsonSchema: "json-schema",
49
- /**
50
- * Use either validation mode.
51
- */
52
- Either: "either",
53
- /**
54
- * Use both validation modes.
55
- */
56
- Both: "both"
57
- };
58
-
59
- // Copyright 2024 IOTA Stiftung.
60
- // SPDX-License-Identifier: Apache-2.0.
61
- /**
62
- * A helper for JSON schemas.
63
- */
64
- class JsonSchemaHelper {
65
- /**
66
- * The schema version.
67
- */
68
- static SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
69
- /**
70
- * The class name.
71
- * @internal
72
- */
73
- static _CLASS_NAME = "JsonSchemaHelper";
74
- /**
75
- * Validates data against the schema.
76
- * @param schema The schema to validate the data with.
77
- * @param data The data to be validated.
78
- * @param additionalTypes Additional types to add for reference, not already in DataTypeHandlerFactory.
79
- * @returns Result containing errors if there are any.
80
- */
81
- static async validate(schema, data, additionalTypes) {
82
- const ajv = new Ajv({
83
- allowUnionTypes: true,
84
- // Disable strict tuples as it causes issues with the schema validation when
85
- // you have an array with fixed elements e.g. myType: [string, ...string[]]
86
- // https://github.com/ajv-validator/ajv/issues/1417
87
- strictTuples: false,
88
- loadSchema: async (uri) => {
89
- const subTypeHandler = DataTypeHandlerFactory.getIfExists(uri);
90
- if (core.Is.function(subTypeHandler?.jsonSchema)) {
91
- const subSchema = await subTypeHandler.jsonSchema();
92
- if (core.Is.object(subSchema)) {
93
- return subSchema;
94
- }
95
- }
96
- try {
97
- // We don't have the type in our local data types, so we try to fetch it from the web
98
- return web.FetchHelper.fetchJson(JsonSchemaHelper._CLASS_NAME, uri, web.HttpMethod.GET, undefined, {
99
- // Cache for an hour
100
- cacheTtlMs: 3600000
101
- });
102
- }
103
- catch {
104
- // Failed to load remotely so return an empty object
105
- // so the schema validation doesn't completely fail
106
- return {};
107
- }
108
- }
109
- });
110
- addFormats(ajv);
111
- // Add the additional types provided by the user
112
- if (core.Is.objectValue(additionalTypes)) {
113
- for (const key in additionalTypes) {
114
- ajv.addSchema(additionalTypes[key], key);
115
- }
116
- }
117
- const compiled = await ajv.compileAsync(schema);
118
- const result = await compiled(data);
119
- const output = {
120
- result
121
- };
122
- if (!output.result) {
123
- output.error = compiled.errors;
124
- }
125
- return output;
126
- }
127
- /**
128
- * Get the property type from a schema.
129
- * @param schema The schema to extract the types from.
130
- * @param propertyName The name of the property to get the type for.
131
- * @returns The types of the property.
132
- */
133
- static getPropertyType(schema, propertyName) {
134
- if (schema.type === "object" && core.Is.objectValue(schema.properties)) {
135
- const propertySchema = schema.properties[propertyName];
136
- if (core.Is.object(propertySchema)) {
137
- if (core.Is.stringValue(propertySchema.$ref)) {
138
- return propertySchema.$ref;
139
- }
140
- return propertySchema.type;
141
- }
142
- }
143
- }
144
- /**
145
- * Convert an entity schema to JSON schema e.g https://example.com/schemas/.
146
- * @param entitySchema The entity schema to convert.
147
- * @param baseDomain The base domain for local schemas e.g. https://example.com/
148
- * @returns The JSON schema for the entity.
149
- */
150
- static entitySchemaToJsonSchema(entitySchema, baseDomain) {
151
- let domain = core.StringHelper.trimTrailingSlashes(baseDomain ?? "");
152
- if (domain.length > 0) {
153
- domain += "/";
154
- }
155
- const properties = {};
156
- const required = [];
157
- if (core.Is.arrayValue(entitySchema?.properties)) {
158
- for (const propertySchema of entitySchema.properties) {
159
- const jsonPropertySchema = {
160
- type: propertySchema.type,
161
- description: propertySchema.description,
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- examples: propertySchema.examples
164
- };
165
- if (core.Is.stringValue(propertySchema.itemType) && propertySchema.type === "array") {
166
- if (propertySchema.itemType === "object") {
167
- jsonPropertySchema.items = {
168
- $ref: propertySchema.itemTypeRef?.startsWith("http")
169
- ? propertySchema.itemTypeRef
170
- : `${domain}${propertySchema.itemTypeRef}`
171
- };
172
- }
173
- else {
174
- jsonPropertySchema.items = {
175
- type: propertySchema.itemType
176
- };
177
- }
178
- }
179
- else if (propertySchema.type === "object") {
180
- delete jsonPropertySchema.type;
181
- jsonPropertySchema.$ref = propertySchema.itemTypeRef?.startsWith("http")
182
- ? propertySchema.itemTypeRef
183
- : `${domain}${propertySchema.itemTypeRef}`;
184
- }
185
- properties[propertySchema.property] = jsonPropertySchema;
186
- if (!propertySchema.optional) {
187
- required.push(propertySchema.property);
188
- }
189
- }
190
- }
191
- return {
192
- $schema: JsonSchemaHelper.SCHEMA_VERSION,
193
- $id: `${domain}${entitySchema?.type}`,
194
- title: entitySchema?.type,
195
- type: entitySchema ? "object" : "null",
196
- description: entitySchema?.options?.description,
197
- required,
198
- properties,
199
- additionalProperties: false
200
- };
201
- }
202
- }
203
-
204
- // Copyright 2024 IOTA Stiftung.
205
- // SPDX-License-Identifier: Apache-2.0.
206
- /**
207
- * Class to help with data types.
208
- */
209
- class DataTypeHelper {
210
- /**
211
- * Validate a data type.
212
- * @param propertyName The name of the property being validated to use in error messages.
213
- * @param dataType The data type to validate.
214
- * @param data The data to validate.
215
- * @param validationFailures The list of validation failures to add to.
216
- * @param options Optional options for validation.
217
- * @param options.failOnMissingType If true, will fail validation if the data type is missing, defaults to false.
218
- * @param options.validationMode The validation mode to use, defaults to either.
219
- * @returns True if the data was valid.
220
- */
221
- static async validate(propertyName, dataType, data, validationFailures, options) {
222
- let isValid = true;
223
- if (core.Is.stringValue(dataType)) {
224
- const handler = DataTypeHandlerFactory.getIfExists(dataType);
225
- if (handler) {
226
- const validationMode = options?.validationMode ?? ValidationMode.Either;
227
- // If we have a validate function use that as it is more specific
228
- // and will produce better error messages
229
- let hasValidated = false;
230
- if ((validationMode === ValidationMode.Validate ||
231
- validationMode === ValidationMode.Both ||
232
- validationMode === ValidationMode.Either) &&
233
- core.Is.function(handler.validate)) {
234
- isValid = await handler.validate(propertyName, data, validationFailures);
235
- hasValidated = true;
236
- }
237
- if ((validationMode === ValidationMode.JsonSchema ||
238
- (validationMode === ValidationMode.Either && !hasValidated) ||
239
- validationMode === ValidationMode.Both) &&
240
- core.Is.function(handler.jsonSchema)) {
241
- // Otherwise use the JSON schema if there is one
242
- const schema = await handler.jsonSchema();
243
- if (core.Is.object(schema)) {
244
- const validationResult = await JsonSchemaHelper.validate(schema, data);
245
- if (core.Is.arrayValue(validationResult.error)) {
246
- validationFailures.push({
247
- property: propertyName,
248
- reason: "validation.schema.failedValidation",
249
- properties: {
250
- value: data,
251
- schemaErrors: validationResult.error,
252
- message: validationResult.error.map(e => e.message).join("\n")
253
- }
254
- });
255
- }
256
- if (!validationResult.result) {
257
- isValid = false;
258
- }
259
- }
260
- }
261
- }
262
- else if (options?.failOnMissingType ?? false) {
263
- // If we don't have a handler for a specific type and we are failing on missing type
264
- validationFailures.push({
265
- property: propertyName,
266
- reason: "validation.schema.missingType",
267
- properties: {
268
- dataType
269
- }
270
- });
271
- isValid = false;
272
- }
273
- }
274
- return isValid;
275
- }
276
- }
277
-
278
- exports.DataTypeHandlerFactory = DataTypeHandlerFactory;
279
- exports.DataTypeHelper = DataTypeHelper;
280
- exports.IdentifierHandlerFactory = IdentifierHandlerFactory;
281
- exports.JsonSchemaHelper = JsonSchemaHelper;
282
- exports.ValidationMode = ValidationMode;
@@ -1,276 +0,0 @@
1
- import { Factory, Urn, Is, StringHelper } from '@twin.org/core';
2
- import { FetchHelper, HttpMethod } from '@twin.org/web';
3
- import Ajv from 'ajv/dist/2020.js';
4
- import addFormats from 'ajv-formats';
5
-
6
- // Copyright 2024 IOTA Stiftung.
7
- // SPDX-License-Identifier: Apache-2.0.
8
- /**
9
- * Factory for creating handlers for data types.
10
- */
11
- // eslint-disable-next-line @typescript-eslint/naming-convention
12
- const DataTypeHandlerFactory = Factory.createFactory("data-type");
13
-
14
- // Copyright 2024 IOTA Stiftung.
15
- // SPDX-License-Identifier: Apache-2.0.
16
- /**
17
- * Factory for creating handlers for identifiers.
18
- */
19
- // eslint-disable-next-line @typescript-eslint/naming-convention
20
- const IdentifierHandlerFactory = Factory.createFactory("namespace", false, (names, uri) => {
21
- Urn.guard("IdentifierHandlerFactory", "uri", uri);
22
- const urn = Urn.fromValidString(uri);
23
- const urnParts = urn.parts();
24
- for (let i = urnParts.length - 1; i >= 0; i--) {
25
- const wholeNamespace = urnParts.slice(i).join(":");
26
- if (names.includes(wholeNamespace)) {
27
- return wholeNamespace;
28
- }
29
- }
30
- });
31
-
32
- // Copyright 2024 IOTA Stiftung.
33
- // SPDX-License-Identifier: Apache-2.0.
34
- /**
35
- * Validation modes for validating data types.
36
- */
37
- // eslint-disable-next-line @typescript-eslint/naming-convention
38
- const ValidationMode = {
39
- /**
40
- * Use the validation method of the data type.
41
- */
42
- Validate: "validate",
43
- /**
44
- * Use the JSON Schema methods of the data type.
45
- */
46
- JsonSchema: "json-schema",
47
- /**
48
- * Use either validation mode.
49
- */
50
- Either: "either",
51
- /**
52
- * Use both validation modes.
53
- */
54
- Both: "both"
55
- };
56
-
57
- // Copyright 2024 IOTA Stiftung.
58
- // SPDX-License-Identifier: Apache-2.0.
59
- /**
60
- * A helper for JSON schemas.
61
- */
62
- class JsonSchemaHelper {
63
- /**
64
- * The schema version.
65
- */
66
- static SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
67
- /**
68
- * The class name.
69
- * @internal
70
- */
71
- static _CLASS_NAME = "JsonSchemaHelper";
72
- /**
73
- * Validates data against the schema.
74
- * @param schema The schema to validate the data with.
75
- * @param data The data to be validated.
76
- * @param additionalTypes Additional types to add for reference, not already in DataTypeHandlerFactory.
77
- * @returns Result containing errors if there are any.
78
- */
79
- static async validate(schema, data, additionalTypes) {
80
- const ajv = new Ajv({
81
- allowUnionTypes: true,
82
- // Disable strict tuples as it causes issues with the schema validation when
83
- // you have an array with fixed elements e.g. myType: [string, ...string[]]
84
- // https://github.com/ajv-validator/ajv/issues/1417
85
- strictTuples: false,
86
- loadSchema: async (uri) => {
87
- const subTypeHandler = DataTypeHandlerFactory.getIfExists(uri);
88
- if (Is.function(subTypeHandler?.jsonSchema)) {
89
- const subSchema = await subTypeHandler.jsonSchema();
90
- if (Is.object(subSchema)) {
91
- return subSchema;
92
- }
93
- }
94
- try {
95
- // We don't have the type in our local data types, so we try to fetch it from the web
96
- return FetchHelper.fetchJson(JsonSchemaHelper._CLASS_NAME, uri, HttpMethod.GET, undefined, {
97
- // Cache for an hour
98
- cacheTtlMs: 3600000
99
- });
100
- }
101
- catch {
102
- // Failed to load remotely so return an empty object
103
- // so the schema validation doesn't completely fail
104
- return {};
105
- }
106
- }
107
- });
108
- addFormats(ajv);
109
- // Add the additional types provided by the user
110
- if (Is.objectValue(additionalTypes)) {
111
- for (const key in additionalTypes) {
112
- ajv.addSchema(additionalTypes[key], key);
113
- }
114
- }
115
- const compiled = await ajv.compileAsync(schema);
116
- const result = await compiled(data);
117
- const output = {
118
- result
119
- };
120
- if (!output.result) {
121
- output.error = compiled.errors;
122
- }
123
- return output;
124
- }
125
- /**
126
- * Get the property type from a schema.
127
- * @param schema The schema to extract the types from.
128
- * @param propertyName The name of the property to get the type for.
129
- * @returns The types of the property.
130
- */
131
- static getPropertyType(schema, propertyName) {
132
- if (schema.type === "object" && Is.objectValue(schema.properties)) {
133
- const propertySchema = schema.properties[propertyName];
134
- if (Is.object(propertySchema)) {
135
- if (Is.stringValue(propertySchema.$ref)) {
136
- return propertySchema.$ref;
137
- }
138
- return propertySchema.type;
139
- }
140
- }
141
- }
142
- /**
143
- * Convert an entity schema to JSON schema e.g https://example.com/schemas/.
144
- * @param entitySchema The entity schema to convert.
145
- * @param baseDomain The base domain for local schemas e.g. https://example.com/
146
- * @returns The JSON schema for the entity.
147
- */
148
- static entitySchemaToJsonSchema(entitySchema, baseDomain) {
149
- let domain = StringHelper.trimTrailingSlashes(baseDomain ?? "");
150
- if (domain.length > 0) {
151
- domain += "/";
152
- }
153
- const properties = {};
154
- const required = [];
155
- if (Is.arrayValue(entitySchema?.properties)) {
156
- for (const propertySchema of entitySchema.properties) {
157
- const jsonPropertySchema = {
158
- type: propertySchema.type,
159
- description: propertySchema.description,
160
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
- examples: propertySchema.examples
162
- };
163
- if (Is.stringValue(propertySchema.itemType) && propertySchema.type === "array") {
164
- if (propertySchema.itemType === "object") {
165
- jsonPropertySchema.items = {
166
- $ref: propertySchema.itemTypeRef?.startsWith("http")
167
- ? propertySchema.itemTypeRef
168
- : `${domain}${propertySchema.itemTypeRef}`
169
- };
170
- }
171
- else {
172
- jsonPropertySchema.items = {
173
- type: propertySchema.itemType
174
- };
175
- }
176
- }
177
- else if (propertySchema.type === "object") {
178
- delete jsonPropertySchema.type;
179
- jsonPropertySchema.$ref = propertySchema.itemTypeRef?.startsWith("http")
180
- ? propertySchema.itemTypeRef
181
- : `${domain}${propertySchema.itemTypeRef}`;
182
- }
183
- properties[propertySchema.property] = jsonPropertySchema;
184
- if (!propertySchema.optional) {
185
- required.push(propertySchema.property);
186
- }
187
- }
188
- }
189
- return {
190
- $schema: JsonSchemaHelper.SCHEMA_VERSION,
191
- $id: `${domain}${entitySchema?.type}`,
192
- title: entitySchema?.type,
193
- type: entitySchema ? "object" : "null",
194
- description: entitySchema?.options?.description,
195
- required,
196
- properties,
197
- additionalProperties: false
198
- };
199
- }
200
- }
201
-
202
- // Copyright 2024 IOTA Stiftung.
203
- // SPDX-License-Identifier: Apache-2.0.
204
- /**
205
- * Class to help with data types.
206
- */
207
- class DataTypeHelper {
208
- /**
209
- * Validate a data type.
210
- * @param propertyName The name of the property being validated to use in error messages.
211
- * @param dataType The data type to validate.
212
- * @param data The data to validate.
213
- * @param validationFailures The list of validation failures to add to.
214
- * @param options Optional options for validation.
215
- * @param options.failOnMissingType If true, will fail validation if the data type is missing, defaults to false.
216
- * @param options.validationMode The validation mode to use, defaults to either.
217
- * @returns True if the data was valid.
218
- */
219
- static async validate(propertyName, dataType, data, validationFailures, options) {
220
- let isValid = true;
221
- if (Is.stringValue(dataType)) {
222
- const handler = DataTypeHandlerFactory.getIfExists(dataType);
223
- if (handler) {
224
- const validationMode = options?.validationMode ?? ValidationMode.Either;
225
- // If we have a validate function use that as it is more specific
226
- // and will produce better error messages
227
- let hasValidated = false;
228
- if ((validationMode === ValidationMode.Validate ||
229
- validationMode === ValidationMode.Both ||
230
- validationMode === ValidationMode.Either) &&
231
- Is.function(handler.validate)) {
232
- isValid = await handler.validate(propertyName, data, validationFailures);
233
- hasValidated = true;
234
- }
235
- if ((validationMode === ValidationMode.JsonSchema ||
236
- (validationMode === ValidationMode.Either && !hasValidated) ||
237
- validationMode === ValidationMode.Both) &&
238
- Is.function(handler.jsonSchema)) {
239
- // Otherwise use the JSON schema if there is one
240
- const schema = await handler.jsonSchema();
241
- if (Is.object(schema)) {
242
- const validationResult = await JsonSchemaHelper.validate(schema, data);
243
- if (Is.arrayValue(validationResult.error)) {
244
- validationFailures.push({
245
- property: propertyName,
246
- reason: "validation.schema.failedValidation",
247
- properties: {
248
- value: data,
249
- schemaErrors: validationResult.error,
250
- message: validationResult.error.map(e => e.message).join("\n")
251
- }
252
- });
253
- }
254
- if (!validationResult.result) {
255
- isValid = false;
256
- }
257
- }
258
- }
259
- }
260
- else if (options?.failOnMissingType ?? false) {
261
- // If we don't have a handler for a specific type and we are failing on missing type
262
- validationFailures.push({
263
- property: propertyName,
264
- reason: "validation.schema.missingType",
265
- properties: {
266
- dataType
267
- }
268
- });
269
- isValid = false;
270
- }
271
- }
272
- return isValid;
273
- }
274
- }
275
-
276
- export { DataTypeHandlerFactory, DataTypeHelper, IdentifierHandlerFactory, JsonSchemaHelper, ValidationMode };