prompt-identifiers-baml 0.1.0 → 0.1.2

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 CHANGED
@@ -11,24 +11,22 @@ npm install prompt-identifiers-baml prompt-identifiers @boundaryml/baml
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { wrapBamlFunction } from 'prompt-identifiers-baml';
15
- import { b } from './baml_client';
14
+ import { wrapBamlFunction } from "prompt-identifiers-baml";
15
+ import { b } from "./baml_client";
16
16
 
17
17
  // Wrap your BAML function
18
18
  const analyzeUser = wrapBamlFunction(b.AnalyzeUser, {
19
- config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
19
+ config: { inputFormat: "UUID", outputFormat: "SafeNumeric" },
20
20
  });
21
21
 
22
22
  // Use normally - IDs are automatically encoded/decoded
23
23
  const result = await analyzeUser({
24
- user_id: '123e4567-e89b-42d3-a456-426655440000',
25
- items: [
26
- { id: '987fcdeb-51a2-43f7-8d9c-0123456789ab', name: 'Order 1' },
27
- ],
24
+ user_id: "123e4567-e89b-42d3-a456-426655440000",
25
+ items: [{ id: "987fcdeb-51a2-43f7-8d9c-0123456789ab", name: "Order 1" }],
28
26
  });
29
27
 
30
28
  // The BAML function receives:
31
- // { user_id: '<000>', items: [{ id: '<001>', name: 'Order 1' }] }
29
+ // { user_id: '~000~', items: [{ id: '~001~', name: 'Order 1' }] }
32
30
  //
33
31
  // You receive the response with original UUIDs restored
34
32
  ```
@@ -36,10 +34,10 @@ const result = await analyzeUser({
36
34
  ## How It Works
37
35
 
38
36
  1. **Before BAML call**: Deep traverses input object and encodes all ID fields
39
- - `123e4567-e89b-42d3-a456-426655440000` → `<000>`
37
+ - `123e4567-e89b-42d3-a456-426655440000` → `~000~`
40
38
 
41
39
  2. **After BAML call**: Deep traverses output object and decodes all placeholders
42
- - `<000>` → `123e4567-e89b-42d3-a456-426655440000`
40
+ - `~000~` → `123e4567-e89b-42d3-a456-426655440000`
43
41
 
44
42
  This is completely transparent - you work with real IDs, the LLM works with compact placeholders.
45
43
 
@@ -49,21 +47,29 @@ This is completely transparent - you work with real IDs, the LLM works with comp
49
47
  wrapBamlFunction(fn, {
50
48
  // Required: encoding configuration
51
49
  config: {
52
- inputFormat: 'UUID', // or 'ULID' or custom RegExp
53
- outputFormat: 'SafeNumeric', // or 'Numeric', 'IdToken', { template: '...' }
50
+ inputFormat: "UUID", // or 'ULID' or custom RegExp
51
+ outputFormat: "SafeNumeric", // or 'Numeric', 'IdToken', { template: '...' }
54
52
  },
55
53
 
56
54
  // Optional: specify which fields to encode
57
55
  // If not provided, all matching strings are encoded
58
- encodeFields: ['user_id', 'items[].id', 'metadata.owner_id'],
56
+ encodeFields: ["user_id", "items[].id", "metadata.owner_id"],
57
+
58
+ // Optional: enable debug mode for detailed diagnostics
59
+ debug: true,
59
60
 
60
61
  // Optional: callbacks for logging/debugging
61
62
  onEncode: (result) => {
62
- console.log(`Encoded ${result.encodedCount} IDs`);
63
- console.log('Mapping:', result.mapping);
63
+ console.log("Mapping:", result.mapping);
64
+ // debugData is only present when debug: true
65
+ if (result.debugData) {
66
+ console.log(`Encoded ${result.debugData.encodedCount} IDs in ${result.debugData.durationMs}ms`);
67
+ }
64
68
  },
65
69
  onDecode: (result) => {
66
- console.log(`Decoded ${result.decodedCount} placeholders`);
70
+ if (result.debugData) {
71
+ console.log(`Decoded ${result.debugData.decodedCount} placeholders in ${result.debugData.durationMs}ms`);
72
+ }
67
73
  },
68
74
  });
69
75
  ```
