@prompd/core 0.5.3 → 0.5.4

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
@@ -1,127 +1,221 @@
1
- # @prompd/core
2
-
3
- Environment-agnostic core for [Prompd](https://prompd.app) — the `.prmd` parser,
4
- the compilation pipeline (Nunjucks templating + output formatters), the `.pdflow`
5
- workflow parser, and an injectable file-system / package-resolver.
6
-
7
- It has **no Node-only imports**, so the same code runs in Node, the browser, and the
8
- backend. Anything platform-specific (disk access, the registry client) is injected
9
- through the `IFileSystem` / `IPackageResolver` interfaces.
10
-
11
- > Status: **beta** (`0.5.x-beta`). The API may still shift before `1.0`.
12
-
13
- ## Install
14
-
15
- ```bash
16
- npm install @prompd/core
17
- # or: pnpm add @prompd/core
18
- ```
19
-
20
- Dual-published as ESM and CommonJS, with TypeScript types.
21
-
22
- ## Quick start
23
-
24
- A `.prmd` file is YAML frontmatter + a Jinja2/Nunjucks body with typed parameters:
25
-
26
- ```ts
27
- import { compile } from '@prompd/core';
28
-
29
- const source = `---
30
- id: greeting
31
- name: Greeting
32
- version: 1.0.0
33
- parameters:
34
- - name: who
35
- type: string
36
- default: World
37
- ---
38
- Hello {{ who }}!`;
39
-
40
- // compile(source, outputFormat?, parameters?, options?) => Promise<string>
41
- await compile(source); // -> "Hello World!" (markdown, default)
42
- await compile(source, 'markdown', { who: 'Prompd' }); // -> "Hello Prompd!"
43
- await compile(source, 'openai', { who: 'Prompd' }); // -> OpenAI chat JSON
44
- await compile(source, 'anthropic',{ who: 'Prompd' }); // -> Anthropic messages JSON
45
- ```
46
-
47
- Output formats: `markdown` (default), `openai`, `anthropic`.
48
-
49
- ## Inheritance, includes & packages
50
-
51
- `inherits:` and `{% include %}` resolve against other files through an injected
52
- `IFileSystem`. In the browser, supply a `MemoryFileSystem`; on the server, supply
53
- your own disk-backed implementation. A package resolver is injected the same way
54
- (omit it where packages aren't available, e.g. the browser).
55
-
56
- ```ts
57
- import { compile, MemoryFileSystem } from '@prompd/core';
58
-
59
- const fs = new MemoryFileSystem({
60
- 'base.prmd': `---
61
- id: base
62
- name: Base
63
- version: 1.0.0
64
- ---
65
- {% block body %}{% endblock %}`,
66
- 'child.prmd': `---
67
- id: child
68
- name: Child
69
- version: 1.0.0
70
- inherits: base.prmd
71
- ---
72
- {% block body %}Hi {{ who }}{% endblock %}`,
73
- });
74
-
75
- const child = await fs.read('child.prmd');
76
- const out = await compile(child, 'markdown', { who: 'there' }, { fileSystem: fs });
77
- ```
78
-
79
- ## Parameters
80
-
81
- Declared in frontmatter and validated/coerced at compile time. Types: `string`,
82
- `number`, `integer`, `float`, `boolean`, `array`, `object`, `json`, `file`,
83
- `base64`, and `date` / `datetime`.
84
-
85
- `date` / `datetime` defaults (and provided values) may be **relative expressions**
86
- resolved at compile time, so each run uses the current date:
87
-
88
- ```yaml
89
- parameters:
90
- - name: start
91
- type: date
92
- default: "now-7d" # also: now, today, now+1w, now-3m, now-1y, now-2h, now-30min
93
- - name: when
94
- type: datetime
95
- default: "now" # -> "YYYY-MM-DDTHH:mm:ss"
96
- ```
97
-
98
- ISO / date-parseable literals (`"2026-01-15"`) pass through unchanged.
99
-
100
- ## Workflows (`.pdflow`)
101
-
102
- ```ts
103
- import { parseWorkflow, getExecutionOrder, validateWorkflow } from '@prompd/core';
104
-
105
- const { file, errors, warnings } = parseWorkflow(jsonText);
106
- const order = getExecutionOrder(file); // topological node order
107
- const result = validateWorkflow(file);
108
- ```
109
-
110
- ## What's exported
111
-
112
- - **Parser:** `PrompdParser`
113
- - **Compiler:** `compile`, `PrompdCompiler`, `CompilerPipeline`, the stages, and the
114
- formatters (`MarkdownFormatter`, `OpenAIFormatter`, `AnthropicFormatter`)
115
- - **File system / packages:** `IFileSystem`, `MemoryFileSystem`, `HybridFileSystem`,
116
- `IPackageResolver`
117
- - **Workflows:** `parseWorkflow`, `getExecutionOrder`, `validateWorkflow`,
118
- `createWorkflowNode`, plus the `WorkflowFile` / node-data types
119
- - **Types & errors:** `PrompdMetadata`, `PrompdParameter`, `CompilationOptions`,
120
- `CompilationError`, `ValidationError`, …
121
-
122
- See the bundled `dist/index.d.ts` for the full surface.
123
-
124
- ## License
125
-
126
- [MIT](./LICENSE) © 2024–2026 Prompd LLC. (The Prompd registry and hosted services
127
- are separately licensed.)
1
+ # @prompd/core
2
+
3
+ Environment-agnostic core for [Prompd](https://prompd.app) — the `.prmd` parser,
4
+ the compilation pipeline (Nunjucks templating + output formatters), the `.pdflow`
5
+ workflow parser, and an injectable file-system / package-resolver.
6
+
7
+ It has **no Node-only imports**, so the same code runs in Node, the browser, and the
8
+ backend. Anything platform-specific (disk access, the registry client) is injected
9
+ through the `IFileSystem` / `IPackageResolver` interfaces.
10
+
11
+ > Status: **beta** (`0.5.x-beta`). The API may still shift before `1.0`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @prompd/core
17
+ # or: pnpm add @prompd/core
18
+ ```
19
+
20
+ Dual-published as ESM and CommonJS, with TypeScript types.
21
+
22
+ ## Quick start
23
+
24
+ A `.prmd` file is YAML frontmatter + a Jinja2/Nunjucks body with typed parameters:
25
+
26
+ ```ts
27
+ import { compile } from '@prompd/core';
28
+
29
+ const source = `---
30
+ id: greeting
31
+ name: Greeting
32
+ version: 1.0.0
33
+ parameters:
34
+ - name: who
35
+ type: string
36
+ default: World
37
+ ---
38
+ Hello {{ who }}!`;
39
+
40
+ // compile(source, outputFormat?, parameters?, options?) => Promise<string>
41
+ await compile(source); // -> "Hello World!" (markdown, default)
42
+ await compile(source, 'markdown', { who: 'Prompd' }); // -> "Hello Prompd!"
43
+ await compile(source, 'openai', { who: 'Prompd' }); // -> OpenAI chat JSON
44
+ await compile(source, 'anthropic',{ who: 'Prompd' }); // -> Anthropic messages JSON
45
+ ```
46
+
47
+ Output formats: `markdown` (default), `openai`, `anthropic`.
48
+
49
+ ## Inheritance, includes & packages
50
+
51
+ `inherits:` and `{% include %}` resolve against other files through an injected
52
+ `IFileSystem`. In the browser, supply a `MemoryFileSystem`; on the server, supply
53
+ your own disk-backed implementation. A package resolver is injected the same way
54
+ (omit it where packages aren't available, e.g. the browser).
55
+
56
+ ```ts
57
+ import { compile, MemoryFileSystem } from '@prompd/core';
58
+
59
+ const fs = new MemoryFileSystem({
60
+ 'base.prmd': `---
61
+ id: base
62
+ name: Base
63
+ version: 1.0.0
64
+ ---
65
+ {% block body %}{% endblock %}`,
66
+ 'child.prmd': `---
67
+ id: child
68
+ name: Child
69
+ version: 1.0.0
70
+ inherits: base.prmd
71
+ ---
72
+ {% block body %}Hi {{ who }}{% endblock %}`,
73
+ });
74
+
75
+ const child = await fs.read('child.prmd');
76
+ const out = await compile(child, 'markdown', { who: 'there' }, { fileSystem: fs });
77
+ ```
78
+
79
+ ## Parameters
80
+
81
+ Declared in frontmatter and validated/coerced at compile time. Types: `string`,
82
+ `number`, `integer`, `float`, `boolean`, `array`, `object`, `json`, `file`,
83
+ `base64`, and `date` / `datetime`.
84
+
85
+ `date` / `datetime` defaults (and provided values) may be **relative expressions**
86
+ resolved at compile time, so each run uses the current date:
87
+
88
+ ```yaml
89
+ parameters:
90
+ - name: start
91
+ type: date
92
+ default: "now-7d" # also: now, today, now+1w, now-3m, now-1y, now-2h, now-30min
93
+ - name: when
94
+ type: datetime
95
+ default: "now" # -> "YYYY-MM-DDTHH:mm:ss"
96
+ ```
97
+
98
+ ISO / date-parseable literals (`"2026-01-15"`) pass through unchanged.
99
+
100
+ ## Workflows (`.pdflow`)
101
+
102
+ ```ts
103
+ import { parseWorkflow, getExecutionOrder, validateWorkflow } from '@prompd/core';
104
+
105
+ const { file, errors, warnings } = parseWorkflow(jsonText);
106
+ const order = getExecutionOrder(file); // topological node order
107
+ const result = validateWorkflow(file);
108
+ ```
109
+
110
+ ## Workflow expressions
111
+
112
+ `.pdflow` node fields written as `{{ expr }}` are parsed and evaluated as a
113
+ small, safe subset of JavaScript — never executed as code (no `new Function`,
114
+ `eval`, or `vm`). Scope lookups read own data properties only, never getters
115
+ or inherited members.
116
+
117
+ Allowed: literals (strings, numbers, booleans, `null`/`undefined`, array/
118
+ object literals with identifier or string keys), identifiers resolved from
119
+ scope, member access (`a.b`, `a[expr]`, `a?.b`, `a?.[expr]`), unary `!`/`-`/`+`,
120
+ the arithmetic/comparison/logical operators, `?:`, parentheses, and calls to a
121
+ fixed method list — strings: `includes` `startsWith` `endsWith` `toLowerCase`
122
+ `toUpperCase` `trim` `indexOf` `slice`; arrays: `includes` `indexOf` `join`
123
+ `slice` — always invoked from `String.prototype`/`Array.prototype` directly,
124
+ never looked up on the receiver.
125
+
126
+ Rejected as an `ExpressionError`: any other call, `new`, tagged templates,
127
+ assignment (`=` and compound), `++`/`--`, `delete`, `typeof`, `void`, `in`,
128
+ `instanceof`, bitwise operators, the comma operator, function/arrow
129
+ expressions, template literals, regex literals, spread, and access to
130
+ `__proto__`/`constructor`/`prototype` by any path. An expression longer than
131
+ `MAX_EXPRESSION_LENGTH` (2,000) characters, or nested deeper than
132
+ `MAX_EXPRESSION_DEPTH` (64) levels, is rejected. Globals (`window`, `fetch`,
133
+ `process`, `require`, …) are simply not in scope: they read as `undefined`.
134
+
135
+ **Arithmetic on a missing value is an error.** `-`, `*`, `/`, `%`, and a
136
+ numeric `+` (neither side a string) with an `undefined` operand throw an
137
+ `ExpressionError` (construct `evaluation error`) instead of producing `NaN`;
138
+ `+` with a string operand still concatenates as JavaScript does. The usual
139
+ cause is a hyphenated node id: `{{ prompt-abc }}` parses as `prompt - abc`.
140
+ The Prompd CLI workflow engine exposes every node output by id as `nodes`,
141
+ so read such an id as `{{ nodes['prompt-abc'].output }}`.
142
+
143
+ ```ts
144
+ import { evaluateExpression } from '@prompd/core';
145
+
146
+ evaluateExpression('{{ user.name }}', { user: { name: 'Ada' } }); // 'Ada'
147
+ ```
148
+
149
+ - `evaluateExpression(expr, scope, options?)` — parses and evaluates one
150
+ `{{ … }}` expression against `scope`; throws `ExpressionError` on anything
151
+ disallowed, invalid, or over a limit.
152
+ - `validateExpression(expr)` — parses without a scope, for validating
153
+ expression syntax while editing (e.g. canvas validation) without needing
154
+ runtime data.
155
+ - `describeExpressionFailure(kind, label, err)` — formats a caught error into
156
+ the message a workflow node fails with. For a well-formed `ExpressionError`
157
+ (as `isExpressionError` reads it) it names the node kind/label, the rejected
158
+ construct and its position; for anything else it returns `<kind> on
159
+ "<label>" has an invalid expression: <message>.`, where `<message>` is the
160
+ value's own string `message` property or `unknown error`. Never throws.
161
+ - `isExpressionError(e)` — true for an `ExpressionError` this module
162
+ constructed (checked by an internal brand, which runs no trap), or for any
163
+ object whose OWN DATA properties are `name === 'ExpressionError'`, a string
164
+ `construct` and a number `position` (e.g. one built by another copy of
165
+ `@prompd/core`). It never uses `instanceof`, never invokes a getter, and
166
+ never throws: a `Proxy` whose traps throw is reported as `false`. An `Error`
167
+ merely named `ExpressionError` is not one.
168
+ - `ExpressionError` — thrown by `evaluateExpression`/`validateExpression`;
169
+ carries `message`, `construct` (what was rejected), and `position`.
170
+
171
+ **`parseJsonStrings` option:** `evaluateExpression(expr, scope, {
172
+ parseJsonStrings: true })` additionally lets a NAMED member access (never
173
+ `.length`, never a canonical numeric index — those always read the string
174
+ itself) navigate INTO a string by parsing it as JSON first, after stripping
175
+ one optional ` ``` `/` ```json ` fence (typical of an LLM's JSON reply).
176
+ Invalid JSON, an empty string, or a string longer than `MAX_JSON_NAV_LENGTH`
177
+ (1,000,000 characters) reads as `undefined`, exactly like a missing property,
178
+ never throws. Off by default.
179
+
180
+ **Limits:** `MAX_EXPRESSION_LENGTH` (2,000), `MAX_EXPRESSION_DEPTH` (64),
181
+ `MAX_ARRAY_SCAN` (1,000,000 — the longest array an allowed array method will
182
+ scan; a longer array is an evaluation error rather than a silent hang), and
183
+ `MAX_JSON_NAV_LENGTH` (1,000,000, `parseJsonStrings` only) are all exported so
184
+ a host can reference or test against the same values.
185
+
186
+ **Coercion is primitive-only.** Every operator that would otherwise coerce a
187
+ value (arithmetic, comparison, template-like concatenation) refuses any
188
+ object, array, or function outright rather than calling its `toString`/
189
+ `valueOf`: `[1] + [2]` is an evaluation error, not `'12'`. `==`/`!=` against an
190
+ object or array compare identity (same reference), never structural
191
+ equality.
192
+
193
+ **Hosts must pass plain data.** The evaluator only ever reads own data
194
+ properties of plain objects/arrays. The one residual: a `Proxy` placed
195
+ anywhere in scope may still have its `getPrototypeOf` and
196
+ `getOwnPropertyDescriptor` traps invoked (to classify it and to read one own
197
+ property), so a Proxy is trusted to report its own shape honestly — it is not
198
+ sandboxed against a trap that lies.
199
+
200
+ ## What's exported
201
+
202
+ - **Parser:** `PrompdParser`
203
+ - **Compiler:** `compile`, `PrompdCompiler`, `CompilerPipeline`, the stages, and the
204
+ formatters (`MarkdownFormatter`, `OpenAIFormatter`, `AnthropicFormatter`)
205
+ - **File system / packages:** `IFileSystem`, `MemoryFileSystem`, `HybridFileSystem`,
206
+ `IPackageResolver`
207
+ - **Workflows:** `parseWorkflow`, `getExecutionOrder`, `validateWorkflow`,
208
+ `createWorkflowNode`, plus the `WorkflowFile` / node-data types
209
+ - **Workflow expressions:** `evaluateExpression`, `validateExpression`,
210
+ `describeExpressionFailure`, `isExpressionError`, `ExpressionError`,
211
+ `MAX_EXPRESSION_LENGTH`, `MAX_EXPRESSION_DEPTH`, `MAX_ARRAY_SCAN`,
212
+ `MAX_JSON_NAV_LENGTH`, and the `ExpressionEvalOptions` type
213
+ - **Types & errors:** `PrompdMetadata`, `PrompdParameter`, `CompilationOptions`,
214
+ `CompilationError`, `ValidationError`, …
215
+
216
+ See the bundled `dist/index.d.ts` for the full surface.
217
+
218
+ ## License
219
+
220
+ [MIT](./LICENSE) © 2024–2026 Prompd LLC. (The Prompd registry and hosted services
221
+ are separately licensed.)