soustack 0.2.1 → 0.3.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/src/schema.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "http://soustack.org/schema/v0.2",
4
- "title": "Soustack Recipe Schema v0.2",
3
+ "$id": "http://soustack.org/schema/v0.3.0",
4
+ "title": "Soustack Recipe Schema v0.3.0",
5
5
  "description": "A portable, scalable, interoperable recipe format.",
6
6
  "type": "object",
7
7
  "required": ["name", "ingredients", "instructions"],
8
+ "additionalProperties": false,
9
+ "patternProperties": {
10
+ "^x-": {}
11
+ },
8
12
  "properties": {
13
+ "$schema": {
14
+ "type": "string",
15
+ "format": "uri",
16
+ "description": "Optional schema hint for tooling compatibility"
17
+ },
9
18
  "id": {
10
19
  "type": "string",
11
20
  "description": "Unique identifier (slug or UUID)"
@@ -14,10 +23,19 @@
14
23
  "type": "string",
15
24
  "description": "The title of the recipe"
16
25
  },
26
+ "title": {
27
+ "type": "string",
28
+ "description": "Optional display title; alias for name"
29
+ },
17
30
  "version": {
18
31
  "type": "string",
19
32
  "pattern": "^\\d+\\.\\d+\\.\\d+$",
20
- "description": "Semantic versioning (e.g., 1.0.0)"
33
+ "description": "DEPRECATED: use recipeVersion for authoring revisions"
34
+ },
35
+ "recipeVersion": {
36
+ "type": "string",
37
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
38
+ "description": "Recipe content revision (semantic versioning, e.g., 1.0.0)"
21
39
  },
22
40
  "description": {
23
41
  "type": "string"
@@ -51,6 +69,11 @@
51
69
  "type": "string",
52
70
  "format": "date-time"
53
71
  },
72
+ "metadata": {
73
+ "type": "object",
74
+ "additionalProperties": true,
75
+ "description": "Free-form vendor metadata"
76
+ },
54
77
  "source": {
55
78
  "type": "object",
56
79
  "properties": {
@@ -110,31 +133,26 @@
110
133
  }
111
134
  },
112
135
  "time": {
113
- "oneOf": [
114
- {
115
- "type": "object",
116
- "properties": {
117
- "prep": { "type": "number" },
118
- "active": { "type": "number" },
119
- "passive": { "type": "number" },
120
- "total": { "type": "number" }
121
- }
122
- },
123
- {
124
- "type": "object",
125
- "properties": {
126
- "prepTime": { "type": "string" },
127
- "cookTime": { "type": "string" }
128
- }
129
- }
130
- ]
136
+ "type": "object",
137
+ "properties": {
138
+ "prep": { "type": "number" },
139
+ "active": { "type": "number" },
140
+ "passive": { "type": "number" },
141
+ "total": { "type": "number" },
142
+ "prepTime": { "type": "string", "format": "duration" },
143
+ "cookTime": { "type": "string", "format": "duration" }
144
+ },
145
+ "minProperties": 1
131
146
  },
132
147
  "quantity": {
133
148
  "type": "object",
134
149
  "required": ["amount"],
135
150
  "properties": {
136
151
  "amount": { "type": "number" },
137
- "unit": { "type": ["string", "null"] }
152
+ "unit": {
153
+ "type": ["string", "null"],
154
+ "description": "Display-friendly unit text; implementations may normalize or canonicalize units separately."
155
+ }
138
156
  }
139
157
  },
140
158
  "scaling": {
@@ -169,7 +187,16 @@
169
187
  "aisle": { "type": "string" },
170
188
  "prep": { "type": "string" },
171
189
  "prepAction": { "type": "string" },
190
+ "prepActions": {
191
+ "type": "array",
192
+ "items": { "type": "string" },
193
+ "description": "Structured prep verbs (e.g., peel, dice) for mise en place workflows."
194
+ },
172
195
  "prepTime": { "type": "number" },
196
+ "form": {
197
+ "type": "string",
198
+ "description": "State of the ingredient as used (packed, sifted, melted, room_temperature, etc.)."
199
+ },
173
200
  "destination": { "type": "string" },
174
201
  "scaling": { "$ref": "#/definitions/scaling" },
175
202
  "critical": { "type": "boolean" },
@@ -228,7 +255,13 @@
228
255
  "type": "object",
229
256
  "required": ["duration", "type"],
230
257
  "properties": {
231
- "duration": { "type": "number" },
258
+ "duration": {
259
+ "anyOf": [
260
+ { "type": "number" },
261
+ { "type": "string", "pattern": "^P" }
262
+ ],
263
+ "description": "Minutes as a number or ISO8601 duration string"
264
+ },
232
265
  "type": { "type": "string", "enum": ["active", "passive"] },
233
266
  "scaling": { "type": "string", "enum": ["linear", "fixed", "sqrt"] }
234
267
  }
@@ -0,0 +1,356 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "http://soustack.org/schema/v0.3.0",
4
+ "title": "Soustack Recipe Schema v0.3.0",
5
+ "description": "A portable, scalable, interoperable recipe format.",
6
+ "type": "object",
7
+ "required": ["name", "ingredients", "instructions"],
8
+ "additionalProperties": false,
9
+ "patternProperties": {
10
+ "^x-": {}
11
+ },
12
+ "properties": {
13
+ "$schema": {
14
+ "type": "string",
15
+ "format": "uri",
16
+ "description": "Optional schema hint for tooling compatibility"
17
+ },
18
+ "id": {
19
+ "type": "string",
20
+ "description": "Unique identifier (slug or UUID)"
21
+ },
22
+ "name": {
23
+ "type": "string",
24
+ "description": "The title of the recipe"
25
+ },
26
+ "title": {
27
+ "type": "string",
28
+ "description": "Optional display title; alias for name"
29
+ },
30
+ "version": {
31
+ "type": "string",
32
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
33
+ "description": "DEPRECATED: use recipeVersion for authoring revisions"
34
+ },
35
+ "recipeVersion": {
36
+ "type": "string",
37
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
38
+ "description": "Recipe content revision (semantic versioning, e.g., 1.0.0)"
39
+ },
40
+ "description": {
41
+ "type": "string"
42
+ },
43
+ "category": {
44
+ "type": "string",
45
+ "examples": ["Main Course", "Dessert"]
46
+ },
47
+ "tags": {
48
+ "type": "array",
49
+ "items": { "type": "string" }
50
+ },
51
+ "image": {
52
+ "description": "Recipe-level hero image(s)",
53
+ "anyOf": [
54
+ {
55
+ "type": "string",
56
+ "format": "uri"
57
+ },
58
+ {
59
+ "type": "array",
60
+ "minItems": 1,
61
+ "items": {
62
+ "type": "string",
63
+ "format": "uri"
64
+ }
65
+ }
66
+ ]
67
+ },
68
+ "dateAdded": {
69
+ "type": "string",
70
+ "format": "date-time"
71
+ },
72
+ "metadata": {
73
+ "type": "object",
74
+ "additionalProperties": true,
75
+ "description": "Free-form vendor metadata"
76
+ },
77
+ "source": {
78
+ "type": "object",
79
+ "properties": {
80
+ "author": { "type": "string" },
81
+ "url": { "type": "string", "format": "uri" },
82
+ "name": { "type": "string" },
83
+ "adapted": { "type": "boolean" }
84
+ }
85
+ },
86
+ "yield": {
87
+ "$ref": "#/definitions/yield"
88
+ },
89
+ "time": {
90
+ "$ref": "#/definitions/time"
91
+ },
92
+ "equipment": {
93
+ "type": "array",
94
+ "items": { "$ref": "#/definitions/equipment" }
95
+ },
96
+ "ingredients": {
97
+ "type": "array",
98
+ "items": {
99
+ "anyOf": [
100
+ { "type": "string" },
101
+ { "$ref": "#/definitions/ingredient" },
102
+ { "$ref": "#/definitions/ingredientSubsection" }
103
+ ]
104
+ }
105
+ },
106
+ "instructions": {
107
+ "type": "array",
108
+ "items": {
109
+ "anyOf": [
110
+ { "type": "string" },
111
+ { "$ref": "#/definitions/instruction" },
112
+ { "$ref": "#/definitions/instructionSubsection" }
113
+ ]
114
+ }
115
+ },
116
+ "storage": {
117
+ "$ref": "#/definitions/storage"
118
+ },
119
+ "substitutions": {
120
+ "type": "array",
121
+ "items": { "$ref": "#/definitions/substitution" }
122
+ }
123
+ },
124
+ "definitions": {
125
+ "yield": {
126
+ "type": "object",
127
+ "required": ["amount", "unit"],
128
+ "properties": {
129
+ "amount": { "type": "number" },
130
+ "unit": { "type": "string" },
131
+ "servings": { "type": "number" },
132
+ "description": { "type": "string" }
133
+ }
134
+ },
135
+ "time": {
136
+ "type": "object",
137
+ "properties": {
138
+ "prep": { "type": "number" },
139
+ "active": { "type": "number" },
140
+ "passive": { "type": "number" },
141
+ "total": { "type": "number" },
142
+ "prepTime": { "type": "string", "format": "duration" },
143
+ "cookTime": { "type": "string", "format": "duration" }
144
+ },
145
+ "minProperties": 1
146
+ },
147
+ "quantity": {
148
+ "type": "object",
149
+ "required": ["amount"],
150
+ "properties": {
151
+ "amount": { "type": "number" },
152
+ "unit": {
153
+ "type": ["string", "null"],
154
+ "description": "Display-friendly unit text; implementations may normalize or canonicalize units separately."
155
+ }
156
+ }
157
+ },
158
+ "scaling": {
159
+ "type": "object",
160
+ "required": ["type"],
161
+ "properties": {
162
+ "type": {
163
+ "type": "string",
164
+ "enum": ["linear", "discrete", "proportional", "fixed", "bakers_percentage"]
165
+ },
166
+ "factor": { "type": "number" },
167
+ "referenceId": { "type": "string" },
168
+ "roundTo": { "type": "number" },
169
+ "min": { "type": "number" },
170
+ "max": { "type": "number" }
171
+ },
172
+ "if": {
173
+ "properties": { "type": { "const": "bakers_percentage" } }
174
+ },
175
+ "then": {
176
+ "required": ["referenceId"]
177
+ }
178
+ },
179
+ "ingredient": {
180
+ "type": "object",
181
+ "required": ["item"],
182
+ "properties": {
183
+ "id": { "type": "string" },
184
+ "item": { "type": "string" },
185
+ "quantity": { "$ref": "#/definitions/quantity" },
186
+ "name": { "type": "string" },
187
+ "aisle": { "type": "string" },
188
+ "prep": { "type": "string" },
189
+ "prepAction": { "type": "string" },
190
+ "prepActions": {
191
+ "type": "array",
192
+ "items": { "type": "string" },
193
+ "description": "Structured prep verbs (e.g., peel, dice) for mise en place workflows."
194
+ },
195
+ "prepTime": { "type": "number" },
196
+ "form": {
197
+ "type": "string",
198
+ "description": "State of the ingredient as used (packed, sifted, melted, room_temperature, etc.)."
199
+ },
200
+ "destination": { "type": "string" },
201
+ "scaling": { "$ref": "#/definitions/scaling" },
202
+ "critical": { "type": "boolean" },
203
+ "optional": { "type": "boolean" },
204
+ "notes": { "type": "string" }
205
+ }
206
+ },
207
+ "ingredientSubsection": {
208
+ "type": "object",
209
+ "required": ["subsection", "items"],
210
+ "properties": {
211
+ "subsection": { "type": "string" },
212
+ "items": {
213
+ "type": "array",
214
+ "items": { "$ref": "#/definitions/ingredient" }
215
+ }
216
+ }
217
+ },
218
+ "equipment": {
219
+ "type": "object",
220
+ "required": ["name"],
221
+ "properties": {
222
+ "id": { "type": "string" },
223
+ "name": { "type": "string" },
224
+ "required": { "type": "boolean" },
225
+ "label": { "type": "string" },
226
+ "capacity": { "$ref": "#/definitions/quantity" },
227
+ "scalingLimit": { "type": "number" },
228
+ "alternatives": {
229
+ "type": "array",
230
+ "items": { "type": "string" }
231
+ }
232
+ }
233
+ },
234
+ "instruction": {
235
+ "type": "object",
236
+ "required": ["text"],
237
+ "properties": {
238
+ "id": { "type": "string" },
239
+ "text": { "type": "string" },
240
+ "image": {
241
+ "type": "string",
242
+ "format": "uri",
243
+ "description": "Optional image that illustrates this instruction"
244
+ },
245
+ "destination": { "type": "string" },
246
+ "dependsOn": {
247
+ "type": "array",
248
+ "items": { "type": "string" }
249
+ },
250
+ "inputs": {
251
+ "type": "array",
252
+ "items": { "type": "string" }
253
+ },
254
+ "timing": {
255
+ "type": "object",
256
+ "required": ["duration", "type"],
257
+ "properties": {
258
+ "duration": {
259
+ "anyOf": [
260
+ { "type": "number" },
261
+ { "type": "string", "pattern": "^P" }
262
+ ],
263
+ "description": "Minutes as a number or ISO8601 duration string"
264
+ },
265
+ "type": { "type": "string", "enum": ["active", "passive"] },
266
+ "scaling": { "type": "string", "enum": ["linear", "fixed", "sqrt"] }
267
+ }
268
+ }
269
+ }
270
+ },
271
+ "instructionSubsection": {
272
+ "type": "object",
273
+ "required": ["subsection", "items"],
274
+ "properties": {
275
+ "subsection": { "type": "string" },
276
+ "items": {
277
+ "type": "array",
278
+ "items": {
279
+ "anyOf": [
280
+ { "type": "string" },
281
+ { "$ref": "#/definitions/instruction" }
282
+ ]
283
+ }
284
+ }
285
+ }
286
+ },
287
+ "storage": {
288
+ "type": "object",
289
+ "properties": {
290
+ "roomTemp": { "$ref": "#/definitions/storageMethod" },
291
+ "refrigerated": { "$ref": "#/definitions/storageMethod" },
292
+ "frozen": {
293
+ "allOf": [
294
+ { "$ref": "#/definitions/storageMethod" },
295
+ {
296
+ "type": "object",
297
+ "properties": { "thawing": { "type": "string" } }
298
+ }
299
+ ]
300
+ },
301
+ "reheating": { "type": "string" },
302
+ "makeAhead": {
303
+ "type": "array",
304
+ "items": {
305
+ "allOf": [
306
+ { "$ref": "#/definitions/storageMethod" },
307
+ {
308
+ "type": "object",
309
+ "required": ["component", "storage"],
310
+ "properties": {
311
+ "component": { "type": "string" },
312
+ "storage": { "type": "string", "enum": ["roomTemp", "refrigerated", "frozen"] }
313
+ }
314
+ }
315
+ ]
316
+ }
317
+ }
318
+ }
319
+ },
320
+ "storageMethod": {
321
+ "type": "object",
322
+ "required": ["duration"],
323
+ "properties": {
324
+ "duration": { "type": "string", "pattern": "^P" },
325
+ "method": { "type": "string" },
326
+ "notes": { "type": "string" }
327
+ }
328
+ },
329
+ "substitution": {
330
+ "type": "object",
331
+ "required": ["ingredient"],
332
+ "properties": {
333
+ "ingredient": { "type": "string" },
334
+ "critical": { "type": "boolean" },
335
+ "notes": { "type": "string" },
336
+ "alternatives": {
337
+ "type": "array",
338
+ "items": {
339
+ "type": "object",
340
+ "required": ["name", "ratio"],
341
+ "properties": {
342
+ "name": { "type": "string" },
343
+ "ratio": { "type": "string" },
344
+ "notes": { "type": "string" },
345
+ "impact": { "type": "string" },
346
+ "dietary": {
347
+ "type": "array",
348
+ "items": { "type": "string" }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ }