@joshuapassos/prompt-ts 1.0.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 +143 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/index.ts +156 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# prompt-ts
|
|
2
|
+
|
|
3
|
+
Type-safe prompt templating engine for TypeScript. Build structured LLM prompts with compile-time placeholder validation, multi-language support, and optional Zod schema integration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add prompt-ts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Type-safe placeholders** — `{{key}}` syntax validated at compile time
|
|
14
|
+
- **System/User message pairs** — structured prompt rendering for chat-based LLMs
|
|
15
|
+
- **Multi-language support** — define templates per language, select at render time
|
|
16
|
+
- **Composable sections** — reusable prompt fragments via `promptSection()`
|
|
17
|
+
- **Zod schema** — optional structured output validation
|
|
18
|
+
- **Conditional options** — only requires `systemOptions`/`userOptions` when the corresponding template has placeholders
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic (string mode)
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { prompt } from "prompt-ts";
|
|
26
|
+
|
|
27
|
+
const p = prompt(
|
|
28
|
+
"greet",
|
|
29
|
+
"You are a {{role}}." as const,
|
|
30
|
+
"Say hello to {{name}}." as const
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const result = p.render({
|
|
34
|
+
systemOptions: { role: "assistant" },
|
|
35
|
+
userOptions: { name: "Alice" },
|
|
36
|
+
});
|
|
37
|
+
// => { systemPrompt: "You are a assistant.", userPrompt: "Say hello to Alice." }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Multi-language mode
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const p = prompt(
|
|
44
|
+
"classify",
|
|
45
|
+
{
|
|
46
|
+
en: "You are a {{category}} classifier" as const,
|
|
47
|
+
pt: "Você é um classificador de {{categoria}}" as const,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
en: "Classify: {{text}}" as const,
|
|
51
|
+
pt: "Classifique: {{texto}}" as const,
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
p.render("en", {
|
|
56
|
+
systemOptions: { category: "sentiment" },
|
|
57
|
+
userOptions: { text: "I'm happy" },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
p.render("pt", {
|
|
61
|
+
systemOptions: { categoria: "sentimentos" },
|
|
62
|
+
userOptions: { texto: "Estou feliz" },
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Composable sections
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { promptSection } from "prompt-ts";
|
|
70
|
+
|
|
71
|
+
const persona = promptSection("You are a {{role}}." as const);
|
|
72
|
+
const tone = promptSection("Be professional and concise." as const);
|
|
73
|
+
|
|
74
|
+
const systemPrompt = [
|
|
75
|
+
persona.render({ role: "translator" }),
|
|
76
|
+
tone.render(),
|
|
77
|
+
].join("\n\n");
|
|
78
|
+
// => "You are a translator.\n\nBe professional and concise."
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Sections can be composed into a `prompt`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const p = prompt(
|
|
85
|
+
"translate",
|
|
86
|
+
systemPrompt,
|
|
87
|
+
"Translate: {{text}}" as const
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
p.render({ userOptions: { text: "Hello world" } });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### With Zod schema
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { z } from "zod";
|
|
97
|
+
|
|
98
|
+
const schema = z.object({
|
|
99
|
+
sentiment: z.enum(["positive", "negative", "neutral"]),
|
|
100
|
+
confidence: z.number(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const p = prompt(
|
|
104
|
+
"sentiment",
|
|
105
|
+
"You are a sentiment classifier." as const,
|
|
106
|
+
"Classify: {{text}}" as const,
|
|
107
|
+
schema
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Access the schema for structured output parsing
|
|
111
|
+
p.zodSchema; // z.ZodObject<...>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API
|
|
115
|
+
|
|
116
|
+
### `prompt(name, systemTemplate, userTemplate, zodSchema?)`
|
|
117
|
+
|
|
118
|
+
| Param | Type | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `name` | `string` | Unique identifier for logging/tracing |
|
|
121
|
+
| `systemTemplate` | `string \| Record<string, string>` | System message template(s) |
|
|
122
|
+
| `userTemplate` | `string \| Record<string, string>` | User message template(s) |
|
|
123
|
+
| `zodSchema` | `z.ZodType<T>` | Optional Zod schema for output validation |
|
|
124
|
+
|
|
125
|
+
#### `.render(options)` (string mode)
|
|
126
|
+
|
|
127
|
+
#### `.render(lang, options)` (multi-language mode)
|
|
128
|
+
|
|
129
|
+
Returns `{ systemPrompt: string, userPrompt: string }`.
|
|
130
|
+
|
|
131
|
+
Options are `{ systemOptions?, userOptions? }` — each is required only when the corresponding template contains `{{placeholders}}`.
|
|
132
|
+
|
|
133
|
+
### `promptSection(template)`
|
|
134
|
+
|
|
135
|
+
Creates a reusable prompt fragment. Returns `{ template, render(vars?) }`.
|
|
136
|
+
|
|
137
|
+
## Scripts
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm test # Run tests
|
|
141
|
+
pnpm build # Compile TypeScript
|
|
142
|
+
pnpm typecheck # Type-check without emitting
|
|
143
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type z from "zod";
|
|
2
|
+
type ExtractNewTypes<S extends string> = S extends `${string}{{${infer Key}}}${infer Rest}` ? [Trim<Key>, ...ExtractNewTypes<Rest>] : [];
|
|
3
|
+
type Separator = " ";
|
|
4
|
+
type Trim<T extends string, Acc extends string = ""> = T extends `${infer Char}${infer Rest}` ? Char extends Separator ? Trim<Rest, Acc> : Trim<Rest, `${Acc}${Char}`> : Acc;
|
|
5
|
+
export type ArrayToObject<T extends string[]> = {
|
|
6
|
+
[K in T[number]]: string | number | boolean;
|
|
7
|
+
};
|
|
8
|
+
type IsEmpty<T extends unknown[]> = T extends [] ? true : false;
|
|
9
|
+
type OptionsField<Keys extends string[]> = IsEmpty<Keys> extends true ? Record<string, never> : ArrayToObject<Keys>;
|
|
10
|
+
type OptionalField<Keys extends string[]> = IsEmpty<Keys> extends true ? true : false;
|
|
11
|
+
type EvalOptions<STemplate extends string, UTemplate extends string> = {
|
|
12
|
+
[K in "systemOptions" as OptionalField<ExtractNewTypes<STemplate>> extends true ? never : K]: OptionsField<ExtractNewTypes<STemplate>>;
|
|
13
|
+
} & {
|
|
14
|
+
[K in "userOptions" as OptionalField<ExtractNewTypes<UTemplate>> extends true ? never : K]: OptionsField<ExtractNewTypes<UTemplate>>;
|
|
15
|
+
} & {
|
|
16
|
+
[K in "systemOptions" as OptionalField<ExtractNewTypes<STemplate>> extends true ? K : never]?: OptionsField<ExtractNewTypes<STemplate>>;
|
|
17
|
+
} & {
|
|
18
|
+
[K in "userOptions" as OptionalField<ExtractNewTypes<UTemplate>> extends true ? K : never]?: OptionsField<ExtractNewTypes<UTemplate>>;
|
|
19
|
+
};
|
|
20
|
+
type HasAnyPlaceholders<STemplate extends string, UTemplate extends string> = [
|
|
21
|
+
IsEmpty<ExtractNewTypes<STemplate>>,
|
|
22
|
+
IsEmpty<ExtractNewTypes<UTemplate>>
|
|
23
|
+
] extends [true, true] ? false : true;
|
|
24
|
+
type EvalArgs<STemplate extends string, UTemplate extends string> = HasAnyPlaceholders<STemplate, UTemplate> extends false ? [options?: EvalOptions<STemplate, UTemplate>] : [options: EvalOptions<STemplate, UTemplate>];
|
|
25
|
+
export type PromptTemplate = string | Record<string, string>;
|
|
26
|
+
type MatchLanguages<S extends PromptTemplate> = S extends string ? PromptTemplate : Record<keyof S & string, string>;
|
|
27
|
+
type NormalizeTemplate<T extends PromptTemplate> = T extends string ? {
|
|
28
|
+
default: T;
|
|
29
|
+
} : T;
|
|
30
|
+
type GetTemplate<T extends PromptTemplate, K extends string> = T extends string ? T : K extends keyof T ? T[K] : never;
|
|
31
|
+
type StringTemplate<T extends PromptTemplate, K extends string> = GetTemplate<T, K> extends string ? GetTemplate<T, K> : never;
|
|
32
|
+
type SectionArgs<T extends string> = IsEmpty<ExtractNewTypes<T>> extends true ? [vars?: Record<string, never>] : [vars: ArrayToObject<ExtractNewTypes<T>>];
|
|
33
|
+
/**
|
|
34
|
+
* Creates a reusable, type-safe prompt fragment with `{{placeholder}}` interpolation.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const persona = promptSection("You are a {{role}}." as const);
|
|
39
|
+
* persona.render({ role: "translator" }); // "You are a translator."
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function promptSection<T extends string>(template: T): {
|
|
43
|
+
template: T;
|
|
44
|
+
render(...args: SectionArgs<T>): string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Type-safe prompt template with system/user message pairs and optional Zod schema for structured output.
|
|
48
|
+
*
|
|
49
|
+
* Supports two modes:
|
|
50
|
+
* - **String mode** — pass plain strings as templates, call `render()` without a language key.
|
|
51
|
+
* - **Multi-language mode** — pass `Record<string, string>` maps, call `render(lang, options)`.
|
|
52
|
+
*
|
|
53
|
+
* Placeholders use the `{{key}}` syntax and are fully type-checked at compile time.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const p = prompt("greet", "Hello {{name}}" as const, "Hi!" as const);
|
|
58
|
+
* p.render({ systemOptions: { name: "Alice" } });
|
|
59
|
+
* // => { systemPrompt: "Hello Alice", userPrompt: "Hi!" }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function prompt<S extends PromptTemplate, U extends MatchLanguages<S>, T = unknown>(promptName: string, systemPrompt: S, userPrompt: U, zodSchema?: z.ZodType<T>): {
|
|
63
|
+
promptName: string;
|
|
64
|
+
zodSchema: z.ZodType<T, z.ZodTypeDef, T> | undefined;
|
|
65
|
+
render<TLang extends keyof NormalizeTemplate<S> & keyof NormalizeTemplate<U> = keyof NormalizeTemplate<S> & keyof NormalizeTemplate<U>>(...args: [S, U] extends [string, string] ? EvalArgs<S extends string ? S : never, U extends string ? U : never> : [lang: TLang, ...EvalArgs<StringTemplate<S, TLang & string>, StringTemplate<U, TLang & string>>]): {
|
|
66
|
+
systemPrompt: string;
|
|
67
|
+
userPrompt: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,KAAK,MAAM,GAAG,KAAK,MAAM,IAAI,EAAE,GACvF,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,GACrC,EAAE,CAAC;AAEP,KAAK,SAAS,GAAG,GAAG,CAAC;AAErB,KAAK,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,GAAG,MAAM,IAAI,EAAE,GACzF,IAAI,SAAS,SAAS,GACpB,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,GACf,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,GAC7B,GAAG,CAAC;AAER,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI;KAC7C,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO;CAC5C,CAAC;AAEF,KAAK,OAAO,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC;AAEhE,KAAK,YAAY,CAAC,IAAI,SAAS,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;AAEpH,KAAK,aAAa,CAAC,IAAI,SAAS,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;AAEtF,KAAK,WAAW,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;KACpE,CAAC,IAAI,eAAe,IAAI,aAAa,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,GAAG,YAAY,CACxG,eAAe,CAAC,SAAS,CAAC,CAC3B;CACF,GAAG;KACD,CAAC,IAAI,aAAa,IAAI,aAAa,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,GAAG,YAAY,CACtG,eAAe,CAAC,SAAS,CAAC,CAC3B;CACF,GAAG;KACD,CAAC,IAAI,eAAe,IAAI,aAAa,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,YAAY,CACzG,eAAe,CAAC,SAAS,CAAC,CAC3B;CACF,GAAG;KACD,CAAC,IAAI,aAAa,IAAI,aAAa,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,YAAY,CACvG,eAAe,CAAC,SAAS,CAAC,CAC3B;CACF,CAAC;AAEF,KAAK,kBAAkB,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAAI;IAC5E,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;CACpC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,GAClB,KAAK,GACL,IAAI,CAAC;AAET,KAAK,QAAQ,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,IAC9D,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,KAAK,GAClD,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,GAC7C,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7D,KAAK,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,MAAM,GAAG,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC;AAErH,KAAK,iBAAiB,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,MAAM,GAAG;IAAE,OAAO,EAAE,CAAC,CAAA;CAAE,GAAG,CAAC,CAAC;AAEzF,KAAK,WAAW,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAEvH,KAAK,cAAc,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,MAAM,IAC5D,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AAE/D,KAAK,WAAW,CAAC,CAAC,SAAS,MAAM,IAC/B,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AASxH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,CAAC;;oBAGvC,WAAW,CAAC,CAAC,CAAC;EAIjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EACvF,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,CAAC,EACf,UAAU,EAAE,CAAC,EACb,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;;WAcpB,KAAK,SAAS,MAAM,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,qEAG5D,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GACpE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;;;;EAuBzG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function replacePlaceholders(template, vars) {
|
|
2
|
+
return template.replaceAll(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
3
|
+
const key = rawKey.trim();
|
|
4
|
+
return vars && key in vars ? String(vars[key]) : match;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a reusable, type-safe prompt fragment with `{{placeholder}}` interpolation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const persona = promptSection("You are a {{role}}." as const);
|
|
13
|
+
* persona.render({ role: "translator" }); // "You are a translator."
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function promptSection(template) {
|
|
17
|
+
return {
|
|
18
|
+
template,
|
|
19
|
+
render(...args) {
|
|
20
|
+
return replacePlaceholders(template, args[0]);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Type-safe prompt template with system/user message pairs and optional Zod schema for structured output.
|
|
26
|
+
*
|
|
27
|
+
* Supports two modes:
|
|
28
|
+
* - **String mode** — pass plain strings as templates, call `render()` without a language key.
|
|
29
|
+
* - **Multi-language mode** — pass `Record<string, string>` maps, call `render(lang, options)`.
|
|
30
|
+
*
|
|
31
|
+
* Placeholders use the `{{key}}` syntax and are fully type-checked at compile time.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const p = prompt("greet", "Hello {{name}}" as const, "Hi!" as const);
|
|
36
|
+
* p.render({ systemOptions: { name: "Alice" } });
|
|
37
|
+
* // => { systemPrompt: "Hello Alice", userPrompt: "Hi!" }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function prompt(promptName, systemPrompt, userPrompt, zodSchema) {
|
|
41
|
+
const normalizedSystem = (typeof systemPrompt === "string" ? { default: systemPrompt } : systemPrompt);
|
|
42
|
+
const normalizedUser = (typeof userPrompt === "string" ? { default: userPrompt } : userPrompt);
|
|
43
|
+
return {
|
|
44
|
+
promptName,
|
|
45
|
+
zodSchema,
|
|
46
|
+
render(...args) {
|
|
47
|
+
let lang;
|
|
48
|
+
let evalOptions;
|
|
49
|
+
if (typeof args[0] === "string") {
|
|
50
|
+
lang = args[0];
|
|
51
|
+
evalOptions = (args[1] ?? {});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
lang = "default";
|
|
55
|
+
evalOptions = (args[0] ?? {});
|
|
56
|
+
}
|
|
57
|
+
const systemOptions = evalOptions.systemOptions;
|
|
58
|
+
const userOptions = evalOptions.userOptions;
|
|
59
|
+
const sys = replacePlaceholders(normalizedSystem[lang], systemOptions);
|
|
60
|
+
const usr = replacePlaceholders(normalizedUser[lang], userOptions);
|
|
61
|
+
return { systemPrompt: sys, userPrompt: usr };
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoEA,SAAS,mBAAmB,CAAC,QAAgB,EAAE,IAAyC;IACtF,OAAO,QAAQ,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QACvE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAmB,QAAW;IACzD,OAAO;QACL,QAAQ;QACR,MAAM,CAAC,GAAG,IAAoB;YAC5B,OAAO,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAwC,CAAC,CAAC;QACvF,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,MAAM,CACpB,UAAkB,EAClB,YAAe,EACf,UAAa,EACb,SAAwB;IAExB,MAAM,gBAAgB,GAAG,CACvB,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,YAAY,CACpD,CAAC;IAE1B,MAAM,cAAc,GAAG,CACrB,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAC9C,CAAC;IAE1B,OAAO;QACL,UAAU;QACV,SAAS;QACT,MAAM,CAIJ,GAAG,IAEiG;YAEpG,IAAI,IAAY,CAAC;YACjB,IAAI,WAAoC,CAAC;YAEzC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAChC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;gBACzB,WAAW,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAA4B,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,SAAS,CAAC;gBACjB,WAAW,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAA4B,CAAC;YAC3D,CAAC;YAED,MAAM,aAAa,GAAG,WAAW,CAAC,aAAoD,CAAC;YACvF,MAAM,WAAW,GAAG,WAAW,CAAC,WAAkD,CAAC;YAEnF,MAAM,GAAG,GAAG,mBAAmB,CAAE,gBAA2C,CAAC,IAAI,CAAW,EAAE,aAAa,CAAC,CAAC;YAE7G,MAAM,GAAG,GAAG,mBAAmB,CAAE,cAAyC,CAAC,IAAI,CAAW,EAAE,WAAW,CAAC,CAAC;YAEzG,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@joshuapassos/prompt-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Type-safe prompt templating engine with Zod schema support",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"author": "Joshua Passos",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"zod": "^3.25.32"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@biomejs/biome": "^2.4.9",
|
|
14
|
+
"knip": "^6.0.6",
|
|
15
|
+
"typescript": "~6.0.2",
|
|
16
|
+
"vitest": "^4.1.2"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/**"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"require": "./dist/index.js",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"clean": "rm -rf dist",
|
|
35
|
+
"lint": "biome check --write .",
|
|
36
|
+
"lint:check": "biome check .",
|
|
37
|
+
"knip": "knip"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type z from "zod";
|
|
2
|
+
|
|
3
|
+
type ExtractNewTypes<S extends string> = S extends `${string}{{${infer Key}}}${infer Rest}`
|
|
4
|
+
? [Trim<Key>, ...ExtractNewTypes<Rest>]
|
|
5
|
+
: [];
|
|
6
|
+
|
|
7
|
+
type Separator = " ";
|
|
8
|
+
|
|
9
|
+
type Trim<T extends string, Acc extends string = ""> = T extends `${infer Char}${infer Rest}`
|
|
10
|
+
? Char extends Separator
|
|
11
|
+
? Trim<Rest, Acc>
|
|
12
|
+
: Trim<Rest, `${Acc}${Char}`>
|
|
13
|
+
: Acc;
|
|
14
|
+
|
|
15
|
+
export type ArrayToObject<T extends string[]> = {
|
|
16
|
+
[K in T[number]]: string | number | boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type IsEmpty<T extends unknown[]> = T extends [] ? true : false;
|
|
20
|
+
|
|
21
|
+
type OptionsField<Keys extends string[]> = IsEmpty<Keys> extends true ? Record<string, never> : ArrayToObject<Keys>;
|
|
22
|
+
|
|
23
|
+
type OptionalField<Keys extends string[]> = IsEmpty<Keys> extends true ? true : false;
|
|
24
|
+
|
|
25
|
+
type EvalOptions<STemplate extends string, UTemplate extends string> = {
|
|
26
|
+
[K in "systemOptions" as OptionalField<ExtractNewTypes<STemplate>> extends true ? never : K]: OptionsField<
|
|
27
|
+
ExtractNewTypes<STemplate>
|
|
28
|
+
>;
|
|
29
|
+
} & {
|
|
30
|
+
[K in "userOptions" as OptionalField<ExtractNewTypes<UTemplate>> extends true ? never : K]: OptionsField<
|
|
31
|
+
ExtractNewTypes<UTemplate>
|
|
32
|
+
>;
|
|
33
|
+
} & {
|
|
34
|
+
[K in "systemOptions" as OptionalField<ExtractNewTypes<STemplate>> extends true ? K : never]?: OptionsField<
|
|
35
|
+
ExtractNewTypes<STemplate>
|
|
36
|
+
>;
|
|
37
|
+
} & {
|
|
38
|
+
[K in "userOptions" as OptionalField<ExtractNewTypes<UTemplate>> extends true ? K : never]?: OptionsField<
|
|
39
|
+
ExtractNewTypes<UTemplate>
|
|
40
|
+
>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type HasAnyPlaceholders<STemplate extends string, UTemplate extends string> = [
|
|
44
|
+
IsEmpty<ExtractNewTypes<STemplate>>,
|
|
45
|
+
IsEmpty<ExtractNewTypes<UTemplate>>,
|
|
46
|
+
] extends [true, true]
|
|
47
|
+
? false
|
|
48
|
+
: true;
|
|
49
|
+
|
|
50
|
+
type EvalArgs<STemplate extends string, UTemplate extends string> =
|
|
51
|
+
HasAnyPlaceholders<STemplate, UTemplate> extends false
|
|
52
|
+
? [options?: EvalOptions<STemplate, UTemplate>]
|
|
53
|
+
: [options: EvalOptions<STemplate, UTemplate>];
|
|
54
|
+
|
|
55
|
+
export type PromptTemplate = string | Record<string, string>;
|
|
56
|
+
|
|
57
|
+
type MatchLanguages<S extends PromptTemplate> = S extends string ? PromptTemplate : Record<keyof S & string, string>;
|
|
58
|
+
|
|
59
|
+
type NormalizeTemplate<T extends PromptTemplate> = T extends string ? { default: T } : T;
|
|
60
|
+
|
|
61
|
+
type GetTemplate<T extends PromptTemplate, K extends string> = T extends string ? T : K extends keyof T ? T[K] : never;
|
|
62
|
+
|
|
63
|
+
type StringTemplate<T extends PromptTemplate, K extends string> =
|
|
64
|
+
GetTemplate<T, K> extends string ? GetTemplate<T, K> : never;
|
|
65
|
+
|
|
66
|
+
type SectionArgs<T extends string> =
|
|
67
|
+
IsEmpty<ExtractNewTypes<T>> extends true ? [vars?: Record<string, never>] : [vars: ArrayToObject<ExtractNewTypes<T>>];
|
|
68
|
+
|
|
69
|
+
function replacePlaceholders(template: string, vars: Record<string, unknown> | undefined) {
|
|
70
|
+
return template.replaceAll(/\{\{([^}]+)\}\}/g, (match, rawKey: string) => {
|
|
71
|
+
const key = rawKey.trim();
|
|
72
|
+
return vars && key in vars ? String(vars[key]) : match;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates a reusable, type-safe prompt fragment with `{{placeholder}}` interpolation.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const persona = promptSection("You are a {{role}}." as const);
|
|
82
|
+
* persona.render({ role: "translator" }); // "You are a translator."
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function promptSection<T extends string>(template: T) {
|
|
86
|
+
return {
|
|
87
|
+
template,
|
|
88
|
+
render(...args: SectionArgs<T>) {
|
|
89
|
+
return replacePlaceholders(template, args[0] as Record<string, unknown> | undefined);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Type-safe prompt template with system/user message pairs and optional Zod schema for structured output.
|
|
96
|
+
*
|
|
97
|
+
* Supports two modes:
|
|
98
|
+
* - **String mode** — pass plain strings as templates, call `render()` without a language key.
|
|
99
|
+
* - **Multi-language mode** — pass `Record<string, string>` maps, call `render(lang, options)`.
|
|
100
|
+
*
|
|
101
|
+
* Placeholders use the `{{key}}` syntax and are fully type-checked at compile time.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const p = prompt("greet", "Hello {{name}}" as const, "Hi!" as const);
|
|
106
|
+
* p.render({ systemOptions: { name: "Alice" } });
|
|
107
|
+
* // => { systemPrompt: "Hello Alice", userPrompt: "Hi!" }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function prompt<S extends PromptTemplate, U extends MatchLanguages<S>, T = unknown>(
|
|
111
|
+
promptName: string,
|
|
112
|
+
systemPrompt: S,
|
|
113
|
+
userPrompt: U,
|
|
114
|
+
zodSchema?: z.ZodType<T>,
|
|
115
|
+
) {
|
|
116
|
+
const normalizedSystem = (
|
|
117
|
+
typeof systemPrompt === "string" ? { default: systemPrompt } : systemPrompt
|
|
118
|
+
) as NormalizeTemplate<S>;
|
|
119
|
+
|
|
120
|
+
const normalizedUser = (
|
|
121
|
+
typeof userPrompt === "string" ? { default: userPrompt } : userPrompt
|
|
122
|
+
) as NormalizeTemplate<U>;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
promptName,
|
|
126
|
+
zodSchema,
|
|
127
|
+
render<
|
|
128
|
+
TLang extends keyof NormalizeTemplate<S> & keyof NormalizeTemplate<U> = keyof NormalizeTemplate<S> &
|
|
129
|
+
keyof NormalizeTemplate<U>,
|
|
130
|
+
>(
|
|
131
|
+
...args: [S, U] extends [string, string]
|
|
132
|
+
? EvalArgs<S extends string ? S : never, U extends string ? U : never>
|
|
133
|
+
: [lang: TLang, ...EvalArgs<StringTemplate<S, TLang & string>, StringTemplate<U, TLang & string>>]
|
|
134
|
+
) {
|
|
135
|
+
let lang: string;
|
|
136
|
+
let evalOptions: Record<string, unknown>;
|
|
137
|
+
|
|
138
|
+
if (typeof args[0] === "string") {
|
|
139
|
+
lang = args[0] as string;
|
|
140
|
+
evalOptions = (args[1] ?? {}) as Record<string, unknown>;
|
|
141
|
+
} else {
|
|
142
|
+
lang = "default";
|
|
143
|
+
evalOptions = (args[0] ?? {}) as Record<string, unknown>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const systemOptions = evalOptions.systemOptions as Record<string, unknown> | undefined;
|
|
147
|
+
const userOptions = evalOptions.userOptions as Record<string, unknown> | undefined;
|
|
148
|
+
|
|
149
|
+
const sys = replacePlaceholders((normalizedSystem as Record<string, string>)[lang] as string, systemOptions);
|
|
150
|
+
|
|
151
|
+
const usr = replacePlaceholders((normalizedUser as Record<string, string>)[lang] as string, userOptions);
|
|
152
|
+
|
|
153
|
+
return { systemPrompt: sys, userPrompt: usr };
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|