markform 0.1.3 → 0.1.5

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.
Files changed (36) hide show
  1. package/README.md +110 -70
  2. package/dist/ai-sdk.d.mts +2 -2
  3. package/dist/ai-sdk.mjs +5 -5
  4. package/dist/{apply-00UmzDKL.mjs → apply-BCCiJzQr.mjs} +371 -26
  5. package/dist/bin.mjs +6 -6
  6. package/dist/{cli-D--Lel-e.mjs → cli-D469amuk.mjs} +386 -96
  7. package/dist/cli.mjs +6 -6
  8. package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-9XZSNOv6.d.mts} +1878 -325
  9. package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
  10. package/dist/index.d.mts +142 -5
  11. package/dist/index.mjs +5 -5
  12. package/dist/session-B_stoXQn.mjs +4 -0
  13. package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
  14. package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
  15. package/dist/shared-CZsyShck.mjs +3 -0
  16. package/dist/{src-Dm8jZ5dl.mjs → src-Df0XX7UB.mjs} +818 -125
  17. package/docs/markform-apis.md +194 -0
  18. package/{DOCS.md → docs/markform-reference.md} +130 -69
  19. package/{SPEC.md → docs/markform-spec.md} +359 -108
  20. package/examples/earnings-analysis/earnings-analysis.form.md +88 -800
  21. package/examples/earnings-analysis/earnings-analysis.valid.ts +16 -148
  22. package/examples/movie-research/movie-research-basic.form.md +41 -37
  23. package/examples/movie-research/movie-research-deep.form.md +110 -98
  24. package/examples/movie-research/movie-research-minimal.form.md +29 -15
  25. package/examples/simple/simple-mock-filled.form.md +105 -41
  26. package/examples/simple/simple-skipped-filled.form.md +103 -41
  27. package/examples/simple/simple-with-skips.session.yaml +93 -25
  28. package/examples/simple/simple.form.md +86 -32
  29. package/examples/simple/simple.session.yaml +98 -25
  30. package/examples/startup-deep-research/startup-deep-research.form.md +130 -103
  31. package/examples/startup-research/startup-research-mock-filled.form.md +55 -55
  32. package/examples/startup-research/startup-research.form.md +36 -36
  33. package/package.json +18 -19
  34. package/dist/session-DdAtY2Ni.mjs +0 -4
  35. package/dist/shared-D7gf27Tr.mjs +0 -3
  36. package/examples/celebrity-deep-research/celebrity-deep-research.form.md +0 -912
@@ -4,9 +4,9 @@
4
4
  * These validators demonstrate parameterized validation patterns.
5
5
  * Parameters are passed via ctx.params from the validate attribute:
6
6
  *
7
- * validate=[{id: "min_words", min: 50}]
7
+ * validate=[{id: "min_words", min: 20}]
8
8
  *
9
- * The validator receives { min: 50 } in ctx.params.
9
+ * The validator receives { min: 20 } in ctx.params.
10
10
  */
11
11
 
12
12
  import type { ValidatorContext, ValidationIssue } from 'markform';
@@ -28,27 +28,17 @@ function getStringValue(values: Record<string, unknown>, fieldId: string): strin
28
28
  return field?.kind === 'string' && field.value ? field.value : null;
29
29
  }
30
30
 
31
- function getNumberValue(values: Record<string, unknown>, fieldId: string): number | null {
32
- const field = values[fieldId] as { kind: string; value?: number | null } | undefined;
33
- return field?.kind === 'number' && field.value != null ? field.value : null;
34
- }
35
-
36
31
  function getStringListItems(values: Record<string, unknown>, fieldId: string): string[] {
37
32
  const field = values[fieldId] as { kind: string; items?: string[] } | undefined;
38
33
  return field?.kind === 'string_list' && field.items ? field.items : [];
39
34
  }
40
35
 
