@formata/stof 0.9.15 → 0.9.16
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 +102 -54
- package/dist/pkg/stof.js +5 -5
- package/dist/pkg/stof_bg.wasm +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,61 +19,51 @@
|
|
|
19
19
|
<br/>
|
|
20
20
|
|
|
21
21
|
## Overview
|
|
22
|
+
|
|
22
23
|
Data and logic have always been separate. That makes things hard. Stof puts them together.
|
|
23
24
|
|
|
24
|
-
A portable document format where validation, functions, and behavior live alongside the data they belong to
|
|
25
|
+
A portable document format where validation, functions, and behavior live alongside the data they belong to — in one document, across any service, language, or runtime.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
- **Superset of JSON** — valid JSON is always valid Stof. Works with YAML, TOML, and more out of the box.
|
|
28
|
+
- **Sandboxed execution** — logic runs in a secure, isolated runtime. Safe to execute untrusted code from external sources.
|
|
29
|
+
- **Built in Rust, runs everywhere** — native crate, WebAssembly for JS/TS (Node, Deno, Bun, browser), and Python bindings via PyPI.
|
|
27
30
|
|
|
28
|
-
>
|
|
31
|
+
> Used in production: [Limitr](https://limitr.dev)'s pricing policy engine — plans, credits, limits, validation logic — runs entirely on Stof.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Data That Does Things
|
|
35
|
+
|
|
36
|
+
Stof starts where JSON ends. Add functions right next to the data they operate on.
|
|
29
37
|
|
|
30
|
-
## Define Data and Logic Together - Combine/Split as Needed
|
|
31
|
-
`npm i @formata/stof`
|
|
32
38
|
```typescript
|
|
33
39
|
import { stofAsync } from '@formata/stof';
|
|
34
40
|
|
|
35
|
-
const doc = await stofAsync`
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const doc = await stofAsync`
|
|
42
|
+
name: "Alice"
|
|
43
|
+
age: 30
|
|
38
44
|
|
|
39
|
-
fn
|
|
40
|
-
|
|
41
|
-
parse(self.json, policy, 'json');
|
|
42
|
-
|
|
43
|
-
policy.plans.pro.price.amount = 50;
|
|
44
|
-
const entitlements = policy.plans.pro.entitlements;
|
|
45
|
-
entitlements.ai_chat.limit.value *= 2;
|
|
46
|
-
|
|
47
|
-
self.yaml = stringify('yaml', policy);
|
|
48
|
-
Std.pln(self.yaml);
|
|
45
|
+
fn greet() -> str {
|
|
46
|
+
'Hello, ' + self.name + '!'
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
|
|
49
|
+
fn can_rent_car() -> bool {
|
|
50
|
+
self.age >= 25
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
52
53
|
|
|
53
|
-
await doc.call('
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
bun run transform.ts
|
|
57
|
-
plans:
|
|
58
|
-
pro:
|
|
59
|
-
label: Pro
|
|
60
|
-
price:
|
|
61
|
-
amount: 50
|
|
62
|
-
entitlements:
|
|
63
|
-
ai_chat:
|
|
64
|
-
description: AI Chat Feature
|
|
65
|
-
limit:
|
|
66
|
-
credit: chat-token
|
|
67
|
-
value: 200000
|
|
68
|
-
resets: true
|
|
69
|
-
reset_inc: 1.0
|
|
54
|
+
console.log(await doc.call('greet')); // Hello, Alice!
|
|
55
|
+
console.log(await doc.call('can_rent_car')); // true
|
|
70
56
|
```
|
|
71
57
|
|
|
58
|
+
No separate schema file. No external validator. The data knows its own rules.
|
|
59
|
+
|
|
60
|
+
|
|
72
61
|
## Units & Types
|
|
73
|
-
|
|
62
|
+
|
|
63
|
+
Rich type system with automatic unit conversions — time, distance, memory, temperature, and more.
|
|
74
64
|
|
|
75
65
|
```typescript
|
|
76
|
-
import { stofAsync } from '
|
|
66
|
+
import { stofAsync } from '@formata/stof';
|
|
77
67
|
|
|
78
68
|
const doc = await stofAsync`
|
|
79
69
|
#[type]
|
|
@@ -102,10 +92,56 @@ const dist = await doc.call('distance', '{ "x": 3, "y": 4 }');
|
|
|
102
92
|
console.log(dist); // 170.52
|
|
103
93
|
```
|
|
104
94
|
|
|
95
|
+
|
|
96
|
+
## Format Interop
|
|
97
|
+
|
|
98
|
+
Combine JSON, YAML, TOML, and Stof in a single document. Parse one format, transform it, export as another.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { stofAsync } from '@formata/stof';
|
|
102
|
+
|
|
103
|
+
const doc = await stofAsync`{
|
|
104
|
+
json: '{"plans":{"pro":{"label":"Pro","price":{"amount":20},"entitlements":{"ai_chat":{"description":"AI Chat Feature","limit":{"credit":"chat-token","value":100000,"resets":true,"reset_inc":1.0}}}}}}'
|
|
105
|
+
yaml: ''
|
|
106
|
+
|
|
107
|
+
fn transform() {
|
|
108
|
+
const policy = new {};
|
|
109
|
+
parse(self.json, policy, 'json');
|
|
110
|
+
|
|
111
|
+
policy.plans.pro.price.amount = 50;
|
|
112
|
+
const entitlements = policy.plans.pro.entitlements;
|
|
113
|
+
entitlements.ai_chat.limit.value *= 2;
|
|
114
|
+
|
|
115
|
+
self.yaml = stringify('yaml', policy);
|
|
116
|
+
Std.pln(self.yaml);
|
|
117
|
+
}
|
|
118
|
+
}`;
|
|
119
|
+
doc.lib('Std', 'pln', (...args: unknown[])=>console.log(...args));
|
|
120
|
+
|
|
121
|
+
await doc.call('transform');
|
|
122
|
+
```
|
|
123
|
+
```
|
|
124
|
+
plans:
|
|
125
|
+
pro:
|
|
126
|
+
label: Pro
|
|
127
|
+
price:
|
|
128
|
+
amount: 50
|
|
129
|
+
entitlements:
|
|
130
|
+
ai_chat:
|
|
131
|
+
description: AI Chat Feature
|
|
132
|
+
limit:
|
|
133
|
+
credit: chat-token
|
|
134
|
+
value: 200000
|
|
135
|
+
resets: true
|
|
136
|
+
reset_inc: 1.0
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
|
|
105
140
|
## Self-Expanding Contexts
|
|
106
|
-
Documents evolve over time. Stof can even parse itself for immediate use in the same call.
|
|
107
141
|
|
|
108
|
-
|
|
142
|
+
This is the capability that changes everything.
|
|
143
|
+
|
|
144
|
+
Stof documents can parse new Stof into themselves at runtime — receiving code over the network and immediately executing it. The program grows while it runs, always sandboxed.
|
|
109
145
|
|
|
110
146
|
```typescript
|
|
111
147
|
import { stofAsync } from '@formata/stof';
|
|
@@ -114,9 +150,10 @@ const doc = await stofAsync`
|
|
|
114
150
|
api: {}
|
|
115
151
|
|
|
116
152
|
fn load_api(stof: str) {
|
|
117
|
-
parse(stof, self.api);
|
|
153
|
+
parse(stof, self.api);
|
|
118
154
|
}`;
|
|
119
155
|
|
|
156
|
+
// Imagine this arriving over HTTP, from another service, or from an agent
|
|
120
157
|
const api = `
|
|
121
158
|
name: 'Stof'
|
|
122
159
|
fn message() -> str { 'Hello, ' + self.name ?? 'World' + '!!' }
|
|
@@ -128,12 +165,14 @@ fn main() {
|
|
|
128
165
|
|
|
129
166
|
doc.lib('Std', 'pln', (...args: unknown[])=>console.log(...args));
|
|
130
167
|
await doc.call('load_api', api);
|
|
131
|
-
await doc.run(); // calls #[main] funcs
|
|
168
|
+
await doc.run(); // calls #[main] funcs
|
|
132
169
|
|
|
133
170
|
// Hello, Stof
|
|
134
171
|
```
|
|
135
172
|
|
|
173
|
+
|
|
136
174
|
## CLI
|
|
175
|
+
|
|
137
176
|
See [installation docs](https://docs.stof.dev/book/installation) for CLI instructions and more information.
|
|
138
177
|
|
|
139
178
|
```rust
|
|
@@ -147,11 +186,14 @@ fn say_hi() {
|
|
|
147
186
|
Hello, world!
|
|
148
187
|
```
|
|
149
188
|
|
|
189
|
+
|
|
150
190
|
## Embedded
|
|
191
|
+
|
|
151
192
|
Stof is written in Rust, but use it where you work. Join the project [Discord](https://discord.gg/Up5kxdeXZt) to contribute.
|
|
152
193
|
|
|
153
194
|
### Rust
|
|
154
|
-
|
|
195
|
+
|
|
196
|
+
```toml
|
|
155
197
|
[dependencies]
|
|
156
198
|
stof = "0.9.*"
|
|
157
199
|
```
|
|
@@ -176,6 +218,7 @@ fn main() {
|
|
|
176
218
|
```
|
|
177
219
|
|
|
178
220
|
### Python
|
|
221
|
+
|
|
179
222
|
`pip install stof`
|
|
180
223
|
|
|
181
224
|
```python
|
|
@@ -206,9 +249,11 @@ if __name__ == "__main__":
|
|
|
206
249
|
```
|
|
207
250
|
|
|
208
251
|
### JavaScript/TypeScript
|
|
252
|
+
|
|
209
253
|
`npm i @formata/stof`
|
|
210
254
|
|
|
211
255
|
#### Initialization
|
|
256
|
+
|
|
212
257
|
Stof uses WebAssembly, so make sure to initialize it once.
|
|
213
258
|
|
|
214
259
|
```typescript
|
|
@@ -226,39 +271,39 @@ import { initStof } from '@formata/stof';
|
|
|
226
271
|
import stofWasm from '@formata/stof/wasm';
|
|
227
272
|
await initStof(await stofWasm());
|
|
228
273
|
```
|
|
274
|
+
|
|
275
|
+
#### Usage
|
|
276
|
+
|
|
229
277
|
```typescript
|
|
230
278
|
import { initStof, StofDoc } from '@formata/stof';
|
|
231
279
|
|
|
232
|
-
// Initialize wasm once at startup
|
|
233
280
|
await initStof();
|
|
234
281
|
|
|
235
|
-
// Create and parse documents
|
|
236
282
|
const doc = new StofDoc();
|
|
237
283
|
doc.parse(`
|
|
238
284
|
name: "Alice"
|
|
239
285
|
age: 30
|
|
240
|
-
fn greet() ->
|
|
241
|
-
|
|
286
|
+
fn greet() -> str {
|
|
287
|
+
'Hello, ' + self.name
|
|
242
288
|
}
|
|
243
289
|
`);
|
|
244
290
|
|
|
245
|
-
// Call functions and access values
|
|
246
291
|
const greeting = await doc.call('greet');
|
|
247
292
|
console.log(greeting); // "Hello, Alice"
|
|
248
293
|
console.log(doc.get('age')); // 30
|
|
249
294
|
```
|
|
250
295
|
|
|
251
296
|
#### JavaScript Interop
|
|
297
|
+
|
|
252
298
|
```typescript
|
|
253
299
|
await initStof();
|
|
254
300
|
const doc = new StofDoc();
|
|
255
301
|
|
|
256
|
-
// Register JS functions
|
|
257
302
|
doc.lib('console', 'log', (...args: unknown[]) => console.log(...args));
|
|
258
303
|
doc.lib('fetch', 'get', async (url: string) => {
|
|
259
304
|
const res = await fetch(url);
|
|
260
305
|
return await res.json();
|
|
261
|
-
}, true); // true = async function
|
|
306
|
+
}, true); // true = async function
|
|
262
307
|
|
|
263
308
|
doc.parse(`
|
|
264
309
|
fn main() {
|
|
@@ -271,21 +316,24 @@ await doc.call('main');
|
|
|
271
316
|
```
|
|
272
317
|
|
|
273
318
|
#### Parse & Export
|
|
319
|
+
|
|
274
320
|
```typescript
|
|
275
|
-
// Parse from JSON
|
|
276
321
|
doc.parse({ name: "Bob", age: 25 });
|
|
277
322
|
|
|
278
|
-
// Export to different formats
|
|
279
323
|
const json = doc.stringify('json');
|
|
280
|
-
const obj = doc.record();
|
|
324
|
+
const obj = doc.record();
|
|
281
325
|
```
|
|
282
326
|
|
|
283
327
|
**Supports**: Node.js, Browser, Deno, Bun, Edge runtimes
|
|
284
328
|
|
|
329
|
+
|
|
285
330
|
## License
|
|
331
|
+
|
|
286
332
|
Apache 2.0. See LICENSE for details.
|
|
287
333
|
|
|
334
|
+
|
|
288
335
|
## Feedback & Community
|
|
336
|
+
|
|
289
337
|
- Open issues or discussions on [GitHub](https://github.com/dev-formata-io/stof)
|
|
290
338
|
- Chat with us on [Discord](https://discord.gg/Up5kxdeXZt)
|
|
291
339
|
- Star the project to support future development!
|
package/dist/pkg/stof.js
CHANGED
|
@@ -1005,6 +1005,11 @@ function __wbg_get_imports() {
|
|
|
1005
1005
|
const ret = arg0.versions;
|
|
1006
1006
|
return ret;
|
|
1007
1007
|
};
|
|
1008
|
+
imports.wbg.__wbindgen_cast_19039c21a5ac44b6 = function(arg0, arg1) {
|
|
1009
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 7077, function: Function { arguments: [Externref], shim_idx: 7078, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1010
|
+
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h72b14ab7db8750ca, wasm_bindgen__convert__closures_____invoke__h53d5cf04cab8438f);
|
|
1011
|
+
return ret;
|
|
1012
|
+
};
|
|
1008
1013
|
imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) {
|
|
1009
1014
|
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
1010
1015
|
const ret = getStringFromWasm0(arg0, arg1);
|
|
@@ -1015,11 +1020,6 @@ function __wbg_get_imports() {
|
|
|
1015
1020
|
const ret = BigInt.asUintN(64, arg0);
|
|
1016
1021
|
return ret;
|
|
1017
1022
|
};
|
|
1018
|
-
imports.wbg.__wbindgen_cast_79b583b6f4ba1f12 = function(arg0, arg1) {
|
|
1019
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx: 7050, function: Function { arguments: [Externref], shim_idx: 7051, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1020
|
-
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h72b14ab7db8750ca, wasm_bindgen__convert__closures_____invoke__h53d5cf04cab8438f);
|
|
1021
|
-
return ret;
|
|
1022
|
-
};
|
|
1023
1023
|
imports.wbg.__wbindgen_cast_9ae0607507abb057 = function(arg0) {
|
|
1024
1024
|
// Cast intrinsic for `I64 -> Externref`.
|
|
1025
1025
|
const ret = arg0;
|
package/dist/pkg/stof_bg.wasm
CHANGED
|
Binary file
|