@@ -72,40 +78,40 @@ wrapBamlFunction(fn, {
72
78
 
73
79
  The `encodeFields` option supports dot notation and array wildcards:
74
80
 
75
- | Pattern | Description | Example Match |
76
- |---------|-------------|---------------|
77
- | `user_id` | Top-level field | `{ user_id: '...' }` |
78
- | `data.user_id` | Nested field | `{ data: { user_id: '...' } }` |
79
- | `items[].id` | Array item field | `{ items: [{ id: '...' }] }` |
80
- | `data.users[].profile.id` | Deep nested | `{ data: { users: [{ profile: { id: '...' } }] } }` |
81
+ | Pattern | Description | Example Match |
82
+ | ------------------------- | ---------------- | --------------------------------------------------- |
83
+ | `user_id` | Top-level field | `{ user_id: '...' }` |
84
+ | `data.user_id` | Nested field | `{ data: { user_id: '...' } }` |
85
+ | `items[].id` | Array item field | `{ items: [{ id: '...' }] }` |
86
+ | `data.users[].profile.id` | Deep nested | `{ data: { users: [{ profile: { id: '...' } }] } }` |
81
87
 
82
88
  ### Input Formats
83
89
 
84
- | Format | Description | Example |
85
- |--------|-------------|---------|
86
- | `'UUID'` | RFC 4122 UUIDs | `123e4567-e89b-42d3-a456-426655440000` |
87
- | `'ULID'` | Crockford Base32 ULIDs | `01ARZ3NDEKTSV4RRFFQ69G5FAV` |
88
- | `RegExp` | Custom pattern | `/user-\d{6}/gi` |
90
+ | Format | Description | Example |
91
+ | -------- | ---------------------- | -------------------------------------- |
92
+ | `'UUID'` | RFC 4122 UUIDs | `123e4567-e89b-42d3-a456-426655440000` |
93
+ | `'ULID'` | Crockford Base32 ULIDs | `01ARZ3NDEKTSV4RRFFQ69G5FAV` |
94
+ | `RegExp` | Custom pattern | `/user-\d{6}/gi` |
89
95
 
90
96
  ### Output Formats
91
97
 
92
- | Format | Description | Example |
93
- |--------|-------------|---------|
94
- | `'SafeNumeric'` | Collision-safe with angle brackets (recommended) | `<000>`, `<001>` |
95
- | `'Numeric'` | Simple numeric with smart triplet expansion | `000`, `001` |
96
- | `'IdToken'` | Base62 encoding | `0`, `A`, `z`, `10` |
97
- | `{ template: '...' }` | Custom template | `{ template: '[ID:{i}]' }` → `[ID:0]` |
98
+ | Format | Description | Example |
99
+ | --------------------- | ------------------------------------------------- | ------------------------------------- |
100
+ | `'SafeNumeric'` | Collision-safe with tildes (recommended) | `~000~`, `~001~` |
101
+ | `'Numeric'` | Simple numeric with smart triplet expansion | `000`, `001` |
102
+ | `'IdToken'` | Base62 encoding | `0`, `A`, `z`, `10` |
103
+ | `{ template: '...' }` | Custom template | `{ template: '[ID:{i}]' }` → `[ID:0]` |
98
104
 
99
105
  ## Streaming Support
100
106
 
101
107
  Use `wrapBamlStreamingFunction` for BAML streaming functions:
102
108
 
103
109
  ```typescript
104
- import { wrapBamlStreamingFunction } from 'prompt-identifiers-baml';
105
- import { b } from './baml_client';
110
+ import { wrapBamlStreamingFunction } from "prompt-identifiers-baml";
111
+ import { b } from "./baml_client";
106
112
 
107
113
  const streamAnalysis = wrapBamlStreamingFunction(b.stream.AnalyzeUser, {
108
- config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
114
+ config: { inputFormat: "UUID", outputFormat: "SafeNumeric" },
109
115
  });
110
116
 
111
117
  // IDs are decoded in real-time as partials arrive
@@ -121,15 +127,15 @@ for await (const partial of streamAnalysis({ user_id: uuid })) {
121
127
  Manually encode an object (useful for custom integrations):
122
128
 
123
129
  ```typescript
124
- import { encodeObject } from 'prompt-identifiers-baml';
130
+ import { encodeObject } from "prompt-identifiers-baml";
125
131
 
126
132
  const { encoded, mapping } = encodeObject(
127
- { user_id: 'uuid-here', data: { owner: 'other-uuid' } },
128
- { inputFormat: 'UUID', outputFormat: 'SafeNumeric' }
133
+ { user_id: "uuid-here", data: { owner: "other-uuid" } },
134
+ { inputFormat: "UUID", outputFormat: "SafeNumeric" }
129
135
  );
130
136
 
131
- // encoded: { user_id: '<000>', data: { owner: '<001>' } }
132
- // mapping: { '<000>': 'uuid-here', '<001>': 'other-uuid' }
137
+ // encoded: { user_id: '~000~', data: { owner: '~001~' } }
138
+ // mapping: { '~000~': 'uuid-here', '~001~': 'other-uuid' }
133
139
  ```
134
140
 
135
141
  ### decodeObject
@@ -137,11 +143,11 @@ const { encoded, mapping } = encodeObject(
137
143
  Manually decode an object:
138
144
 
139
145
  ```typescript
140
- import { decodeObject } from 'prompt-identifiers-baml';
146
+ import { decodeObject } from "prompt-identifiers-baml";
141
147
 
142
148
  const decoded = decodeObject(
143
- { user_id: '<000>', summary: 'User <000> is active' },
144
- { '<000>': 'uuid-here' }
149
+ { user_id: "~000~", summary: "User ~000~ is active" },
150
+ { "~000~": "uuid-here" }
145
151
  );
146
152
 
147
153
  // decoded: { user_id: 'uuid-here', summary: 'User uuid-here is active' }
@@ -165,11 +171,13 @@ const result: AnalyzeUserOutput = await analyzeUser({
165
171
  ## Auto-Detection vs Explicit Fields
166
172
 
167
173
  **Auto-detection mode** (no `encodeFields`):
174
+
168
175
  - Encodes ALL string fields matching the input pattern
169
176
  - Simple to use, works for most cases
170
177
  - May encode fields you don't want encoded
171
178
 
172
179
  **Explicit fields** (with `encodeFields`):
180
+
173
181
  - Only encodes specified fields
174
182
  - More precise control
175
183
  - Recommended for production use
@@ -177,13 +185,13 @@ const result: AnalyzeUserOutput = await analyzeUser({
177
185
  ```typescript
178
186
  // Auto-detection: all UUIDs encoded
179
187
  const wrapped1 = wrapBamlFunction(fn, {
180
- config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
188
+ config: { inputFormat: "UUID", outputFormat: "SafeNumeric" },
181
189
  });
182
190
 
183
191
  // Explicit: only user_id and item IDs encoded
184
192
  const wrapped2 = wrapBamlFunction(fn, {
185
- config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
186
- encodeFields: ['user_id', 'items[].id'],
193
+ config: { inputFormat: "UUID", outputFormat: "SafeNumeric" },
194
+ encodeFields: ["user_id", "items[].id"],
187
195
  });
188
196
  ```
189
197
 
package/dist/index.d.mts CHANGED
@@ -7,6 +7,28 @@ import { EncodeConfig } from 'prompt-identifiers';
7
7
  * in inputs and decode them in outputs.
8
8
  */
9
9
 
10
+ /** Debug data included in onEncode callback when debug is true */
11
+ interface EncodeDebugData {
12
+ /** Number of unique IDs encoded */
13
+ encodedCount: number;
14
+ /** Original input object before encoding */
15
+ input: unknown;
16
+ /** Encoded input object */
17
+ output: unknown;
18
+ /** Time spent encoding in milliseconds */
19
+ durationMs: number;
20
+ }
21
+ /** Debug data included in onDecode callback when debug is true */
22
+ interface DecodeDebugData {
23
+ /** Number of fields containing decoded placeholders */
24
+ decodedCount: number;
25
+ /** Raw output from LLM (encoded) */
26
+ input: unknown;
27
+ /** Decoded output with original IDs restored */
28
+ output: unknown;
29
+ /** Time spent decoding in milliseconds */
30
+ durationMs: number;
31
+ }
10
32
  /** Configuration options for the BAML wrapper */
11
33
  interface WrapBamlFunctionOptions {
12
34
  /** Encoding configuration (inputFormat and outputFormat) */
@@ -25,18 +47,26 @@ interface WrapBamlFunctionOptions {
25
47
  * encodeFields: ['user_id', 'items[].id', 'metadata.owner_id']
26
48
  */
27
49
  encodeFields?: string[];
50
+ /**
51
+ * Enable debug mode to populate debugData in callbacks with
52
+ * input/output snapshots, counts, and timing information.
53
+ */
54
+ debug?: boolean;
28
55
  /**
29
56
  * Optional callback fired after encoding IDs in the input.
57
+ * Receives the placeholder→ID mapping. When debug is true,
58
+ * also receives debugData with input, output, counts, and timing.
30
59
  */
31
60
  onEncode?: (result: {
32
61
  mapping: Record<string, string>;
33
- encodedCount: number;
62
+ debugData?: EncodeDebugData;
34
63
  }) => void;
35
64
  /**
36
65
  * Optional callback fired after decoding IDs in the output.
66
+ * When debug is true, receives debugData with input, output, counts, and timing.
37
67
  */
38
68
  onDecode?: (result: {
39
- decodedCount: number;
69
+ debugData?: DecodeDebugData;
40
70
  }) => void;
41
71
  }
42
72
  /** A BAML function type (sync or async) */
@@ -111,4 +141,4 @@ declare function encodeObject<T>(obj: T, config: EncodeConfig, encodeFields?: st
111
141
  */
112
142
  declare function decodeObject<T>(obj: T, mapping: Record<string, string>): T;
113
143
 
114
- export { type BamlFunction, type BamlStreamingFunction, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
144
+ export { type BamlFunction, type BamlStreamingFunction, type DecodeDebugData, type EncodeDebugData, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,28 @@ import { EncodeConfig } from 'prompt-identifiers';
7
7
  * in inputs and decode them in outputs.
8
8
  */
9
9
 
10
+ /** Debug data included in onEncode callback when debug is true */
11
+ interface EncodeDebugData {
12
+ /** Number of unique IDs encoded */
13
+ encodedCount: number;
14
+ /** Original input object before encoding */
15
+ input: unknown;
16
+ /** Encoded input object */
17
+ output: unknown;
18
+ /** Time spent encoding in milliseconds */
19
+ durationMs: number;
20
+ }
21
+ /** Debug data included in onDecode callback when debug is true */
22
+ interface DecodeDebugData {
23
+ /** Number of fields containing decoded placeholders */
24
+ decodedCount: number;
25
+ /** Raw output from LLM (encoded) */
26
+ input: unknown;
27
+ /** Decoded output with original IDs restored */
28
+ output: unknown;
29
+ /** Time spent decoding in milliseconds */
30
+ durationMs: number;
31
+ }
10
32
  /** Configuration options for the BAML wrapper */
11
33
  interface WrapBamlFunctionOptions {
12
34
  /** Encoding configuration (inputFormat and outputFormat) */
@@ -25,18 +47,26 @@ interface WrapBamlFunctionOptions {
25
47
  * encodeFields: ['user_id', 'items[].id', 'metadata.owner_id']
26
48
  */
27
49
  encodeFields?: string[];
50
+ /**
51
+ * Enable debug mode to populate debugData in callbacks with
52
+ * input/output snapshots, counts, and timing information.
53
+ */
54
+ debug?: boolean;
28
55
  /**
29
56
  * Optional callback fired after encoding IDs in the input.
57
+ * Receives the placeholder→ID mapping. When debug is true,
58
+ * also receives debugData with input, output, counts, and timing.
30
59
  */
31
60
  onEncode?: (result: {
32
61
  mapping: Record<string, string>;
33
- encodedCount: number;
62
+ debugData?: EncodeDebugData;
34
63
  }) => void;
35
64
  /**
36
65
  * Optional callback fired after decoding IDs in the output.
66
+ * When debug is true, receives debugData with input, output, counts, and timing.
37
67
  */
38
68
  onDecode?: (result: {
39
- decodedCount: number;
69
+ debugData?: DecodeDebugData;
40
70
  }) => void;
41
71
  }
42
72
  /** A BAML function type (sync or async) */
@@ -111,4 +141,4 @@ declare function encodeObject<T>(obj: T, config: EncodeConfig, encodeFields?: st
111
141
  */
112
142
  declare function decodeObject<T>(obj: T, mapping: Record<string, string>): T;
113
143
 
114
- export { type BamlFunction, type BamlStreamingFunction, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
144
+ export { type BamlFunction, type BamlStreamingFunction, type DecodeDebugData, type EncodeDebugData, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
package/dist/index.js CHANGED
@@ -70,94 +70,6 @@ function matchesFieldPath(currentPath, targetSegments) {
70
70
  }
71
71
  return true;
72
72
  }
73
- function getInputPattern(inputFormat) {
74
- if (inputFormat instanceof RegExp) {
75
- const flags = inputFormat.flags.includes("g") ? inputFormat.flags : inputFormat.flags + "g";
76
- return new RegExp(inputFormat.source, flags);
77
- }
78
- if (inputFormat === "UUID") {
79
- return /\b[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
80
- }
81
- return /\b[0-9A-HJKMNP-TV-Z]{26}\b/gi;
82
- }
83
- function formatPlaceholder(outputFormat, index) {
84
- if (outputFormat === "SafeNumeric") {
85
- const s = index.toString();
86
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
87
- return `<${s.padStart(width, "0")}>`;
88
- }
89
- if (outputFormat === "Numeric") {
90
- const s = index.toString();
91
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
92
- return s.padStart(width, "0");
93
- }
94
- if (outputFormat === "IdToken") {
95
- const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
96
- if (index < 62) return BASE62[index];
97
- let result = "";
98
- let n = index;
99
- while (n > 0) {
100
- result = BASE62[n % 62] + result;
101
- n = Math.floor(n / 62);
102
- }
103
- return result;
104
- }
105
- if (outputFormat === "Passthrough") {
106
- return "";
107
- }
108
- if (typeof outputFormat === "function") {
109
- return outputFormat(index);
110
- }
111
- const template = outputFormat.template;
112
- const match = template.match(/\{i(?::([^}]+))?\}/);
113
- if (!match) return `${index}`;
114
- const specifier = match[1];
115
- let formatted;
116
- if (!specifier) {
117
- formatted = index.toString();
118
- } else if (specifier === "base62") {
119
- const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
120
- if (index < 62) formatted = BASE62[index];
121
- else {
122
- formatted = "";
123
- let n = index;
124
- while (n > 0) {
125
- formatted = BASE62[n % 62] + formatted;
126
- n = Math.floor(n / 62);
127
- }
128
- }
129
- } else if (specifier === "zeroFilled") {
130
- const s = index.toString();
131
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
132
- formatted = s.padStart(width, "0");
133
- } else {
134
- const padMatch = specifier.match(/^(\d+)$/);
135
- if (padMatch) {
136
- formatted = index.toString().padStart(parseInt(padMatch[1], 10), "0");
137
- } else {
138
- formatted = index.toString();
139
- }
140
- }
141
- return template.replace(match[0], formatted);
142
- }
143
- function encodeStringWithContext(text, ctx) {
144
- if (ctx.config.outputFormat === "Passthrough") {
145
- return text;
146
- }
147
- const pattern = getInputPattern(ctx.config.inputFormat);
148
- pattern.lastIndex = 0;
149
- return text.replace(pattern, (match) => {
150
- const id = match.toLowerCase();
151
- if (ctx.idToPlaceholder.has(id)) {
152
- return ctx.idToPlaceholder.get(id);
153
- }
154
- const placeholder = formatPlaceholder(ctx.config.outputFormat, ctx.nextIndex);
155
- ctx.nextIndex++;
156
- ctx.idToPlaceholder.set(id, placeholder);
157
- ctx.mapping[placeholder] = id;
158
- return placeholder;
159
- });
160
- }
161
73
  function deepEncode(value, ctx, path = []) {
162
74
  if (value === null || value === void 0) {
163
75
  return value;
@@ -167,12 +79,10 @@ function deepEncode(value, ctx, path = []) {
167
79
  if (!shouldEncode) {
168
80
  return value;
169
81
  }
170
- return encodeStringWithContext(value, ctx);
82
+ return (0, import_prompt_identifiers.encode)(value, ctx.config, ctx.state).encoded;
171
83
  }
172
84
  if (Array.isArray(value)) {
173
- return value.map(
174
- (item, index) => deepEncode(item, ctx, [...path, String(index)])
175
- );
85
+ return value.map((item, index) => deepEncode(item, ctx, [...path, String(index)]));
176
86
  }
177
87
  if (typeof value === "object") {
178
88
  const result = {};
@@ -207,57 +117,93 @@ function deepDecode(value, mapping, countRef) {
207
117
  return value;
208
118
  }
209
119
  function wrapBamlFunction(fn, options) {
210
- const { config, encodeFields, onEncode, onDecode } = options;
120
+ const { config, encodeFields, onEncode, onDecode, debug } = options;
211
121
  const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
212
122
  return async (input) => {
213
123
  const ctx = {
214
124
  config,
215
125
  fieldPaths,
216
- idToPlaceholder: /* @__PURE__ */ new Map(),
217
- mapping: {},
218
- nextIndex: 0
126
+ state: (0, import_prompt_identifiers.createEncodeState)()
219
127
  };
128
+ const startEncode = debug ? performance.now() : 0;
220
129
  const encodedInput = deepEncode(input, ctx);
130
+ const encodeDurationMs = debug ? performance.now() - startEncode : 0;
221
131
  onEncode?.({
222
- mapping: ctx.mapping,
223
- encodedCount: Object.keys(ctx.mapping).length
132
+ mapping: ctx.state.mapping,
133
+ ...debug && {
134
+ debugData: {
135
+ encodedCount: Object.keys(ctx.state.mapping).length,
136
+ input,
137
+ output: encodedInput,
138
+ durationMs: encodeDurationMs
139
+ }
140
+ }
224
141
  });
225
142
  const output = await fn(encodedInput);
226
143
  const countRef = { count: 0 };
227
- const decodedOutput = deepDecode(output, ctx.mapping, countRef);
228
- onDecode?.({ decodedCount: countRef.count });
144
+ const startDecode = debug ? performance.now() : 0;
145
+ const decodedOutput = deepDecode(output, ctx.state.mapping, countRef);
146
+ const decodeDurationMs = debug ? performance.now() - startDecode : 0;
147
+ onDecode?.({
148
+ ...debug && {
149
+ debugData: {
150
+ decodedCount: countRef.count,
151
+ input: output,
152
+ output: decodedOutput,
153
+ durationMs: decodeDurationMs
154
+ }
155
+ }
156
+ });
229
157
  return decodedOutput;
230
158
  };
231
159
  }
232
160
  function wrapBamlStreamingFunction(fn, options) {
233
- const { config, encodeFields, onEncode, onDecode } = options;
161
+ const { config, encodeFields, onEncode, onDecode, debug } = options;
234
162
  const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
235
163
  return async function* (input) {
236
164
  const ctx = {
237
165
  config,
238
166
  fieldPaths,
239
- idToPlaceholder: /* @__PURE__ */ new Map(),
240
- mapping: {},
241
- nextIndex: 0
167
+ state: (0, import_prompt_identifiers.createEncodeState)()
242
168
  };
169
+ const startEncode = debug ? performance.now() : 0;
243
170
  const encodedInput = deepEncode(input, ctx);
171
+ const encodeDurationMs = debug ? performance.now() - startEncode : 0;
244
172
  onEncode?.({
245
- mapping: ctx.mapping,
246
- encodedCount: Object.keys(ctx.mapping).length
173
+ mapping: ctx.state.mapping,
174
+ ...debug && {
175
+ debugData: {
176
+ encodedCount: Object.keys(ctx.state.mapping).length,
177
+ input,
178
+ output: encodedInput,
179
+ durationMs: encodeDurationMs
180
+ }
181
+ }
247
182
  });
248
183
  const generator = fn(encodedInput);
249
184
  let totalDecoded = 0;
185
+ const startDecode = debug ? performance.now() : 0;
250
186
  while (true) {
251
187
  const { value, done } = await generator.next();
252
188
  if (done) {
253
189
  const countRef2 = { count: 0 };
254
- const decodedValue2 = deepDecode(value, ctx.mapping, countRef2);
190
+ const decodedValue2 = deepDecode(value, ctx.state.mapping, countRef2);
255
191
  totalDecoded += countRef2.count;
256
- onDecode?.({ decodedCount: totalDecoded });
192
+ const decodeDurationMs = debug ? performance.now() - startDecode : 0;
193
+ onDecode?.({
194
+ ...debug && {
195
+ debugData: {
196
+ decodedCount: totalDecoded,
197
+ input: value,
198
+ output: decodedValue2,
199
+ durationMs: decodeDurationMs
200
+ }
201
+ }
202
+ });
257
203
  return decodedValue2;
258
204
  }
259
205
  const countRef = { count: 0 };
260
- const decodedValue = deepDecode(value, ctx.mapping, countRef);
206
+ const decodedValue = deepDecode(value, ctx.state.mapping, countRef);
261
207
  totalDecoded += countRef.count;
262
208
  yield decodedValue;
263
209
  }
@@ -268,12 +214,10 @@ function encodeObject(obj, config, encodeFields) {
268
214
  const ctx = {
269
215
  config,
270
216
  fieldPaths,
271
- idToPlaceholder: /* @__PURE__ */ new Map(),
272
- mapping: {},
273
- nextIndex: 0
217
+ state: (0, import_prompt_identifiers.createEncodeState)()
274
218
  };
275
219
  const encoded = deepEncode(obj, ctx);
276
- return { encoded, mapping: ctx.mapping };
220
+ return { encoded, mapping: ctx.state.mapping };
277
221
  }
278
222
  function decodeObject(obj, mapping) {
279
223
  const countRef = { count: 0 };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { decode } from "prompt-identifiers";
2
+ import { createEncodeState, decode, encode } from "prompt-identifiers";
3
3
  function parseFieldPath(path) {
4
4
  const segments = [];
5
5
  let current = "";
@@ -43,94 +43,6 @@ function matchesFieldPath(currentPath, targetSegments) {
43
43
  }
44
44
  return true;
45
45
  }
46
- function getInputPattern(inputFormat) {
47
- if (inputFormat instanceof RegExp) {
48
- const flags = inputFormat.flags.includes("g") ? inputFormat.flags : inputFormat.flags + "g";
49
- return new RegExp(inputFormat.source, flags);
50
- }
51
- if (inputFormat === "UUID") {
52
- return /\b[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
53
- }
54
- return /\b[0-9A-HJKMNP-TV-Z]{26}\b/gi;
55
- }
56
- function formatPlaceholder(outputFormat, index) {
57
- if (outputFormat === "SafeNumeric") {
58
- const s = index.toString();
59
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
60
- return `<${s.padStart(width, "0")}>`;
61
- }
62
- if (outputFormat === "Numeric") {
63
- const s = index.toString();
64
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
65
- return s.padStart(width, "0");
66
- }
67
- if (outputFormat === "IdToken") {
68
- const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
69
- if (index < 62) return BASE62[index];
70
- let result = "";
71
- let n = index;
72
- while (n > 0) {
73
- result = BASE62[n % 62] + result;
74
- n = Math.floor(n / 62);
75
- }
76
- return result;
77
- }
78
- if (outputFormat === "Passthrough") {
79
- return "";
80
- }
81
- if (typeof outputFormat === "function") {
82
- return outputFormat(index);
83
- }
84
- const template = outputFormat.template;
85
- const match = template.match(/\{i(?::([^}]+))?\}/);
86
- if (!match) return `${index}`;
87
- const specifier = match[1];
88
- let formatted;
89
- if (!specifier) {
90
- formatted = index.toString();
91
- } else if (specifier === "base62") {
92
- const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
93
- if (index < 62) formatted = BASE62[index];
94
- else {
95
- formatted = "";
96
- let n = index;
97
- while (n > 0) {
98
- formatted = BASE62[n % 62] + formatted;
99
- n = Math.floor(n / 62);
100
- }
101
- }
102
- } else if (specifier === "zeroFilled") {
103
- const s = index.toString();
104
- const width = Math.max(3, Math.ceil(s.length / 3) * 3);
105
- formatted = s.padStart(width, "0");
106
- } else {
107
- const padMatch = specifier.match(/^(\d+)$/);
108
- if (padMatch) {
109
- formatted = index.toString().padStart(parseInt(padMatch[1], 10), "0");
110
- } else {
111
- formatted = index.toString();
112
- }
113
- }
114
- return template.replace(match[0], formatted);
115
- }
116
- function encodeStringWithContext(text, ctx) {
117
- if (ctx.config.outputFormat === "Passthrough") {
118
- return text;
119
- }
120
- const pattern = getInputPattern(ctx.config.inputFormat);
121
- pattern.lastIndex = 0;
122
- return text.replace(pattern, (match) => {
123
- const id = match.toLowerCase();
124
- if (ctx.idToPlaceholder.has(id)) {
125
- return ctx.idToPlaceholder.get(id);
126
- }
127
- const placeholder = formatPlaceholder(ctx.config.outputFormat, ctx.nextIndex);
128
- ctx.nextIndex++;
129
- ctx.idToPlaceholder.set(id, placeholder);
130
- ctx.mapping[placeholder] = id;
131
- return placeholder;
132
- });
133
- }
134
46
  function deepEncode(value, ctx, path = []) {
135
47
  if (value === null || value === void 0) {
136
48
  return value;
@@ -140,12 +52,10 @@ function deepEncode(value, ctx, path = []) {
140
52
  if (!shouldEncode) {
141
53
  return value;
142
54
  }
143
- return encodeStringWithContext(value, ctx);
55
+ return encode(value, ctx.config, ctx.state).encoded;
144
56
  }
145
57
  if (Array.isArray(value)) {
146
- return value.map(
147
- (item, index) => deepEncode(item, ctx, [...path, String(index)])
148
- );
58
+ return value.map((item, index) => deepEncode(item, ctx, [...path, String(index)]));
149
59
  }
150
60
  if (typeof value === "object") {
151
61
  const result = {};
@@ -180,57 +90,93 @@ function deepDecode(value, mapping, countRef) {
180
90
  return value;
181
91
  }
182
92
  function wrapBamlFunction(fn, options) {
183
- const { config, encodeFields, onEncode, onDecode } = options;
93
+ const { config, encodeFields, onEncode, onDecode, debug } = options;
184
94
  const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
185
95
  return async (input) => {
186
96
  const ctx = {
187
97
  config,
188
98
  fieldPaths,
189
- idToPlaceholder: /* @__PURE__ */ new Map(),
190
- mapping: {},
191
- nextIndex: 0
99
+ state: createEncodeState()
192
100
  };
101
+ const startEncode = debug ? performance.now() : 0;
193
102
  const encodedInput = deepEncode(input, ctx);
103
+ const encodeDurationMs = debug ? performance.now() - startEncode : 0;
194
104
  onEncode?.({
195
- mapping: ctx.mapping,
196
- encodedCount: Object.keys(ctx.mapping).length
105
+ mapping: ctx.state.mapping,
106
+ ...debug && {
107
+ debugData: {
108
+ encodedCount: Object.keys(ctx.state.mapping).length,
109
+ input,
110
+ output: encodedInput,
111
+ durationMs: encodeDurationMs
112
+ }
113
+ }
197
114
  });
198
115
  const output = await fn(encodedInput);
199
116
  const countRef = { count: 0 };
200
- const decodedOutput = deepDecode(output, ctx.mapping, countRef);
201
- onDecode?.({ decodedCount: countRef.count });
117
+ const startDecode = debug ? performance.now() : 0;
118
+ const decodedOutput = deepDecode(output, ctx.state.mapping, countRef);
119
+ const decodeDurationMs = debug ? performance.now() - startDecode : 0;
120
+ onDecode?.({
121
+ ...debug && {
122
+ debugData: {
123
+ decodedCount: countRef.count,
124
+ input: output,
125
+ output: decodedOutput,
126
+ durationMs: decodeDurationMs
127
+ }
128
+ }
129
+ });
202
130
  return decodedOutput;
203
131
  };
204
132
  }
205
133
  function wrapBamlStreamingFunction(fn, options) {
206
- const { config, encodeFields, onEncode, onDecode } = options;
134
+ const { config, encodeFields, onEncode, onDecode, debug } = options;
207
135
  const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
208
136
  return async function* (input) {
209
137
  const ctx = {
210
138
  config,
211
139
  fieldPaths,
212
- idToPlaceholder: /* @__PURE__ */ new Map(),
213
- mapping: {},
214
- nextIndex: 0
140
+ state: createEncodeState()
215
141
  };
142
+ const startEncode = debug ? performance.now() : 0;
216
143
  const encodedInput = deepEncode(input, ctx);
144
+ const encodeDurationMs = debug ? performance.now() - startEncode : 0;
217
145
  onEncode?.({
218
- mapping: ctx.mapping,
219
- encodedCount: Object.keys(ctx.mapping).length
146
+ mapping: ctx.state.mapping,
147
+ ...debug && {
148
+ debugData: {
149
+ encodedCount: Object.keys(ctx.state.mapping).length,
150
+ input,
151
+ output: encodedInput,
152
+ durationMs: encodeDurationMs
153
+ }
154
+ }
220
155
  });
221
156
  const generator = fn(encodedInput);
222
157
  let totalDecoded = 0;
158
+ const startDecode = debug ? performance.now() : 0;
223
159
  while (true) {
224
160
  const { value, done } = await generator.next();
225
161
  if (done) {
226
162
  const countRef2 = { count: 0 };
227
- const decodedValue2 = deepDecode(value, ctx.mapping, countRef2);
163
+ const decodedValue2 = deepDecode(value, ctx.state.mapping, countRef2);
228
164
  totalDecoded += countRef2.count;
229
- onDecode?.({ decodedCount: totalDecoded });
165
+ const decodeDurationMs = debug ? performance.now() - startDecode : 0;
166
+ onDecode?.({
167
+ ...debug && {
168
+ debugData: {
169
+ decodedCount: totalDecoded,
170
+ input: value,
171
+ output: decodedValue2,
172
+ durationMs: decodeDurationMs
173
+ }
174
+ }
175
+ });
230
176
  return decodedValue2;
231
177
  }
232
178
  const countRef = { count: 0 };
233
- const decodedValue = deepDecode(value, ctx.mapping, countRef);
179
+ const decodedValue = deepDecode(value, ctx.state.mapping, countRef);
234
180
  totalDecoded += countRef.count;
235
181
  yield decodedValue;
236
182
  }
@@ -241,12 +187,10 @@ function encodeObject(obj, config, encodeFields) {
241
187
  const ctx = {
242
188
  config,
243
189
  fieldPaths,
244
- idToPlaceholder: /* @__PURE__ */ new Map(),
245
- mapping: {},
246
- nextIndex: 0
190
+ state: createEncodeState()
247
191
  };
248
192
  const encoded = deepEncode(obj, ctx);
249
- return { encoded, mapping: ctx.mapping };
193
+ return { encoded, mapping: ctx.state.mapping };
250
194
  }
251
195
  function decodeObject(obj, mapping) {
252
196
  const countRef = { count: 0 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-identifiers-baml",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "BAML integration for prompt-identifiers. Efficient ID compression for token optimization.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -39,7 +39,7 @@
39
39
  "url": "https://github.com/fogx/prompt-identifiers"
40
40
  },
41
41
  "peerDependencies": {
42
- "prompt-identifiers": ">=0.1.0",
42
+ "prompt-identifiers": ">=0.1.2",
43
43
  "@boundaryml/baml": ">=0.70.0"
44
44
  },
45
45
  "peerDependenciesMeta": {
@@ -54,7 +54,7 @@
54
54
  "ts-jest": "^29.0.0",
55
55
  "tsup": "^8.5.1",
56
56
  "typescript": "^5.0.0",
57
- "prompt-identifiers": "0.1.0"
57
+ "prompt-identifiers": "0.1.2"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",