41
- function getMultiSelectSelections(values: Record<string, unknown>, fieldId: string): string[] {
42
- const field = values[fieldId] as { kind: string; selected?: string[] } | undefined;
43
- return field?.kind === 'multi_select' && field.selected ? field.selected : [];
44
- }
45
-
46
36
  function getSingleSelectValue(values: Record<string, unknown>, fieldId: string): string | null {
47
37
  const field = values[fieldId] as { kind: string; selected?: string | null } | undefined;
48
38
  return field?.kind === 'single_select' && field.selected ? field.selected : null;
49
39
  }
50
40
 
51
- // Parameterized Validators
41
+ // Validators
52
42
 
53
43
  /**
54
44
  * Validate minimum word count.
@@ -118,45 +108,6 @@ function maxWords(ctx: ValidatorContext): ValidationIssue[] {
118
108
  return [];
119
109
  }
120
110
 
121
- /**
122
- * Validate that specified number fields sum to a target.
123
- * Params: { fields: string[], target?: number, tolerance?: number }
124
- */
125
- function sumTo(ctx: ValidatorContext): ValidationIssue[] {
126
- const fields = ctx.params.fields as string[];
127
- const target = (ctx.params.target as number) ?? 100;
128
- const tolerance = (ctx.params.tolerance as number) ?? 0.1;
129
-
130
- if (!Array.isArray(fields)) {
131
- return [
132
- {
133
- severity: 'error',
134
- message: 'sum_to requires "fields" array parameter',
135
- ref: ctx.targetId,
136
- source: 'code',
137
- },
138
- ];
139
- }
140
-
141
- const values = fields.map((fieldId) => getNumberValue(ctx.values, fieldId) ?? 0);
142
- const sum = values.reduce((a, b) => a + b, 0);
143
-
144
- // Only validate if at least one value is set
145
- if (values.every((v) => v === 0)) return [];
146
-
147
- if (Math.abs(sum - target) > tolerance) {
148
- return [
149
- {
150
- severity: 'error',
151
- message: `Fields must sum to ${target}% (currently ${sum.toFixed(1)}%)`,
152
- ref: fields[0],
153
- source: 'code',
154
- },
155
- ];
156
- }
157
- return [];
158
- }
159
-
160
111
  /**
161
112
  * Validate that string-list items with "Label: XX%" format sum to target.
162
113
  * Params: { target?: number }
@@ -197,117 +148,38 @@ function sumToPercentList(ctx: ValidatorContext): ValidationIssue[] {
197
148
  }
198
149
 
199
150
  /**
200
- * Require field when another field has a value.
201
- * Params: { when: string, then?: string }
151
+ * Require field when another field has any value set.
152
+ * Params: { when: string }
202
153
  */
