yarramate 1.15.1 → 1.15.2

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.
@@ -1,5 +1,6 @@
1
1
  import Ajv2020Module from 'ajv/dist/2020.js';
2
2
  import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
3
+ import { lazyValidator } from './schema-validation.js';
3
4
  import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.json' with { type: 'json'
4
5
  };
5
6
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
@@ -8,10 +9,10 @@ import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.jso
8
9
  // to keep track of (#252).
9
10
  const ajv2020Module = Ajv2020Module;
10
11
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
11
- const validateSchema = new Ajv2020({ allErrors: true }).compile(adapterMappingSchema);
12
+ const validateSchema = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(adapterMappingSchema));
12
13
  const mappingLocations = new WeakMap();
13
14
  export function loadAdapterMapping(source) {
14
- const loaded = loadSourceDocument(source, validateSchema, 'Adapter mapping');
15
+ const loaded = loadSourceDocument(source, validateSchema(), 'Adapter mapping');
15
16
  if (!loaded.ok)
16
17
  return loaded;
17
18
  const { value, yaml, lineCounter } = loaded.document;
@@ -22,6 +22,7 @@ export const posixDirectoryOf = (path) => {
22
22
  };
23
23
  import operationsSchema from '../schema/yarramate-operations.schema.json' with { type: 'json'
24
24
  };
25
+ import { lazyValidator } from './schema-validation.js';
25
26
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
26
27
  // `module.exports` and a bundler sees the unwrapped class, and this file is
27
28
  // reachable from a browser through `./apply-operations.js` (#252).
@@ -29,10 +30,12 @@ const ajv2020Module = Ajv2020Module;
29
30
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
30
31
  // `discriminator` routes a batch entry to the single branch its `op` names, so
31
32
  // one malformed operation reports one fault instead of ten near-misses.
32
- const validateOperations = new Ajv2020({
33
+ // Keeps its own Ajv instance: `discriminator` changes how a schema compiles,
34
+ // so it cannot share one with the nine that do not set it.
35
+ const validateOperations = lazyValidator(() => new Ajv2020({
33
36
  allErrors: true,
34
37
  discriminator: true,
35
- }).compile(operationsSchema);
38
+ }).compile(operationsSchema));
36
39
  // Scalar fields replace; list fields append; `remove` retracts (ADR 0062).
37
40
  // An answer enriches what is there and may explicitly take back what it
38
41
  // asserted — it never silently shrinks anything.
@@ -310,7 +313,7 @@ export const applyOperations = (input) => {
310
313
  ok: false,
311
314
  diagnostics,
312
315
  });
313
- const loadedOperations = loadSourceDocument(operations, validateOperations, 'Operations');
316
+ const loadedOperations = loadSourceDocument(operations, validateOperations(), 'Operations');
314
317
  if (!loadedOperations.ok)
315
318
  return failed(loadedOperations.diagnostics);
316
319
  const operationList = loadedOperations.document.value.operations;
package/dist/compiler.js CHANGED
@@ -11,12 +11,13 @@ import patternSchema from '../schema/yarramate-pattern.schema.json' with { type:
11
11
  };
12
12
  import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-claims.js';
13
13
  import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
14
+ import { lazyValidator } from './schema-validation.js';
14
15
  const coreProfile = 'yarramate/core@0.1';
15
16
  const ajv2020Module = Ajv2020Import;
