@pdtf/schemas 3.4.1-7 → 3.5.0

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.
@@ -9,7 +9,18 @@
9
9
  "Bash(grep:*)",
10
10
  "Bash(rm:*)",
11
11
  "Bash(npm test:*)",
12
- "Bash(npm install:*)"
12
+ "Bash(npm install:*)",
13
+ "Bash(git merge:*)",
14
+ "Bash(git add:*)",
15
+ "Bash(git commit:*)",
16
+ "Bash(git push:*)",
17
+ "Bash(git fetch:*)",
18
+ "Bash(git checkout:*)",
19
+ "Bash(gh pr view:*)",
20
+ "WebFetch(domain:github.com)",
21
+ "Bash(tree:*)",
22
+ "Bash(sed:*)",
23
+ "Bash(find:*)"
13
24
  ],
14
25
  "deny": []
15
26
  }
package/index.js CHANGED
@@ -10,6 +10,17 @@ const ajv = new Ajv({
10
10
  // Adds date formats among other types to the validator.
11
11
  addFormats(ajv);
12
12
 
13
+ // Enhanced caching structure for subschemas and validators
14
+ const schemaCache = new Map();
15
+
16
+ // Cache performance tracking
17
+ const cacheStats = {
18
+ hits: 0,
19
+ misses: 0,
20
+ totalQueries: 0,
21
+ cacheStartTime: Date.now()
22
+ };
23
+
13
24
  const verifiedClaimsSchema = require("./src/schemas/verifiedClaims/pdtf-verified-claims.json");
14
25
  const v2CoreSchema = require("./src/schemas/v2/pdtf-transaction.json");
15
26
  const v3CoreSchema = require("./src/schemas/v3/pdtf-transaction.json");
@@ -180,32 +191,79 @@ const generateOverlayKey = (overlays) => {
180
191
  return overlays.join(".");
181
192
  };
182
193
 
194
+ // Enhanced caching function that stores both subschemas and validators
195
+ const getCachedSchemaData = (path, schemaId, overlays) => {
196
+ const overlayKey = generateOverlayKey(overlays);
197
+ const cacheKey = `${path}-${schemaId}-${overlayKey}`;
198
+
199
+ cacheStats.totalQueries++;
200
+ let cached = schemaCache.get(cacheKey);
201
+ if (!cached) {
202
+ cacheStats.misses++;
203
+ // Compute subschema using the original logic
204
+ const sourceSchema = getTransactionSchema(schemaId, overlays);
205
+ const pathArray = path.split("/").slice(1);
206
+ let subSchema = sourceSchema;
207
+
208
+ if (pathArray.length >= 1) {
209
+ subSchema = pathArray.reduce((schema, pathElement) => {
210
+ if (!schema) return undefined;
211
+ const { type, items, properties, oneOf } = schema;
212
+ if (type === "array") return items;
213
+ if (properties?.[pathElement]) return properties[pathElement];
214
+ if (oneOf) {
215
+ let matchingProperty;
216
+ oneOf.forEach((aOneOf) => {
217
+ if (aOneOf.type === "array" && !Number.isNaN(pathElement)) {
218
+ matchingProperty = aOneOf.items;
219
+ } else if (aOneOf.properties?.[pathElement]) {
220
+ matchingProperty = aOneOf.properties?.[pathElement];
221
+ }
222
+ });
223
+ if (matchingProperty) return matchingProperty;
224
+ }
225
+ return undefined;
226
+ }, sourceSchema);
227
+ }
228
+
229
+ // Add schema to AJV and get validator
230
+ // Only add valid schemas to AJV
231
+ let validator;
232
+ if (subSchema && typeof subSchema === 'object') {
233
+ // Check if schema already exists in AJV to avoid duplicates
234
+ validator = ajv.getSchema(cacheKey);
235
+ if (!validator) {
236
+ ajv.addSchema(subSchema, cacheKey);
237
+ validator = ajv.getSchema(cacheKey);
238
+ }
239
+ } else {
240
+ // For invalid paths, create a validator that always fails
241
+ validator = () => false;
242
+ validator.errors = [`Invalid path: schema is ${subSchema}`];
243
+ }
244
+
245
+ cached = {
246
+ subSchema,
247
+ validator,
248
+ cacheKey,
249
+ createdAt: Date.now()
250
+ };
251
+ schemaCache.set(cacheKey, cached);
252
+ } else {
253
+ cacheStats.hits++;
254
+ }
255
+
256
+ return cached;
257
+ };
258
+
183
259
  const getValidator = (schemaId, overlays) => {
184
260
  return getSubschemaValidator("", schemaId, overlays);
185
261
  };
186
262
 
187
- // common functions for v1 and v2
263
+ // common functions for v1 and v2 - now with caching
188
264
  const getSubschema = (path, schemaId, overlays) => {
189
- const sourceSchema = getTransactionSchema(schemaId, overlays);
190
- const pathArray = path.split("/").slice(1);
191
- if (pathArray.length < 1) return sourceSchema;
192
- return pathArray.reduce((schema, pathElement) => {
193
- const { type, items, properties, oneOf } = schema;
194
- if (type === "array") return items;
195
- if (properties?.[pathElement]) return properties[pathElement];
196
- if (oneOf) {
197
- let matchingProperty;
198
- oneOf.forEach((aOneOf) => {
199
- if (aOneOf.type === "array" && !Number.isNaN(pathElement)) {
200
- matchingProperty = aOneOf.items;
201
- } else if (aOneOf.properties?.[pathElement]) {
202
- matchingProperty = aOneOf.properties?.[pathElement];
203
- }
204
- });
205
- if (matchingProperty) return matchingProperty;
206
- }
207
- return undefined;
208
- }, sourceSchema);
265
+ const cached = getCachedSchemaData(path, schemaId, overlays);
266
+ return cached.subSchema;
209
267
  };
210
268
 
211
269
  const isPathValid = (path, schemaId, overlays) => {
@@ -217,18 +275,8 @@ const isPathValid = (path, schemaId, overlays) => {
217
275
  };
218
276
 
219
277
  const getSubschemaValidator = (path, schemaId, overlays) => {
220
- const subSchema = getSubschema(path, schemaId, overlays);
221
- const overlayKey = generateOverlayKey(overlays);
222
- // see if we can retrieve the schema by path, schemaId and overlays
223
- const cacheKey = `${path}-${schemaId}-${overlayKey}`;
224
- let validator = ajv.getSchema(cacheKey);
225
- // retrieve whole schema by $id if available
226
- if (!validator && subSchema.$id) validator = ajv.getSchema(cacheKey);
227
- if (!validator) {
228
- ajv.addSchema(subSchema, cacheKey);
229
- validator = ajv.getSchema(cacheKey);
230
- }
231
- return validator;
278
+ const cached = getCachedSchemaData(path, schemaId, overlays);
279
+ return cached.validator;
232
280
  };
233
281
 
234
282
  // v1, deprecated
@@ -312,6 +360,181 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
312
360
  return validationErrorsArr;
313
361
  };
314
362
 
363
+ // Cache management functions
364
+ const getCacheStats = () => {
365
+ const runtime = Date.now() - cacheStats.cacheStartTime;
366
+ const hitRate = cacheStats.totalQueries > 0 ? (cacheStats.hits / cacheStats.totalQueries * 100) : 0;
367
+
368
+ return {
369
+ totalEntries: schemaCache.size,
370
+ hits: cacheStats.hits,
371
+ misses: cacheStats.misses,
372
+ totalQueries: cacheStats.totalQueries,
373
+ hitRate: parseFloat(hitRate.toFixed(2)),
374
+ runtimeMs: runtime,
375
+ cacheKeys: Array.from(schemaCache.keys()),
376
+ memoryUsage: {
377
+ entriesCount: schemaCache.size,
378
+ // Rough estimate of memory usage per entry
379
+ estimatedSizeKB: Math.round((schemaCache.size * 2) / 1024 * 100) / 100 // Rough estimate
380
+ }
381
+ };
382
+ };
383
+
384
+ const getDetailedCacheStats = () => {
385
+ const baseStats = getCacheStats();
386
+ const entries = Array.from(schemaCache.entries()).map(([key, value]) => ({
387
+ key,
388
+ createdAt: value.createdAt,
389
+ age: Date.now() - value.createdAt,
390
+ hasValidator: typeof value.validator === 'function',
391
+ hasSubSchema: value.subSchema !== undefined
392
+ }));
393
+
394
+ return {
395
+ ...baseStats,
396
+ entries: entries.sort((a, b) => b.createdAt - a.createdAt), // Most recent first
397
+ oldestEntry: entries.length > 0 ? Math.max(...entries.map(e => e.age)) : 0,
398
+ newestEntry: entries.length > 0 ? Math.min(...entries.map(e => e.age)) : 0
399
+ };
400
+ };
401
+
402
+ const clearSchemaCache = (pattern) => {
403
+ if (pattern) {
404
+ // Clear entries matching pattern
405
+ const keysToDelete = [];
406
+ schemaCache.forEach((value, key) => {
407
+ if (key.includes(pattern)) {
408
+ keysToDelete.push(key);
409
+ }
410
+ });
411
+ keysToDelete.forEach(key => schemaCache.delete(key));
412
+
413
+ // Also remove matching schemas from AJV
414
+ keysToDelete.forEach(key => {
415
+ try {
416
+ ajv.removeSchema(key);
417
+ } catch (e) {
418
+ // Schema might not exist in AJV, ignore
419
+ }
420
+ });
421
+
422
+ return keysToDelete.length;
423
+ } else {
424
+ // Clear all cache
425
+ const entriesCleared = schemaCache.size;
426
+ schemaCache.clear();
427
+ // Reset stats
428
+ cacheStats.hits = 0;
429
+ cacheStats.misses = 0;
430
+ cacheStats.totalQueries = 0;
431
+ cacheStats.cacheStartTime = Date.now();
432
+
433
+ // Also clear AJV's internal cache to prevent duplicates
434
+ ajv.removeSchema();
435
+
436
+ return entriesCleared;
437
+ }
438
+ };
439
+
440
+ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json", overlaysList = []) => {
441
+ const warmupStats = {
442
+ totalAttempted: 0,
443
+ successful: 0,
444
+ failed: 0,
445
+ errors: []
446
+ };
447
+
448
+ // Default common paths if none provided
449
+ const defaultPaths = [
450
+ "/propertyPack",
451
+ "/participants",
452
+ "/status",
453
+ "/propertyPack/surveys",
454
+ "/propertyPack/valuations",
455
+ "/propertyPack/waterAndDrainage",
456
+ "/propertyPack/ownership",
457
+ "/propertyPack/notices"
458
+ ];
459
+
460
+ // Default common overlays if none provided
461
+ const defaultOverlays = [
462
+ [],
463
+ ["baspiV5"],
464
+ ["ta6ed4"],
465
+ ["nts2023"],
466
+ ["baspiV5", "ta6ed4"]
467
+ ];
468
+
469
+ const pathsToWarm = paths || defaultPaths;
470
+ const overlaysToWarm = overlaysList.length > 0 ? overlaysList : defaultOverlays;
471
+
472
+ pathsToWarm.forEach(path => {
473
+ overlaysToWarm.forEach(overlays => {
474
+ warmupStats.totalAttempted++;
475
+ try {
476
+ // This will populate the cache
477
+ getSubschema(path, schemaId, overlays);
478
+ getSubschemaValidator(path, schemaId, overlays);
479
+ warmupStats.successful++;
480
+ } catch (error) {
481
+ warmupStats.failed++;
482
+ warmupStats.errors.push({
483
+ path,
484
+ overlays,
485
+ error: error.message
486
+ });
487
+ }
488
+ });
489
+ });
490
+
491
+ return warmupStats;
492
+ };
493
+
494
+ const pruneCacheByAge = (maxAgeMs) => {
495
+ const now = Date.now();
496
+ const keysToDelete = [];
497
+
498
+ schemaCache.forEach((value, key) => {
499
+ if (now - value.createdAt > maxAgeMs) {
500
+ keysToDelete.push(key);
501
+ }
502
+ });
503
+
504
+ keysToDelete.forEach(key => {
505
+ schemaCache.delete(key);
506
+ try {
507
+ ajv.removeSchema(key);
508
+ } catch (e) {
509
+ // Schema might not exist in AJV, ignore
510
+ }
511
+ });
512
+
513
+ return keysToDelete.length;
514
+ };
515
+
516
+ const setCacheMaxSize = (maxSize) => {
517
+ if (schemaCache.size <= maxSize) return 0;
518
+
519
+ // Get entries sorted by creation time (oldest first)
520
+ const entries = Array.from(schemaCache.entries())
521
+ .sort((a, b) => a[1].createdAt - b[1].createdAt);
522
+
523
+ const toRemove = schemaCache.size - maxSize;
524
+ const keysToDelete = entries.slice(0, toRemove).map(([key]) => key);
525
+
526
+ keysToDelete.forEach(key => {
527
+ schemaCache.delete(key);
528
+ try {
529
+ ajv.removeSchema(key);
530
+ } catch (e) {
531
+ // Schema might not exist in AJV, ignore
532
+ }
533
+ });
534
+
535
+ return keysToDelete.length;
536
+ };
537
+
315
538
  module.exports = {
316
539
  ajv,
317
540
  getTransactionSchema,
@@ -324,4 +547,11 @@ module.exports = {
324
547
  validateVerifiedClaims,
325
548
  overlaysMap,
326
549
  extensionOverlays,
550
+ // Enhanced cache management functions
551
+ getCacheStats,
552
+ getDetailedCacheStats,
553
+ clearSchemaCache,
554
+ warmupCache,
555
+ pruneCacheByAge,
556
+ setCacheMaxSize,
327
557
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdtf/schemas",
3
- "version": "3.4.1-7",
3
+ "version": "3.5.0",
4
4
  "description": "Property Data Trust Framework Schemas and Utilities",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -29,6 +29,7 @@
29
29
  "ajv": "^8.12.0",
30
30
  "ajv-formats": "^2.1.1",
31
31
  "deepmerge": "^4.3.1",
32
+ "gpt-3-encoder": "^1.1.4",
32
33
  "jsonpointer": "^5.0.0",
33
34
  "traverse": "^0.6.7"
34
35
  },
@@ -2,6 +2,7 @@
2
2
  "id": "ADC58DEdisuWkmjp9MEJr9",
3
3
  "transactionId": "LnRdGiBUkLbnuNJ89p4CEj",
4
4
  "schemaVersion": "3.2.1",
5
+ "timestamp": 1643116604527,
5
6
  "verification": {
6
7
  "trust_framework": "uk_pdtf",
7
8
  "time": "2022-01-25T13:16:44.527Z",
@@ -2,6 +2,7 @@
2
2
  "id": "DsSaQjKeXTB2qvKkgSfJkh",
3
3
  "transactionId": "LnRdGiBUkLbnuNJ89p4CEj",
4
4
  "schemaVersion": "3.2.1",
5
+ "timestamp": 1643115903108,
5
6
  "verification": {
6
7
  "trust_framework": "uk_pdtf",
7
8
  "time": "2022-01-25T13:05:03.108Z",