@kravc/schema 2.7.6 → 2.8.0-alpha.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 (176) hide show
  1. package/README.md +19 -14
  2. package/dist/CredentialFactory.d.ts +345 -0
  3. package/dist/CredentialFactory.d.ts.map +1 -0
  4. package/dist/CredentialFactory.js +381 -0
  5. package/dist/CredentialFactory.js.map +1 -0
  6. package/dist/Schema.d.ts +448 -0
  7. package/dist/Schema.d.ts.map +1 -0
  8. package/dist/Schema.js +506 -0
  9. package/dist/Schema.js.map +1 -0
  10. package/dist/ValidationError.d.ts +70 -0
  11. package/dist/ValidationError.d.ts.map +1 -0
  12. package/dist/ValidationError.js +78 -0
  13. package/dist/ValidationError.js.map +1 -0
  14. package/dist/Validator.d.ts +483 -0
  15. package/dist/Validator.d.ts.map +1 -0
  16. package/dist/Validator.js +570 -0
  17. package/dist/Validator.js.map +1 -0
  18. package/dist/helpers/JsonSchema.d.ts +99 -0
  19. package/dist/helpers/JsonSchema.d.ts.map +1 -0
  20. package/dist/helpers/JsonSchema.js +3 -0
  21. package/dist/helpers/JsonSchema.js.map +1 -0
  22. package/dist/helpers/cleanupAttributes.d.ts +34 -0
  23. package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
  24. package/dist/helpers/cleanupAttributes.js +113 -0
  25. package/dist/helpers/cleanupAttributes.js.map +1 -0
  26. package/dist/helpers/cleanupNulls.d.ts +27 -0
  27. package/dist/helpers/cleanupNulls.d.ts.map +1 -0
  28. package/dist/helpers/cleanupNulls.js +96 -0
  29. package/dist/helpers/cleanupNulls.js.map +1 -0
  30. package/dist/helpers/createSchemasMap.d.ts +67 -0
  31. package/dist/helpers/createSchemasMap.d.ts.map +1 -0
  32. package/dist/helpers/createSchemasMap.js +200 -0
  33. package/dist/helpers/createSchemasMap.js.map +1 -0
  34. package/dist/helpers/getReferenceIds.d.ts +169 -0
  35. package/dist/helpers/getReferenceIds.d.ts.map +1 -0
  36. package/dist/helpers/getReferenceIds.js +241 -0
  37. package/dist/helpers/getReferenceIds.js.map +1 -0
  38. package/dist/helpers/got.d.ts +60 -0
  39. package/dist/helpers/got.d.ts.map +1 -0
  40. package/dist/helpers/got.js +72 -0
  41. package/dist/helpers/got.js.map +1 -0
  42. package/dist/helpers/mapObjectProperties.d.ts +150 -0
  43. package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
  44. package/dist/helpers/mapObjectProperties.js +229 -0
  45. package/dist/helpers/mapObjectProperties.js.map +1 -0
  46. package/dist/helpers/normalizeAttributes.d.ts +213 -0
  47. package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
  48. package/dist/helpers/normalizeAttributes.js +243 -0
  49. package/dist/helpers/normalizeAttributes.js.map +1 -0
  50. package/dist/helpers/normalizeProperties.d.ts +168 -0
  51. package/dist/helpers/normalizeProperties.d.ts.map +1 -0
  52. package/dist/helpers/normalizeProperties.js +223 -0
  53. package/dist/helpers/normalizeProperties.js.map +1 -0
  54. package/dist/helpers/normalizeRequired.d.ts +159 -0
  55. package/dist/helpers/normalizeRequired.d.ts.map +1 -0
  56. package/dist/helpers/normalizeRequired.js +206 -0
  57. package/dist/helpers/normalizeRequired.js.map +1 -0
  58. package/dist/helpers/normalizeType.d.ts +81 -0
  59. package/dist/helpers/normalizeType.d.ts.map +1 -0
  60. package/dist/helpers/normalizeType.js +210 -0
  61. package/dist/helpers/normalizeType.js.map +1 -0
  62. package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
  63. package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
  64. package/dist/helpers/nullifyEmptyValues.js +191 -0
  65. package/dist/helpers/nullifyEmptyValues.js.map +1 -0
  66. package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
  67. package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
  68. package/dist/helpers/removeRequiredAndDefault.js +138 -0
  69. package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
  70. package/dist/helpers/validateId.d.ts +39 -0
  71. package/dist/helpers/validateId.d.ts.map +1 -0
  72. package/dist/helpers/validateId.js +51 -0
  73. package/dist/helpers/validateId.js.map +1 -0
  74. package/dist/index.d.ts +9 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +21 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/ld/documentLoader.d.ts +8 -0
  79. package/dist/ld/documentLoader.d.ts.map +1 -0
  80. package/dist/ld/documentLoader.js +24 -0
  81. package/dist/ld/documentLoader.js.map +1 -0
  82. package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
  83. package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
  84. package/dist/ld/getLinkedDataAttributeType.js +32 -0
  85. package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
  86. package/dist/ld/getLinkedDataContext.d.ts +19 -0
  87. package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
  88. package/dist/ld/getLinkedDataContext.js +50 -0
  89. package/dist/ld/getLinkedDataContext.js.map +1 -0
  90. package/eslint.config.mjs +32 -52
  91. package/examples/credentials/createAccountCredential.ts +27 -0
  92. package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
  93. package/examples/index.ts +7 -0
  94. package/examples/schemas/FavoriteItemSchema.ts +27 -0
  95. package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
  96. package/examples/schemas/PreferencesSchema.ts +29 -0
  97. package/examples/schemas/ProfileSchema.ts +91 -0
  98. package/examples/schemas/Status.yaml +3 -0
  99. package/examples/schemas/StatusSchema.ts +12 -0
  100. package/jest.config.mjs +5 -0
  101. package/package.json +27 -20
  102. package/src/CredentialFactory.ts +392 -0
  103. package/src/Schema.ts +583 -0
  104. package/src/ValidationError.ts +90 -0
  105. package/src/Validator.ts +603 -0
  106. package/src/__tests__/CredentialFactory.test.ts +588 -0
  107. package/src/__tests__/Schema.test.ts +371 -0
  108. package/src/__tests__/ValidationError.test.ts +235 -0
  109. package/src/__tests__/Validator.test.ts +787 -0
  110. package/src/helpers/JsonSchema.ts +119 -0
  111. package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
  112. package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
  113. package/src/helpers/__tests__/createSchemasMap.test.ts +238 -0
  114. package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
  115. package/src/helpers/__tests__/got.test.ts +193 -0
  116. package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
  117. package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
  118. package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
  119. package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
  120. package/src/helpers/__tests__/normalizeType.test.ts +772 -0
  121. package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
  122. package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
  123. package/src/helpers/__tests__/validateId.test.ts +118 -0
  124. package/src/helpers/cleanupAttributes.ts +151 -0
  125. package/src/helpers/cleanupNulls.ts +106 -0
  126. package/src/helpers/createSchemasMap.ts +212 -0
  127. package/src/helpers/getReferenceIds.ts +273 -0
  128. package/src/helpers/got.ts +73 -0
  129. package/src/helpers/mapObjectProperties.ts +272 -0
  130. package/src/helpers/normalizeAttributes.ts +247 -0
  131. package/src/helpers/normalizeProperties.ts +249 -0
  132. package/src/helpers/normalizeRequired.ts +233 -0
  133. package/src/helpers/normalizeType.ts +235 -0
  134. package/src/helpers/nullifyEmptyValues.ts +207 -0
  135. package/src/helpers/removeRequiredAndDefault.ts +151 -0
  136. package/src/helpers/validateId.ts +53 -0
  137. package/src/index.ts +17 -0
  138. package/src/ld/__tests__/documentLoader.test.ts +57 -0
  139. package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
  140. package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
  141. package/src/ld/documentLoader.ts +28 -0
  142. package/src/ld/getLinkedDataAttributeType.ts +46 -0
  143. package/src/ld/getLinkedDataContext.ts +80 -0
  144. package/tsconfig.json +27 -0
  145. package/types/credentials-context.d.ts +14 -0
  146. package/types/security-context.d.ts +6 -0
  147. package/examples/Status.yaml +0 -3
  148. package/examples/createAccountCredential.js +0 -27
  149. package/examples/createMineSweeperScoreCredential.js +0 -63
  150. package/examples/index.js +0 -9
  151. package/src/CredentialFactory.js +0 -67
  152. package/src/CredentialFactory.spec.js +0 -131
  153. package/src/Schema.js +0 -104
  154. package/src/Schema.spec.js +0 -172
  155. package/src/ValidationError.js +0 -31
  156. package/src/Validator.js +0 -128
  157. package/src/Validator.spec.js +0 -355
  158. package/src/helpers/cleanupAttributes.js +0 -71
  159. package/src/helpers/cleanupNulls.js +0 -42
  160. package/src/helpers/getReferenceIds.js +0 -71
  161. package/src/helpers/mapObject.js +0 -65
  162. package/src/helpers/normalizeAttributes.js +0 -28
  163. package/src/helpers/normalizeProperties.js +0 -61
  164. package/src/helpers/normalizeRequired.js +0 -37
  165. package/src/helpers/normalizeType.js +0 -41
  166. package/src/helpers/nullifyEmptyValues.js +0 -57
  167. package/src/helpers/removeRequiredAndDefault.js +0 -30
  168. package/src/helpers/validateId.js +0 -19
  169. package/src/index.d.ts +0 -25
  170. package/src/index.js +0 -8
  171. package/src/ld/documentLoader.js +0 -25
  172. package/src/ld/documentLoader.spec.js +0 -12
  173. package/src/ld/getLinkedDataContext.js +0 -63
  174. package/src/ld/getLinkedDataType.js +0 -38
  175. /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
  176. /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
