@peasant-labs/schema 0.1.0-rc6
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.
- package/LICENSE +13 -0
- package/README.md +56 -0
- package/dist/fixtures/quality.d.ts +81 -0
- package/dist/fixtures/quality.js +36 -0
- package/dist/fixtures/timeline.d.ts +22 -0
- package/dist/fixtures/timeline.js +2 -0
- package/dist/fixtures.d.ts +2 -0
- package/dist/fixtures.js +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +36 -0
- package/dist/internal/generated/contract/zod.gen.d.ts +2740 -0
- package/dist/internal/generated/contract/zod.gen.js +1196 -0
- package/dist/internal/generated/enums.gen.d.ts +318 -0
- package/dist/internal/generated/enums.gen.js +348 -0
- package/dist/internal/generated/local-api.d.ts +946 -0
- package/dist/internal/generated/local-api.js +1 -0
- package/dist/internal/generated/public-contract.gen.d.ts +2 -0
- package/dist/internal/generated/public-contract.gen.js +2 -0
- package/dist/internal/generated/quality-fixtures.gen.d.ts +2 -0
- package/dist/internal/generated/quality-fixtures.gen.js +367 -0
- package/dist/internal/generated/testcase.gen.d.ts +17 -0
- package/dist/internal/generated/testcase.gen.js +20 -0
- package/dist/internal/generated/timeline-fixtures.gen.d.ts +2 -0
- package/dist/internal/generated/timeline-fixtures.gen.js +542 -0
- package/dist/internal/generated/versions.gen.d.ts +4 -0
- package/dist/internal/generated/versions.gen.js +5 -0
- package/dist/internal/generated/village-api.d.ts +585 -0
- package/dist/internal/generated/village-api.js +1 -0
- package/dist/local-api.d.ts +4 -0
- package/dist/local-api.js +1 -0
- package/dist/testcase.d.ts +32 -0
- package/dist/testcase.js +144 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/village-api.d.ts +4 -0
- package/dist/village-api.js +1 -0
- package/package.json +71 -0
package/dist/testcase.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { parseAllDocuments } from "yaml";
|
|
2
|
+
// Classification and ProvenanceSource are generated from
|
|
3
|
+
// testcase/testcase.go's AllClassifications/AllProvenanceSources (see
|
|
4
|
+
// typescript/scripts/generate-contract-support.mjs's renderTestcaseModel) so
|
|
5
|
+
// this meta-testing vocabulary can never silently drift from the Go source it
|
|
6
|
+
// mirrors. Only the YAML-loading mechanics below are handwritten.
|
|
7
|
+
import { Classification, AllClassifications, isClassification, ProvenanceSource, AllProvenanceSources, isProvenanceSource, } from "./internal/generated/testcase.gen.js";
|
|
8
|
+
export { Classification, AllClassifications, isClassification, ProvenanceSource, AllProvenanceSources, isProvenanceSource };
|
|
9
|
+
export function checkMin(corpus, minimum) {
|
|
10
|
+
if (!Number.isSafeInteger(minimum) || minimum < 0)
|
|
11
|
+
return new Error(`minimum must be a non-negative safe integer, got ${String(minimum)}`);
|
|
12
|
+
if (corpus.cases.length < minimum)
|
|
13
|
+
return new Error(`corpus has ${corpus.cases.length} case(s), want at least ${minimum}`);
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function validateCase(testCase) {
|
|
17
|
+
const path = `case ${JSON.stringify(testCase.name)}`;
|
|
18
|
+
if (testCase.name.trim() === "")
|
|
19
|
+
return new Error(`${path}: name is empty`);
|
|
20
|
+
if (!isClassification(testCase.classification))
|
|
21
|
+
return new Error(`${path}: classification ${JSON.stringify(testCase.classification)} is not one of ${AllClassifications.join("/")}`);
|
|
22
|
+
if (!isProvenanceSource(testCase.provenance.source))
|
|
23
|
+
return new Error(`${path}: provenance source ${JSON.stringify(testCase.provenance.source)} is not a known source`);
|
|
24
|
+
if (testCase.provenance.ref.trim() === "")
|
|
25
|
+
return new Error(`${path}: provenance ref is empty (a case must cite why it exists)`);
|
|
26
|
+
if (testCase.mutation.description.trim() === "")
|
|
27
|
+
return new Error(`${path}: mutation description is empty (a case must describe the change under test)`);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
export function validateCorpus(corpus) {
|
|
31
|
+
const names = new Set();
|
|
32
|
+
for (const [index, testCase] of corpus.cases.entries()) {
|
|
33
|
+
if (names.has(testCase.name))
|
|
34
|
+
return new Error(`corpus case ${index}: duplicate case name ${JSON.stringify(testCase.name)}`);
|
|
35
|
+
names.add(testCase.name);
|
|
36
|
+
const error = validateCase(testCase);
|
|
37
|
+
if (error !== undefined)
|
|
38
|
+
return new Error(`corpus case ${index}: ${error.message}`, { cause: error });
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
export class CorpusError extends Error {
|
|
43
|
+
path;
|
|
44
|
+
constructor(path, message, options) {
|
|
45
|
+
super(`${path}: ${message}`, options);
|
|
46
|
+
this.name = "CorpusError";
|
|
47
|
+
this.path = path;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function loadCorpus(source, decoders) {
|
|
51
|
+
let documents;
|
|
52
|
+
try {
|
|
53
|
+
documents = parseAllDocuments(source, { strict: true, uniqueKeys: true });
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new CorpusError("$", "YAML parsing failed", { cause: error });
|
|
57
|
+
}
|
|
58
|
+
if (documents.length !== 1) {
|
|
59
|
+
throw new CorpusError("$", `expected exactly one YAML document, got ${documents.length}`);
|
|
60
|
+
}
|
|
61
|
+
const document = documents[0];
|
|
62
|
+
if (document === undefined) {
|
|
63
|
+
throw new CorpusError("$", "YAML parser returned no document");
|
|
64
|
+
}
|
|
65
|
+
if (document.errors.length > 0) {
|
|
66
|
+
throw new CorpusError("$", `YAML parsing failed: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
67
|
+
}
|
|
68
|
+
let value;
|
|
69
|
+
try {
|
|
70
|
+
value = document.toJS({ maxAliasCount: 0 });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new CorpusError("$", "YAML conversion failed", { cause: error });
|
|
74
|
+
}
|
|
75
|
+
const root = requireRecord(value, "$", ["cases"]);
|
|
76
|
+
requireKeys(root, "$", ["cases"]);
|
|
77
|
+
if (!Array.isArray(root.cases)) {
|
|
78
|
+
throw new CorpusError("$.cases", "must be a sequence");
|
|
79
|
+
}
|
|
80
|
+
const cases = root.cases.map((candidate, index) => {
|
|
81
|
+
const path = `$.cases[${index}]`;
|
|
82
|
+
const row = requireRecord(candidate, path, ["name", "input", "expected", "classification", "provenance", "mutation"]);
|
|
83
|
+
requireKeys(row, path, ["name", "input", "expected", "classification", "provenance", "mutation"]);
|
|
84
|
+
const name = requireString(row.name, `${path}.name`);
|
|
85
|
+
const classification = row.classification;
|
|
86
|
+
if (!isClassification(classification)) {
|
|
87
|
+
throw new CorpusError(`${path}.classification`, `${JSON.stringify(classification)} is not one of must-pass/must-fail`);
|
|
88
|
+
}
|
|
89
|
+
const provenanceValue = requireRecord(row.provenance, `${path}.provenance`, ["source", "ref"]);
|
|
90
|
+
requireKeys(provenanceValue, `${path}.provenance`, ["source", "ref"]);
|
|
91
|
+
const sourceValue = provenanceValue.source;
|
|
92
|
+
if (!isProvenanceSource(sourceValue)) {
|
|
93
|
+
throw new CorpusError(`${path}.provenance.source`, `${JSON.stringify(sourceValue)} is not a known source`);
|
|
94
|
+
}
|
|
95
|
+
const provenance = {
|
|
96
|
+
source: sourceValue,
|
|
97
|
+
ref: requireString(provenanceValue.ref, `${path}.provenance.ref`),
|
|
98
|
+
};
|
|
99
|
+
const mutationValue = requireRecord(row.mutation, `${path}.mutation`, ["description"]);
|
|
100
|
+
requireKeys(mutationValue, `${path}.mutation`, ["description"]);
|
|
101
|
+
const mutation = {
|
|
102
|
+
description: requireString(mutationValue.description, `${path}.mutation.description`),
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
input: decoders.decodeInput(row.input, `${path}.input`),
|
|
107
|
+
expected: decoders.decodeExpected(row.expected, `${path}.expected`),
|
|
108
|
+
classification,
|
|
109
|
+
provenance,
|
|
110
|
+
mutation,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
const corpus = { cases };
|
|
114
|
+
const err = validateCorpus(corpus);
|
|
115
|
+
if (err !== undefined) {
|
|
116
|
+
throw new CorpusError("$", err.message, { cause: err });
|
|
117
|
+
}
|
|
118
|
+
return corpus;
|
|
119
|
+
}
|
|
120
|
+
function requireRecord(value, path, allowedKeys) {
|
|
121
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
122
|
+
throw new CorpusError(path, "must be a mapping");
|
|
123
|
+
}
|
|
124
|
+
const record = Object.fromEntries(Object.entries(value));
|
|
125
|
+
for (const key of Object.keys(record)) {
|
|
126
|
+
if (!allowedKeys.includes(key)) {
|
|
127
|
+
throw new CorpusError(`${path}.${key}`, "unknown key");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return record;
|
|
131
|
+
}
|
|
132
|
+
function requireKeys(record, path, requiredKeys) {
|
|
133
|
+
for (const key of requiredKeys) {
|
|
134
|
+
if (!Object.hasOwn(record, key)) {
|
|
135
|
+
throw new CorpusError(`${path}.${key}`, "required key is missing");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function requireString(value, path) {
|
|
140
|
+
if (typeof value !== "string") {
|
|
141
|
+
throw new CorpusError(path, "must be a string");
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { VillageAPIVersion } from "./index.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peasant-labs/schema",
|
|
3
|
+
"version": "0.1.0-rc6",
|
|
4
|
+
"description": "Generated TypeScript contract bindings for the peasant-labs schema module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./local-api": {
|
|
19
|
+
"types": "./dist/local-api.d.ts",
|
|
20
|
+
"default": "./dist/local-api.js"
|
|
21
|
+
},
|
|
22
|
+
"./village-api": {
|
|
23
|
+
"types": "./dist/village-api.d.ts",
|
|
24
|
+
"default": "./dist/village-api.js"
|
|
25
|
+
},
|
|
26
|
+
"./types": {
|
|
27
|
+
"types": "./dist/types.d.ts",
|
|
28
|
+
"default": "./dist/types.js"
|
|
29
|
+
},
|
|
30
|
+
"./testcase": {
|
|
31
|
+
"types": "./dist/testcase.d.ts",
|
|
32
|
+
"default": "./dist/testcase.js"
|
|
33
|
+
},
|
|
34
|
+
"./fixtures": {
|
|
35
|
+
"types": "./dist/fixtures.d.ts",
|
|
36
|
+
"default": "./dist/fixtures.js"
|
|
37
|
+
},
|
|
38
|
+
"./fixtures/quality": {
|
|
39
|
+
"types": "./dist/fixtures/quality.d.ts",
|
|
40
|
+
"default": "./dist/fixtures/quality.js"
|
|
41
|
+
},
|
|
42
|
+
"./fixtures/timeline": {
|
|
43
|
+
"types": "./dist/fixtures/timeline.d.ts",
|
|
44
|
+
"default": "./dist/fixtures/timeline.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"yaml": "2.9.0",
|
|
49
|
+
"zod": "^4.4.3"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@hey-api/openapi-ts": "0.99.0",
|
|
53
|
+
"@types/node": "24.13.3",
|
|
54
|
+
"openapi-typescript": "7.13.0",
|
|
55
|
+
"typescript": "5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=24"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"generate": "openapi-ts -f openapi-ts.config.mjs && node scripts/generate-contract-support.mjs",
|
|
65
|
+
"build": "node scripts/build.mjs",
|
|
66
|
+
"typecheck": "pnpm run build && tsc --noEmit -p tsconfig.test.json",
|
|
67
|
+
"test": "pnpm run build && node --test tests/*.test.mjs",
|
|
68
|
+
"package:audit": "pnpm run build && node scripts/package-audit.mjs",
|
|
69
|
+
"package:smoke": "pnpm run build && node scripts/package-smoke.mjs"
|
|
70
|
+
}
|
|
71
|
+
}
|