claude-code-jsonl-generator 0.1.3-beta.13 → 0.1.3-beta.14
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/dist/cli.cjs +3 -2
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/{emit-guards-BKEtrVDH.cjs → emit-guards-CwEZ3nRU.cjs} +11 -12
- package/dist/{emit-guards-DSL78hvA.js → emit-guards-DPY2-6nD.js} +12 -13
- package/dist/emit-guards-DPY2-6nD.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/emit-guards-DSL78hvA.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_emit_guards = require("./emit-guards-
|
|
2
|
+
const require_emit_guards = require("./emit-guards-CwEZ3nRU.cjs");
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_emit_guards.__toESM(node_path, 1);
|
|
@@ -30,7 +30,8 @@ async function main() {
|
|
|
30
30
|
const typeBlocks = [];
|
|
31
31
|
const guardBlocks = [];
|
|
32
32
|
const registry = [];
|
|
33
|
-
|
|
33
|
+
const sortedSamples = [...scan.samplesByType].toSorted((a, b) => a[0].localeCompare(b[0]));
|
|
34
|
+
for (const [type, samples] of sortedSamples) {
|
|
34
35
|
const shape = require_emit_guards.inferShape(samples);
|
|
35
36
|
const name = `${require_emit_guards.pascalCase(type)}Entry`;
|
|
36
37
|
const fn = `is${require_emit_guards.pascalCase(type)}`;
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as inferShape, n as emitInterface, r as pascalCase, s as scanCorpus, t as emitGuardFunction } from "./emit-guards-
|
|
2
|
+
import { a as inferShape, n as emitInterface, r as pascalCase, s as scanCorpus, t as emitGuardFunction } from "./emit-guards-DPY2-6nD.js";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
// src/cli.ts
|
|
@@ -29,7 +29,8 @@ async function main() {
|
|
|
29
29
|
const typeBlocks = [];
|
|
30
30
|
const guardBlocks = [];
|
|
31
31
|
const registry = [];
|
|
32
|
-
|
|
32
|
+
const sortedSamples = [...scan.samplesByType].toSorted((a, b) => a[0].localeCompare(b[0]));
|
|
33
|
+
for (const [type, samples] of sortedSamples) {
|
|
33
34
|
const shape = inferShape(samples);
|
|
34
35
|
const name = `${pascalCase(type)}Entry`;
|
|
35
36
|
const fn = `is${pascalCase(type)}`;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { scanCorpus } from './scanner'\nimport { inferShape } from './infer'\nimport { emitInterface, pascalCase } from './emit-types'\nimport { emitGuardFunction } from './emit-guards'\n\n/**\n * 引数を解釈する。`--corpus <dir> --out <parser/src/generated>`。\n *\n * @param argv - process.argv.slice(2)\n */\nfunction parseArgs(argv: string[]): { corpus: string; out: string } {\n let corpus = ''\n let out = ''\n for (let i = 0; i < argv.length; i += 1) {\n if (argv[i] === '--corpus') corpus = argv[i + 1] ?? ''\n else if (argv[i] === '--out') out = argv[i + 1] ?? ''\n }\n if (!corpus || !out) {\n console.error('Usage: claude-code-jsonl-generator --corpus <dir> --out <parser/src/generated>')\n process.exit(2)\n }\n return { corpus, out }\n}\n\nasync function main(): Promise<void> {\n const { corpus, out } = parseArgs(process.argv.slice(2))\n const scan = await scanCorpus(corpus)\n console.error(`scanned: ${scan.totalLines} lines, ${scan.samplesByType.size} types, ${scan.malformedLines} malformed`)\n\n const typeBlocks: string[] = []\n const guardBlocks: string[] = []\n const registry: string[] = []\n
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { scanCorpus } from './scanner'\nimport { inferShape } from './infer'\nimport { emitInterface, pascalCase } from './emit-types'\nimport { emitGuardFunction } from './emit-guards'\n\n/**\n * 引数を解釈する。`--corpus <dir> --out <parser/src/generated>`。\n *\n * @param argv - process.argv.slice(2)\n */\nfunction parseArgs(argv: string[]): { corpus: string; out: string } {\n let corpus = ''\n let out = ''\n for (let i = 0; i < argv.length; i += 1) {\n if (argv[i] === '--corpus') corpus = argv[i + 1] ?? ''\n else if (argv[i] === '--out') out = argv[i + 1] ?? ''\n }\n if (!corpus || !out) {\n console.error('Usage: claude-code-jsonl-generator --corpus <dir> --out <parser/src/generated>')\n process.exit(2)\n }\n return { corpus, out }\n}\n\nasync function main(): Promise<void> {\n const { corpus, out } = parseArgs(process.argv.slice(2))\n const scan = await scanCorpus(corpus)\n console.error(`scanned: ${scan.totalLines} lines, ${scan.samplesByType.size} types, ${scan.malformedLines} malformed`)\n\n const typeBlocks: string[] = []\n const guardBlocks: string[] = []\n const registry: string[] = []\n const sortedSamples = [...scan.samplesByType].toSorted((a, b) =>\n a[0].localeCompare(b[0])\n )\n for (const [type, samples] of sortedSamples) {\n const shape = inferShape(samples)\n const name = `${pascalCase(type)}Entry`\n const fn = `is${pascalCase(type)}`\n typeBlocks.push(emitInterface(name, type, shape))\n guardBlocks.push(emitGuardFunction(fn, type, shape))\n registry.push(` '${type}': ${fn},`)\n }\n\n const typesHeader = `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\\n\\n`\n const typesOut = typesHeader + typeBlocks.join('\\n\\n') + '\\n'\n\n const guardsHeader =\n `/** GENERATED by claude-code-jsonl-generator. Do not edit by hand. */\\n\\n` +\n `function isObject(v: unknown): v is Record<string, unknown> {\\n` +\n ` return typeof v === 'object' && v !== null && !Array.isArray(v)\\n}\\n` +\n `function isString(v: unknown): v is string { return typeof v === 'string' }\\n` +\n `function isNumber(v: unknown): v is number { return typeof v === 'number' }\\n` +\n `function isBoolean(v: unknown): v is boolean { return typeof v === 'boolean' }\\n\\n`\n const guardsRegistry =\n `\\n\\nexport const guards = {\\n${registry.join('\\n')}\\n} as const satisfies Record<string, (v: unknown) => boolean>\\n` +\n `export const KNOWN_TYPES = Object.keys(guards) as (keyof typeof guards)[]\\n` +\n `export function guardByType(type: string, v: unknown): boolean | undefined {\\n` +\n ` const g = (guards as Record<string, (x: unknown) => boolean>)[type]\\n` +\n ` return g === undefined ? undefined : g(v)\\n}\\n`\n const guardsOut = guardsHeader + guardBlocks.join('\\n\\n') + guardsRegistry\n\n await mkdir(out, { recursive: true })\n await writeFile(path.join(out,'types.generated.ts'), typesOut, 'utf8')\n await writeFile(path.join(out,'guards.generated.ts'), guardsOut, 'utf8')\n console.error(`written: ${path.join(out,'types.generated.ts')}, ${path.join(out,'guards.generated.ts')}`)\n console.error('NOTE: review the diff before committing. content-block guards / message shapes are curated manually and are not overwritten by this generator.')\n}\n\nmain().catch((error: unknown) => {\n console.error(error)\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;;AAaA,SAAS,UAAU,MAAiD;CAClE,IAAI,SAAS;CACb,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GACpC,IAAI,KAAK,OAAO,YAAY,SAAS,KAAK,IAAI,MAAM;MAC/C,IAAI,KAAK,OAAO,SAAS,MAAM,KAAK,IAAI,MAAM;CAErD,IAAI,CAAC,UAAU,CAAC,KAAK;EACnB,QAAQ,MAAM,gFAAgF;EAC9F,QAAQ,KAAK,CAAC;CAChB;CACA,OAAO;EAAE;EAAQ;CAAI;AACvB;AAEA,eAAe,OAAsB;CACnC,MAAM,EAAE,QAAQ,QAAQ,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;CACvD,MAAM,OAAO,MAAM,WAAW,MAAM;CACpC,QAAQ,MAAM,YAAY,KAAK,WAAW,UAAU,KAAK,cAAc,KAAK,UAAU,KAAK,eAAe,WAAW;CAErH,MAAM,aAAuB,CAAC;CAC9B,MAAM,cAAwB,CAAC;CAC/B,MAAM,WAAqB,CAAC;CAC5B,MAAM,gBAAgB,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC,UAAU,GAAG,MACzD,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CACzB;CACA,KAAK,MAAM,CAAC,MAAM,YAAY,eAAe;EAC3C,MAAM,QAAQ,WAAW,OAAO;EAChC,MAAM,OAAO,GAAG,WAAW,IAAI,EAAE;EACjC,MAAM,KAAK,KAAK,WAAW,IAAI;EAC/B,WAAW,KAAK,cAAc,MAAM,MAAM,KAAK,CAAC;EAChD,YAAY,KAAK,kBAAkB,IAAI,MAAM,KAAK,CAAC;EACnD,SAAS,KAAK,MAAM,KAAK,KAAK,GAAG,EAAE;CACrC;CAGA,MAAM,WAAW,8EAAc,WAAW,KAAK,MAAM,IAAI;CAEzD,MAAM,eACJ;CAMF,MAAM,iBACJ,gCAAgC,SAAS,KAAK,IAAI,EAAE;CAKtD,MAAM,YAAY,eAAe,YAAY,KAAK,MAAM,IAAI;CAE5D,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CACpC,MAAM,UAAU,KAAK,KAAK,KAAI,oBAAoB,GAAG,UAAU,MAAM;CACrE,MAAM,UAAU,KAAK,KAAK,KAAI,qBAAqB,GAAG,WAAW,MAAM;CACvE,QAAQ,MAAM,YAAY,KAAK,KAAK,KAAI,oBAAoB,EAAE,IAAI,KAAK,KAAK,KAAI,qBAAqB,GAAG;CACxG,QAAQ,MAAM,gJAAgJ;AAChK;AAEA,KAAK,CAAC,CAAC,OAAO,UAAmB;CAC/B,QAAQ,MAAM,KAAK;CACnB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
|
@@ -33,7 +33,8 @@ node_path = __toESM(node_path, 1);
|
|
|
33
33
|
*/
|
|
34
34
|
async function collectJsonlFiles(dir) {
|
|
35
35
|
const out = [];
|
|
36
|
-
|
|
36
|
+
const entries = await (0, node_fs_promises.readdir)(dir);
|
|
37
|
+
for (const entry of entries) {
|
|
37
38
|
const full = node_path.default.join(dir, entry);
|
|
38
39
|
if ((await (0, node_fs_promises.stat)(full)).isDirectory()) out.push(...await collectJsonlFiles(full));
|
|
39
40
|
else if (entry.endsWith(".jsonl")) out.push(full);
|
|
@@ -51,7 +52,8 @@ async function scanCorpus(corpusDir, maxSamplesPerType = 5e3) {
|
|
|
51
52
|
let totalLines = 0;
|
|
52
53
|
let malformedLines = 0;
|
|
53
54
|
let typelessLines = 0;
|
|
54
|
-
|
|
55
|
+
const files = await collectJsonlFiles(corpusDir);
|
|
56
|
+
for (const file of files) {
|
|
55
57
|
const rl = (0, node_readline.createInterface)({
|
|
56
58
|
input: (0, node_fs.createReadStream)(file, { encoding: "utf8" }),
|
|
57
59
|
crlfDelay: Infinity
|
|
@@ -73,10 +75,9 @@ async function scanCorpus(corpusDir, maxSamplesPerType = 5e3) {
|
|
|
73
75
|
const record = obj;
|
|
74
76
|
const type = record.type;
|
|
75
77
|
const bucket = samplesByType.get(type) ?? [];
|
|
76
|
-
if (bucket.length < maxSamplesPerType)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
78
|
+
if (!(bucket.length < maxSamplesPerType)) continue;
|
|
79
|
+
bucket.push(record);
|
|
80
|
+
samplesByType.set(type, bucket);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
return {
|
|
@@ -96,12 +97,11 @@ function jsonKind(v) {
|
|
|
96
97
|
if (v === null) return "null";
|
|
97
98
|
if (Array.isArray(v)) return "array";
|
|
98
99
|
const t = typeof v;
|
|
99
|
-
|
|
100
|
+
return [
|
|
100
101
|
"string",
|
|
101
102
|
"number",
|
|
102
103
|
"boolean"
|
|
103
|
-
].includes(t)
|
|
104
|
-
return "object";
|
|
104
|
+
].includes(t) ? t : "object";
|
|
105
105
|
}
|
|
106
106
|
/** `InferOptions.maxLiterals` 省略時の既定値。 */
|
|
107
107
|
const DEFAULT_MAX_LITERALS = 20;
|
|
@@ -127,8 +127,7 @@ function inferShape(samples, options = {}) {
|
|
|
127
127
|
if (cur === null) {} else if (kind === "string") {
|
|
128
128
|
const set = cur ?? /* @__PURE__ */ new Set();
|
|
129
129
|
set.add(value);
|
|
130
|
-
|
|
131
|
-
else literals.set(key, set);
|
|
130
|
+
literals.set(key, set.size > maxLiterals ? null : set);
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
const fields = /* @__PURE__ */ new Map();
|
|
@@ -171,7 +170,7 @@ function tsTypeOf(kinds, literals) {
|
|
|
171
170
|
object: "Record<string, unknown>"
|
|
172
171
|
};
|
|
173
172
|
if (literals && literals.size > 0 && kinds.has("string")) {
|
|
174
|
-
const lits = [...literals].toSorted().map((s) => `'${escapeSingleQuote$1(s)}'`);
|
|
173
|
+
const lits = [...literals].toSorted((a, b) => a.localeCompare(b)).map((s) => `'${escapeSingleQuote$1(s)}'`);
|
|
175
174
|
parts.push(`${lits.join(" | ")} | (string & {})`);
|
|
176
175
|
} else if (kinds.has("string")) parts.push("string");
|
|
177
176
|
for (const k of [
|
|
@@ -11,7 +11,8 @@ import path from "node:path";
|
|
|
11
11
|
*/
|
|
12
12
|
async function collectJsonlFiles(dir) {
|
|
13
13
|
const out = [];
|
|
14
|
-
|
|
14
|
+
const entries = await readdir(dir);
|
|
15
|
+
for (const entry of entries) {
|
|
15
16
|
const full = path.join(dir, entry);
|
|
16
17
|
if ((await stat(full)).isDirectory()) out.push(...await collectJsonlFiles(full));
|
|
17
18
|
else if (entry.endsWith(".jsonl")) out.push(full);
|
|
@@ -29,7 +30,8 @@ async function scanCorpus(corpusDir, maxSamplesPerType = 5e3) {
|
|
|
29
30
|
let totalLines = 0;
|
|
30
31
|
let malformedLines = 0;
|
|
31
32
|
let typelessLines = 0;
|
|
32
|
-
|
|
33
|
+
const files = await collectJsonlFiles(corpusDir);
|
|
34
|
+
for (const file of files) {
|
|
33
35
|
const rl = createInterface({
|
|
34
36
|
input: createReadStream(file, { encoding: "utf8" }),
|
|
35
37
|
crlfDelay: Infinity
|
|
@@ -51,10 +53,9 @@ async function scanCorpus(corpusDir, maxSamplesPerType = 5e3) {
|
|
|
51
53
|
const record = obj;
|
|
52
54
|
const type = record.type;
|
|
53
55
|
const bucket = samplesByType.get(type) ?? [];
|
|
54
|
-
if (bucket.length < maxSamplesPerType)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
56
|
+
if (!(bucket.length < maxSamplesPerType)) continue;
|
|
57
|
+
bucket.push(record);
|
|
58
|
+
samplesByType.set(type, bucket);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
return {
|
|
@@ -74,12 +75,11 @@ function jsonKind(v) {
|
|
|
74
75
|
if (v === null) return "null";
|
|
75
76
|
if (Array.isArray(v)) return "array";
|
|
76
77
|
const t = typeof v;
|
|
77
|
-
|
|
78
|
+
return [
|
|
78
79
|
"string",
|
|
79
80
|
"number",
|
|
80
81
|
"boolean"
|
|
81
|
-
].includes(t)
|
|
82
|
-
return "object";
|
|
82
|
+
].includes(t) ? t : "object";
|
|
83
83
|
}
|
|
84
84
|
/** `InferOptions.maxLiterals` 省略時の既定値。 */
|
|
85
85
|
const DEFAULT_MAX_LITERALS = 20;
|
|
@@ -105,8 +105,7 @@ function inferShape(samples, options = {}) {
|
|
|
105
105
|
if (cur === null) {} else if (kind === "string") {
|
|
106
106
|
const set = cur ?? /* @__PURE__ */ new Set();
|
|
107
107
|
set.add(value);
|
|
108
|
-
|
|
109
|
-
else literals.set(key, set);
|
|
108
|
+
literals.set(key, set.size > maxLiterals ? null : set);
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
const fields = /* @__PURE__ */ new Map();
|
|
@@ -149,7 +148,7 @@ function tsTypeOf(kinds, literals) {
|
|
|
149
148
|
object: "Record<string, unknown>"
|
|
150
149
|
};
|
|
151
150
|
if (literals && literals.size > 0 && kinds.has("string")) {
|
|
152
|
-
const lits = [...literals].toSorted().map((s) => `'${escapeSingleQuote$1(s)}'`);
|
|
151
|
+
const lits = [...literals].toSorted((a, b) => a.localeCompare(b)).map((s) => `'${escapeSingleQuote$1(s)}'`);
|
|
153
152
|
parts.push(`${lits.join(" | ")} | (string & {})`);
|
|
154
153
|
} else if (kinds.has("string")) parts.push("string");
|
|
155
154
|
for (const k of [
|
|
@@ -237,4 +236,4 @@ function emitGuardFunction(fnName, type, shape) {
|
|
|
237
236
|
}
|
|
238
237
|
export { inferShape as a, tsTypeOf as i, emitInterface as n, jsonKind as o, pascalCase as r, scanCorpus as s, emitGuardFunction as t };
|
|
239
238
|
|
|
240
|
-
//# sourceMappingURL=emit-guards-
|
|
239
|
+
//# sourceMappingURL=emit-guards-DPY2-6nD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-guards-DPY2-6nD.js","names":["IDENTIFIER_RE","escapeSingleQuote"],"sources":["../src/scanner.ts","../src/infer.ts","../src/emit-types.ts","../src/emit-guards.ts"],"sourcesContent":["import { readdir, stat } from 'node:fs/promises'\nimport { createReadStream } from 'node:fs'\nimport { createInterface } from 'node:readline'\nimport path from 'node:path'\n\n/** スキャン結果。 */\nexport interface ScanResult {\n /** type 文字列 → その type の生 JSON オブジェクト配列。 */\n samplesByType: Map<string, Record<string, unknown>[]>\n /** 走査した有効行数 (パース成功)。 */\n totalLines: number\n /** JSON 構文エラー行数。 */\n malformedLines: number\n /** type フィールドを持たない/文字列でない行数。 */\n typelessLines: number\n}\n\n/**\n * `.jsonl` ファイルを再帰収集する。\n *\n * @param dir - 起点ディレクトリ\n * @returns 絶対パス配列\n */\nasync function collectJsonlFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n const entries = await readdir(dir)\n for (const entry of entries) {\n const full = path.join(dir, entry)\n const st = await stat(full)\n if (st.isDirectory()) {\n out.push(...(await collectJsonlFiles(full)))\n } else if (entry.endsWith('.jsonl')) {\n out.push(full)\n }\n }\n return out\n}\n\n/**\n * コーパスを走査し type ごとに生サンプルを集約する。\n *\n * @param corpusDir - コーパスのルートディレクトリ\n * @param maxSamplesPerType - type ごとの保持上限 (メモリ保護、既定 5000)\n */\nexport async function scanCorpus(\n corpusDir: string,\n maxSamplesPerType = 5000\n): Promise<ScanResult> {\n const samplesByType = new Map<string, Record<string, unknown>[]>()\n let totalLines = 0\n let malformedLines = 0\n let typelessLines = 0\n\n const files = await collectJsonlFiles(corpusDir)\n for (const file of files) {\n const rl = createInterface({\n input: createReadStream(file, { encoding: 'utf8' }),\n crlfDelay: Infinity,\n })\n for await (const raw of rl) {\n if (raw.trim() === '') continue\n let obj: unknown\n try {\n obj = JSON.parse(raw)\n } catch {\n malformedLines += 1\n continue\n }\n totalLines += 1\n if (\n typeof obj !== 'object' || obj === null || Array.isArray(obj) ||\n typeof (obj as Record<string, unknown>).type !== 'string'\n ) {\n typelessLines += 1\n continue\n }\n const record = obj as Record<string, unknown>\n const type = record.type as string\n const bucket = samplesByType.get(type) ?? []\n if (!(bucket.length < maxSamplesPerType)) {\n continue\n }\n\n bucket.push(record)\n samplesByType.set(type, bucket)\n }\n }\n\n return { samplesByType, totalLines, malformedLines, typelessLines }\n}\n","/** JSON 値の種別。 */\nexport type JsonKind = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object'\n\n/**\n * JSON 値の種別を返す。\n *\n * @param v - 対象値\n */\nexport function jsonKind(v: unknown): JsonKind {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n const t = typeof v\n return (['string', 'number', 'boolean'] as string[]).includes(t) ? (t as 'string' | 'number' | 'boolean') : 'object'\n}\n\n/** 1 フィールドの推論結果。 */\nexport interface FieldShape {\n /** 全サンプルに存在したか。 */\n required: boolean\n /** 観測された JSON 種別集合。 */\n kinds: Set<JsonKind>\n /** 観測された文字列リテラル集合 (閾値以下のときのみ)。 */\n literals?: Set<string>\n}\n\n/** 1 type (または content ブロック) の推論結果。 */\nexport interface Shape {\n /** サンプル総数。 */\n count: number\n /** フィールド名 → 推論結果。 */\n fields: Map<string, FieldShape>\n}\n\n/** `InferOptions.maxLiterals` 省略時の既定値。 */\nconst DEFAULT_MAX_LITERALS = 20\n\n/** 推論オプション。 */\nexport interface InferOptions {\n /** 文字列リテラルを列挙として保持する上限。超えたら literals を破棄。既定は `DEFAULT_MAX_LITERALS`。 */\n maxLiterals?: number\n}\n\n/**\n * 同一 type のサンプル群からフィールド形状を推論する。\n *\n * @param samples - 生 JSON オブジェクト配列\n * @param options - 推論オプション\n */\nexport function inferShape(\n samples: Record<string, unknown>[],\n options: InferOptions = {}\n): Shape {\n const maxLiterals = options.maxLiterals ?? DEFAULT_MAX_LITERALS\n const count = samples.length\n const presence = new Map<string, number>()\n const kinds = new Map<string, Set<JsonKind>>()\n const literals = new Map<string, Set<string> | null>()\n\n for (const sample of samples) {\n for (const [key, value] of Object.entries(sample)) {\n presence.set(key, (presence.get(key) ?? 0) + 1)\n\n const kset = kinds.get(key) ?? new Set<JsonKind>()\n const kind = jsonKind(value)\n kset.add(kind)\n kinds.set(key, kset)\n\n // 文字列リテラル収集 (null は「破棄済み」マーカー)\n const cur = literals.get(key)\n if (cur === null) {\n // すでに破棄済み\n } else if (kind === 'string') {\n const set = cur ?? new Set<string>()\n set.add(value as string)\n literals.set(key, set.size > maxLiterals ? null : set)\n }\n }\n }\n\n const fields = new Map<string, FieldShape>()\n for (const [key, seen] of presence) {\n const lit = literals.get(key)\n const kindSet = kinds.get(key) ?? new Set<JsonKind>()\n // 全て文字列だった場合のみ literals を採用する\n const allString = kindSet.size === 1 && kindSet.has('string')\n fields.set(key, {\n required: seen === count,\n kinds: kindSet,\n literals: allString && lit ? lit : undefined,\n })\n }\n\n return { count, fields }\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/**\n * 観測種別集合 (と任意のリテラル集合) を TypeScript 型文字列に写像する。\n *\n * @param kinds - 観測された JSON 種別\n * @param literals - 文字列リテラル集合 (あれば)\n */\nexport function tsTypeOf(kinds: Set<JsonKind>, literals?: Set<string>): string {\n const parts: string[] = []\n const mapped: Record<JsonKind, string> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n null: 'null',\n array: 'unknown[]',\n object: 'Record<string, unknown>',\n }\n // 文字列リテラルが与えられていれば開いた union にする\n if (literals && literals.size > 0 && kinds.has('string')) {\n const lits = [...literals].toSorted((a, b) => a.localeCompare(b)).map((s) => `'${escapeSingleQuote(s)}'`)\n parts.push(`${lits.join(' | ')} | (string & {})`)\n } else if (kinds.has('string')) {\n parts.push('string')\n }\n for (const k of ['number', 'boolean', 'array', 'object', 'null'] as JsonKind[]) {\n if (kinds.has(k)) parts.push(mapped[k])\n }\n // 重複除去して結合\n return [...new Set(parts)].join(' | ')\n}\n\n/** JS 識別子として安全ならそのまま、そうでなければクォートする。 */\nfunction propKey(name: string): string {\n return IDENTIFIER_RE.test(name) ? name : `'${escapeSingleQuote(name)}'`\n}\n\n/** interface 名として使うため type 文字列を PascalCase 化する (エミッタ内部用)。 */\nexport function pascalCase(type: string): string {\n return type\n .split(/[-_]/)\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join('')\n}\n\n/**\n * 1 type の interface 文字列を生成する。\n *\n * @param interfaceName - 出力する interface 名 (例 LastPromptEntry)\n * @param type - 判別子文字列 (例 last-prompt)\n * @param shape - 推論結果\n */\nexport function emitInterface(\n interfaceName: string,\n type: string,\n shape: Shape\n): string {\n const lines: string[] = [\n `export interface ${interfaceName} {`,\n ` _kind: 'known'`,\n ` type: '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type') continue\n const opt = field.required ? '' : '?'\n const tsType = tsTypeOf(field.kinds, field.literals)\n lines.push(` ${propKey(name)}${opt}: ${tsType}`)\n }\n lines.push('}')\n return lines.join('\\n')\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/** 単一種別の required フィールドに対する検査式を返す。 */\nfunction checkExpr(name: string, kinds: Set<JsonKind>): string | undefined {\n const access = `v.${IDENTIFIER_RE.test(name) ? name : `['${escapeSingleQuote(name)}']`}`\n // object|null → isObject or null 許容\n const hasNull = kinds.has('null')\n const nonNull = [...kinds].filter((k) => k !== 'null')\n if (nonNull.length !== 1) {\n // 複数種別が混在する場合は存在確認のみ (過剰厳格を避ける)\n return `'${escapeSingleQuote(name)}' in v`\n }\n const kind = nonNull[0]\n const base: Partial<Record<JsonKind, string>> = {\n string: `isString(${access})`,\n number: `isNumber(${access})`,\n boolean: `isBoolean(${access})`,\n object: `isObject(${access})`,\n array: `Array.isArray(${access})`,\n }\n const expr = base[kind]\n if (expr === undefined) return `'${escapeSingleQuote(name)}' in v`\n return hasNull ? `(${access} === null || ${expr})` : expr\n}\n\n/**\n * 1 type のガード関数文字列を生成する。required フィールドのみ検査する。\n *\n * @param fnName - 関数名 (例 isMode)\n * @param type - 判別子\n * @param shape - 推論結果\n */\nexport function emitGuardFunction(\n fnName: string,\n type: string,\n shape: Shape\n): string {\n const checks: string[] = [\n `isObject(v)`,\n `v.type === '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type' || !field.required) continue\n const expr = checkExpr(name, field.kinds)\n if (expr) checks.push(expr)\n }\n return [\n `function ${fnName}(v: unknown): boolean {`,\n ` return ${checks.join(' &&\\n ')}`,\n `}`,\n ].join('\\n')\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_emit_guards = require("./emit-guards-
|
|
2
|
+
const require_emit_guards = require("./emit-guards-CwEZ3nRU.cjs");
|
|
3
3
|
let zod = require("zod");
|
|
4
4
|
// src/schemas.ts
|
|
5
5
|
const entryBase = {
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/scanner.ts","../src/infer.ts","../src/schemas.ts","../src/emit-types.ts","../src/emit-guards.ts"],"mappings":";;UAMiB;;EAEf,eAAe,YAAY;;EAE3B;;EAEA;;EAEA;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/scanner.ts","../src/infer.ts","../src/schemas.ts","../src/emit-types.ts","../src/emit-guards.ts"],"mappings":";;UAMiB;;EAEf,eAAe,YAAY;;EAE3B;;EAEA;;EAEA;;;;;;;;wBA8BoB,WACpB,mBACA,6BACC,QAAQ;;KC9CC;;;;;;wBAOI,SAAS,aAAa;;UAQrB;;EAEf;;EAEA,OAAO,IAAI;;EAEX,WAAW;;;UAII;;EAEf;;EAEA,QAAQ,YAAY;;;UAOL;;EAEf;;;;;;;;wBASc,WACd,SAAS,2BACT,UAAS,eACR;;qBC0FU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkBA;;;;;;wBAOG,gBAAgB,eAAe,EAAE;;;;;;;wBCtJjC,SAAS,OAAO,IAAI,WAAW,WAAW;;wBA8B1C,WAAW;;;;;;;;wBAcX,cACd,uBACA,cACA,OAAO;;;;;;;;wBCvBO,kBACd,gBACA,cACA,OAAO"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/scanner.ts","../src/infer.ts","../src/schemas.ts","../src/emit-types.ts","../src/emit-guards.ts"],"mappings":";;UAMiB;;EAEf,eAAe,YAAY;;EAE3B;;EAEA;;EAEA;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/scanner.ts","../src/infer.ts","../src/schemas.ts","../src/emit-types.ts","../src/emit-guards.ts"],"mappings":";;UAMiB;;EAEf,eAAe,YAAY;;EAE3B;;EAEA;;EAEA;;;;;;;;wBA8BoB,WACpB,mBACA,6BACC,QAAQ;;KC9CC;;;;;;wBAOI,SAAS,aAAa;;UAQrB;;EAEf;;EAEA,OAAO,IAAI;;EAEX,WAAW;;;UAII;;EAEf;;EAEA,QAAQ,YAAY;;;UAOL;;EAEf;;;;;;;;wBASc,WACd,SAAS,2BACT,UAAS,eACR;;qBC0FU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkBA;;;;;;wBAOG,gBAAgB,eAAe,EAAE;;;;;;;wBCtJjC,SAAS,OAAO,IAAI,WAAW,WAAW;;wBA8B1C,WAAW;;;;;;;;wBAcX,cACd,uBACA,cACA,OAAO;;;;;;;;wBCvBO,kBACd,gBACA,cACA,OAAO"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as inferShape, i as tsTypeOf, n as emitInterface, o as jsonKind, r as pascalCase, s as scanCorpus, t as emitGuardFunction } from "./emit-guards-
|
|
1
|
+
import { a as inferShape, i as tsTypeOf, n as emitInterface, o as jsonKind, r as pascalCase, s as scanCorpus, t as emitGuardFunction } from "./emit-guards-DPY2-6nD.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
// src/schemas.ts
|
|
4
4
|
const entryBase = {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"emit-guards-DSL78hvA.js","names":["IDENTIFIER_RE","escapeSingleQuote"],"sources":["../src/scanner.ts","../src/infer.ts","../src/emit-types.ts","../src/emit-guards.ts"],"sourcesContent":["import { readdir, stat } from 'node:fs/promises'\nimport { createReadStream } from 'node:fs'\nimport { createInterface } from 'node:readline'\nimport path from 'node:path'\n\n/** スキャン結果。 */\nexport interface ScanResult {\n /** type 文字列 → その type の生 JSON オブジェクト配列。 */\n samplesByType: Map<string, Record<string, unknown>[]>\n /** 走査した有効行数 (パース成功)。 */\n totalLines: number\n /** JSON 構文エラー行数。 */\n malformedLines: number\n /** type フィールドを持たない/文字列でない行数。 */\n typelessLines: number\n}\n\n/**\n * `.jsonl` ファイルを再帰収集する。\n *\n * @param dir - 起点ディレクトリ\n * @returns 絶対パス配列\n */\nasync function collectJsonlFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n for (const entry of await readdir(dir)) {\n const full = path.join(dir, entry)\n const st = await stat(full)\n if (st.isDirectory()) {\n out.push(...(await collectJsonlFiles(full)))\n } else if (entry.endsWith('.jsonl')) {\n out.push(full)\n }\n }\n return out\n}\n\n/**\n * コーパスを走査し type ごとに生サンプルを集約する。\n *\n * @param corpusDir - コーパスのルートディレクトリ\n * @param maxSamplesPerType - type ごとの保持上限 (メモリ保護、既定 5000)\n */\nexport async function scanCorpus(\n corpusDir: string,\n maxSamplesPerType = 5000\n): Promise<ScanResult> {\n const samplesByType = new Map<string, Record<string, unknown>[]>()\n let totalLines = 0\n let malformedLines = 0\n let typelessLines = 0\n\n for (const file of await collectJsonlFiles(corpusDir)) {\n const rl = createInterface({\n input: createReadStream(file, { encoding: 'utf8' }),\n crlfDelay: Infinity,\n })\n for await (const raw of rl) {\n if (raw.trim() === '') continue\n let obj: unknown\n try {\n obj = JSON.parse(raw)\n } catch {\n malformedLines += 1\n continue\n }\n totalLines += 1\n if (\n typeof obj !== 'object' || obj === null || Array.isArray(obj) ||\n typeof (obj as Record<string, unknown>).type !== 'string'\n ) {\n typelessLines += 1\n continue\n }\n const record = obj as Record<string, unknown>\n const type = record.type as string\n const bucket = samplesByType.get(type) ?? []\n if (bucket.length < maxSamplesPerType) {\n bucket.push(record)\n samplesByType.set(type, bucket)\n }\n }\n }\n\n return { samplesByType, totalLines, malformedLines, typelessLines }\n}\n","/** JSON 値の種別。 */\nexport type JsonKind = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object'\n\n/**\n * JSON 値の種別を返す。\n *\n * @param v - 対象値\n */\nexport function jsonKind(v: unknown): JsonKind {\n if (v === null) return 'null'\n if (Array.isArray(v)) return 'array'\n const t = typeof v\n if ((['string', 'number', 'boolean'] as string[]).includes(t)) {\n return t as 'string' | 'number' | 'boolean'\n }\n return 'object'\n}\n\n/** 1 フィールドの推論結果。 */\nexport interface FieldShape {\n /** 全サンプルに存在したか。 */\n required: boolean\n /** 観測された JSON 種別集合。 */\n kinds: Set<JsonKind>\n /** 観測された文字列リテラル集合 (閾値以下のときのみ)。 */\n literals?: Set<string>\n}\n\n/** 1 type (または content ブロック) の推論結果。 */\nexport interface Shape {\n /** サンプル総数。 */\n count: number\n /** フィールド名 → 推論結果。 */\n fields: Map<string, FieldShape>\n}\n\n/** `InferOptions.maxLiterals` 省略時の既定値。 */\nconst DEFAULT_MAX_LITERALS = 20\n\n/** 推論オプション。 */\nexport interface InferOptions {\n /** 文字列リテラルを列挙として保持する上限。超えたら literals を破棄。既定は `DEFAULT_MAX_LITERALS`。 */\n maxLiterals?: number\n}\n\n/**\n * 同一 type のサンプル群からフィールド形状を推論する。\n *\n * @param samples - 生 JSON オブジェクト配列\n * @param options - 推論オプション\n */\nexport function inferShape(\n samples: Record<string, unknown>[],\n options: InferOptions = {}\n): Shape {\n const maxLiterals = options.maxLiterals ?? DEFAULT_MAX_LITERALS\n const count = samples.length\n const presence = new Map<string, number>()\n const kinds = new Map<string, Set<JsonKind>>()\n const literals = new Map<string, Set<string> | null>()\n\n for (const sample of samples) {\n for (const [key, value] of Object.entries(sample)) {\n presence.set(key, (presence.get(key) ?? 0) + 1)\n\n const kset = kinds.get(key) ?? new Set<JsonKind>()\n const kind = jsonKind(value)\n kset.add(kind)\n kinds.set(key, kset)\n\n // 文字列リテラル収集 (null は「破棄済み」マーカー)\n const cur = literals.get(key)\n if (cur === null) {\n // すでに破棄済み\n } else if (kind === 'string') {\n const set = cur ?? new Set<string>()\n set.add(value as string)\n if (set.size > maxLiterals) {\n literals.set(key, null)\n } else {\n literals.set(key, set)\n }\n }\n }\n }\n\n const fields = new Map<string, FieldShape>()\n for (const [key, seen] of presence) {\n const lit = literals.get(key)\n const kindSet = kinds.get(key) ?? new Set<JsonKind>()\n // 全て文字列だった場合のみ literals を採用する\n const allString = kindSet.size === 1 && kindSet.has('string')\n fields.set(key, {\n required: seen === count,\n kinds: kindSet,\n literals: allString && lit ? lit : undefined,\n })\n }\n\n return { count, fields }\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/**\n * 観測種別集合 (と任意のリテラル集合) を TypeScript 型文字列に写像する。\n *\n * @param kinds - 観測された JSON 種別\n * @param literals - 文字列リテラル集合 (あれば)\n */\nexport function tsTypeOf(kinds: Set<JsonKind>, literals?: Set<string>): string {\n const parts: string[] = []\n const mapped: Record<JsonKind, string> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n null: 'null',\n array: 'unknown[]',\n object: 'Record<string, unknown>',\n }\n // 文字列リテラルが与えられていれば開いた union にする\n if (literals && literals.size > 0 && kinds.has('string')) {\n const lits = [...literals].toSorted().map((s) => `'${escapeSingleQuote(s)}'`)\n parts.push(`${lits.join(' | ')} | (string & {})`)\n } else if (kinds.has('string')) {\n parts.push('string')\n }\n for (const k of ['number', 'boolean', 'array', 'object', 'null'] as JsonKind[]) {\n if (kinds.has(k)) parts.push(mapped[k])\n }\n // 重複除去して結合\n return [...new Set(parts)].join(' | ')\n}\n\n/** JS 識別子として安全ならそのまま、そうでなければクォートする。 */\nfunction propKey(name: string): string {\n return IDENTIFIER_RE.test(name) ? name : `'${escapeSingleQuote(name)}'`\n}\n\n/** interface 名として使うため type 文字列を PascalCase 化する (エミッタ内部用)。 */\nexport function pascalCase(type: string): string {\n return type\n .split(/[-_]/)\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join('')\n}\n\n/**\n * 1 type の interface 文字列を生成する。\n *\n * @param interfaceName - 出力する interface 名 (例 LastPromptEntry)\n * @param type - 判別子文字列 (例 last-prompt)\n * @param shape - 推論結果\n */\nexport function emitInterface(\n interfaceName: string,\n type: string,\n shape: Shape\n): string {\n const lines: string[] = [\n `export interface ${interfaceName} {`,\n ` _kind: 'known'`,\n ` type: '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type') continue\n const opt = field.required ? '' : '?'\n const tsType = tsTypeOf(field.kinds, field.literals)\n lines.push(` ${propKey(name)}${opt}: ${tsType}`)\n }\n lines.push('}')\n return lines.join('\\n')\n}\n","import type { JsonKind, Shape } from './infer'\n\n/** JS 識別子として安全な名前かどうかを判定する正規表現。 */\nconst IDENTIFIER_RE = /^[A-Za-z_$][\\w$]*$/\n\n/** シングルクォート文字列リテラルとして安全になるよう `'` をエスケープする。 */\nfunction escapeSingleQuote(s: string): string {\n return s.replaceAll('\\'', String.raw`\\'`)\n}\n\n/** 単一種別の required フィールドに対する検査式を返す。 */\nfunction checkExpr(name: string, kinds: Set<JsonKind>): string | undefined {\n const access = `v.${IDENTIFIER_RE.test(name) ? name : `['${escapeSingleQuote(name)}']`}`\n // object|null → isObject or null 許容\n const hasNull = kinds.has('null')\n const nonNull = [...kinds].filter((k) => k !== 'null')\n if (nonNull.length !== 1) {\n // 複数種別が混在する場合は存在確認のみ (過剰厳格を避ける)\n return `'${escapeSingleQuote(name)}' in v`\n }\n const kind = nonNull[0]\n const base: Partial<Record<JsonKind, string>> = {\n string: `isString(${access})`,\n number: `isNumber(${access})`,\n boolean: `isBoolean(${access})`,\n object: `isObject(${access})`,\n array: `Array.isArray(${access})`,\n }\n const expr = base[kind]\n if (expr === undefined) return `'${escapeSingleQuote(name)}' in v`\n return hasNull ? `(${access} === null || ${expr})` : expr\n}\n\n/**\n * 1 type のガード関数文字列を生成する。required フィールドのみ検査する。\n *\n * @param fnName - 関数名 (例 isMode)\n * @param type - 判別子\n * @param shape - 推論結果\n */\nexport function emitGuardFunction(\n fnName: string,\n type: string,\n shape: Shape\n): string {\n const checks: string[] = [\n `isObject(v)`,\n `v.type === '${escapeSingleQuote(type)}'`,\n ]\n for (const [name, field] of shape.fields) {\n if (name === 'type' || !field.required) continue\n const expr = checkExpr(name, field.kinds)\n if (expr) checks.push(expr)\n }\n return [\n `function ${fnName}(v: unknown): boolean {`,\n ` return ${checks.join(' &&\\n ')}`,\n `}`,\n ].join('\\n')\n}\n"],"mappings":"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"}
|