soustack 0.2.3 → 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/README.md +100 -7
- package/dist/cli/index.js +738 -793
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +117 -19
- package/dist/index.d.ts +117 -19
- package/dist/index.js +1264 -801
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1260 -802
- package/dist/index.mjs.map +1 -1
- package/dist/scrape.d.mts +36 -10
- package/dist/scrape.d.ts +36 -10
- package/dist/scrape.js +105 -3
- package/dist/scrape.js.map +1 -1
- package/dist/scrape.mjs +105 -3
- package/dist/scrape.mjs.map +1 -1
- package/package.json +7 -4
- 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/README.md
CHANGED
|
@@ -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
|
|
|
@@ -68,21 +69,61 @@ const scaled = scaleRecipe(recipe, { multiplier: 2 });
|
|
|
68
69
|
|
|
69
70
|
### Profile-aware validation
|
|
70
71
|
|
|
71
|
-
Use profiles to enforce integration contracts
|
|
72
|
+
Use profiles to enforce integration contracts. Available profiles:
|
|
73
|
+
- **minimal**: Basic recipe structure with minimal requirements
|
|
74
|
+
- **core**: Enhanced profile with structured ingredients and instructions
|
|
72
75
|
|
|
73
76
|
```ts
|
|
74
77
|
import { detectProfiles, validateRecipe } from 'soustack';
|
|
75
78
|
|
|
76
79
|
// Discover which profiles a recipe already satisfies
|
|
77
|
-
const profiles = detectProfiles(recipe); // e.g. ['
|
|
80
|
+
const profiles = detectProfiles(recipe); // e.g. ['minimal', 'core']
|
|
78
81
|
|
|
79
|
-
// Validate
|
|
80
|
-
const result = validateRecipe(recipe, {
|
|
82
|
+
// Validate with a specific profile (defaults to 'core' if not specified)
|
|
83
|
+
const result = validateRecipe(recipe, { profile: 'minimal' });
|
|
81
84
|
if (!result.valid) {
|
|
82
85
|
console.error('Profile validation failed', result.errors);
|
|
83
86
|
}
|
|
87
|
+
|
|
88
|
+
// Validate with modules
|
|
89
|
+
const recipeWithModules = {
|
|
90
|
+
profile: 'minimal',
|
|
91
|
+
modules: ['nutrition@1', 'times@1'],
|
|
92
|
+
name: 'Test Recipe',
|
|
93
|
+
ingredients: ['1 cup flour'],
|
|
94
|
+
instructions: ['Mix'],
|
|
95
|
+
nutrition: { calories: 100, protein_g: 5 }, // Module payload required if declared
|
|
96
|
+
times: { prepMinutes: 10, cookMinutes: 20, totalMinutes: 30 }, // v0.3: uses *Minutes fields
|
|
97
|
+
};
|
|
98
|
+
const result2 = validateRecipe(recipeWithModules);
|
|
99
|
+
// Validates using: base + minimal profile + nutrition@1 module + times@1 module
|
|
100
|
+
// Module contract: if module is declared, payload must exist (and vice versa)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Imperial → metric ingredient conversion
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { convertLineItemToMetric } from 'soustack';
|
|
107
|
+
|
|
108
|
+
const flour = convertLineItemToMetric(
|
|
109
|
+
{ ingredient: 'flour', quantity: 2, unit: 'cup' },
|
|
110
|
+
'mass'
|
|
111
|
+
);
|
|
112
|
+
// -> { ingredient: 'flour', quantity: 240, unit: 'g', notes: 'Converted using 120g per cup...' }
|
|
113
|
+
|
|
114
|
+
const liquid = convertLineItemToMetric(
|
|
115
|
+
{ ingredient: 'milk', quantity: 2, unit: 'cup' },
|
|
116
|
+
'volume'
|
|
117
|
+
);
|
|
118
|
+
// -> { ingredient: 'milk', quantity: 473, unit: 'ml' }
|
|
84
119
|
```
|
|
85
120
|
|
|
121
|
+
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:
|
|
122
|
+
|
|
123
|
+
- `UnknownUnitError` for unsupported unit tokens
|
|
124
|
+
- `UnsupportedConversionError` if you request a mismatched dimension
|
|
125
|
+
- `MissingEquivalencyError` when no volume→mass density is registered for the ingredient/unit combo
|
|
126
|
+
|
|
86
127
|
### Browser-safe vs. Node-only entrypoints
|
|
87
128
|
|
|
88
129
|
- **Browser-safe:** `import { extractSchemaOrgRecipeFromHTML, fromSchemaOrg, validateRecipe, scaleRecipe } from 'soustack';`
|
|
@@ -92,10 +133,60 @@ if (!result.valid) {
|
|
|
92
133
|
|
|
93
134
|
## Spec compatibility & bundled schemas
|
|
94
135
|
|
|
95
|
-
- Targets Soustack spec **v0.
|
|
96
|
-
- Ships the base schema
|
|
136
|
+
- Targets Soustack spec **v0.3.0** (`spec/SOUSTACK_SPEC_VERSION`, exported as `SOUSTACK_SPEC_VERSION`).
|
|
137
|
+
- Ships the base schema, profile schemas, and module schemas in `spec/schemas/recipe/` and mirrors them into `src/schemas/recipe/` for consumers.
|
|
97
138
|
- Vendored fixtures live in `spec/fixtures` so tests can run offline, and version drift can be checked via `npm run validate:version`.
|
|
98
139
|
|
|
140
|
+
### Composed Validation Model
|
|
141
|
+
|
|
142
|
+
Soustack v0.3.0 uses a **composed validation model** where recipes are validated using JSON Schema's `allOf` composition:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"allOf": [
|
|
147
|
+
{ "$ref": "base.schema.json" },
|
|
148
|
+
{ "$ref": "profiles/{profile}.schema.json" },
|
|
149
|
+
{ "$ref": "modules/{module1}/{version}.schema.json" },
|
|
150
|
+
{ "$ref": "modules/{module2}/{version}.schema.json" }
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The validator:
|
|
156
|
+
- **Base schema**: Defines the core recipe structure (`@type`, `name`, `ingredients`, `instructions`, `profile`, `modules`)
|
|
157
|
+
- **Profile overlay**: Adds profile-specific requirements (e.g., `minimal` or `core`)
|
|
158
|
+
- **Module overlays**: Each declared module adds its own validation rules
|
|
159
|
+
|
|
160
|
+
**Defaults:**
|
|
161
|
+
- If `profile` is missing, it defaults to `"core"`
|
|
162
|
+
- If `modules` is missing, it defaults to `[]`
|
|
163
|
+
|
|
164
|
+
**Module Contract:** Modules enforce a symmetric contract:
|
|
165
|
+
- If a module is declared in `modules`, the corresponding payload must exist
|
|
166
|
+
- If a payload exists (e.g., `nutrition`, `times`), the module must be declared
|
|
167
|
+
- The validator automatically infers modules from payloads and enforces this contract
|
|
168
|
+
|
|
169
|
+
**Caching:** Validators are cached by `${profile}::${sortedModules.join(",")}` for performance.
|
|
170
|
+
|
|
171
|
+
### Module Resolution
|
|
172
|
+
|
|
173
|
+
Modules are resolved to schema references using the pattern:
|
|
174
|
+
- Module identifier format: `<name>@<version>` (e.g., `nutrition@1`, `schedule@1`)
|
|
175
|
+
- Schema reference: `https://soustack.org/schemas/recipe/modules/<name>/<version>.schema.json`
|
|
176
|
+
|
|
177
|
+
The module registry (`schemas/registry/modules.json`) defines which modules are available and their properties, including:
|
|
178
|
+
- `schemaOrgMappable`: Whether the module can be converted to Schema.org format
|
|
179
|
+
- `minProfile`: Minimum profile required to use the module
|
|
180
|
+
- `allowedOnMinimal`: Whether the module can be used with the minimal profile
|
|
181
|
+
|
|
182
|
+
**Available Modules (v0.3.0):**
|
|
183
|
+
- `attribution@1`: Source attribution (url, author, datePublished)
|
|
184
|
+
- `taxonomy@1`: Classification (keywords, category, cuisine)
|
|
185
|
+
- `media@1`: Images and videos (images, videos arrays)
|
|
186
|
+
- `times@1`: Timing information (prepMinutes, cookMinutes, totalMinutes)
|
|
187
|
+
- `nutrition@1`: Nutritional data (calories, protein_g as numbers)
|
|
188
|
+
- `schedule@1`: Task scheduling (requires core profile, includes instruction dependencies)
|
|
189
|
+
|
|
99
190
|
## Programmatic Usage
|
|
100
191
|
|
|
101
192
|
```ts
|
|
@@ -113,7 +204,7 @@ import {
|
|
|
113
204
|
} from 'soustack/scrape';
|
|
114
205
|
|
|
115
206
|
// Validate a Soustack recipe JSON object with profile enforcement
|
|
116
|
-
const validation = validateRecipe(recipe, {
|
|
207
|
+
const validation = validateRecipe(recipe, { profile: 'core' });
|
|
117
208
|
if (!validation.valid) {
|
|
118
209
|
console.error(validation.errors);
|
|
119
210
|
}
|
|
@@ -167,6 +258,8 @@ async function convert(url: string) {
|
|
|
167
258
|
|
|
168
259
|
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
260
|
|
|
261
|
+
**BREAKING CHANGE in v0.3.0:** `toSchemaOrg()` now targets the **minimal 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.
|
|
262
|
+
|
|
170
263
|
```ts
|
|
171
264
|
import { fromSchemaOrg, toSchemaOrg, normalizeImage } from 'soustack';
|
|
172
265
|
|