yarramate 1.15.2 → 1.15.3

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,6 +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
+ import { compileValidator } from './schema-validation.js';
4
4
  import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.json' with { type: 'json'
5
5
  };
6
6
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
@@ -9,10 +9,10 @@ import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.jso
9
9
  // to keep track of (#252).
10
10
  const ajv2020Module = Ajv2020Module;
11
11
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
12
- const validateSchema = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(adapterMappingSchema));
12
+ const validateSchema = compileValidator(adapterMappingSchema);
13
13
  const mappingLocations = new WeakMap();
14
14
  export function loadAdapterMapping(source) {
15
- const loaded = loadSourceDocument(source, validateSchema(), 'Adapter mapping');
15
+ const loaded = loadSourceDocument(source, validateSchema, 'Adapter mapping');
16
16
  if (!loaded.ok)
17
17
  return loaded;
18
18
  const { value, yaml, lineCounter } = loaded.document;
@@ -22,7 +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
+ import { compileValidatorWith } from './schema-validation.js';
26
26
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
27
27
  // `module.exports` and a bundler sees the unwrapped class, and this file is
28
28
  // reachable from a browser through `./apply-operations.js` (#252).
@@ -32,10 +32,7 @@ const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
32
32
  // one malformed operation reports one fault instead of ten near-misses.
33
33
  // Keeps its own Ajv instance: `discriminator` changes how a schema compiles,
34
34
  // so it cannot share one with the nine that do not set it.
35
- const validateOperations = lazyValidator(() => new Ajv2020({
36
- allErrors: true,
37
- discriminator: true,
38
- }).compile(operationsSchema));
35
+ const validateOperations = compileValidatorWith({ allErrors: true, discriminator: true }, operationsSchema);
39
36
  // Scalar fields replace; list fields append; `remove` retracts (ADR 0062).
40
37
  // An answer enriches what is there and may explicitly take back what it
41
38
  // asserted — it never silently shrinks anything.
@@ -313,7 +310,7 @@ export const applyOperations = (input) => {
313
310
  ok: false,
314
311
  diagnostics,
315
312
  });
316
- const loadedOperations = loadSourceDocument(operations, validateOperations(), 'Operations');
313
+ const loadedOperations = loadSourceDocument(operations, validateOperations, 'Operations');
317
314
  if (!loadedOperations.ok)
318
315
  return failed(loadedOperations.diagnostics);
319
316
  const operationList = loadedOperations.document.value.operations;
package/dist/compiler.js CHANGED
@@ -11,13 +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
+ import { compileValidator } from './schema-validation.js';
15
15
  const coreProfile = 'yarramate/core@0.1';
16
16
  const ajv2020Module = Ajv2020Import;
