create-nola-lang 0.1.2 → 0.1.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 +3 -2
- package/dist/agents.d.ts +27 -2
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +136 -67
- package/dist/agents.js.map +1 -1
- package/dist/{chunk-QTJ64H2Y.js → chunk-VZM5EYBG.js} +138 -64
- package/dist/flow.d.ts +3 -0
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js +9 -7
- package/dist/flow.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/main.js +1 -1
- package/dist/package-manager.d.ts +17 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +8 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/skill-install.d.ts +2 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -2
- package/dist/skill-install.js.map +1 -1
- package/package.json +7 -5
- package/skills/nola/SKILL.md +75 -0
- package/skills/nola/references/config.md +227 -0
- package/skills/nola/references/patterns.md +277 -0
- package/skills/nola/references/pitfalls.md +289 -0
- package/skills/nola/references/syntax.md +377 -0
- package/templates/starter/_gitignore +2 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Nola patterns — worked examples
|
|
2
|
+
|
|
3
|
+
## The starter project, end to end
|
|
4
|
+
|
|
5
|
+
This is the shape every Nola project takes: `.tsi` files hold the infer
|
|
6
|
+
functions, a plain `.ts` entry point calls them, and `nola run` executes the
|
|
7
|
+
entry with the loader and `nola.config.ts` in place.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
my-app/
|
|
11
|
+
nola.config.ts # providers
|
|
12
|
+
package.json # nola-lang in devDependencies
|
|
13
|
+
tsconfig.json # include: ["src"]
|
|
14
|
+
src/
|
|
15
|
+
person.tsi # the Nola source
|
|
16
|
+
main.ts # a plain TypeScript consumer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`src/person.tsi` — the type and the infer function live together:
|
|
20
|
+
|
|
21
|
+
```tsi
|
|
22
|
+
export interface Person {
|
|
23
|
+
name: string;
|
|
24
|
+
age: number;
|
|
25
|
+
employer: string;
|
|
26
|
+
job: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export infer function extractPerson(.message: string) {
|
|
30
|
+
const person = ask ..`the person described in the text`<Person>;
|
|
31
|
+
return person;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`src/main.ts` — plain TypeScript. Note the literal `.tsi` extension in the
|
|
36
|
+
import, and that `await` on the returned `Intent` is what runs the inference:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { extractPerson } from "./person.tsi";
|
|
40
|
+
|
|
41
|
+
const person = await extractPerson(
|
|
42
|
+
"Alice Smith, 32, is a staff engineer at Acme Corp working on distributed systems.",
|
|
43
|
+
);
|
|
44
|
+
console.log(JSON.stringify(person));
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Run it:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
nola run src/main.ts # or: npm start
|
|
51
|
+
nola check # type-checks .tsi and .ts together
|
|
52
|
+
nola build # dist/ — plain JS + source maps + .d.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Composing several asks in one invocation
|
|
56
|
+
|
|
57
|
+
Every `ask` in one invocation shares that invocation's context — the `..`
|
|
58
|
+
contextual parameters and the function's instruction marker. That is what lets
|
|
59
|
+
you split one big prompt into several small, individually-typed asks instead of
|
|
60
|
+
demanding everything at once.
|
|
61
|
+
|
|
62
|
+
What is NOT shared is the answers. Earlier results are recorded on the
|
|
63
|
+
invocation's frame, but history does not compose into later prompts yet: a
|
|
64
|
+
later ask does NOT see what an earlier ask returned. When it needs an earlier
|
|
65
|
+
answer, pass it forward explicitly with `${}` interpolation.
|
|
66
|
+
|
|
67
|
+
```tsi
|
|
68
|
+
export infer function solve(.problem: string) {
|
|
69
|
+
// Both asks see `problem` (the contextual parameter). They do NOT see each
|
|
70
|
+
// other's answers automatically — the reasoning is handed to the second ask
|
|
71
|
+
// explicitly through `${}`.
|
|
72
|
+
const reasoning = ask ..`think step by step about the problem before answering`;
|
|
73
|
+
const answer = ask ..`the final numeric answer, given this reasoning: ${reasoning}`<number>;
|
|
74
|
+
return { reasoning, answer };
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Once an `ask` returns, its result is an ORDINARY typed value — branch on it,
|
|
79
|
+
pass it to plain functions, put it in an object literal, interpolate it into a
|
|
80
|
+
later prompt:
|
|
81
|
+
|
|
82
|
+
```tsi
|
|
83
|
+
export type Category = "billing" | "refund" | "fraud" | "other";
|
|
84
|
+
|
|
85
|
+
export infer function classifyMessage(.message: string) {
|
|
86
|
+
const category = ask ..`the category of the customer message`<Category>;
|
|
87
|
+
const urgent = ask ..`does the message need urgent attention`<"yes" | "no">;
|
|
88
|
+
|
|
89
|
+
// plain TS from here on
|
|
90
|
+
if (category === "fraud") return { category, urgent: true, escalate: true };
|
|
91
|
+
return { category, urgent: urgent === "yes", escalate: false };
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Plain TypeScript orchestrates the loop; the infer functions stay small:
|
|
96
|
+
|
|
97
|
+
```tsi
|
|
98
|
+
// src/research.tsi
|
|
99
|
+
export interface Conclusion {
|
|
100
|
+
answer: string;
|
|
101
|
+
/** the collected notes that directly support the answer */
|
|
102
|
+
evidence: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export infer function nextQuery(.question: string, .notes: string[]) {
|
|
106
|
+
return ask ..`the single best search query to advance the research; keywords only`<string>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export infer function conclude(.question: string, .notes: string[]) {
|
|
110
|
+
return ask ..`answer the research question using only the collected notes`<Conclusion>;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// src/main.ts
|
|
116
|
+
import { conclude, nextQuery } from "./research.tsi";
|
|
117
|
+
import { search } from "./search.js";
|
|
118
|
+
|
|
119
|
+
const question = "who maintains the project?";
|
|
120
|
+
const notes: string[] = [];
|
|
121
|
+
for (let i = 0; i < 3; i++) {
|
|
122
|
+
const query = await nextQuery(question, notes);
|
|
123
|
+
notes.push(await search(query));
|
|
124
|
+
}
|
|
125
|
+
const conclusion = await conclude(question, notes);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Call intents — let the LLM fill a function's arguments
|
|
129
|
+
|
|
130
|
+
When you already have a function that DOES something, do not extract its
|
|
131
|
+
arguments one at a time and then call it. Make the call itself the intent: the
|
|
132
|
+
LLM fills every extractor-shaped argument in one provider call, and the
|
|
133
|
+
function runs with the results.
|
|
134
|
+
|
|
135
|
+
`src/tickets.ts` — an ordinary TypeScript helper, nothing Nola about it:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
export async function createTicket(title: string, priority: number): Promise<string> {
|
|
139
|
+
const res = await fetch("https://example.test/tickets", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
body: JSON.stringify({ title, priority }),
|
|
142
|
+
});
|
|
143
|
+
return (await res.json()).id as string;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`src/file-ticket.tsi` — the helper is imported with the NodeNext `.js`
|
|
148
|
+
specifier, and the call gets extractor arguments:
|
|
149
|
+
|
|
150
|
+
```tsi
|
|
151
|
+
import { createTicket } from "./tickets.js";
|
|
152
|
+
|
|
153
|
+
export infer function fileTicket(.request: string) {
|
|
154
|
+
// Sigil-less: the extractor argument makes this call an intent. `2` is a
|
|
155
|
+
// plain argument and is passed through untouched.
|
|
156
|
+
const id = ask createTicket(..`a short ticket title for the request`<string>, 2);
|
|
157
|
+
return id;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Add instruction text for the call with the marker form — it is the only
|
|
162
|
+
spelling that carries a hint:
|
|
163
|
+
|
|
164
|
+
```tsi
|
|
165
|
+
export infer function fileTicketCarefully(.request: string) {
|
|
166
|
+
return ask createTicket`file the ticket exactly as the customer described it`(
|
|
167
|
+
..`a short ticket title`<string>,
|
|
168
|
+
..`priority 1-5, where 1 is most urgent`<number>,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Every extractor slot needs an explicit `<T>`, and all slots of one call
|
|
174
|
+
resolve together in a single provider round trip.
|
|
175
|
+
|
|
176
|
+
`createTicket` is async, but `id` is a `string`, not a `Promise<string>` — a
|
|
177
|
+
call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
|
|
178
|
+
`await ask createTicket(...)` is redundant. Because the callee runs inside the
|
|
179
|
+
ask, `.withRetry(n)` on a call intent re-invokes it on failure — only use it
|
|
180
|
+
when the target is idempotent.
|
|
181
|
+
|
|
182
|
+
## Typing the answers
|
|
183
|
+
|
|
184
|
+
Prefer a NAMED, exported `type` or `interface` for `<T>` over an inline object
|
|
185
|
+
literal: it documents the contract, it is reusable from plain TS, and JSDoc
|
|
186
|
+
comments on its members become descriptions in the schema the LLM sees.
|
|
187
|
+
|
|
188
|
+
```tsi
|
|
189
|
+
export interface LineItem {
|
|
190
|
+
description: string;
|
|
191
|
+
quantity: number;
|
|
192
|
+
/** price per unit in USD */
|
|
193
|
+
unitPrice: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface Invoice {
|
|
197
|
+
invoiceNumber: string;
|
|
198
|
+
issuedTo: string;
|
|
199
|
+
lineItems: LineItem[];
|
|
200
|
+
/** grand total in USD */
|
|
201
|
+
total: number;
|
|
202
|
+
/** ISO date; omit when the document has none */
|
|
203
|
+
dueDate?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export infer function extractInvoice(.document: string) {
|
|
207
|
+
return ask ..`the invoice data from the document`<Invoice>;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Closed label sets keep the model on-rails — a string-literal union, a string
|
|
212
|
+
enum, or an inline union all work:
|
|
213
|
+
|
|
214
|
+
```tsi
|
|
215
|
+
export type Category = "billing" | "refund" | "fraud" | "other";
|
|
216
|
+
|
|
217
|
+
export enum Sentiment {
|
|
218
|
+
Positive = "positive",
|
|
219
|
+
Neutral = "neutral",
|
|
220
|
+
Negative = "negative",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export infer function triage(.message: string) {
|
|
224
|
+
const category = ask ..`the category of the customer message`<Category>;
|
|
225
|
+
const sentiment = ask ..`the overall sentiment of the message`<Sentiment>;
|
|
226
|
+
const urgent = ask ..`does the message need urgent attention`<"yes" | "no">;
|
|
227
|
+
return { category, sentiment, urgent: urgent === "yes" };
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Types from another file
|
|
232
|
+
|
|
233
|
+
Cross-file types are supported. Import the type from a plain `.ts` file with a
|
|
234
|
+
type-only import and the NodeNext `.js` specifier; the toolchain derives the
|
|
235
|
+
schema for you:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// src/models.ts
|
|
239
|
+
export interface Person {
|
|
240
|
+
name: string;
|
|
241
|
+
age: number;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```tsi
|
|
246
|
+
// src/report.tsi
|
|
247
|
+
import type { Person } from "./models.js";
|
|
248
|
+
|
|
249
|
+
export infer function extractPerson(.text: string) {
|
|
250
|
+
return ask ..`the person described in the text`<Person>;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Recursive types are legal too:
|
|
255
|
+
|
|
256
|
+
```tsi
|
|
257
|
+
export type TreeNode = {
|
|
258
|
+
label: string;
|
|
259
|
+
children?: TreeNode[];
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export infer function parseTree(.input: string) {
|
|
263
|
+
return ask ..`the tree structure described in the input`<TreeNode>;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`Date` fields work and come back as real `Date` instances:
|
|
268
|
+
|
|
269
|
+
```tsi
|
|
270
|
+
export type CalendarEvent = { title: string; at: Date };
|
|
271
|
+
|
|
272
|
+
export infer function nextEvent(.calendar: string) {
|
|
273
|
+
const event = ask ..`the next event on the calendar`<CalendarEvent>;
|
|
274
|
+
const when: Date = event.at; // a Date, not a string
|
|
275
|
+
return when;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Common Nola errors and their fixes
|
|
2
|
+
|
|
3
|
+
Diagnostic codes are stable: `NOLA1xxx` parse, `NOLA2xxx` compile, `NOLA3xxx`
|
|
4
|
+
run time.
|
|
5
|
+
|
|
6
|
+
## NOLA1009 — `ask with` needs a static provider name
|
|
7
|
+
|
|
8
|
+
> expected a provider name after `ask with` — for a dynamic provider use
|
|
9
|
+
> `.withProvider(...)` on the intent.
|
|
10
|
+
|
|
11
|
+
The alias after `with` must be a bare identifier naming a key of the
|
|
12
|
+
`providers` map in `nola.config.ts`. A string literal, a variable expression or
|
|
13
|
+
a parenthesized expression will not parse.
|
|
14
|
+
|
|
15
|
+
```tsi
|
|
16
|
+
export infer function summarize(.text: string, useFast: boolean) {
|
|
17
|
+
// WRONG
|
|
18
|
+
const a = ask with "fast" ..`a rough summary`<string>;
|
|
19
|
+
const b = ask with providers.fast ..`a rough summary`<string>;
|
|
20
|
+
|
|
21
|
+
// RIGHT — name it in nola.config.ts, then use that name
|
|
22
|
+
const c = ask with fast ..`a rough summary`<string>;
|
|
23
|
+
|
|
24
|
+
// RIGHT — dynamic choice
|
|
25
|
+
const d = ask (..`a rough summary`<string>).withProvider(useFast ? "fast" : "careful");
|
|
26
|
+
|
|
27
|
+
return { a, b, c, d };
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// nola.config.ts
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
providers: {
|
|
35
|
+
default: openai({ model: "gpt-5-mini" }),
|
|
36
|
+
fast: openai({ model: "gpt-5-nano" }),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## NOLA1010 — `.` on a non-infer function
|
|
42
|
+
|
|
43
|
+
> `.` context parameters are only allowed on infer function parameters.
|
|
44
|
+
|
|
45
|
+
```tsi
|
|
46
|
+
// WRONG — a plain function has no inference context to put the value in
|
|
47
|
+
function summarize(.text: string) {
|
|
48
|
+
return text.slice(0, 10);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// RIGHT
|
|
52
|
+
export infer function summarize(.text: string) {
|
|
53
|
+
return ask ..`a one-sentence summary`<string>;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If the function is genuinely plain TypeScript, drop the `.`; the parameter is
|
|
58
|
+
an ordinary argument.
|
|
59
|
+
|
|
60
|
+
## NOLA2001 — `ask` outside an infer function body
|
|
61
|
+
|
|
62
|
+
> `ask` is only allowed directly inside an infer function body.
|
|
63
|
+
|
|
64
|
+
`ask` is not legal at module level, and not inside a nested closure — not even
|
|
65
|
+
one written inside an infer function.
|
|
66
|
+
|
|
67
|
+
```tsi
|
|
68
|
+
// WRONG
|
|
69
|
+
const kind = ask ..`the kind`<string>; // module level
|
|
70
|
+
|
|
71
|
+
export infer function f(.t: string) {
|
|
72
|
+
const g = () => ask ..`the kind`<string>; // nested closure
|
|
73
|
+
return g();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// RIGHT — ask directly in the body; from plain TS, await the infer function
|
|
77
|
+
export infer function f(.t: string) {
|
|
78
|
+
return ask ..`the kind`<string>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Constructing an extractor outside a body is fine — only resolving it is
|
|
83
|
+
restricted:
|
|
84
|
+
|
|
85
|
+
```tsi
|
|
86
|
+
export const nameIntent = ..`the user's full name`<string>; // legal, inert
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## NOLA2002 — a type the compiler cannot turn into a schema
|
|
90
|
+
|
|
91
|
+
> unsupported type for intent schema: …
|
|
92
|
+
|
|
93
|
+
An extractor's `<T>` must describe a JSON-shaped value: strings, numbers,
|
|
94
|
+
booleans, `Date`, arrays, plain object/interface/type-alias shapes,
|
|
95
|
+
string-literal unions, string enums, and references to those (same-file or
|
|
96
|
+
imported). Ambient lib types like `Map`, `Set`, `RegExp`, functions and
|
|
97
|
+
generics are not derivable.
|
|
98
|
+
|
|
99
|
+
```tsi
|
|
100
|
+
export infer function tally(.doc: string) {
|
|
101
|
+
// WRONG
|
|
102
|
+
const wrong = ask ..`counts per label`<Map<string, number>>;
|
|
103
|
+
|
|
104
|
+
// RIGHT — a JSON-shaped type; convert afterwards in plain TS
|
|
105
|
+
const counts = ask ..`counts per label`<{ label: string; count: number }[]>;
|
|
106
|
+
const asMap = new Map(counts.map((c) => [c.label, c.count]));
|
|
107
|
+
return asMap;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Always give an extractor a concrete `<T>` in an expression position. An
|
|
112
|
+
extractor is a value-producing expression; it is not a statement, a type, or a
|
|
113
|
+
declaration.
|
|
114
|
+
|
|
115
|
+
## NOLA2008 — an underivable `.` contextual parameter type
|
|
116
|
+
|
|
117
|
+
> contextual parameter 'm' has a type that cannot be derived for inference:
|
|
118
|
+
> unsupported type for intent schema: Map<string, number>. Set
|
|
119
|
+
> compiler.underivableContextType to "prune" or "omit" in nola.config.ts to
|
|
120
|
+
> allow it.
|
|
121
|
+
|
|
122
|
+
The same derivability rules apply to contextual parameters, because their
|
|
123
|
+
values are serialized into the prompt.
|
|
124
|
+
|
|
125
|
+
```tsi
|
|
126
|
+
// WRONG
|
|
127
|
+
export infer function topLabel(.index: Map<string, number>) {
|
|
128
|
+
return ask ..`the label with the highest count`<string>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// RIGHT — pass a JSON-shaped view as the contextual parameter
|
|
132
|
+
export infer function topLabel(.index: { label: string; count: number }[]) {
|
|
133
|
+
return ask ..`the label with the highest count`<string>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// RIGHT — keep the exotic value, but as a PLAIN parameter (the LLM never
|
|
137
|
+
// sees its value, so nothing needs deriving)
|
|
138
|
+
export infer function topLabel(.summary: string, index: Map<string, number>) {
|
|
139
|
+
const label = ask ..`the label with the highest count`<string>;
|
|
140
|
+
return { label, count: index.get(label) ?? 0 };
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If you must keep an underivable member on a contextual type, relax the policy
|
|
145
|
+
in `nola.config.ts` — `"prune"` drops just the underivable members and keeps
|
|
146
|
+
the rest of the type; `"omit"` drops the whole type silently:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
export default defineConfig({
|
|
150
|
+
providers: { default: openai({ model: "gpt-5-mini" }) },
|
|
151
|
+
compiler: { underivableContextType: "prune" }, // default is "error"
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Keep that value a literal — the editor reads it statically and never executes
|
|
156
|
+
your config.
|
|
157
|
+
|
|
158
|
+
## NOLA2004 — a call-intent slot with no `<T>`
|
|
159
|
+
|
|
160
|
+
> an extractor used as a call-intent argument must have an explicit `<T>`.
|
|
161
|
+
|
|
162
|
+
```tsi
|
|
163
|
+
declare function createTicket(title: string, priority: number): Promise<string>;
|
|
164
|
+
|
|
165
|
+
export infer function fileTicket(.request: string) {
|
|
166
|
+
// WRONG — the slot has no type
|
|
167
|
+
const wrong = ask createTicket(..`a short ticket title`, 2);
|
|
168
|
+
|
|
169
|
+
// RIGHT
|
|
170
|
+
const id = ask createTicket(..`a short ticket title`<string>, 2);
|
|
171
|
+
return id;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## NOLA3010 — bare `await` on a raw extract or call intent
|
|
176
|
+
|
|
177
|
+
> extract/call intents carry no construction scope — only `ask` supplies their
|
|
178
|
+
> frame.
|
|
179
|
+
|
|
180
|
+
A raw extractor or call intent has no context of its own; it borrows the frame
|
|
181
|
+
of the `ask` that resolves it. Awaiting one directly (typically from plain TS,
|
|
182
|
+
or after storing it in a variable) throws at run time.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// WRONG — nothing supplies the inference context
|
|
186
|
+
import { nameIntent } from "./person.tsi";
|
|
187
|
+
const name = await nameIntent;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```tsi
|
|
191
|
+
// RIGHT — resolve it with `ask` inside an infer function
|
|
192
|
+
import { nameIntent } from "./person.tsi";
|
|
193
|
+
|
|
194
|
+
export infer function whoIsIt(.text: string) {
|
|
195
|
+
return ask nameIntent;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// RIGHT — from plain TS, await the INFER FUNCTION's result (that is an
|
|
201
|
+
// Intent, which does open its own invocation)
|
|
202
|
+
import { whoIsIt } from "./person.tsi";
|
|
203
|
+
|
|
204
|
+
const name = await whoIsIt("Alice Smith, 32, works at Acme Corp.");
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Import mistakes
|
|
208
|
+
|
|
209
|
+
- `.tsi` imports keep the LITERAL extension:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { extractPerson } from "./person.tsi"; // RIGHT
|
|
213
|
+
import { extractPerson } from "./person"; // WRONG — unresolved
|
|
214
|
+
import { extractPerson } from "./person.js"; // WRONG — no such file
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- Plain TypeScript imports use the NodeNext `.js` specifier, even though the
|
|
218
|
+
file on disk is `.ts`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { createTicket } from "./tickets.js"; // RIGHT
|
|
222
|
+
import { createTicket } from "./tickets.ts"; // WRONG — TS5097
|
|
223
|
+
import { createTicket } from "./tickets"; // WRONG — TS2835
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- Never import a `*.nola.*` module. Those are internal companion modules the
|
|
227
|
+
compiler generates for cross-file types; only generated code imports them,
|
|
228
|
+
and a hand-written file with such a name is NOLA2006.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { Person } from "./models.nola.js"; // WRONG — internal
|
|
232
|
+
import type { Person } from "./models.js"; // RIGHT
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Never write generated-code names
|
|
236
|
+
|
|
237
|
+
`__nola` and any identifier starting with `__nola` are reserved in `.tsi`.
|
|
238
|
+
`__nola.ask(...)`, `__nola.intents.ExtractIntent(...)`, `__nola.types.string()`
|
|
239
|
+
and `__nola_file_ctx()` are what the compiler EMITS — they are not an API you
|
|
240
|
+
call, and writing them by hand is an error.
|
|
241
|
+
|
|
242
|
+
```tsi
|
|
243
|
+
export infer function read(.doc: string) {
|
|
244
|
+
// WRONG — this is emitted code, not a user-facing API
|
|
245
|
+
const wrong = await __nola.ask(
|
|
246
|
+
__nola.intents.ExtractIntent({ instruction: "the value", type: __nola.types.string(), loc: "1:1" }),
|
|
247
|
+
__frame,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// RIGHT
|
|
251
|
+
const v = ask ..`the value`<string>;
|
|
252
|
+
return v;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## NOLA2009 — `${.member}` outside a Nola instruction
|
|
257
|
+
|
|
258
|
+
`${.x}` is prompt-scope access and only means something inside an
|
|
259
|
+
infer-function marker, an extractor's backticks, or a call-intent hint. In a
|
|
260
|
+
plain template literal it is an error — use a lexical value there.
|
|
261
|
+
|
|
262
|
+
## NOLA2010 — a Nola construct inside a marker / call-hint hole
|
|
263
|
+
|
|
264
|
+
Marker and call-hint literals are re-emitted from source, so `..`, call
|
|
265
|
+
intents and `ask` cannot appear in their holes. Compute the value first and
|
|
266
|
+
interpolate the result, or move the ask into the function body.
|
|
267
|
+
|
|
268
|
+
## NOLA3014 — a prompt template rendered nothing (or threw)
|
|
269
|
+
|
|
270
|
+
A `${.member}` template must produce text. An empty result usually means the
|
|
271
|
+
template only read members that were undefined; a throw is a bug in the
|
|
272
|
+
template's own JS. Fixed at the template — there is no retry.
|
|
273
|
+
|
|
274
|
+
## NOLA3015 — running `.tsi` under Bun or Deno
|
|
275
|
+
|
|
276
|
+
The loader is Node's module-hooks API; Bun and Deno do not run it. Any package
|
|
277
|
+
manager is fine (`bun install`, `bun run start`, `pnpm start`, `yarn start` —
|
|
278
|
+
the `nola` bin is a Node script), but `bun --bun`, `bun src/main.ts` and
|
|
279
|
+
`deno run` cannot load `.tsi`. Run on Node: `nola run` or
|
|
280
|
+
`node --import nola-lang/register src/main.ts`.
|
|
281
|
+
|
|
282
|
+
## Other things that will not parse
|
|
283
|
+
|
|
284
|
+
- `async infer function f(...)` — an infer function is never `async` in source;
|
|
285
|
+
`await` is already legal in its body.
|
|
286
|
+
- `export default infer function f(...)` — export by name instead.
|
|
287
|
+
- `infer` on a method, arrow function or function expression — top-level
|
|
288
|
+
function declarations only.
|
|
289
|
+
- `fn(..)` — the bare derive-all call form is reserved (NOLA1004).
|