@prompd/core 0.5.2 → 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 +221 -127
- package/dist/index.cjs +838 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +830 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
-
##
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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.)
|