16
17
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
17
- const validateDocument = new Ajv2020({ allErrors: true }).compile(documentSchema);
18
- const validateProfile = new Ajv2020({ allErrors: true }).compile(profileSchema);
19
- const validatePattern = new Ajv2020({ allErrors: true }).compile(patternSchema);
18
+ const validateDocument = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(documentSchema));
19
+ const validateProfile = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(profileSchema));
20
+ const validatePattern = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(patternSchema));
20
21
  const immutableMap = (entries) => {
21
22
  const backing = new Map(entries);
22
23
  const facade = {
@@ -160,12 +161,12 @@ const parseWorkspaceSource = (input) => {
160
161
  fresh,
161
162
  };
162
163
  }
163
- const valid = parseDiagnostics.length === 0 && validateDocument(value);
164
+ const valid = parseDiagnostics.length === 0 && validateDocument()(value);
164
165
  const schemaDiagnostics = parseDiagnostics.length > 0
165
166
  ? parseDiagnostics
166
167
  : valid
167
168
  ? []
168
- : (validateDocument.errors ?? []).map((error) => {
169
+ : (validateDocument().errors ?? []).map((error) => {
169
170
  const property = error.keyword === 'additionalProperties'
170
171
  ? String(error.params.additionalProperty)
171
172
  : undefined;
@@ -330,8 +331,8 @@ function compileWorkspaceResolved(parsed) {
330
331
  profileDiagnostics.push(...entry.schemaDiagnostics);
331
332
  continue;
332
333
  }
333
- if (!validateProfile(value)) {
334
- for (const error of validateProfile.errors ?? []) {
334
+ if (!validateProfile()(value)) {
335
+ for (const error of validateProfile().errors ?? []) {
335
336
  const property = error.keyword === 'additionalProperties'
336
337
  ? String(error.params.additionalProperty)
337
338
  : undefined;
@@ -390,8 +391,8 @@ function compileWorkspaceResolved(parsed) {
390
391
  if (entry.schemaDiagnostics.length > 0) {
391
392
  profileDiagnostics.push(...entry.schemaDiagnostics);
392
393
  }
393
- else if (!validateProfile(value)) {
394
- for (const error of validateProfile.errors ?? []) {
394
+ else if (!validateProfile()(value)) {
395
+ for (const error of validateProfile().errors ?? []) {
395
396
  profileDiagnostics.push({
396
397
  severity: 'error',
397
398
  code: 'YM201',
@@ -640,8 +641,8 @@ function compileWorkspaceResolved(parsed) {
640
641
  patternDiagnostics.push(...entry.schemaDiagnostics);
641
642
  continue;
642
643
  }
643
- if (!validatePattern(value)) {
644
- for (const error of validatePattern.errors ?? []) {
644
+ if (!validatePattern()(value)) {
645
+ for (const error of validatePattern().errors ?? []) {
645
646
  const property = error.keyword === 'additionalProperties'
646
647
  ? String(error.params.additionalProperty)
647
648
  : undefined;
@@ -1,6 +1,7 @@
1
1
  import Ajv2020Module from 'ajv/dist/2020.js';
2
2
  import { LineCounter, parseDocument } from 'yaml';
3
3
  import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
4
+ import { lazyValidator } from './schema-validation.js';
4
5
  import coreContractSchema from '../schema/yarramate-core-contract.schema.json' with { type: 'json'
5
6
  };
6
7
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
@@ -9,9 +10,9 @@ import coreContractSchema from '../schema/yarramate-core-contract.schema.json' w
9
10
  // to keep track of (#252).
10
11
  const ajv2020Module = Ajv2020Module;
11
12
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
12
- const validateCoreContract = new Ajv2020({ allErrors: true }).compile(coreContractSchema);
13
+ const validateCoreContract = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(coreContractSchema));
13
14
  export function loadCoreContract(source) {
14
- const loaded = loadSourceDocument(source, validateCoreContract, 'Core contract');
15
+ const loaded = loadSourceDocument(source, validateCoreContract(), 'Core contract');
15
16
  if (!loaded.ok)
16
17
  return loaded;
17
18
  const { value, yaml, lineCounter } = loaded.document;
package/dist/evidence.js CHANGED
@@ -8,11 +8,12 @@ import evidenceSchema from '../schema/yarramate-evidence.schema.json' with { typ
8
8
  // to keep track of (#252).
9
9
  const ajv2020Module = Ajv2020Module;
10
10
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
11
- const validateEvidenceSchema = new Ajv2020({ allErrors: true }).compile(evidenceSchema);
11
+ import { lazyValidator } from './schema-validation.js';
12
+ const validateEvidenceSchema = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(evidenceSchema));
12
13
  const evidenceLocations = new WeakMap();
13
14
  const observationTarget = (observation) => 'subject' in observation ? observation.subject : observation.claim;
14
15
  export function loadEvidence(source) {
15
- const loaded = loadSourceDocument(source, validateEvidenceSchema, 'Evidence');
16
+ const loaded = loadSourceDocument(source, validateEvidenceSchema(), 'Evidence');
16
17
  if (!loaded.ok)
17
18
  return loaded;
18
19
  const { value, yaml, lineCounter } = loaded.document;
@@ -10,7 +10,8 @@ import catalogueSchema from '../schema/yarramate-question-catalogue.schema.json'
10
10
  // to keep track of (#252).
11
11
  const ajv2020Module = Ajv2020Module;
12
12
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
13
- const validateCatalogue = new Ajv2020({ allErrors: true }).compile(catalogueSchema);
13
+ import { lazyValidator } from './schema-validation.js';
14
+ const validateCatalogue = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(catalogueSchema));
14
15
  /**
15
16
  * The version of condition evaluation itself, not of the package.
16
17
  *
@@ -916,7 +917,7 @@ const unauthorableOffers = (catalogue, profileContext) => {
916
917
  return found;
917
918
  };
918
919
  const loadCatalogueDocument = (catalogueSource) => {
919
- const loaded = loadSourceDocument(catalogueSource, validateCatalogue, 'Question catalogue');
920
+ const loaded = loadSourceDocument(catalogueSource, validateCatalogue(), 'Question catalogue');
920
921
  if (!loaded.ok)
921
922
  return { ok: false, diagnostics: loaded.diagnostics };
922
923
  return {
@@ -6,7 +6,7 @@ import projectionSchema from '../schema/yarramate-projection.schema.json' with {
6
6
  };
7
7
  const ajv2020Module = Ajv2020Import;
8
8
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
9
- const validateProjection = new Ajv2020({ allErrors: true }).compile(projectionSchema);
9
+ const validateProjection = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(projectionSchema));
10
10
  /**
11
11
  * A relationship kind a view may draw as nesting, and the default. Defined in
12
12
  * `./nesting.js`, which imports nothing, and re-exported here so a consumer
@@ -20,8 +20,9 @@ export { DEFAULT_NESTING } from './nesting.js';
20
20
  * nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
21
21
  */
22
22
  export { DEFAULT_DIRECTION } from './layout-direction.js';
23
+ import { lazyValidator } from './schema-validation.js';
23
24
  export function loadProjection(source) {
24
- const loaded = loadSourceDocument(source, validateProjection, 'Projection');
25
+ const loaded = loadSourceDocument(source, validateProjection(), 'Projection');
25
26
  return loaded.ok
26
27
  ? { ok: true, projection: loaded.document.value }
27
28
  : loaded;
@@ -0,0 +1,30 @@
1
+ import type { ValidateFunction } from 'ajv';
2
+ /**
3
+ * Defers compiling a JSON Schema until something actually validates against
4
+ * it, then reuses the compiled validator forever.
5
+ *
6
+ * Ten validators used to be constructed and compiled at MODULE SCOPE, so
7
+ * importing the package paid for every one of them whether or not a caller
8
+ * ever validated anything. Measured on the published 1.15.1 dist: ten
9
+ * `Ajv.compile` calls costing **123.7ms, 80% of the barrel's entire import
10
+ * time**, and one more on the `yarramate/interrogation` subpath, which is the
11
+ * entry we tell Workers consumers to prefer. That is not an abstract cost:
12
+ * Cloudflare Workers budget STARTUP CPU separately from request CPU and refuse
13
+ * a Worker that exceeds it, so an adopter's deploy was rejected outright
14
+ * (error 10021) by work no request had asked for.
15
+ *
16
+ * Deferring moves that cost to first use, where the budget is seconds rather
17
+ * than milliseconds, and a caller pays only for the schemas it actually
18
+ * touches - a consumer that compiles a workspace no longer pays for the
19
+ * evidence, adapter-mapping and core-contract validators it never calls.
20
+ *
21
+ * The accessor is a function rather than a getter so the deferral is visible
22
+ * at every call site: `validateDocument()(value)` reads as "get the validator,
23
+ * then use it", and `validateDocument().errors` cannot accidentally be read
24
+ * off a validator that was never run. Ajv attaches `errors` to the validator
25
+ * itself, so the two must come from the same object.
26
+ *
27
+ * `test/schema-validation-laziness.test.ts` asserts that importing the package
28
+ * compiles NOTHING, and fails on the next module-scope validator anyone adds.
29
+ */
30
+ export declare const lazyValidator: <T = unknown>(compile: () => ValidateFunction<T>) => (() => ValidateFunction<T>);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Defers compiling a JSON Schema until something actually validates against
3
+ * it, then reuses the compiled validator forever.
4
+ *
5
+ * Ten validators used to be constructed and compiled at MODULE SCOPE, so
6
+ * importing the package paid for every one of them whether or not a caller
7
+ * ever validated anything. Measured on the published 1.15.1 dist: ten
8
+ * `Ajv.compile` calls costing **123.7ms, 80% of the barrel's entire import
9
+ * time**, and one more on the `yarramate/interrogation` subpath, which is the
10
+ * entry we tell Workers consumers to prefer. That is not an abstract cost:
11
+ * Cloudflare Workers budget STARTUP CPU separately from request CPU and refuse
12
+ * a Worker that exceeds it, so an adopter's deploy was rejected outright
13
+ * (error 10021) by work no request had asked for.
14
+ *
15
+ * Deferring moves that cost to first use, where the budget is seconds rather
16
+ * than milliseconds, and a caller pays only for the schemas it actually
17
+ * touches - a consumer that compiles a workspace no longer pays for the
18
+ * evidence, adapter-mapping and core-contract validators it never calls.
19
+ *
20
+ * The accessor is a function rather than a getter so the deferral is visible
21
+ * at every call site: `validateDocument()(value)` reads as "get the validator,
22
+ * then use it", and `validateDocument().errors` cannot accidentally be read
23
+ * off a validator that was never run. Ajv attaches `errors` to the validator
24
+ * itself, so the two must come from the same object.
25
+ *
26
+ * `test/schema-validation-laziness.test.ts` asserts that importing the package
27
+ * compiles NOTHING, and fails on the next module-scope validator anyone adds.
28
+ */
29
+ export const lazyValidator = (compile) => {
30
+ let compiled;
31
+ return () => (compiled ??= compile());
32
+ };