203
- function requiredIf(ctx: ValidatorContext): ValidationIssue[] {
154
+ function requiredIfSet(ctx: ValidatorContext): ValidationIssue[] {
204
155
  const triggerField = ctx.params.when as string;
205
- const targetField = (ctx.params.then as string) ?? ctx.targetId;
206
156
 
207
157
  if (!triggerField) {
208
158
  return [
209
159
  {
210
160
  severity: 'error',
211
- message: 'required_if requires "when" parameter',
161
+ message: 'required_if_set requires "when" parameter',
212
162
  ref: ctx.targetId,
213
163
  source: 'code',
214
164
  },
215
165
  ];
216
166
  }
217
167
 
218
- const trigger = ctx.values[triggerField] as Record<string, unknown> | undefined;
219
- const target = ctx.values[targetField] as Record<string, unknown> | undefined;
220
-
168
+ // Check if trigger field has a value
221
169
  const triggerHasValue =
222
- (trigger?.kind === 'string' && (trigger.value as string)?.trim()) ||
223
- (trigger?.kind === 'number' && trigger.value != null) ||
224
- (trigger?.kind === 'multi_select' && (trigger.selected as string[])?.length > 0);
170
+ getStringValue(ctx.values, triggerField) !== null ||
171
+ getSingleSelectValue(ctx.values, triggerField) !== null ||
172
+ getStringListItems(ctx.values, triggerField).length > 0;
225
173
 
226
- const targetEmpty =
227
- !target ||
228
- (target.kind === 'string' && !(target.value as string)?.trim()) ||
229
- (target.kind === 'number' && target.value == null);
174
+ // Check if target field is empty
175
+ const targetValue = getStringValue(ctx.values, ctx.targetId);
176
+ const targetEmpty = !targetValue || targetValue.trim().length === 0;
230
177
 
231
178
  if (triggerHasValue && targetEmpty) {
232
179
  return [
233
180
  {
234
181
  severity: 'error',
235
- message: `This field is required when ${triggerField} has a value`,
236
- ref: targetField,
237
- source: 'code',
238
- },
239
- ];
240
- }
241
- return [];
242
- }
243
-
244
- /**
245
- * Require field when another field equals a specific value.
246
- * Params: { when: string, equals: string, then?: string }
247
- */
248
- function requiredIfEquals(ctx: ValidatorContext): ValidationIssue[] {
249
- const triggerField = ctx.params.when as string;
250
- const expectedValue = ctx.params.equals as string;
251
- const targetField = (ctx.params.then as string) ?? ctx.targetId;
252
-
253
- if (!triggerField || expectedValue === undefined) {
254
- return [
255
- {
256
- severity: 'error',
257
- message: 'required_if_equals requires "when" and "equals" parameters',
258
- ref: ctx.targetId,
259
- source: 'code',
260
- },
261
- ];
262
- }
263
-
264
- const triggerValue = getSingleSelectValue(ctx.values, triggerField);
265
- const target = getStringValue(ctx.values, targetField);
266
-
267
- if (triggerValue === expectedValue && (!target || target.trim().length === 0)) {
268
- return [
269
- {
270
- severity: 'error',
271
- message: `This field is required when ${triggerField} is "${expectedValue}"`,
272
- ref: targetField,
273
- source: 'code',
274
- },
275
- ];
276
- }
277
- return [];
278
- }
279
-
280
- /**
281
- * Validate that list items match a regex pattern.
282
- * Params: { pattern: string, example?: string }
283
- */
284
- function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
285
- const pattern = ctx.params.pattern as string;
286
- const example = (ctx.params.example as string) ?? '';
287
-
288
- if (!pattern) {
289
- return [
290
- {
291
- severity: 'error',
292
- message: 'item_format requires "pattern" parameter',
293
- ref: ctx.targetId,
294
- source: 'code',
295
- },
296
- ];
297
- }
298
-
299
- const items = getStringListItems(ctx.values, ctx.targetId);
300
- if (items.length === 0) return [];
301
-
302
- const regex = new RegExp(pattern);
303
- const malformed = items.filter((item) => !regex.test(item));
304
-
305
- if (malformed.length > 0) {
306
- const hint = example ? ` Expected format: "${example}"` : '';
307
- return [
308
- {
309
- severity: 'warning',
310
- message: `${malformed.length} item(s) don't match expected format.${hint}`,
182
+ message: `This field is required when "${triggerField}" has a value`,
311
183
  ref: ctx.targetId,
312
184
  source: 'code',
313
185
  },
@@ -319,12 +191,8 @@ function itemFormat(ctx: ValidatorContext): ValidationIssue[] {
319
191
  // Exported Validators Registry
320
192
 
321
193
  export const validators: Record<string, (ctx: ValidatorContext) => ValidationIssue[]> = {
322
- // Parameterized validators
323
194
  min_words: minWords,
324
195
  max_words: maxWords,
325
- sum_to: sumTo,
326
196
  sum_to_percent_list: sumToPercentList,
327
- required_if: requiredIf,
328
- required_if_equals: requiredIfEquals,
329
- item_format: itemFormat,
197
+ required_if_set: requiredIfSet,
330
198
  };
@@ -34,131 +34,135 @@ markform:
34
34
  Standard research form for gathering ratings and key statistics for any film. Pulls from IMDB, Rotten Tomatoes, and Metacritic.
35
35
  {% /description %}
36
36
 
37
- {% field-group id="movie_input" title="Movie Identification" %}
37
+ {% group id="movie_input" title="Movie Identification" %}
38
38
 
39
- {% string-field id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /string-field %}
39
+ {% field kind="string" id="movie" label="Movie" role="user" required=true minLength=1 maxLength=300 %}{% /field %}
40
40
 
41
41
  {% instructions ref="movie" %}
42
42
  Enter the movie title (add any details to help identify, like "Barbie 2023" or "the Batman movie with Robert Pattinson")
43
43
  {% /instructions %}
44
44
 
45
- {% /field-group %}
45
+ {% /group %}
46
46
 
47
- {% field-group id="title_identification" title="Title Identification" %}
47
+ {% group id="title_identification" title="Title Identification" %}
48
48
 
49
- {% string-field id="full_title" label="Full Title" role="agent" required=true %}{% /string-field %}
49
+ {% field kind="string" id="full_title" label="Full Title" role="agent" required=true %}{% /field %}
50
50
 
51
51
  {% instructions ref="full_title" %}
52
52
  Look up what film the user had in mind and fill in the official title including subtitle if any (e.g., "The Lord of the Rings: The Fellowship of the Ring").
53
53
  {% /instructions %}
54
54
 
55
- {% /field-group %}
55
+ {% /group %}
56
56
 
57
- {% field-group id="sources" title="Sources" %}
57
+ {% group id="sources" title="Sources" %}
58
58
 
59
- {% url-field id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /url-field %}
59
+ {% field kind="url" id="imdb_url" label="IMDB URL" role="agent" required=true %}{% /field %}
60
60
 
61
61
  {% instructions ref="imdb_url" %}
62
62
  Direct link to the movie's IMDB page (e.g., https://www.imdb.com/title/tt0111161/).
63
63
  {% /instructions %}
64
64
 
65
- {% url-field id="rt_url" label="Rotten Tomatoes URL" role="agent" %}{% /url-field %}
65
+ {% field kind="url" id="rt_url" label="Rotten Tomatoes URL" role="agent" %}{% /field %}
66
66
 
67
67
  {% instructions ref="rt_url" %}
68
68
  Direct link to the movie's Rotten Tomatoes page.
69
69
  {% /instructions %}
70
70
 
71
- {% url-field id="metacritic_url" label="Metacritic URL" role="agent" %}{% /url-field %}
71
+ {% field kind="url" id="metacritic_url" label="Metacritic URL" role="agent" %}{% /field %}
72
72
 
73
73
  {% instructions ref="metacritic_url" %}
74
74
  Direct link to the movie's Metacritic page.
75
75
  {% /instructions %}
76
76
 
77
- {% /field-group %}
77
+ {% /group %}
78
78
 
79
- {% field-group id="basic_details" title="Basic Details" %}
79
+ {% group id="basic_details" title="Basic Details" %}
80
80
 
81
- {% number-field id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /number-field %}
81
+ {% field kind="number" id="year" label="Release Year" role="agent" required=true min=1888 max=2030 %}{% /field %}
82
82
 
83
- {% string-list id="directors" label="Director(s)" role="agent" required=true %}{% /string-list %}
83
+ {% field kind="string_list" id="directors" label="Director(s)" role="agent" required=true %}{% /field %}
84
84
 
85
85
  {% instructions ref="directors" %}
86
86
  One director per line. Most films have one; some have two or more co-directors.
87
87
  {% /instructions %}
88
88
 
89
- {% number-field id="runtime_minutes" label="Runtime (minutes)" role="agent" min=1 max=1000 %}{% /number-field %}
89
+ {% field kind="number" id="runtime_minutes" label="Runtime (minutes)" role="agent" min=1 max=1000 %}{% /field %}
90
90
 
91
- {% single-select id="mpaa_rating" label="MPAA Rating" role="agent" %}
91
+ {% field kind="single_select" id="mpaa_rating" label="MPAA Rating" role="agent" %}
92
92
  - [ ] G {% #g %}
93
93
  - [ ] PG {% #pg %}
94
94
  - [ ] PG-13 {% #pg_13 %}
95
95
  - [ ] R {% #r %}
96
96
  - [ ] NC-17 {% #nc_17 %}
97
97
  - [ ] NR/Unrated {% #nr %}
98
- {% /single-select %}
98
+ {% /field %}
99
99
 
100
- {% /field-group %}
100
+ {% /group %}
101
101
 
102
- {% field-group id="imdb_ratings" title="IMDB Ratings" %}
102
+ {% group id="imdb_ratings" title="IMDB Ratings" %}
103
103
 
104
- {% number-field id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /number-field %}
104
+ {% field kind="number" id="imdb_rating" label="IMDB Rating" role="agent" min=1.0 max=10.0 %}{% /field %}
105
105
 
106
106
  {% instructions ref="imdb_rating" %}
107
107
  IMDB user rating (1.0-10.0 scale).
108
108
  {% /instructions %}
109
109
 
110
- {% number-field id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /number-field %}
110
+ {% field kind="number" id="imdb_votes" label="IMDB Vote Count" role="agent" min=0 %}{% /field %}
111
111
 
112
112
  {% instructions ref="imdb_votes" %}
113
113
  Number of IMDB user votes (e.g., 2800000 for a popular film).
114
114
  {% /instructions %}
115
115
 
116
- {% /field-group %}
116
+ {% /group %}
117
117
 
118
- {% field-group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
118
+ {% group id="rotten_tomatoes_ratings" title="Rotten Tomatoes Ratings" %}
119
119
 
120
- {% number-field id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /number-field %}
120
+ {% field kind="number" id="rt_critics_score" label="Tomatometer (Critics)" role="agent" min=0 max=100 %}{% /field %}
121
121
 
122
122
  {% instructions ref="rt_critics_score" %}
123
123
  Tomatometer percentage (0-100).
124
124
  {% /instructions %}
125
125
 
126
- {% number-field id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /number-field %}
126
+ {% field kind="number" id="rt_critics_count" label="Critics Review Count" role="agent" min=0 %}{% /field %}
127
127
 
128
- {% number-field id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /number-field %}
128
+ {% field kind="number" id="rt_audience_score" label="Audience Score" role="agent" min=0 max=100 %}{% /field %}
129
129
 
130
130
  {% instructions ref="rt_audience_score" %}
131
131
  Audience Score percentage (0-100).
132
132
  {% /instructions %}
133
133
 
134
- {% /field-group %}
134
+ {% /group %}
135
135
 
136
- {% field-group id="metacritic_ratings" title="Metacritic Ratings" %}
136
+ {% group id="metacritic_ratings" title="Metacritic Ratings" %}
137
137
 
138
- {% number-field id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /number-field %}
138
+ {% field kind="number" id="metacritic_score" label="Metacritic Score" role="agent" min=0 max=100 %}{% /field %}
139
139
 
140
140
  {% instructions ref="metacritic_score" %}
141
141
  Metascore (0-100 scale). Leave empty if not available.
142
142
  {% /instructions %}
143
143
 
144
- {% /field-group %}
144
+ {% /group %}
145
145
 
146
- {% field-group id="summary" title="Summary" %}
146
+ {% group id="summary" title="Summary" %}
147
147
 
148
- {% string-field id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /string-field %}
148
+ {% field kind="string" id="logline" label="One-Line Summary" role="agent" maxLength=300 %}{% /field %}
149
149
 
150
150
  {% instructions ref="logline" %}
151
151
  Brief plot summary in 1-2 sentences, no spoilers.
152
152
  {% /instructions %}
153
153
 
154
- {% string-list id="notable_awards" label="Notable Awards" role="agent" %}{% /string-list %}
154
+ {% field kind="table" id="notable_awards" label="Notable Awards" role="agent"
155
+ columnIds=["award", "category", "year"]
156
+ columnTypes=["string", "string", "year"] %}
157
+ | Award | Category | Year |
158
+ |-------|----------|------|
159
+ {% /field %}
155
160
 
156
161
  {% instructions ref="notable_awards" %}
157
- Major awards won. One per line.
158
- Format: Award | Category | Year
159
- Example: "Oscar | Best Picture | 1995"
162
+ Major awards won.
163
+ Example: Oscar | Best Picture | 1995
160
164
  {% /instructions %}
161
165
 
162
- {% /field-group %}
166
+ {% /group %}
163
167
 
164
168
  {% /form %}