@@ -0,0 +1,378 @@
1
+ import getLinkedDataContext, { type PropertySchema } from '../getLinkedDataContext';
2
+
3
+ describe('getLinkedDataContext(properties, vocabUri)', () => {
4
+ describe('basic functionality', () => {
5
+ it('should create context with @vocab, @version, and @protected', () => {
6
+ const properties: Record<string, PropertySchema> = {};
7
+ const vocabUri = 'https://example.com/vocab';
8
+
9
+ const result = getLinkedDataContext(properties, vocabUri);
10
+
11
+ expect(result).toHaveProperty('@vocab', 'https://example.com/vocab#');
12
+ expect(result).toHaveProperty('@version', 1.1);
13
+ expect(result).toHaveProperty('@protected', true);
14
+ });
15
+
16
+ it('should include properties in context with @id', () => {
17
+ const properties: Record<string, PropertySchema> = {
18
+ name: { type: 'string' },
19
+ active: { type: 'boolean' },
20
+ };
21
+ const vocabUri = 'https://example.com/vocab';
22
+
23
+ const result = getLinkedDataContext(properties, vocabUri);
24
+
25
+ expect(result.name).toEqual({ '@id': 'name' });
26
+ expect(result.active).toEqual({ '@id': 'active' });
27
+ });
28
+ });
29
+
30
+ describe('protected properties', () => {
31
+ it('should skip id property', () => {
32
+ const properties: Record<string, PropertySchema> = {
33
+ id: { type: 'string' },
34
+ name: { type: 'string' },
35
+ };
36
+ const vocabUri = 'https://example.com/vocab';
37
+
38
+ const result = getLinkedDataContext(properties, vocabUri);
39
+
40
+ expect(result).not.toHaveProperty('id');
41
+ expect(result.name).toEqual({ '@id': 'name' });
42
+ });
43
+
44
+ it('should skip type property', () => {
45
+ const properties: Record<string, PropertySchema> = {
46
+ type: { type: 'string' },
47
+ name: { type: 'string' },
48
+ };
49
+ const vocabUri = 'https://example.com/vocab';
50
+
51
+ const result = getLinkedDataContext(properties, vocabUri);
52
+
53
+ expect(result).not.toHaveProperty('type');
54
+ expect(result.name).toEqual({ '@id': 'name' });
55
+ });
56
+
57
+ it('should skip schema property', () => {
58
+ const properties: Record<string, PropertySchema> = {
59
+ schema: { type: 'string' },
60
+ name: { type: 'string' },
61
+ };
62
+ // Use schema.org vocab so the header doesn't add a 'schema' property
63
+ const vocabUri = 'https://schema.org/';
64
+
65
+ const result = getLinkedDataContext(properties, vocabUri);
66
+
67
+ // The input 'schema' property should be skipped (it's protected)
68
+ expect(result).not.toHaveProperty('schema');
69
+ expect(result.name).toEqual({ '@id': 'name' });
70
+ });
71
+
72
+ it('should skip properties starting with @', () => {
73
+ const properties: Record<string, PropertySchema> = {
74
+ '@context': { type: 'string' },
75
+ '@type': { type: 'string' },
76
+ '@id': { type: 'string' },
77
+ name: { type: 'string' },
78
+ };
79
+ const vocabUri = 'https://example.com/vocab';
80
+
81
+ const result = getLinkedDataContext(properties, vocabUri);
82
+
83
+ expect(result).not.toHaveProperty('@context');
84
+ expect(result).not.toHaveProperty('@type');
85
+ expect(result).not.toHaveProperty('@id');
86
+ expect(result.name).toEqual({ '@id': 'name' });
87
+ });
88
+
89
+ it('should skip all protected properties together', () => {
90
+ const properties: Record<string, PropertySchema> = {
91
+ id: { type: 'string' },
92
+ type: { type: 'string' },
93
+ schema: { type: 'string' },
94
+ '@custom': { type: 'string' },
95
+ name: { type: 'string' },
96
+ };
97
+ // Use schema.org vocab so the header doesn't add a 'schema' property
98
+ const vocabUri = 'https://schema.org/';
99
+
100
+ const result = getLinkedDataContext(properties, vocabUri);
101
+
102
+ // All protected properties from input should be skipped
103
+ expect(result).not.toHaveProperty('id');
104
+ expect(result).not.toHaveProperty('type');
105
+ expect(result).not.toHaveProperty('schema');
106
+ expect(result).not.toHaveProperty('@custom');
107
+ expect(result.name).toEqual({ '@id': 'name' });
108
+ });
109
+ });
110
+
111
+ describe('properties with $ref', () => {
112
+ it('should include property with $ref but without @type', () => {
113
+ const properties: Record<string, PropertySchema> = {
114
+ reference: { $ref: '#/definitions/SomeType' },
115
+ };
116
+ const vocabUri = 'https://example.com/vocab';
117
+
118
+ const result = getLinkedDataContext(properties, vocabUri);
119
+
120
+ expect(result.reference).toEqual({ '@id': 'reference' });
121
+ expect(result.reference).not.toHaveProperty('@type');
122
+ });
123
+
124
+ it('should not add @type even if property has type and format', () => {
125
+ const properties: Record<string, PropertySchema> = {
126
+ reference: {
127
+ $ref: '#/definitions/SomeType',
128
+ type: 'integer',
129
+ format: 'int32',
130
+ },
131
+ };
132
+ const vocabUri = 'https://example.com/vocab';
133
+
134
+ const result = getLinkedDataContext(properties, vocabUri);
135
+
136
+ expect(result.reference).toEqual({ '@id': 'reference' });
137
+ expect(result.reference).not.toHaveProperty('@type');
138
+ });
139
+ });
140
+
141
+ describe('properties with linked data types', () => {
142
+ it('should add @type for integer type', () => {
143
+ const properties: Record<string, PropertySchema> = {
144
+ age: { type: 'integer' },
145
+ };
146
+ const vocabUri = 'https://example.com/vocab';
147
+
148
+ const result = getLinkedDataContext(properties, vocabUri);
149
+
150
+ expect(result.age).toEqual({
151
+ '@id': 'age',
152
+ '@type': 'schema:Integer',
153
+ });
154
+ });
155
+
156
+ it('should add @type for number type', () => {
157
+ const properties: Record<string, PropertySchema> = {
158
+ price: { type: 'number' },
159
+ };
160
+ const vocabUri = 'https://example.com/vocab';
161
+
162
+ const result = getLinkedDataContext(properties, vocabUri);
163
+
164
+ expect(result.price).toEqual({
165
+ '@id': 'price',
166
+ '@type': 'schema:Number',
167
+ });
168
+ });
169
+
170
+ it('should add @type for date format', () => {
171
+ const properties: Record<string, PropertySchema> = {
172
+ birthDate: { type: 'string', format: 'date' },
173
+ };
174
+ const vocabUri = 'https://example.com/vocab';
175
+
176
+ const result = getLinkedDataContext(properties, vocabUri);
177
+
178
+ expect(result.birthDate).toEqual({
179
+ '@id': 'birthDate',
180
+ '@type': 'schema:Date',
181
+ });
182
+ });
183
+
184
+ it('should add @type for date-time format', () => {
185
+ const properties: Record<string, PropertySchema> = {
186
+ createdAt: { type: 'string', format: 'date-time' },
187
+ };
188
+ const vocabUri = 'https://example.com/vocab';
189
+
190
+ const result = getLinkedDataContext(properties, vocabUri);
191
+
192
+ expect(result.createdAt).toEqual({
193
+ '@id': 'createdAt',
194
+ '@type': 'schema:DateTime',
195
+ });
196
+ });
197
+
198
+ it('should add @type when @type is explicitly defined', () => {
199
+ const properties: Record<string, PropertySchema> = {
200
+ custom: { '@type': 'schema:CustomType', type: 'string' },
201
+ };
202
+ const vocabUri = 'https://example.com/vocab';
203
+
204
+ const result = getLinkedDataContext(properties, vocabUri);
205
+
206
+ expect(result.custom).toEqual({
207
+ '@id': 'custom',
208
+ '@type': 'schema:CustomType',
209
+ });
210
+ });
211
+
212
+ it('should not add @type when getLinkedDataType returns undefined', () => {
213
+ const properties: Record<string, PropertySchema> = {
214
+ name: { type: 'string' },
215
+ active: { type: 'boolean' },
216
+ };
217
+ const vocabUri = 'https://example.com/vocab';
218
+
219
+ const result = getLinkedDataContext(properties, vocabUri);
220
+
221
+ expect(result.name).toEqual({ '@id': 'name' });
222
+ expect(result.name).not.toHaveProperty('@type');
223
+ expect(result.active).toEqual({ '@id': 'active' });
224
+ expect(result.active).not.toHaveProperty('@type');
225
+ });
226
+ });
227
+
228
+ describe('vocab URI handling', () => {
229
+ it('should append # to vocab URI that does not end with / or #', () => {
230
+ const properties: Record<string, PropertySchema> = {};
231
+ const vocabUri = 'https://example.com/vocab';
232
+
233
+ const result = getLinkedDataContext(properties, vocabUri);
234
+
235
+ expect(result['@vocab']).toBe('https://example.com/vocab#');
236
+ });
237
+
238
+ it('should not append # to vocab URI ending with /', () => {
239
+ const properties: Record<string, PropertySchema> = {};
240
+ const vocabUri = 'https://example.com/vocab/';
241
+
242
+ const result = getLinkedDataContext(properties, vocabUri);
243
+
244
+ expect(result['@vocab']).toBe('https://example.com/vocab/');
245
+ });
246
+
247
+ it('should not append # to vocab URI ending with #', () => {
248
+ const properties: Record<string, PropertySchema> = {};
249
+ const vocabUri = 'https://example.com/vocab#';
250
+
251
+ const result = getLinkedDataContext(properties, vocabUri);
252
+
253
+ expect(result['@vocab']).toBe('https://example.com/vocab#');
254
+ });
255
+ });
256
+
257
+ describe('schema.org domain handling', () => {
258
+ it('should not add schema property when vocab is schema.org', () => {
259
+ const properties: Record<string, PropertySchema> = {};
260
+ const vocabUri = 'https://schema.org/';
261
+
262
+ const result = getLinkedDataContext(properties, vocabUri);
263
+
264
+ expect(result).not.toHaveProperty('schema');
265
+ expect(result['@vocab']).toBe('https://schema.org/');
266
+ });
267
+
268
+ it('should add schema property when vocab is schema.org with # (exact match required)', () => {
269
+ const properties: Record<string, PropertySchema> = {};
270
+ const vocabUri = 'https://schema.org/#';
271
+
272
+ const result = getLinkedDataContext(properties, vocabUri);
273
+
274
+ // Note: The implementation checks if vocab === 'https://schema.org/' exactly,
275
+ // so 'https://schema.org/#' will not match and schema will be added
276
+ // This tests the actual behavior of the implementation
277
+ expect(result).toHaveProperty('schema', 'https://schema.org/');
278
+ expect(result['@vocab']).toBe('https://schema.org/#');
279
+ });
280
+
281
+ it('should add schema property when vocab is not schema.org', () => {
282
+ const properties: Record<string, PropertySchema> = {};
283
+ const vocabUri = 'https://example.com/vocab';
284
+
285
+ const result = getLinkedDataContext(properties, vocabUri);
286
+
287
+ expect(result).toHaveProperty('schema', 'https://schema.org/');
288
+ });
289
+
290
+ it('should add schema property for custom vocab URI', () => {
291
+ const properties: Record<string, PropertySchema> = {};
292
+ const vocabUri = 'https://custom-domain.com/ontology';
293
+
294
+ const result = getLinkedDataContext(properties, vocabUri);
295
+
296
+ expect(result).toHaveProperty('schema', 'https://schema.org/');
297
+ });
298
+ });
299
+
300
+ describe('mixed scenarios', () => {
301
+ it('should handle complex properties object with various types', () => {
302
+ const properties: Record<string, PropertySchema> = {
303
+ id: { type: 'string' }, // protected
304
+ type: { type: 'string' }, // protected
305
+ name: { type: 'string' }, // no @type
306
+ age: { type: 'integer' }, // schema:Integer
307
+ price: { type: 'number' }, // schema:Number
308
+ birthDate: { type: 'string', format: 'date' }, // schema:Date
309
+ createdAt: { type: 'string', format: 'date-time' }, // schema:DateTime
310
+ reference: { $ref: '#/definitions/Person' }, // $ref, no @type
311
+ custom: { '@type': 'schema:CustomType' }, // explicit @type
312
+ active: { type: 'boolean' }, // no @type
313
+ '@context': { type: 'string' }, // protected
314
+ };
315
+ const vocabUri = 'https://example.com/vocab';
316
+
317
+ const result = getLinkedDataContext(properties, vocabUri);
318
+
319
+ // Check header
320
+ expect(result['@vocab']).toBe('https://example.com/vocab#');
321
+ expect(result['@version']).toBe(1.1);
322
+ expect(result['@protected']).toBe(true);
323
+ expect(result.schema).toBe('https://schema.org/');
324
+
325
+ // Check protected properties are skipped
326
+ expect(result).not.toHaveProperty('id');
327
+ expect(result).not.toHaveProperty('type');
328
+ expect(result).not.toHaveProperty('@context');
329
+
330
+ // Check properties without @type
331
+ expect(result.name).toEqual({ '@id': 'name' });
332
+ expect(result.active).toEqual({ '@id': 'active' });
333
+
334
+ // Check properties with @type
335
+ expect(result.age).toEqual({ '@id': 'age', '@type': 'schema:Integer' });
336
+ expect(result.price).toEqual({ '@id': 'price', '@type': 'schema:Number' });
337
+ expect(result.birthDate).toEqual({ '@id': 'birthDate', '@type': 'schema:Date' });
338
+ expect(result.createdAt).toEqual({ '@id': 'createdAt', '@type': 'schema:DateTime' });
339
+ expect(result.custom).toEqual({ '@id': 'custom', '@type': 'schema:CustomType' });
340
+
341
+ // Check property with $ref
342
+ expect(result.reference).toEqual({ '@id': 'reference' });
343
+ });
344
+
345
+ it('should handle empty properties object', () => {
346
+ const properties: Record<string, PropertySchema> = {};
347
+ const vocabUri = 'https://example.com/vocab';
348
+
349
+ const result = getLinkedDataContext(properties, vocabUri);
350
+
351
+ expect(result).toEqual({
352
+ '@vocab': 'https://example.com/vocab#',
353
+ '@version': 1.1,
354
+ '@protected': true,
355
+ schema: 'https://schema.org/',
356
+ });
357
+ });
358
+
359
+ it('should handle properties object with only protected properties', () => {
360
+ const properties: Record<string, PropertySchema> = {
361
+ id: { type: 'string' },
362
+ type: { type: 'string' },
363
+ schema: { type: 'string' },
364
+ '@context': { type: 'string' },
365
+ };
366
+ const vocabUri = 'https://example.com/vocab';
367
+
368
+ const result = getLinkedDataContext(properties, vocabUri);
369
+
370
+ expect(result).toEqual({
371
+ '@vocab': 'https://example.com/vocab#',
372
+ '@version': 1.1,
373
+ '@protected': true,
374
+ schema: 'https://schema.org/',
375
+ });
376
+ });
377
+ });
378
+ });
@@ -0,0 +1,28 @@
1
+ import { contexts as securityContextsMap } from 'security-context';
2
+ import { contexts as credentialsContextsMap } from 'credentials-context';
3
+
4
+ const CONTEXTS = new Map([
5
+ ...securityContextsMap,
6
+ ...credentialsContextsMap
7
+ ]);
8
+
9
+ /** Returns document from the context via url */
10
+ const documentLoader = (documentUrl: string) => {
11
+ const contextUrl = null;
12
+
13
+ const [ url ] = documentUrl.split('#');
14
+
15
+ const document = CONTEXTS.get(url);
16
+
17
+ if (!document) {
18
+ throw new Error(`Custom context "${documentUrl}" is not supported`);
19
+ }
20
+
21
+ return {
22
+ document,
23
+ contextUrl,
24
+ documentUrl
25
+ };
26
+ };
27
+
28
+ export default documentLoader;
@@ -0,0 +1,46 @@
1
+ import { get } from 'lodash';
2
+
3
+ export type PropertySchema = {
4
+ '@type'?: string;
5
+ $ref?: string;
6
+ type?: string;
7
+ format?: string;
8
+ };
9
+
10
+ /** Returns linked data attribute type for a property schema, unless @type is defined */
11
+ const getLinkedDataAttributeType = (propertySchema: PropertySchema): string | undefined => {
12
+ const isOverriden = !!get(propertySchema, '@type');
13
+
14
+ if (isOverriden) {
15
+ return get(propertySchema, '@type');
16
+ }
17
+
18
+ // TODO: Add support for all types and formats, extend schema library with
19
+ // support for additional formats, e.g. URL, Duration etc.
20
+ const { type, format } = propertySchema;
21
+
22
+ const isDate = format === 'date';
23
+ const isNumber = type === 'number';
24
+ const isInteger = type === 'integer';
25
+ const isDateTime = format === 'date-time';
26
+
27
+ if (isInteger) {
28
+ return 'schema:Integer';
29
+ }
30
+
31
+ if (isNumber) {
32
+ return 'schema:Number';
33
+ }
34
+
35
+ if (isDate) {
36
+ return 'schema:Date';
37
+ }
38
+
39
+ if (isDateTime) {
40
+ return 'schema:DateTime';
41
+ }
42
+
43
+ return;
44
+ };
45
+
46
+ export default getLinkedDataAttributeType;
@@ -0,0 +1,80 @@
1
+ import getLinkedDataAttributeType, { type PropertySchema } from './getLinkedDataAttributeType';
2
+
3
+ export type { PropertySchema };
4
+
5
+ const SCHEMA_ORG_URI = 'https://schema.org/';
6
+
7
+ const PROTECTED_PROPERTIES = [
8
+ 'id',
9
+ 'type',
10
+ 'schema'
11
+ ];
12
+
13
+ type ContextProperty = {
14
+ '@id': string;
15
+ '@type'?: string;
16
+ };
17
+
18
+ type ContextHeader = {
19
+ '@vocab': string;
20
+ '@version': number;
21
+ '@protected': boolean;
22
+ schema?: string;
23
+ };
24
+
25
+ export type LinkedDataContext = ContextHeader & {
26
+ [key: string]: ContextProperty | string | number | boolean | undefined;
27
+ };
28
+
29
+ /** Returns linked data context for a properties object */
30
+ const getLinkedDataContext = (properties: Record<string, PropertySchema>, vocabUri: string): LinkedDataContext => {
31
+ const context = {} as Record<string, ContextProperty>;
32
+
33
+ // TODO: Add support for embedded object, array and enum.
34
+ for (const key in properties) {
35
+ const propertySchema = properties[key];
36
+
37
+ const isProtected =
38
+ PROTECTED_PROPERTIES.includes(key) ||
39
+ key.startsWith('@');
40
+
41
+ if (isProtected) {
42
+ continue;
43
+ }
44
+
45
+ context[key] = { '@id': key };
46
+
47
+ const { $ref } = propertySchema;
48
+
49
+ if ($ref) {
50
+ continue;
51
+ }
52
+
53
+ const linkedDataType = getLinkedDataAttributeType(propertySchema);
54
+
55
+ if (linkedDataType) {
56
+ context[key]['@type'] = linkedDataType;
57
+ }
58
+ }
59
+
60
+ const vocab = vocabUri.endsWith('/') || vocabUri.endsWith('#') ? vocabUri : `${vocabUri}#`;
61
+
62
+ const contextHeader = {
63
+ '@vocab': vocab,
64
+ '@version': 1.1,
65
+ '@protected': true
66
+ } as ContextHeader;
67
+
68
+ const isSchemaOrgDomain = vocab === SCHEMA_ORG_URI;
69
+
70
+ if (!isSchemaOrgDomain) {
71
+ contextHeader.schema = SCHEMA_ORG_URI;
72
+ }
73
+
74
+ return {
75
+ ...contextHeader,
76
+ ...context
77
+ };
78
+ };
79
+
80
+ export default getLinkedDataContext;
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./dist",
5
+ "target": "esnext",
6
+ "module": "nodenext",
7
+ "isolatedModules": true,
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "strictNullChecks": true,
11
+ "noUnusedLocals": true,
12
+ "noUnusedParameters": true,
13
+ "skipLibCheck": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": [
19
+ "src",
20
+ "types"
21
+ ],
22
+ "exclude": [
23
+ "node_modules",
24
+ "dist",
25
+ "**/*.test.ts"
26
+ ]
27
+ }
@@ -0,0 +1,14 @@
1
+ declare module 'credentials-context' {
2
+ /**
3
+ * Map of credentials context URLs to their JSON-LD context documents
4
+ */
5
+ export const contexts: Map<string, Record<string, unknown>>;
6
+
7
+ /**
8
+ * Constants for credentials context
9
+ */
10
+ export const constants: {
11
+ CREDENTIALS_CONTEXT_V1_URL: string;
12
+ [key: string]: string;
13
+ };
14
+ }
@@ -0,0 +1,6 @@
1
+ declare module 'security-context' {
2
+ /**
3
+ * Map of security context URLs to their JSON-LD context documents
4
+ */
5
+ export const contexts: Map<string, Record<string, unknown>>;
6
+ }
@@ -1,3 +0,0 @@
1
- enum:
2
- - 'Pending'
3
- - 'Active'
@@ -1,27 +0,0 @@
1
- 'use strict'
2
-
3
- const { Schema, CredentialFactory } = require('../src')
4
-
5
- const accountSchema = new Schema({
6
- id: {},
7
- username: { required: true },
8
- createdAt: { format: 'date-time', required: true },
9
- dateOfBirth: { format: 'date' }
10
- }, 'Account')
11
-
12
- const factory = new CredentialFactory('https://example.com/schema/AccountV1', accountSchema)
13
-
14
- const createAccountCredential = (holder, username) => {
15
- const id = `https://example.com/account/${username}`
16
-
17
- const createdAt = new Date().toISOString()
18
- const subject = {
19
- id: holder,
20
- username,
21
- createdAt
22
- }
23
-
24
- return factory.createCredential(id, holder, subject)
25
- }
26
-
27
- module.exports = createAccountCredential
@@ -1,63 +0,0 @@
1
- 'use strict'
2
-
3
- const { Schema, CredentialFactory } = require('../src')
4
-
5
- const GAME_NAME = 'MineSweeper'
6
- const GAME_VERSION = '1.0'
7
-
8
- const SCHEMA_ORG_URI = 'https://schema.org/'
9
-
10
- const playerSchema = new Schema({
11
- id: {},
12
- hasVideoGameScore: { $ref: 'VideoGameScore', required: true }
13
- }, 'Player')
14
-
15
- const videoGameSchema = new Schema({
16
- id: {},
17
- name: { type: 'string', required: true },
18
- version: { type: 'string', required: true }
19
- }, 'VideoGame', SCHEMA_ORG_URI)
20
-
21
- const videoGameScoreSchema = new Schema({
22
- game: { $ref: 'VideoGame', required: true },
23
- wins: { type: 'integer', required: true },
24
- losses: { type: 'integer', required: true },
25
- winRate: { type: 'number', required: true },
26
- bestScore: { type: 'integer', required: true },
27
- // TODO: Add duration as format:
28
- endurance: { type: 'string', required: true, '@type': 'schema:Duration' },
29
- dateCreated: { type: 'string', 'format': 'date-time', required: true },
30
- bestRoundTime: { type: 'integer', required: true }
31
- // difficultyLevel:
32
- // enum:
33
- // - EASY
34
- // - MEDIUM
35
- // - HARD
36
- // required: true
37
- }, 'VideoGameScore')
38
-
39
- const CREDENTIAL_URI = `https://example.com/schema/${GAME_NAME}ScoreV1`
40
- const SCHEMAS = [ playerSchema, videoGameSchema, videoGameScoreSchema ]
41
- const factory = new CredentialFactory(CREDENTIAL_URI, SCHEMAS)
42
-
43
- const createMineSweeperScoreCredential = (gameId, playerId, playerScore) => {
44
- const credentialId = 'https://example.com/credentials/CREDENTIAL_ID'
45
-
46
- const game = {
47
- id: gameId,
48
- name: GAME_NAME,
49
- version: GAME_VERSION
50
- }
51
-
52
- const player = {
53
- id: playerId,
54
- hasVideoGameScore: {
55
- ...playerScore,
56
- game
57
- }
58
- }
59
-
60
- return factory.createCredential(credentialId, playerId, player)
61
- }
62
-
63
- module.exports = createMineSweeperScoreCredential
package/examples/index.js DELETED
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- const createAccountCredential = require('./createAccountCredential')
4
- const createMineSweeperScoreCredential = require('./createMineSweeperScoreCredential')
5
-
6
- module.exports = {
7
- createAccountCredential,
8
- createMineSweeperScoreCredential
9
- }