soustack 0.2.3 → 0.4.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 +128 -18
- package/dist/cli/index.js +1706 -665
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +172 -28
- package/dist/index.d.ts +172 -28
- package/dist/index.js +2028 -662
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2022 -662
- package/dist/index.mjs.map +1 -1
- package/dist/{scrape.d.mts → scrape/index.d.mts} +38 -10
- package/dist/{scrape.d.ts → scrape/index.d.ts} +38 -10
- package/dist/{scrape.js → scrape/index.js} +268 -62
- package/dist/scrape/index.js.map +1 -0
- package/dist/{scrape.mjs → scrape/index.mjs} +268 -62
- package/dist/scrape/index.mjs.map +1 -0
- package/package.json +15 -9
- package/src/profiles/base.schema.json +2 -2
- package/src/profiles/cookable.schema.json +4 -4
- package/src/profiles/illustrated.schema.json +4 -4
- package/src/profiles/quantified.schema.json +4 -4
- package/src/profiles/scalable.schema.json +6 -6
- package/src/profiles/schedulable.schema.json +4 -4
- package/src/schema.json +15 -3
- package/src/soustack.schema.json +15 -3
- package/dist/scrape.js.map +0 -1
- package/dist/scrape.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ npm install soustack
|
|
|
37
37
|
|
|
38
38
|
## What's Included
|
|
39
39
|
|
|
40
|
-
- **Validation**: `validateRecipe()` validates Soustack JSON against the bundled schema.
|
|
40
|
+
- **Validation**: `validateRecipe()` validates Soustack JSON against the bundled schema and optional conformance checks.
|
|
41
41
|
- **Scaling & Computation**: `scaleRecipe()` scales a recipe while honoring per-ingredient scaling rules and instruction timing.
|
|
42
42
|
- **Schema.org Conversion**:
|
|
43
43
|
- `fromSchemaOrg()` (Schema.org JSON-LD → Soustack)
|
|
@@ -45,6 +45,7 @@ npm install soustack
|
|
|
45
45
|
- **Web Extraction**:
|
|
46
46
|
- Browser-safe HTML parsing: `extractSchemaOrgRecipeFromHTML()` (convert to Soustack with `fromSchemaOrg()`)
|
|
47
47
|
- Node-only scraping entrypoint: `scrapeRecipe()` and helpers via `import { ... } from 'soustack/scrape'`
|
|
48
|
+
- **Unit Conversion**: `convertLineItemToMetric()` converts ingredient line items from imperial volumes/masses into metric with deterministic rounding and ingredient-aware equivalencies.
|
|
48
49
|
|
|
49
50
|
## 🚀 Quickstart
|
|
50
51
|
|
|
@@ -53,36 +54,87 @@ Validate and scale a recipe in just a few lines:
|
|
|
53
54
|
```ts
|
|
54
55
|
import { validateRecipe, scaleRecipe } from 'soustack';
|
|
55
56
|
|
|
56
|
-
// Validate against the bundled Soustack schema
|
|
57
|
-
const {
|
|
58
|
-
if (!
|
|
59
|
-
throw new Error(JSON.stringify(
|
|
57
|
+
// Validate against the bundled Soustack schema + conformance rules
|
|
58
|
+
const { ok, schemaErrors, conformanceIssues, warnings } = validateRecipe(recipe);
|
|
59
|
+
if (!ok) {
|
|
60
|
+
throw new Error(JSON.stringify({ schemaErrors, conformanceIssues }, null, 2));
|
|
60
61
|
}
|
|
61
62
|
if (warnings?.length) {
|
|
62
63
|
console.warn('Non-blocking warnings', warnings);
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// Schema-only validation (skip conformance checks)
|
|
67
|
+
const schemaOnly = validateRecipe(recipe, { mode: 'schema' });
|
|
68
|
+
if (!schemaOnly.ok) {
|
|
69
|
+
console.error(schemaOnly.schemaErrors);
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
// Scale to a new yield (multiplier, target yield, or servings)
|
|
66
73
|
const scaled = scaleRecipe(recipe, { multiplier: 2 });
|
|
67
74
|
```
|
|
68
75
|
|
|
69
76
|
### Profile-aware validation
|
|
70
77
|
|
|
71
|
-
Use profiles to enforce integration contracts
|
|
78
|
+
Use profiles to enforce integration contracts. Available profiles:
|
|
79
|
+
- **base**
|
|
80
|
+
- **equipped**
|
|
81
|
+
- **illustrated**
|
|
82
|
+
- **lite**
|
|
83
|
+
- **prepped**
|
|
84
|
+
- **scalable**
|
|
85
|
+
- **timed**
|
|
72
86
|
|
|
73
87
|
```ts
|
|
74
88
|
import { detectProfiles, validateRecipe } from 'soustack';
|
|
75
89
|
|
|
76
90
|
// Discover which profiles a recipe already satisfies
|
|
77
|
-
const profiles = detectProfiles(recipe);
|
|
91
|
+
const profiles = detectProfiles(recipe);
|
|
78
92
|
|
|
79
|
-
// Validate
|
|
80
|
-
const result = validateRecipe(recipe, {
|
|
81
|
-
if (!result.
|
|
82
|
-
console.error('Profile validation failed', result.
|
|
93
|
+
// Validate with a specific profile
|
|
94
|
+
const result = validateRecipe(recipe, { profile: 'base' });
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
console.error('Profile validation failed', result.schemaErrors);
|
|
83
97
|
}
|
|
98
|
+
|
|
99
|
+
// Validate with modules
|
|
100
|
+
const recipeWithModules = {
|
|
101
|
+
profile: 'base',
|
|
102
|
+
modules: ['nutrition@1', 'times@1'],
|
|
103
|
+
name: 'Test Recipe',
|
|
104
|
+
ingredients: ['1 cup flour'],
|
|
105
|
+
instructions: ['Mix'],
|
|
106
|
+
nutrition: { calories: 100, protein_g: 5 }, // Module payload required if declared
|
|
107
|
+
times: { prepMinutes: 10, cookMinutes: 20, totalMinutes: 30 }, // v0.3: uses *Minutes fields
|
|
108
|
+
};
|
|
109
|
+
const result2 = validateRecipe(recipeWithModules);
|
|
110
|
+
// Validates using: base + profile + nutrition@1 module + times@1 module
|
|
111
|
+
// Module contract: if module is declared, payload must exist (and vice versa)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Imperial → metric ingredient conversion
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { convertLineItemToMetric } from 'soustack';
|
|
118
|
+
|
|
119
|
+
const flour = convertLineItemToMetric(
|
|
120
|
+
{ ingredient: 'flour', quantity: 2, unit: 'cup' },
|
|
121
|
+
'mass'
|
|
122
|
+
);
|
|
123
|
+
// -> { ingredient: 'flour', quantity: 240, unit: 'g', notes: 'Converted using 120g per cup...' }
|
|
124
|
+
|
|
125
|
+
const liquid = convertLineItemToMetric(
|
|
126
|
+
{ ingredient: 'milk', quantity: 2, unit: 'cup' },
|
|
127
|
+
'volume'
|
|
128
|
+
);
|
|
129
|
+
// -> { ingredient: 'milk', quantity: 473, unit: 'ml' }
|
|
84
130
|
```
|
|
85
131
|
|
|
132
|
+
The converter rounds using “sane” defaults (1 g/ml under 1 kg/1 L, then 5 g/10 ml and 2 decimal places for kg/L) and surfaces typed errors:
|
|
133
|
+
|
|
134
|
+
- `UnknownUnitError` for unsupported unit tokens
|
|
135
|
+
- `UnsupportedConversionError` if you request a mismatched dimension
|
|
136
|
+
- `MissingEquivalencyError` when no volume→mass density is registered for the ingredient/unit combo
|
|
137
|
+
|
|
86
138
|
### Browser-safe vs. Node-only entrypoints
|
|
87
139
|
|
|
88
140
|
- **Browser-safe:** `import { extractSchemaOrgRecipeFromHTML, fromSchemaOrg, validateRecipe, scaleRecipe } from 'soustack';`
|
|
@@ -92,10 +144,60 @@ if (!result.valid) {
|
|
|
92
144
|
|
|
93
145
|
## Spec compatibility & bundled schemas
|
|
94
146
|
|
|
95
|
-
- Targets Soustack spec **v0.
|
|
96
|
-
- Ships the base schema
|
|
147
|
+
- Targets Soustack spec **v0.3.0** (`spec/SOUSTACK_SPEC_VERSION`, exported as `SOUSTACK_SPEC_VERSION`).
|
|
148
|
+
- Ships the base schema, profile schemas, and module schemas in `spec/schemas/recipe/` and mirrors them into `src/schemas/recipe/` for consumers.
|
|
97
149
|
- Vendored fixtures live in `spec/fixtures` so tests can run offline, and version drift can be checked via `npm run validate:version`.
|
|
98
150
|
|
|
151
|
+
### Composed Validation Model
|
|
152
|
+
|
|
153
|
+
Soustack v0.3.0 uses a **composed validation model** where recipes are validated using JSON Schema's `allOf` composition:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"allOf": [
|
|
158
|
+
{ "$ref": "base.schema.json" },
|
|
159
|
+
{ "$ref": "profiles/{profile}.schema.json" },
|
|
160
|
+
{ "$ref": "modules/{module1}/{version}.schema.json" },
|
|
161
|
+
{ "$ref": "modules/{module2}/{version}.schema.json" }
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The validator:
|
|
167
|
+
- **Base schema**: Defines the core recipe structure (`@type`, `name`, `ingredients`, `instructions`, `profile`, `modules`)
|
|
168
|
+
- **Profile overlay**: Adds profile-specific requirements (e.g., `base` or `lite`)
|
|
169
|
+
- **Module overlays**: Each declared module adds its own validation rules
|
|
170
|
+
|
|
171
|
+
**Defaults:**
|
|
172
|
+
- If `profile` is missing, it defaults to the schema bundle's configured default
|
|
173
|
+
- If `modules` is missing, it defaults to `[]`
|
|
174
|
+
|
|
175
|
+
**Module Contract:** Modules enforce a symmetric contract:
|
|
176
|
+
- If a module is declared in `modules`, the corresponding payload must exist
|
|
177
|
+
- If a payload exists (e.g., `nutrition`, `times`), the module must be declared
|
|
178
|
+
- The validator automatically infers modules from payloads and enforces this contract
|
|
179
|
+
|
|
180
|
+
**Caching:** Validators are cached by `${profile}::${sortedModules.join(",")}` for performance.
|
|
181
|
+
|
|
182
|
+
### Module Resolution
|
|
183
|
+
|
|
184
|
+
Modules are resolved to schema references using the pattern:
|
|
185
|
+
- Module identifier format: `<name>@<version>` (e.g., `nutrition@1`, `schedule@1`)
|
|
186
|
+
- Schema reference: `https://soustack.org/schemas/recipe/modules/<name>/<version>.schema.json`
|
|
187
|
+
|
|
188
|
+
The module registry (`schemas/registry/modules.json`) defines which modules are available and their properties, including:
|
|
189
|
+
- `schemaOrgMappable`: Whether the module can be converted to Schema.org format
|
|
190
|
+
- `minProfile`: Minimum profile required to use the module
|
|
191
|
+
- `allowedOnLite`: Whether the module can be used with the lite profile
|
|
192
|
+
|
|
193
|
+
**Available Modules (v0.3.0):**
|
|
194
|
+
- `attribution@1`: Source attribution (url, author, datePublished)
|
|
195
|
+
- `taxonomy@1`: Classification (keywords, category, cuisine)
|
|
196
|
+
- `media@1`: Images and videos (images, videos arrays)
|
|
197
|
+
- `times@1`: Timing information (prepMinutes, cookMinutes, totalMinutes)
|
|
198
|
+
- `nutrition@1`: Nutritional data (calories, protein_g as numbers)
|
|
199
|
+
- `schedule@1`: Task scheduling (requires timed profile, includes instruction dependencies)
|
|
200
|
+
|
|
99
201
|
## Programmatic Usage
|
|
100
202
|
|
|
101
203
|
```ts
|
|
@@ -113,9 +215,9 @@ import {
|
|
|
113
215
|
} from 'soustack/scrape';
|
|
114
216
|
|
|
115
217
|
// Validate a Soustack recipe JSON object with profile enforcement
|
|
116
|
-
const validation = validateRecipe(recipe, {
|
|
117
|
-
if (!validation.
|
|
118
|
-
console.error(validation.
|
|
218
|
+
const validation = validateRecipe(recipe, { profile: 'base' });
|
|
219
|
+
if (!validation.ok) {
|
|
220
|
+
console.error({ schemaErrors: validation.schemaErrors, conformanceIssues: validation.conformanceIssues });
|
|
119
221
|
}
|
|
120
222
|
|
|
121
223
|
// Scale a recipe to a target yield amount (returns a "computed recipe")
|
|
@@ -167,6 +269,8 @@ async function convert(url: string) {
|
|
|
167
269
|
|
|
168
270
|
Use the helpers to move between Schema.org JSON-LD and Soustack's structured recipe format. The conversion automatically handles image normalization, supporting multiple image formats from Schema.org.
|
|
169
271
|
|
|
272
|
+
**BREAKING CHANGE in v0.3.0:** `toSchemaOrg()` now targets the **lite profile** and only includes modules that are marked as `schemaOrgMappable` in the modules registry. Non-mappable modules (e.g., `nutrition@1`, `schedule@1`) are excluded from the conversion.
|
|
273
|
+
|
|
170
274
|
```ts
|
|
171
275
|
import { fromSchemaOrg, toSchemaOrg, normalizeImage } from 'soustack';
|
|
172
276
|
|
|
@@ -268,10 +372,16 @@ const parsed = extractRecipeFromHTML(html);
|
|
|
268
372
|
|
|
269
373
|
```bash
|
|
270
374
|
# Validate with profiles (JSON output for pipelines)
|
|
271
|
-
npx soustack validate recipe.soustack.json --profile
|
|
375
|
+
npx soustack validate recipe.soustack.json --profile base --strict --json
|
|
376
|
+
|
|
377
|
+
# Schema-only validation (skip semantic conformance checks)
|
|
378
|
+
npx soustack validate recipe.soustack.json --schema-only
|
|
379
|
+
|
|
380
|
+
# Stable JSON conformance report for CI
|
|
381
|
+
npx soustack check recipe.soustack.json --json
|
|
272
382
|
|
|
273
383
|
# Repo-wide test run (validates every *.soustack.json)
|
|
274
|
-
npx soustack test --profile
|
|
384
|
+
npx soustack test --profile base
|
|
275
385
|
|
|
276
386
|
# Convert Schema.org ↔ Soustack
|
|
277
387
|
npx soustack convert --from schemaorg --to soustack recipe.jsonld -o recipe.soustack.json
|