17
17
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
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));
18
+ const validateDocument = compileValidator(documentSchema);
19
+ const validateProfile = compileValidator(profileSchema);
20
+ const validatePattern = compileValidator(patternSchema);
21
21
  const immutableMap = (entries) => {
22
22
  const backing = new Map(entries);
23
23
  const facade = {
@@ -161,12 +161,12 @@ const parseWorkspaceSource = (input) => {
161
161
  fresh,
162
162
  };
163
163
  }
164
- const valid = parseDiagnostics.length === 0 && validateDocument()(value);
164
+ const valid = parseDiagnostics.length === 0 && validateDocument(value);
165
165
  const schemaDiagnostics = parseDiagnostics.length > 0
166
166
  ? parseDiagnostics
167
167
  : valid
168
168
  ? []
169
- : (validateDocument().errors ?? []).map((error) => {
169
+ : (validateDocument.errors ?? []).map((error) => {
170
170
  const property = error.keyword === 'additionalProperties'
171
171
  ? String(error.params.additionalProperty)
172
172
  : undefined;
@@ -331,8 +331,8 @@ function compileWorkspaceResolved(parsed) {
331
331
  profileDiagnostics.push(...entry.schemaDiagnostics);
332
332
  continue;
333
333
  }
334
- if (!validateProfile()(value)) {
335
- for (const error of validateProfile().errors ?? []) {
334
+ if (!validateProfile(value)) {
335
+ for (const error of validateProfile.errors ?? []) {
336
336
  const property = error.keyword === 'additionalProperties'
337
337
  ? String(error.params.additionalProperty)
338
338
  : undefined;
@@ -391,8 +391,8 @@ function compileWorkspaceResolved(parsed) {
391
391
  if (entry.schemaDiagnostics.length > 0) {
392
392
  profileDiagnostics.push(...entry.schemaDiagnostics);
393
393
  }
394
- else if (!validateProfile()(value)) {
395
- for (const error of validateProfile().errors ?? []) {
394
+ else if (!validateProfile(value)) {
395
+ for (const error of validateProfile.errors ?? []) {
396
396
  profileDiagnostics.push({
397
397
  severity: 'error',
398
398
  code: 'YM201',
@@ -641,8 +641,8 @@ function compileWorkspaceResolved(parsed) {
641
641
  patternDiagnostics.push(...entry.schemaDiagnostics);
642
642
  continue;
643
643
  }
644
- if (!validatePattern()(value)) {
645
- for (const error of validatePattern().errors ?? []) {
644
+ if (!validatePattern(value)) {
645
+ for (const error of validatePattern.errors ?? []) {
646
646
  const property = error.keyword === 'additionalProperties'
647
647
  ? String(error.params.additionalProperty)
648
648
  : undefined;
@@ -1,7 +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
+ import { compileValidator } from './schema-validation.js';
5
5
  import coreContractSchema from '../schema/yarramate-core-contract.schema.json' with { type: 'json'
6
6
  };
7
7
  // `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
@@ -10,9 +10,9 @@ import coreContractSchema from '../schema/yarramate-core-contract.schema.json' w
10
10
  // to keep track of (#252).
11
11
  const ajv2020Module = Ajv2020Module;
12
12
  const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
13
- const validateCoreContract = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(coreContractSchema));
13
+ const validateCoreContract = compileValidator(coreContractSchema);
14
14
  export function loadCoreContract(source) {
15
- const loaded = loadSourceDocument(source, validateCoreContract(), 'Core contract');
15
+ const loaded = loadSourceDocument(source, validateCoreContract, 'Core contract');
16
16
  if (!loaded.ok)
17
17
  return loaded;
18
18
  const { value, yaml, lineCounter } = loaded.document;
package/dist/evidence.js CHANGED
@@ -8,12 +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
- import { lazyValidator } from './schema-validation.js';
12
- const validateEvidenceSchema = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(evidenceSchema));
11
+ import { compileValidator } from './schema-validation.js';
12
+ const validateEvidenceSchema = compileValidator(evidenceSchema);
13
13
  const evidenceLocations = new WeakMap();
14
14
  const observationTarget = (observation) => 'subject' in observation ? observation.subject : observation.claim;
15
15
  export function loadEvidence(source) {
16
- const loaded = loadSourceDocument(source, validateEvidenceSchema(), 'Evidence');
16
+ const loaded = loadSourceDocument(source, validateEvidenceSchema, 'Evidence');
17
17
  if (!loaded.ok)
18
18
  return loaded;
19
19
  const { value, yaml, lineCounter } = loaded.document;
@@ -10,8 +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
- import { lazyValidator } from './schema-validation.js';
14
- const validateCatalogue = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(catalogueSchema));
13
+ import { compileValidator } from './schema-validation.js';
14
+ const validateCatalogue = compileValidator(catalogueSchema);
15
15
  /**
16
16
  * The version of condition evaluation itself, not of the package.
17
17
  *
@@ -917,7 +917,7 @@ const unauthorableOffers = (catalogue, profileContext) => {
917
917
  return found;
918
918
  };
919
919
  const loadCatalogueDocument = (catalogueSource) => {
920
- const loaded = loadSourceDocument(catalogueSource, validateCatalogue(), 'Question catalogue');
920
+ const loaded = loadSourceDocument(catalogueSource, validateCatalogue, 'Question catalogue');
921
921
  if (!loaded.ok)
922
922
  return { ok: false, diagnostics: loaded.diagnostics };
923
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 = lazyValidator(() => new Ajv2020({ allErrors: true }).compile(projectionSchema));
9
+ const validateProjection = compileValidator(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,9 +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
+ import { compileValidator } from './schema-validation.js';
24
24
  export function loadProjection(source) {
25
- const loaded = loadSourceDocument(source, validateProjection(), 'Projection');
25
+ const loaded = loadSourceDocument(source, validateProjection, 'Projection');
26
26
  return loaded.ok
27
27
  ? { ok: true, projection: loaded.document.value }
28
28
  : loaded;
@@ -1,30 +1,12 @@
1
+ import Ajv2020Module from 'ajv/dist/2020.js';
1
2
  import type { ValidateFunction } from 'ajv';
3
+ declare const Ajv2020: typeof Ajv2020Module & typeof Ajv2020Module.Ajv2020;
4
+ /** Compiles on the shared `allErrors` instance. */
5
+ export declare const compileValidator: <T = unknown>(schema: object) => ValidateFunction<T>;
2
6
  /**
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.
7
+ * For a schema needing options the shared instance does not carry. Only
8
+ * `apply-command` uses it: `discriminator` changes how a schema compiles, so
9
+ * it cannot ride on an instance that does not set it.
29
10
  */
30
- export declare const lazyValidator: <T = unknown>(compile: () => ValidateFunction<T>) => (() => ValidateFunction<T>);
11
+ export declare const compileValidatorWith: <T = unknown>(options: ConstructorParameters<typeof Ajv2020>[0], schema: object) => ValidateFunction<T>;
12
+ export {};
@@ -1,32 +1,46 @@
1
+ import Ajv2020Module from 'ajv/dist/2020.js';
2
+ // The published dual-export dance every other module here does; see the
3
+ // comment in `compiler.ts` for why (#252).
4
+ const ajv2020Module = Ajv2020Module;
5
+ const Ajv2020 = ajv2020Module.default ?? ajv2020Module;
1
6
  /**
2
- * Defers compiling a JSON Schema until something actually validates against
3
- * it, then reuses the compiled validator forever.
7
+ * Compiles a JSON Schema AT MODULE SCOPE, on an instance shared with every
8
+ * other schema that wants the same options.
4
9
  *
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.
10
+ * **Compilation must happen at import, and this is not a preference.** Ajv
11
+ * compiles a schema by generating source and calling `new Function`.
12
+ * Cloudflare's `workerd` permits code generation during module evaluation and
13
+ * FORBIDS it at request time - measured on the real runtime binary:
14
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.
15
+ * ```
16
+ * {"atStartup":"ALLOWED",
17
+ * "atRequest":"EvalError: Code generation from strings disallowed for this context"}
18
+ * ```
19
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.
20
+ * 1.15.2 deferred these compiles to first use to keep startup CPU down, which
21
+ * moved them into the one context that cannot run them: every request that
22
+ * validated anything threw. The startup budget was real, but a package that
23
+ * cannot serve a request is not a fix for it. Deferral is therefore not an
24
+ * option here, and `test/schema-validation-eagerness.test.ts` asserts the
25
+ * opposite of what its predecessor did - that importing the package compiles
26
+ * every validator, so no request has to.
25
27
  *
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
+ * The cost is paid down by SHARING one Ajv instance rather than building ten.
29
+ * Each instance compiles the 2020-12 meta-schema again, and that dominates:
30
+ * ten instances cost 81.6ms against 35.0ms for one, a 57% reduction with no
31
+ * change to when anything runs. Sharing is safe here because no schema
32
+ * registers formats and all 39 ship distinct `$id`s, so nothing collides.
33
+ *
34
+ * The real ceiling-lifter is precompiled standalone validators (Ajv's
35
+ * `code: { source: true }`), which remove Ajv from the runtime altogether and
36
+ * satisfy both constraints at once. This is the shape that is correct today.
37
+ */
38
+ const sharedAjv = new Ajv2020({ allErrors: true });
39
+ /** Compiles on the shared `allErrors` instance. */
40
+ export const compileValidator = (schema) => sharedAjv.compile(schema);
41
+ /**
42
+ * For a schema needing options the shared instance does not carry. Only
43
+ * `apply-command` uses it: `discriminator` changes how a schema compiles, so
44
+ * it cannot ride on an instance that does not set it.
28
45
  */
29
- export const lazyValidator = (compile) => {
30
- let compiled;
31
- return () => (compiled ??= compile());
32
- };
46
+ export const compileValidatorWith = (options, schema) => new Ajv2020(options).compile(schema);