dcp-wrap 0.2.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.
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PicoClaw DCP Hook — out-of-process JSON-RPC over stdio.
4
+ *
5
+ * Two-hook pattern:
6
+ * before_tool — injects queryType=agent for MCP tools with DCP output mode
7
+ * after_tool — DCP-encodes JSON tool results (fallback for tools without native DCP)
8
+ *
9
+ * Config via PICOCLAW_DCP_TOOLS env (JSON):
10
+ * {
11
+ * "mcp_engram_engram_pull": { "id": "engram-recall:v1", "fields": [...] },
12
+ * "web_fetch": "auto"
13
+ * }
14
+ *
15
+ * Config via PICOCLAW_DCP_AGENT_TOOLS env (comma-separated):
16
+ * Tools where before_tool injects queryType=agent.
17
+ * Example: "mcp_engram_engram_pull,mcp_engram_engram_ls"
18
+ */
19
+ import { createInterface } from "node:readline";
20
+ import { dcpEncode, SchemaGenerator } from "./index.js";
21
+ // --- State ---
22
+ let toolsConfig = {};
23
+ let agentQueryTools = new Set();
24
+ const autoSchemaCache = new Map();
25
+ // --- Config loading ---
26
+ function loadConfig() {
27
+ const envTools = process.env.PICOCLAW_DCP_TOOLS;
28
+ if (envTools) {
29
+ try {
30
+ toolsConfig = JSON.parse(envTools);
31
+ }
32
+ catch {
33
+ log(`Failed to parse PICOCLAW_DCP_TOOLS`);
34
+ }
35
+ }
36
+ const envAgent = process.env.PICOCLAW_DCP_AGENT_TOOLS;
37
+ if (envAgent) {
38
+ agentQueryTools = new Set(envAgent.split(",").map((s) => s.trim()).filter(Boolean));
39
+ }
40
+ }
41
+ // --- Logging ---
42
+ function log(msg) {
43
+ process.stderr.write(`[dcp-hook] ${msg}\n`);
44
+ }
45
+ // --- JSON-RPC ---
46
+ function sendResponse(id, result) {
47
+ const msg = { jsonrpc: "2.0", id, result };
48
+ process.stdout.write(JSON.stringify(msg) + "\n");
49
+ }
50
+ function sendError(id, code, message) {
51
+ const msg = { jsonrpc: "2.0", id, error: { code, message } };
52
+ process.stdout.write(JSON.stringify(msg) + "\n");
53
+ }
54
+ // --- DCP encoding ---
55
+ function tryParseJSON(text) {
56
+ try {
57
+ const parsed = JSON.parse(text);
58
+ if (Array.isArray(parsed)) {
59
+ if (parsed.length > 0 && typeof parsed[0] === "object" && parsed[0] !== null) {
60
+ return parsed;
61
+ }
62
+ return null;
63
+ }
64
+ if (typeof parsed === "object" && parsed !== null) {
65
+ return [parsed];
66
+ }
67
+ return null;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function encodeToolResult(toolName, forLlm) {
74
+ const config = toolsConfig[toolName];
75
+ if (!config)
76
+ return null;
77
+ const records = tryParseJSON(forLlm);
78
+ if (!records)
79
+ return null;
80
+ if (config === "auto") {
81
+ return encodeAuto(toolName, records);
82
+ }
83
+ const schema = { id: config.id, fields: config.fields };
84
+ return dcpEncode(records, schema);
85
+ }
86
+ function encodeAuto(toolName, records) {
87
+ let schema = autoSchemaCache.get(toolName);
88
+ if (!schema) {
89
+ const gen = new SchemaGenerator();
90
+ const draft = gen.fromSamples(records, {
91
+ domain: toolName,
92
+ maxDepth: 3,
93
+ maxFields: 20,
94
+ });
95
+ schema = { id: draft.schema.id, fields: draft.schema.fields };
96
+ autoSchemaCache.set(toolName, schema);
97
+ log(`auto-schema ${toolName}: ${schema.fields.join(",")}`);
98
+ }
99
+ return dcpEncode(records, schema);
100
+ }
101
+ // --- Hook handlers ---
102
+ function handleHello(_params) {
103
+ return { ok: true, name: "dcp-encoder" };
104
+ }
105
+ function handleBeforeTool(params) {
106
+ const payload = params;
107
+ if (agentQueryTools.has(payload.tool)) {
108
+ return {
109
+ action: "modify",
110
+ call: {
111
+ ...payload,
112
+ arguments: { ...payload.arguments, queryType: "agent" },
113
+ },
114
+ };
115
+ }
116
+ return { action: "continue" };
117
+ }
118
+ function handleAfterTool(params) {
119
+ const payload = params;
120
+ const toolName = payload.tool;
121
+ const result = payload.result;
122
+ if (!result || result.is_error || !result.for_llm) {
123
+ return { action: "continue" };
124
+ }
125
+ const encoded = encodeToolResult(toolName, result.for_llm);
126
+ if (!encoded) {
127
+ return { action: "continue" };
128
+ }
129
+ const originalLen = result.for_llm.length;
130
+ const encodedLen = encoded.length;
131
+ const pct = ((1 - encodedLen / originalLen) * 100).toFixed(0);
132
+ log(`${toolName}: ${originalLen}→${encodedLen} (${pct}%)`);
133
+ return {
134
+ action: "modify",
135
+ result: {
136
+ ...payload,
137
+ result: { ...result, for_llm: encoded },
138
+ },
139
+ };
140
+ }
141
+ function handleRequest(method, params) {
142
+ switch (method) {
143
+ case "hook.hello":
144
+ return handleHello((params ?? {}));
145
+ case "hook.before_tool":
146
+ return handleBeforeTool(params);
147
+ case "hook.after_tool":
148
+ return handleAfterTool(params);
149
+ case "hook.before_llm":
150
+ case "hook.after_llm":
151
+ case "hook.approve_tool":
152
+ return { action: "continue" };
153
+ default:
154
+ throw new Error(`method not found: ${method}`);
155
+ }
156
+ }
157
+ // --- Main loop ---
158
+ function main() {
159
+ loadConfig();
160
+ const dcpTools = Object.keys(toolsConfig);
161
+ const agentTools = [...agentQueryTools];
162
+ log(`dcp=${dcpTools.join(",") || "none"} agent=${agentTools.join(",") || "none"}`);
163
+ const rl = createInterface({ input: process.stdin });
164
+ rl.on("line", (line) => {
165
+ if (!line.trim())
166
+ return;
167
+ let msg;
168
+ try {
169
+ msg = JSON.parse(line);
170
+ }
171
+ catch {
172
+ return;
173
+ }
174
+ if (!msg.id)
175
+ return;
176
+ try {
177
+ const result = handleRequest(msg.method ?? "", msg.params);
178
+ sendResponse(msg.id, result);
179
+ }
180
+ catch (err) {
181
+ sendError(msg.id, -32000, err.message);
182
+ }
183
+ });
184
+ rl.on("close", () => {
185
+ process.exit(0);
186
+ });
187
+ }
188
+ main();
189
+ //# sourceMappingURL=picoclaw-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picoclaw-hook.js","sourceRoot":"","sources":["../src/picoclaw-hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,200 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { spawn } from "node:child_process";
4
+ import { resolve, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const hookPath = resolve(__dirname, "../dist/picoclaw-hook.js");
8
+ function startHook(cfg) {
9
+ const env = { ...process.env };
10
+ if (cfg?.tools) {
11
+ env.PICOCLAW_DCP_TOOLS = JSON.stringify(cfg.tools);
12
+ }
13
+ if (cfg?.agentTools) {
14
+ env.PICOCLAW_DCP_AGENT_TOOLS = cfg.agentTools.join(",");
15
+ }
16
+ return spawn("node", [hookPath], {
17
+ stdio: ["pipe", "pipe", "pipe"],
18
+ env,
19
+ });
20
+ }
21
+ function rpc(proc, id, method, params) {
22
+ return new Promise((resolve, reject) => {
23
+ const timeout = setTimeout(() => reject(new Error(`timeout for ${method}`)), 5000);
24
+ const onData = (chunk) => {
25
+ const lines = chunk.toString().split("\n").filter(Boolean);
26
+ for (const line of lines) {
27
+ try {
28
+ const msg = JSON.parse(line);
29
+ if (msg.id === id) {
30
+ clearTimeout(timeout);
31
+ proc.stdout.off("data", onData);
32
+ if (msg.error)
33
+ reject(new Error(msg.error.message));
34
+ else
35
+ resolve(msg.result);
36
+ }
37
+ }
38
+ catch { /* ignore parse errors */ }
39
+ }
40
+ };
41
+ proc.stdout.on("data", onData);
42
+ proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
43
+ });
44
+ }
45
+ describe("picoclaw-hook", () => {
46
+ it("responds to hook.hello", async () => {
47
+ const proc = startHook();
48
+ try {
49
+ const result = await rpc(proc, 1, "hook.hello", { name: "test", version: 1, modes: ["tool"] });
50
+ assert.deepStrictEqual(result, { ok: true, name: "dcp-encoder" });
51
+ }
52
+ finally {
53
+ proc.kill();
54
+ }
55
+ });
56
+ it("passes through unconfigured tools", async () => {
57
+ const proc = startHook({ tools: {} });
58
+ try {
59
+ await rpc(proc, 1, "hook.hello", {});
60
+ const result = await rpc(proc, 2, "hook.after_tool", {
61
+ meta: { session_key: "s1" },
62
+ tool: "unknown_tool",
63
+ result: { for_llm: '{"foo":"bar"}', silent: false, is_error: false },
64
+ });
65
+ assert.deepStrictEqual(result, { action: "continue" });
66
+ }
67
+ finally {
68
+ proc.kill();
69
+ }
70
+ });
71
+ it("DCP-encodes configured tool results", async () => {
72
+ const proc = startHook({
73
+ tools: {
74
+ mcp_engram_pull: {
75
+ id: "engram-recall:v1",
76
+ fields: ["id", "relevance", "summary", "tags"],
77
+ },
78
+ },
79
+ });
80
+ try {
81
+ await rpc(proc, 1, "hook.hello", {});
82
+ const toolOutput = JSON.stringify([
83
+ { id: "abc-123", relevance: 0.95, summary: "Test node", tags: ["howto", "gateway"], hitCount: 3, weight: 1.2 },
84
+ { id: "def-456", relevance: 0.72, summary: "Another node", tags: ["why"], hitCount: 1, weight: 0.5 },
85
+ ]);
86
+ const result = (await rpc(proc, 2, "hook.after_tool", {
87
+ meta: { session_key: "s1" },
88
+ tool: "mcp_engram_pull",
89
+ result: { for_llm: toolOutput, silent: false, is_error: false },
90
+ }));
91
+ assert.equal(result.action, "modify");
92
+ const encoded = result.result.result.for_llm;
93
+ assert.ok(encoded.includes("$S"));
94
+ assert.ok(encoded.includes("engram-recall:v1"));
95
+ assert.ok(encoded.length < toolOutput.length);
96
+ const lines = encoded.split("\n");
97
+ assert.equal(lines.length, 3, "header + 2 data rows");
98
+ const header = JSON.parse(lines[0]);
99
+ assert.equal(header[0], "$S");
100
+ assert.equal(header[1], "engram-recall:v1");
101
+ }
102
+ finally {
103
+ proc.kill();
104
+ }
105
+ });
106
+ it("passes through error results without encoding", async () => {
107
+ const proc = startHook({
108
+ tools: { mcp_engram_pull: { id: "test:v1", fields: ["id", "summary"] } },
109
+ });
110
+ try {
111
+ await rpc(proc, 1, "hook.hello", {});
112
+ const result = await rpc(proc, 2, "hook.after_tool", {
113
+ meta: {},
114
+ tool: "mcp_engram_pull",
115
+ result: { for_llm: "Error: connection refused", is_error: true },
116
+ });
117
+ assert.deepStrictEqual(result, { action: "continue" });
118
+ }
119
+ finally {
120
+ proc.kill();
121
+ }
122
+ });
123
+ it("passes through non-JSON tool output", async () => {
124
+ const proc = startHook({
125
+ tools: { mcp_engram_pull: { id: "test:v1", fields: ["id", "summary"] } },
126
+ });
127
+ try {
128
+ await rpc(proc, 1, "hook.hello", {});
129
+ const result = await rpc(proc, 2, "hook.after_tool", {
130
+ meta: {},
131
+ tool: "mcp_engram_pull",
132
+ result: { for_llm: "This is just plain text output", is_error: false },
133
+ });
134
+ assert.deepStrictEqual(result, { action: "continue" });
135
+ }
136
+ finally {
137
+ proc.kill();
138
+ }
139
+ });
140
+ it("auto-generates schema for 'auto' tools", async () => {
141
+ const proc = startHook({ tools: { web_search: "auto" } });
142
+ try {
143
+ await rpc(proc, 1, "hook.hello", {});
144
+ const toolOutput = JSON.stringify([
145
+ { title: "Result 1", url: "https://example.com/1", snippet: "First result snippet", score: 0.9 },
146
+ { title: "Result 2", url: "https://example.com/2", snippet: "Second result snippet", score: 0.7 },
147
+ ]);
148
+ const result = (await rpc(proc, 2, "hook.after_tool", {
149
+ meta: {},
150
+ tool: "web_search",
151
+ result: { for_llm: toolOutput, is_error: false },
152
+ }));
153
+ assert.equal(result.action, "modify");
154
+ const encoded = result.result.result.for_llm;
155
+ assert.ok(encoded.includes("$S"));
156
+ assert.ok(encoded.length < toolOutput.length);
157
+ }
158
+ finally {
159
+ proc.kill();
160
+ }
161
+ });
162
+ it("before_tool injects queryType=agent for configured tools", async () => {
163
+ const proc = startHook({
164
+ agentTools: ["mcp_engram_engram_pull", "mcp_engram_engram_ls"],
165
+ });
166
+ try {
167
+ await rpc(proc, 1, "hook.hello", {});
168
+ const result = (await rpc(proc, 2, "hook.before_tool", {
169
+ meta: { session_key: "s1" },
170
+ tool: "mcp_engram_engram_pull",
171
+ arguments: { query: "DCP", limit: 10 },
172
+ }));
173
+ assert.equal(result.action, "modify");
174
+ assert.equal(result.call?.arguments?.queryType, "agent");
175
+ assert.equal(result.call?.arguments?.query, "DCP");
176
+ assert.equal(result.call?.arguments?.limit, 10);
177
+ }
178
+ finally {
179
+ proc.kill();
180
+ }
181
+ });
182
+ it("before_tool passes through non-agent tools", async () => {
183
+ const proc = startHook({
184
+ agentTools: ["mcp_engram_engram_pull"],
185
+ });
186
+ try {
187
+ await rpc(proc, 1, "hook.hello", {});
188
+ const result = await rpc(proc, 2, "hook.before_tool", {
189
+ meta: {},
190
+ tool: "web_search",
191
+ arguments: { query: "test" },
192
+ });
193
+ assert.deepStrictEqual(result, { action: "continue" });
194
+ }
195
+ finally {
196
+ proc.kill();
197
+ }
198
+ });
199
+ });
200
+ //# sourceMappingURL=picoclaw-hook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picoclaw-hook.test.js","sourceRoot":"","sources":["../src/picoclaw-hook.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { DcpSchemaDef, FieldTypeDef } from "./types.js";
2
+ export declare class DcpSchema {
3
+ readonly id: string;
4
+ readonly description: string;
5
+ readonly fields: string[];
6
+ readonly fieldCount: number;
7
+ readonly types: Record<string, FieldTypeDef>;
8
+ constructor(def: DcpSchemaDef);
9
+ /** Bitmask with all field bits set. */
10
+ get fullMask(): number;
11
+ /** Return the bit value for a field (MSB = first field). */
12
+ fieldBit(fieldName: string): number;
13
+ /** Return field names corresponding to set bits in mask. */
14
+ fieldsFromMask(mask: number): string[];
15
+ /** Generate cutdown schema ID: base_id#hex_mask. */
16
+ cutdownId(mask: number): string;
17
+ /** Generate $S header array. */
18
+ sHeader(mask?: number): unknown[];
19
+ /** Validate a positional array against this schema. Returns error messages. */
20
+ validateRow(row: unknown[], mask?: number): string[];
21
+ /** Export as JSON-serializable definition. */
22
+ toDef(): DcpSchemaDef;
23
+ /** Save schema definition to a JSON file. */
24
+ save(path: string): void;
25
+ /** Load schema from a JSON file. */
26
+ static fromFile(path: string): DcpSchema;
27
+ /** Load schema from a parsed JSON object. */
28
+ static fromDef(data: Record<string, unknown>): DcpSchema;
29
+ }
package/dist/schema.js ADDED
@@ -0,0 +1,120 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export class DcpSchema {
4
+ id;
5
+ description;
6
+ fields;
7
+ fieldCount;
8
+ types;
9
+ constructor(def) {
10
+ this.id = def.id;
11
+ this.description = def.description;
12
+ this.fields = [...def.fields];
13
+ this.fieldCount = def.fieldCount;
14
+ this.types = { ...def.types };
15
+ }
16
+ /** Bitmask with all field bits set. */
17
+ get fullMask() {
18
+ return (1 << this.fieldCount) - 1;
19
+ }
20
+ /** Return the bit value for a field (MSB = first field). */
21
+ fieldBit(fieldName) {
22
+ const idx = this.fields.indexOf(fieldName);
23
+ if (idx === -1)
24
+ throw new Error(`field not found: ${fieldName}`);
25
+ return 1 << (this.fieldCount - 1 - idx);
26
+ }
27
+ /** Return field names corresponding to set bits in mask. */
28
+ fieldsFromMask(mask) {
29
+ return this.fields.filter((_, i) => mask & (1 << (this.fieldCount - 1 - i)));
30
+ }
31
+ /** Generate cutdown schema ID: base_id#hex_mask. */
32
+ cutdownId(mask) {
33
+ if (mask === this.fullMask)
34
+ return this.id;
35
+ return `${this.id}#${mask.toString(16)}`;
36
+ }
37
+ /** Generate $S header array. */
38
+ sHeader(mask) {
39
+ const m = mask ?? this.fullMask;
40
+ const active = this.fieldsFromMask(m);
41
+ const sid = this.cutdownId(m);
42
+ return ["$S", sid, ...active];
43
+ }
44
+ /** Validate a positional array against this schema. Returns error messages. */
45
+ validateRow(row, mask) {
46
+ const active = mask != null ? this.fieldsFromMask(mask) : this.fields;
47
+ const errors = [];
48
+ if (row.length !== active.length) {
49
+ errors.push(`expected ${active.length} fields, got ${row.length}`);
50
+ return errors;
51
+ }
52
+ for (let i = 0; i < active.length; i++) {
53
+ const fname = active[i];
54
+ const ft = this.types[fname];
55
+ if (!ft)
56
+ continue;
57
+ const value = row[i];
58
+ const err = validateField(ft, value);
59
+ if (err)
60
+ errors.push(`field ${fname} (pos ${i}): ${err}`);
61
+ }
62
+ return errors;
63
+ }
64
+ /** Export as JSON-serializable definition. */
65
+ toDef() {
66
+ return {
67
+ $dcp: "schema",
68
+ id: this.id,
69
+ description: this.description,
70
+ fields: [...this.fields],
71
+ fieldCount: this.fieldCount,
72
+ types: { ...this.types },
73
+ };
74
+ }
75
+ /** Save schema definition to a JSON file. */
76
+ save(path) {
77
+ mkdirSync(dirname(path), { recursive: true });
78
+ writeFileSync(path, JSON.stringify(this.toDef(), null, 2) + "\n", "utf-8");
79
+ }
80
+ /** Load schema from a JSON file. */
81
+ static fromFile(path) {
82
+ const data = JSON.parse(readFileSync(path, "utf-8"));
83
+ return DcpSchema.fromDef(data);
84
+ }
85
+ /** Load schema from a parsed JSON object. */
86
+ static fromDef(data) {
87
+ if (data.$dcp !== "schema") {
88
+ throw new Error("not a DCP schema: missing or invalid $dcp marker");
89
+ }
90
+ return new DcpSchema(data);
91
+ }
92
+ }
93
+ function validateField(ft, value) {
94
+ const types = Array.isArray(ft.type) ? ft.type : [ft.type];
95
+ if (value === null || value === undefined) {
96
+ return types.includes("null") ? null : "null not allowed";
97
+ }
98
+ let typeOk = false;
99
+ for (const t of types) {
100
+ if (t === "string" && typeof value === "string")
101
+ typeOk = true;
102
+ else if (t === "number" && typeof value === "number")
103
+ typeOk = true;
104
+ else if (t === "boolean" && typeof value === "boolean")
105
+ typeOk = true;
106
+ }
107
+ if (!typeOk)
108
+ return `expected ${ft.type}, got ${typeof value}`;
109
+ if (ft.enum != null && !ft.enum.includes(value)) {
110
+ return `value ${JSON.stringify(value)} not in enum`;
111
+ }
112
+ if (ft.min != null && typeof value === "number" && value < ft.min) {
113
+ return `value ${value} < min ${ft.min}`;
114
+ }
115
+ if (ft.max != null && typeof value === "number" && value > ft.max) {
116
+ return `value ${value} > max ${ft.max}`;
117
+ }
118
+ return null;
119
+ }
120
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /** Type definition for a single schema field. */
2
+ export interface FieldTypeDef {
3
+ type: string | string[];
4
+ description?: string;
5
+ enum?: unknown[];
6
+ min?: number;
7
+ max?: number;
8
+ }
9
+ /** DCP schema definition (the $dcp JSON document). */
10
+ export interface DcpSchemaDef {
11
+ $dcp: "schema";
12
+ id: string;
13
+ description: string;
14
+ fields: string[];
15
+ fieldCount: number;
16
+ types: Record<string, FieldTypeDef>;
17
+ }
18
+ /** Mapping: schema field name → dot.notation.path in source. */
19
+ export interface FieldMappingDef {
20
+ schemaId: string;
21
+ paths: Record<string, string>;
22
+ }
23
+ /** Options for schema generation. */
24
+ export interface GenerateOptions {
25
+ domain: string;
26
+ version?: number;
27
+ description?: string;
28
+ include?: string[];
29
+ exclude?: string[];
30
+ fieldNames?: Record<string, string>;
31
+ /** Max nesting depth to flatten (default: 3). Deeper paths are ignored. */
32
+ maxDepth?: number;
33
+ /** Max fields in generated schema (default: 20). Lowest-presence fields are dropped. */
34
+ maxFields?: number;
35
+ /** Min presence rate to include a field, 0-1 (default: 0.1). Fields appearing in <10% of samples are dropped. */
36
+ minPresence?: number;
37
+ }
38
+ /** Inference report for a single field. */
39
+ export interface FieldReport {
40
+ name: string;
41
+ sourcePath: string;
42
+ category: "identifier" | "classifier" | "numeric" | "text" | "other";
43
+ inferredType: FieldTypeDef;
44
+ presenceRate: number;
45
+ uniqueCount: number;
46
+ sampleCount: number;
47
+ isGroupKeyCandidate: boolean;
48
+ }
49
+ /** Result of schema generation — inspectable before committing. */
50
+ export interface SchemaDraft {
51
+ schema: DcpSchemaDef;
52
+ mapping: FieldMappingDef;
53
+ fieldReports: FieldReport[];
54
+ }
55
+ /** Encoded DCP output. */
56
+ export interface EncodedBatch {
57
+ header: string;
58
+ rows: string[];
59
+ schemaId: string;
60
+ mask: number;
61
+ isCutdown: boolean;
62
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}