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.
- package/README.md +172 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +151 -0
- package/dist/cli.js.map +1 -0
- package/dist/decoder.d.ts +35 -0
- package/dist/decoder.js +95 -0
- package/dist/decoder.js.map +1 -0
- package/dist/decoder.test.d.ts +1 -0
- package/dist/decoder.test.js +144 -0
- package/dist/decoder.test.js.map +1 -0
- package/dist/encoder.d.ts +38 -0
- package/dist/encoder.js +105 -0
- package/dist/encoder.js.map +1 -0
- package/dist/generator.d.ts +10 -0
- package/dist/generator.js +232 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +21 -0
- package/dist/mapping.js +90 -0
- package/dist/mapping.js.map +1 -0
- package/dist/picoclaw-hook.d.ts +19 -0
- package/dist/picoclaw-hook.js +189 -0
- package/dist/picoclaw-hook.js.map +1 -0
- package/dist/picoclaw-hook.test.d.ts +1 -0
- package/dist/picoclaw-hook.test.js +200 -0
- package/dist/picoclaw-hook.test.js.map +1 -0
- package/dist/schema.d.ts +29 -0
- package/dist/schema.js +120 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/picoclaw-integration.md +548 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# dcp-wrap
|
|
2
|
+
|
|
3
|
+
Convert JSON to [DCP](https://dcp-docs.pages.dev) positional-array format — fewer tokens, same accuracy.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
DCP strips repeated keys from structured data. Instead of sending `{"endpoint":"/v1/users","method":"GET","status":200}` per record, DCP declares the schema once and writes values by position:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
["$S","api-response:v1","endpoint","method","status","latency_ms"]
|
|
11
|
+
["/v1/users","GET",200,42]
|
|
12
|
+
["/v1/orders","POST",201,187]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
40–60% token reduction when feeding data to LLMs. Zero accuracy cost. See [benchmark](https://dcp-docs.pages.dev/dcp/specification#benchmark-dcp-vs-json-vs-natural-language).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install dcp-wrap
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or use directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx dcp-wrap
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## CLI
|
|
30
|
+
|
|
31
|
+
### Infer schema from JSON
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cat api-response.json | npx dcp-wrap init api-response
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Output:
|
|
38
|
+
```
|
|
39
|
+
Schema: api-response:v1
|
|
40
|
+
Fields: 4
|
|
41
|
+
|
|
42
|
+
endpoint: string (source: endpoint, unique: 4/4)
|
|
43
|
+
method: string (source: method, unique: 2/4) [enum(2)]
|
|
44
|
+
status: number (source: status, unique: 2/4)
|
|
45
|
+
latency_ms: number (source: latency_ms, unique: 4/4)
|
|
46
|
+
|
|
47
|
+
Saved: dcp-schemas/api-response.v1.json
|
|
48
|
+
Saved: dcp-schemas/api-response.v1.mapping.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The generator infers field types, detects enums, numeric ranges, and orders fields by DCP convention (identifiers → classifiers → numerics → text).
|
|
52
|
+
|
|
53
|
+
### Encode JSON to DCP
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cat data.json | npx dcp-wrap encode --schema dcp-schemas/api-response.v1.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Output:
|
|
60
|
+
```
|
|
61
|
+
["$S","api-response:v1","endpoint","method","status","latency_ms"]
|
|
62
|
+
["/v1/users","GET",200,42]
|
|
63
|
+
["/v1/orders","POST",201,187]
|
|
64
|
+
["/v1/auth","POST",200,95]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Inspect a schema
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx dcp-wrap inspect dcp-schemas/api-response.v1.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Programmatic API
|
|
74
|
+
|
|
75
|
+
### Quick — one function, no files
|
|
76
|
+
|
|
77
|
+
For known structures where you define the schema inline:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { dcpEncode } from "dcp-wrap";
|
|
81
|
+
|
|
82
|
+
const dcp = dcpEncode(results, {
|
|
83
|
+
id: "engram-recall:v1",
|
|
84
|
+
fields: ["id", "relevance", "summary", "tags", "hitCount", "weight", "status"],
|
|
85
|
+
});
|
|
86
|
+
// ["$S","engram-recall:v1","id","relevance","summary","tags","hitCount","weight","status"]
|
|
87
|
+
// ["abc123",0.95,"port conflict fix","docker,gotcha",12,3.2,"fixed"]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Array fields are auto-joined with comma. Use `transform` for custom handling:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const dcp = dcpEncode(records, schema, {
|
|
94
|
+
transform: { relevance: (v) => +(v as number).toFixed(3) },
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Full — schema generation + encoding
|
|
99
|
+
|
|
100
|
+
For unknown JSON where you want schema inference:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { SchemaGenerator, DcpEncoder, DcpSchema, FieldMapping } from "dcp-wrap";
|
|
104
|
+
|
|
105
|
+
const gen = new SchemaGenerator();
|
|
106
|
+
const draft = gen.fromSamples(jsonRecords, { domain: "github-pr" });
|
|
107
|
+
|
|
108
|
+
const schema = new DcpSchema(draft.schema);
|
|
109
|
+
const mapping = new FieldMapping(draft.mapping);
|
|
110
|
+
const encoder = new DcpEncoder(schema, mapping);
|
|
111
|
+
|
|
112
|
+
const batch = encoder.encode(jsonRecords);
|
|
113
|
+
console.log(DcpEncoder.toString(batch));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Nested JSON
|
|
117
|
+
|
|
118
|
+
Nested objects are automatically flattened via dot-notation:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{"id": "pr-1", "metadata": {"author": "alice", "state": "open"}}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The generator maps `metadata.author` → `author`, `metadata.state` → `state`. The mapping file records the full paths for encoding.
|
|
125
|
+
|
|
126
|
+
## Working with messy data
|
|
127
|
+
|
|
128
|
+
Real-world APIs return deeply nested objects, inconsistent fields, and dozens of keys you don't need. The generator applies three guards by default:
|
|
129
|
+
|
|
130
|
+
| Guard | Default | What it does |
|
|
131
|
+
|-------|---------|-------------|
|
|
132
|
+
| `maxDepth` | 3 | Stops flattening at 3 levels. `a.b.c` is resolved; `a.b.c.d.e` is kept as an opaque value. |
|
|
133
|
+
| `maxFields` | 20 | Keeps the top 20 fields by DCP priority (identifiers → classifiers → numerics → text). The rest are dropped. |
|
|
134
|
+
| `minPresence` | 0.1 | Fields appearing in less than 10% of samples are excluded. |
|
|
135
|
+
|
|
136
|
+
Override when needed:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const draft = gen.fromSamples(samples, {
|
|
140
|
+
domain: "some-api",
|
|
141
|
+
maxDepth: 2, // very flat — only top-level and one level of nesting
|
|
142
|
+
maxFields: 10, // aggressive trim
|
|
143
|
+
minPresence: 0.5, // field must appear in at least half the samples
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Always review the generated schema before using it in production.** The generator infers — it does not know your intent. Check:
|
|
148
|
+
|
|
149
|
+
- Are the right fields included? Use `include` / `exclude` to override.
|
|
150
|
+
- Are field names sensible? Nested paths become leaf names (`metadata.author` → `author`). Use `fieldNames` to rename.
|
|
151
|
+
- Are array fields handled correctly? Arrays are auto-joined with comma in `dcpEncode()`. Use `transform` for custom serialization.
|
|
152
|
+
- Is the schema ID correct? The generator derives `{domain}:v{version}` from your input. This ID is how consumers identify the schema.
|
|
153
|
+
|
|
154
|
+
## Design
|
|
155
|
+
|
|
156
|
+
- **Zero runtime dependencies**
|
|
157
|
+
- Schema generation follows DCP field ordering convention
|
|
158
|
+
- Supports JSON arrays and newline-delimited JSON (NDJSON) as input
|
|
159
|
+
- Handles nested objects, enum detection, nullable fields, numeric ranges
|
|
160
|
+
- Schema + mapping files are plain JSON — review, version, and edit them
|
|
161
|
+
|
|
162
|
+
dcp-wrap handles JSON → DCP conversion. For shadow index optimization, agent profiling, and the full protocol design, see [dcp-docs.pages.dev](https://dcp-docs.pages.dev).
|
|
163
|
+
|
|
164
|
+
## PicoClaw Integration
|
|
165
|
+
|
|
166
|
+
dcp-wrap ships with an out-of-process hook for [PicoClaw](https://github.com/sipeed/picoclaw) that DCP-encodes tool results before they reach the LLM. No core modification needed — just configure a process hook.
|
|
167
|
+
|
|
168
|
+
See [docs/picoclaw-integration.md](docs/picoclaw-integration.md) for setup guide, Docker instructions, and gotchas.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
Apache-2.0
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { SchemaGenerator, formatReport } from "./generator.js";
|
|
5
|
+
import { DcpSchema } from "./schema.js";
|
|
6
|
+
import { FieldMapping } from "./mapping.js";
|
|
7
|
+
import { DcpEncoder } from "./encoder.js";
|
|
8
|
+
function readStdinSync() {
|
|
9
|
+
try {
|
|
10
|
+
return readFileSync(0, "utf-8");
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function readInput(args) {
|
|
17
|
+
const samplesIdx = args.indexOf("--samples");
|
|
18
|
+
const inputIdx = args.indexOf("--input");
|
|
19
|
+
const fileIdx = samplesIdx !== -1 ? samplesIdx : inputIdx;
|
|
20
|
+
let raw;
|
|
21
|
+
if (fileIdx !== -1 && args[fileIdx + 1]) {
|
|
22
|
+
raw = readFileSync(args[fileIdx + 1], "utf-8");
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
raw = readStdinSync();
|
|
26
|
+
}
|
|
27
|
+
if (!raw.trim()) {
|
|
28
|
+
console.error("Error: no input data. Pipe JSON or use --samples <file>");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Try JSON array first, then newline-delimited JSON
|
|
32
|
+
const trimmed = raw.trim();
|
|
33
|
+
if (trimmed.startsWith("[")) {
|
|
34
|
+
const parsed = JSON.parse(trimmed);
|
|
35
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "object") {
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// NDJSON
|
|
40
|
+
const lines = trimmed.split("\n").filter((l) => l.trim());
|
|
41
|
+
return lines.map((l) => JSON.parse(l));
|
|
42
|
+
}
|
|
43
|
+
function cmdInit(args) {
|
|
44
|
+
const domain = args[0];
|
|
45
|
+
if (!domain) {
|
|
46
|
+
console.error("Usage: dcp-wrap init <domain> [--samples file.json]");
|
|
47
|
+
console.error(" e.g.: cat data.json | dcp-wrap init github-pr");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const samples = readInput(args.slice(1));
|
|
51
|
+
const gen = new SchemaGenerator();
|
|
52
|
+
const draft = gen.fromSamples(samples, { domain });
|
|
53
|
+
// Print report to stderr
|
|
54
|
+
console.error(formatReport(draft));
|
|
55
|
+
console.error("");
|
|
56
|
+
// Save schema
|
|
57
|
+
const outDir = "dcp-schemas";
|
|
58
|
+
const schemaPath = join(outDir, `${domain}.v1.json`);
|
|
59
|
+
const mappingPath = join(outDir, `${domain}.v1.mapping.json`);
|
|
60
|
+
mkdirSync(outDir, { recursive: true });
|
|
61
|
+
writeFileSync(schemaPath, JSON.stringify(draft.schema, null, 2) + "\n", "utf-8");
|
|
62
|
+
writeFileSync(mappingPath, JSON.stringify(draft.mapping, null, 2) + "\n", "utf-8");
|
|
63
|
+
console.error(`Saved: ${schemaPath}`);
|
|
64
|
+
console.error(`Saved: ${mappingPath}`);
|
|
65
|
+
// Print schema to stdout
|
|
66
|
+
console.log(JSON.stringify(draft.schema, null, 2));
|
|
67
|
+
}
|
|
68
|
+
function cmdEncode(args) {
|
|
69
|
+
const schemaIdx = args.indexOf("--schema");
|
|
70
|
+
if (schemaIdx === -1 || !args[schemaIdx + 1]) {
|
|
71
|
+
console.error("Usage: dcp-wrap encode --schema <schema.json> [--input file.json]");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
const schemaPath = args[schemaIdx + 1];
|
|
75
|
+
const schema = DcpSchema.fromFile(schemaPath);
|
|
76
|
+
// Try to load mapping from adjacent file
|
|
77
|
+
const mappingPath = schemaPath.replace(".json", ".mapping.json");
|
|
78
|
+
let mapping;
|
|
79
|
+
try {
|
|
80
|
+
const mappingDef = JSON.parse(readFileSync(mappingPath, "utf-8"));
|
|
81
|
+
mapping = new FieldMapping(mappingDef);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Auto-bind: assume field names match source keys
|
|
85
|
+
const paths = {};
|
|
86
|
+
for (const f of schema.fields)
|
|
87
|
+
paths[f] = f;
|
|
88
|
+
mapping = new FieldMapping({ schemaId: schema.id, paths });
|
|
89
|
+
}
|
|
90
|
+
const records = readInput(args);
|
|
91
|
+
const encoder = new DcpEncoder(schema, mapping);
|
|
92
|
+
const batch = encoder.encode(records);
|
|
93
|
+
console.log(DcpEncoder.toString(batch));
|
|
94
|
+
}
|
|
95
|
+
function cmdInspect(args) {
|
|
96
|
+
const schemaPath = args[0];
|
|
97
|
+
if (!schemaPath) {
|
|
98
|
+
console.error("Usage: dcp-wrap inspect <schema.json>");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const schema = DcpSchema.fromFile(schemaPath);
|
|
102
|
+
console.log(`Schema: ${schema.id}`);
|
|
103
|
+
console.log(`Fields: ${schema.fieldCount}`);
|
|
104
|
+
console.log(`Header: ${JSON.stringify(schema.sHeader())}`);
|
|
105
|
+
console.log("");
|
|
106
|
+
for (let i = 0; i < schema.fields.length; i++) {
|
|
107
|
+
const f = schema.fields[i];
|
|
108
|
+
const t = schema.types[f];
|
|
109
|
+
const typeStr = t
|
|
110
|
+
? Array.isArray(t.type) ? t.type.join("|") : t.type
|
|
111
|
+
: "unknown";
|
|
112
|
+
const extras = [];
|
|
113
|
+
if (t?.enum)
|
|
114
|
+
extras.push(`enum: ${JSON.stringify(t.enum)}`);
|
|
115
|
+
if (t?.min != null)
|
|
116
|
+
extras.push(`min: ${t.min}`);
|
|
117
|
+
if (t?.max != null)
|
|
118
|
+
extras.push(`max: ${t.max}`);
|
|
119
|
+
const extStr = extras.length > 0 ? ` (${extras.join(", ")})` : "";
|
|
120
|
+
console.log(` [${i}] ${f}: ${typeStr}${extStr}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ── Main ───────────────────────────────────────────────────
|
|
124
|
+
const args = process.argv.slice(2);
|
|
125
|
+
const command = args[0];
|
|
126
|
+
switch (command) {
|
|
127
|
+
case "init":
|
|
128
|
+
cmdInit(args.slice(1));
|
|
129
|
+
break;
|
|
130
|
+
case "encode":
|
|
131
|
+
cmdEncode(args.slice(1));
|
|
132
|
+
break;
|
|
133
|
+
case "inspect":
|
|
134
|
+
cmdInspect(args.slice(1));
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
console.error("dcp-wrap — Convert JSON to DCP positional-array format");
|
|
138
|
+
console.error("");
|
|
139
|
+
console.error("Commands:");
|
|
140
|
+
console.error(" init <domain> [--samples file.json] Infer schema from JSON samples");
|
|
141
|
+
console.error(" encode --schema <file> [--input file] Encode JSON to DCP");
|
|
142
|
+
console.error(" inspect <schema.json> Show schema details");
|
|
143
|
+
console.error("");
|
|
144
|
+
console.error("Examples:");
|
|
145
|
+
console.error(" cat api-response.json | dcp-wrap init github-pr");
|
|
146
|
+
console.error(" cat data.json | dcp-wrap encode --schema dcp-schemas/github-pr.v1.json");
|
|
147
|
+
console.error("");
|
|
148
|
+
console.error("Learn more: https://dcp-docs.pages.dev");
|
|
149
|
+
process.exit(command ? 1 : 0);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DcpSchema } from "./schema.js";
|
|
2
|
+
/** Result of decoding a single DCP row. */
|
|
3
|
+
export interface DecodeResult {
|
|
4
|
+
keyValues: Record<string, unknown>;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
/** Template map: key (e.g. enum value or "default") → template string with {{field}} placeholders. */
|
|
8
|
+
export type TemplateMap = Record<string, string>;
|
|
9
|
+
export declare class DcpDecoder {
|
|
10
|
+
private readonly schema;
|
|
11
|
+
private readonly templates;
|
|
12
|
+
private readonly templateField;
|
|
13
|
+
/**
|
|
14
|
+
* @param schema - DcpSchema instance
|
|
15
|
+
* @param templates - Optional templates keyed by enum value or "default".
|
|
16
|
+
* Use {{fieldName}} for interpolation.
|
|
17
|
+
* @param templateField - Field name whose value selects the template (typically an enum field).
|
|
18
|
+
* If omitted, auto-detects first enum field.
|
|
19
|
+
*/
|
|
20
|
+
constructor(schema: DcpSchema, templates?: TemplateMap, templateField?: string);
|
|
21
|
+
/** Decode a positional array into key-values and human-readable text. */
|
|
22
|
+
decode(row: unknown[], mask?: number): DecodeResult;
|
|
23
|
+
/** Decode multiple rows. */
|
|
24
|
+
decodeRows(rows: unknown[][], mask?: number): DecodeResult[];
|
|
25
|
+
/**
|
|
26
|
+
* Parse a raw DCP string (header + rows) and decode all rows.
|
|
27
|
+
* Handles $S header line, returns decoded results.
|
|
28
|
+
*/
|
|
29
|
+
decodeRaw(raw: string): {
|
|
30
|
+
header: unknown[];
|
|
31
|
+
results: DecodeResult[];
|
|
32
|
+
};
|
|
33
|
+
private renderTemplate;
|
|
34
|
+
private detectEnumField;
|
|
35
|
+
}
|
package/dist/decoder.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export class DcpDecoder {
|
|
2
|
+
schema;
|
|
3
|
+
templates;
|
|
4
|
+
templateField;
|
|
5
|
+
/**
|
|
6
|
+
* @param schema - DcpSchema instance
|
|
7
|
+
* @param templates - Optional templates keyed by enum value or "default".
|
|
8
|
+
* Use {{fieldName}} for interpolation.
|
|
9
|
+
* @param templateField - Field name whose value selects the template (typically an enum field).
|
|
10
|
+
* If omitted, auto-detects first enum field.
|
|
11
|
+
*/
|
|
12
|
+
constructor(schema, templates, templateField) {
|
|
13
|
+
this.schema = schema;
|
|
14
|
+
this.templates = templates ?? {};
|
|
15
|
+
this.templateField = templateField ?? this.detectEnumField();
|
|
16
|
+
}
|
|
17
|
+
/** Decode a positional array into key-values and human-readable text. */
|
|
18
|
+
decode(row, mask) {
|
|
19
|
+
const fields = mask != null
|
|
20
|
+
? this.schema.fieldsFromMask(mask)
|
|
21
|
+
: this.schema.fields;
|
|
22
|
+
const keyValues = {};
|
|
23
|
+
for (let i = 0; i < fields.length; i++) {
|
|
24
|
+
keyValues[fields[i]] = i < row.length ? row[i] : null;
|
|
25
|
+
}
|
|
26
|
+
const text = this.renderTemplate(keyValues);
|
|
27
|
+
return { keyValues, text };
|
|
28
|
+
}
|
|
29
|
+
/** Decode multiple rows. */
|
|
30
|
+
decodeRows(rows, mask) {
|
|
31
|
+
return rows.map((row) => this.decode(row, mask));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a raw DCP string (header + rows) and decode all rows.
|
|
35
|
+
* Handles $S header line, returns decoded results.
|
|
36
|
+
*/
|
|
37
|
+
decodeRaw(raw) {
|
|
38
|
+
const lines = raw
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map((l) => l.trim())
|
|
41
|
+
.filter((l) => l.length > 0);
|
|
42
|
+
if (lines.length === 0)
|
|
43
|
+
return { header: [], results: [] };
|
|
44
|
+
let headerLine = [];
|
|
45
|
+
let dataLines = lines;
|
|
46
|
+
// Check if first line is a $S header
|
|
47
|
+
const first = JSON.parse(lines[0]);
|
|
48
|
+
if (Array.isArray(first) && first[0] === "$S") {
|
|
49
|
+
headerLine = first;
|
|
50
|
+
dataLines = lines.slice(1);
|
|
51
|
+
}
|
|
52
|
+
const results = dataLines.map((line) => {
|
|
53
|
+
const row = JSON.parse(line);
|
|
54
|
+
return this.decode(row);
|
|
55
|
+
});
|
|
56
|
+
return { header: headerLine, results };
|
|
57
|
+
}
|
|
58
|
+
renderTemplate(kv) {
|
|
59
|
+
// Try value-specific template
|
|
60
|
+
if (this.templateField && this.templates) {
|
|
61
|
+
const val = kv[this.templateField];
|
|
62
|
+
if (typeof val === "string" && this.templates[val]) {
|
|
63
|
+
return interpolate(this.templates[val], kv);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Try default template
|
|
67
|
+
if (this.templates?.["default"]) {
|
|
68
|
+
return interpolate(this.templates["default"], kv);
|
|
69
|
+
}
|
|
70
|
+
// Fallback: field: value pairs
|
|
71
|
+
return this.schema.fields
|
|
72
|
+
.map((f) => {
|
|
73
|
+
const v = kv[f];
|
|
74
|
+
return v !== null && v !== undefined ? `${f}: ${v}` : null;
|
|
75
|
+
})
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join(" | ");
|
|
78
|
+
}
|
|
79
|
+
detectEnumField() {
|
|
80
|
+
for (const f of this.schema.fields) {
|
|
81
|
+
const ft = this.schema.types[f];
|
|
82
|
+
if (ft?.enum && ft.enum.length > 0)
|
|
83
|
+
return f;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Replace {{field}} placeholders. Nulls become empty string. */
|
|
89
|
+
function interpolate(template, kv) {
|
|
90
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
91
|
+
const val = kv[key];
|
|
92
|
+
return val !== null && val !== undefined ? String(val) : "";
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decoder.js","sourceRoot":"","sources":["../src/decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { DcpSchema } from "./schema.js";
|
|
4
|
+
import { DcpDecoder } from "./decoder.js";
|
|
5
|
+
const reportDef = {
|
|
6
|
+
$dcp: "schema",
|
|
7
|
+
id: "ctrl-report:v1",
|
|
8
|
+
description: "Task completion report controller",
|
|
9
|
+
fields: ["action", "target", "detail", "cost"],
|
|
10
|
+
fieldCount: 4,
|
|
11
|
+
types: {
|
|
12
|
+
action: { type: "string", enum: ["done", "error", "skip", "partial"] },
|
|
13
|
+
target: { type: "string" },
|
|
14
|
+
detail: { type: ["string", "null"] },
|
|
15
|
+
cost: { type: ["number", "null"] },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const reportTemplates = {
|
|
19
|
+
done: "✓ {{target}} — {{detail}} ({{cost}}ms)",
|
|
20
|
+
error: "✗ {{target}} — {{detail}}",
|
|
21
|
+
skip: "⊘ {{target}} skipped",
|
|
22
|
+
partial: "◐ {{target}} — {{detail}}",
|
|
23
|
+
default: "{{action}} {{target}} {{detail}}",
|
|
24
|
+
};
|
|
25
|
+
const logDef = {
|
|
26
|
+
$dcp: "schema",
|
|
27
|
+
id: "ctrl-log:v1",
|
|
28
|
+
description: "Event log controller",
|
|
29
|
+
fields: ["ts", "level", "source", "msg"],
|
|
30
|
+
fieldCount: 4,
|
|
31
|
+
types: {
|
|
32
|
+
ts: { type: "string" },
|
|
33
|
+
level: { type: "string", enum: ["info", "warn", "error", "debug"] },
|
|
34
|
+
source: { type: "string" },
|
|
35
|
+
msg: { type: "string" },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const logTemplates = {
|
|
39
|
+
error: "[{{ts}}] ERROR {{source}}: {{msg}}",
|
|
40
|
+
warn: "[{{ts}}] WARN {{source}}: {{msg}}",
|
|
41
|
+
info: "[{{ts}}] INFO {{source}}: {{msg}}",
|
|
42
|
+
default: "[{{ts}}] {{level}} {{source}}: {{msg}}",
|
|
43
|
+
};
|
|
44
|
+
describe("DcpDecoder", () => {
|
|
45
|
+
describe("decode", () => {
|
|
46
|
+
it("decodes report:done with template", () => {
|
|
47
|
+
const schema = new DcpSchema(reportDef);
|
|
48
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
49
|
+
const result = decoder.decode(["done", "/v1/auth", "200 ok", 42]);
|
|
50
|
+
assert.deepEqual(result.keyValues, {
|
|
51
|
+
action: "done",
|
|
52
|
+
target: "/v1/auth",
|
|
53
|
+
detail: "200 ok",
|
|
54
|
+
cost: 42,
|
|
55
|
+
});
|
|
56
|
+
assert.equal(result.text, "✓ /v1/auth — 200 ok (42ms)");
|
|
57
|
+
});
|
|
58
|
+
it("decodes report:error", () => {
|
|
59
|
+
const schema = new DcpSchema(reportDef);
|
|
60
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
61
|
+
const result = decoder.decode(["error", "/v1/orders", "timeout", 0]);
|
|
62
|
+
assert.equal(result.text, "✗ /v1/orders — timeout");
|
|
63
|
+
});
|
|
64
|
+
it("decodes report:skip with nulls", () => {
|
|
65
|
+
const schema = new DcpSchema(reportDef);
|
|
66
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
67
|
+
const result = decoder.decode(["skip", "cleanup", null, null]);
|
|
68
|
+
assert.equal(result.text, "⊘ cleanup skipped");
|
|
69
|
+
});
|
|
70
|
+
it("decodes log row with level-specific template", () => {
|
|
71
|
+
const schema = new DcpSchema(logDef);
|
|
72
|
+
const decoder = new DcpDecoder(schema, logTemplates, "level");
|
|
73
|
+
const result = decoder.decode([
|
|
74
|
+
"2026-03-28T14:30:00Z",
|
|
75
|
+
"error",
|
|
76
|
+
"db-writer",
|
|
77
|
+
"connection refused",
|
|
78
|
+
]);
|
|
79
|
+
assert.equal(result.text, "[2026-03-28T14:30:00Z] ERROR db-writer: connection refused");
|
|
80
|
+
});
|
|
81
|
+
it("falls back to field:value when no templates", () => {
|
|
82
|
+
const schema = new DcpSchema(reportDef);
|
|
83
|
+
const decoder = new DcpDecoder(schema);
|
|
84
|
+
const result = decoder.decode(["done", "/v1/auth", "ok", 42]);
|
|
85
|
+
assert.equal(result.text, "action: done | target: /v1/auth | detail: ok | cost: 42");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe("decodeRows", () => {
|
|
89
|
+
it("decodes multiple rows", () => {
|
|
90
|
+
const schema = new DcpSchema(reportDef);
|
|
91
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
92
|
+
const results = decoder.decodeRows([
|
|
93
|
+
["done", "/v1/auth", "200 ok", 42],
|
|
94
|
+
["error", "/v1/orders", "timeout", 0],
|
|
95
|
+
]);
|
|
96
|
+
assert.equal(results.length, 2);
|
|
97
|
+
assert.ok(results[0].text.includes("/v1/auth"));
|
|
98
|
+
assert.ok(results[1].text.includes("/v1/orders"));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("decodeRaw", () => {
|
|
102
|
+
it("parses header + rows from raw DCP string", () => {
|
|
103
|
+
const schema = new DcpSchema(reportDef);
|
|
104
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
105
|
+
const raw = [
|
|
106
|
+
'["$S","ctrl-report:v1","action","target","detail","cost"]',
|
|
107
|
+
'["done","/v1/auth","200 ok",42]',
|
|
108
|
+
'["error","/v1/orders","timeout",0]',
|
|
109
|
+
].join("\n");
|
|
110
|
+
const { header, results } = decoder.decodeRaw(raw);
|
|
111
|
+
assert.equal(header[0], "$S");
|
|
112
|
+
assert.equal(results.length, 2);
|
|
113
|
+
assert.ok(results[0].text.includes("✓"));
|
|
114
|
+
assert.ok(results[1].text.includes("✗"));
|
|
115
|
+
});
|
|
116
|
+
it("handles rows without header", () => {
|
|
117
|
+
const schema = new DcpSchema(reportDef);
|
|
118
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
119
|
+
const raw = '["done","/v1/auth","ok",42]';
|
|
120
|
+
const { header, results } = decoder.decodeRaw(raw);
|
|
121
|
+
assert.equal(header.length, 0);
|
|
122
|
+
assert.equal(results.length, 1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("with DcpSchema.validateRow", () => {
|
|
126
|
+
it("validate + decode roundtrip", () => {
|
|
127
|
+
const schema = new DcpSchema(reportDef);
|
|
128
|
+
const decoder = new DcpDecoder(schema, reportTemplates);
|
|
129
|
+
const row = ["done", "/v1/auth", "200 ok", 42];
|
|
130
|
+
const errors = schema.validateRow(row);
|
|
131
|
+
assert.equal(errors.length, 0);
|
|
132
|
+
const result = decoder.decode(row);
|
|
133
|
+
assert.equal(result.text, "✓ /v1/auth — 200 ok (42ms)");
|
|
134
|
+
});
|
|
135
|
+
it("validation catches bad enum", () => {
|
|
136
|
+
const schema = new DcpSchema(reportDef);
|
|
137
|
+
const row = ["invalid", "/v1/auth", "ok", 42];
|
|
138
|
+
const errors = schema.validateRow(row);
|
|
139
|
+
assert.ok(errors.length > 0);
|
|
140
|
+
assert.ok(errors[0].includes("not in enum"));
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=decoder.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decoder.test.js","sourceRoot":"","sources":["../src/decoder.test.ts"],"names":[],"mappings":"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"}
|