@prestack/thread 0.1.7 → 0.1.8
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/CHANGELOG.md +84 -0
- package/dist/core/attach.js +40 -6
- package/dist/core/attach.ts +38 -6
- package/dist/core/descend.js +13 -17
- package/dist/core/descend.ts +13 -15
- package/dist/integrations/attach.js +5 -3
- package/dist/integrations/attach.ts +8 -4
- package/dist/integrations/cli.js +18 -2
- package/dist/integrations/cli.ts +19 -2
- package/dist/integrations/disk.js +4 -3
- package/dist/integrations/disk.ts +4 -3
- package/dist/integrations/generators/vault.ts +0 -1
- package/dist/integrations/target.d.ts +1 -1
- package/dist/integrations/target.ts +1 -1
- package/dist/integrity/generators/gate.ts +1 -2
- package/dist/integrity/generators/publish.ts +0 -1
- package/dist/integrity/lint.js +0 -6
- package/dist/integrity/lint.ts +0 -10
- package/dist/integrity/slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +0 -2
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -3
- package/dist/integrity/slopnators/sl_unthreaded.js +0 -2
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -5
- package/dist/integrity/sweep.ts +0 -2
- package/dist/knowledge/generators/docsgen.ts +1 -1
- package/dist/operations/generators/capability.js +20 -9
- package/dist/operations/generators/capability.ts +19 -12
- package/dist/operations/generators/compile.js +41 -28
- package/dist/operations/generators/compile.ts +48 -33
- package/dist/operations/generators/permits.js +6 -3
- package/dist/operations/generators/permits.ts +7 -5
- package/dist/operations/generators/trial.ts +1 -1
- package/package.json +1 -1
|
@@ -152,7 +152,8 @@ function answers(node, found) {
|
|
|
152
152
|
answers(one, found);
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
|
-
function spliced(text,
|
|
155
|
+
function spliced(text, shape, roles) {
|
|
156
|
+
const { names, mine } = shape;
|
|
156
157
|
const read = arrowIn(text, roles);
|
|
157
158
|
if (typeof read === 'string')
|
|
158
159
|
return read;
|
|
@@ -190,6 +191,7 @@ function spliced(text, names, mine, roles) {
|
|
|
190
191
|
return;
|
|
191
192
|
}
|
|
192
193
|
edits.push({ from: up.getStart(), to: up.getEnd(), text: said });
|
|
194
|
+
shape.heard.add(said);
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
195
197
|
because = `reaches for \`${read.held}\` itself rather than one thing on it, and once every node is a local there is no such thing to hand it`;
|
|
@@ -232,19 +234,20 @@ function spliced(text, names, mine, roles) {
|
|
|
232
234
|
return { form: 'call', text: whole, whole };
|
|
233
235
|
}
|
|
234
236
|
const ANSWERS = `${OWN}ANSWER`;
|
|
235
|
-
|
|
237
|
+
const linesOf = (one, name, unread) => (unread ? `${one.text.replace(`${ANSWERS} = `, 'void (')})` : one.text.replace(ANSWERS, name)).split('\n');
|
|
238
|
+
function answered(name, one, opened, kind, unread) {
|
|
236
239
|
if (one.form === 'expression')
|
|
237
|
-
return [`${kind} ${name}: unknown = ${one.text}`];
|
|
240
|
+
return [unread ? `void (${one.text})` : `${kind} ${name}: unknown = ${one.text}`];
|
|
238
241
|
if (one.form === 'statements') {
|
|
239
|
-
return [`let ${name}: unknown
|
|
242
|
+
return [...(unread ? [] : [`let ${name}: unknown`]), '{', ...linesOf(one, name, unread).map((line) => ` ${line}`), '}'];
|
|
240
243
|
}
|
|
241
|
-
return [`${kind} ${name}: unknown = await ${asFn(one.whole, opened)}(opened)`];
|
|
244
|
+
return [`${unread ? '' : `${kind} ${name}: unknown = `}await ${asFn(one.whole, opened)}(opened)`];
|
|
242
245
|
}
|
|
243
246
|
const asFn = (text, opened, more = '') => `((${text}) as (${more}opened: ${opened}) => Promise<unknown>)`;
|
|
244
247
|
function forked(name, said, shape) {
|
|
245
248
|
if (said.length !== 2)
|
|
246
249
|
return `takes a chooser and the ways it chooses between, and was handed ${String(said.length)} thing(s)`;
|
|
247
|
-
const chose = spliced(said[0] ?? '', shape
|
|
250
|
+
const chose = spliced(said[0] ?? '', shape, ROLES.block);
|
|
248
251
|
if (typeof chose === 'string')
|
|
249
252
|
return `chooses with a block that ${chose}`;
|
|
250
253
|
const held = inside(said[1] ?? '', '{', '}');
|
|
@@ -252,25 +255,26 @@ function forked(name, said, shape) {
|
|
|
252
255
|
const named = ways?.every((one) => one !== undefined && PLAIN.test(one.key)) === true ? ways : undefined;
|
|
253
256
|
if (named === undefined || named.length === 0)
|
|
254
257
|
return 'names its ways in a way this cannot read — write each one as `name: block`';
|
|
258
|
+
const unread = shape.unread.has(name);
|
|
255
259
|
const picked = `${OWN}${name}_said`;
|
|
256
260
|
const runs = [
|
|
257
|
-
`let ${name}: unknown
|
|
261
|
+
...(unread ? [] : [`let ${name}: unknown`]),
|
|
258
262
|
chose.form === 'expression'
|
|
259
263
|
? `const ${picked}: string = await (${chose.text})`
|
|
260
264
|
: `const ${picked}: string = await ((${said[0] ?? ''}) as (opened: ${shape.reads}) => Promise<string>)(opened)`,
|
|
261
265
|
`switch (${picked}) {`,
|
|
262
266
|
];
|
|
263
267
|
for (const way of named) {
|
|
264
|
-
const each = spliced(way?.held ?? '', shape
|
|
268
|
+
const each = spliced(way?.held ?? '', shape, ROLES.block);
|
|
265
269
|
if (typeof each === 'string')
|
|
266
270
|
return `has a way \`${way?.key ?? ''}\` that ${each}`;
|
|
267
271
|
runs.push(` case ${(way?.key ?? '').startsWith("'") ? (way?.key ?? '') : quoted(way?.key ?? '')}: {`);
|
|
268
272
|
if (each.form === 'expression')
|
|
269
|
-
runs.push(` ${name} = ${each.text}`);
|
|
273
|
+
runs.push(unread ? ` void (${each.text})` : ` ${name} = ${each.text}`);
|
|
270
274
|
else if (each.form === 'statements')
|
|
271
|
-
runs.push(...each
|
|
275
|
+
runs.push(...linesOf(each, name, unread).map((line) => ` ${line}`));
|
|
272
276
|
else
|
|
273
|
-
runs.push(` ${name} = await ${asFn(each.whole, shape.all)}(opened)`);
|
|
277
|
+
runs.push(` ${unread ? '' : `${name} = `}await ${asFn(each.whole, shape.all)}(opened)`);
|
|
274
278
|
runs.push(' break');
|
|
275
279
|
runs.push(' }');
|
|
276
280
|
}
|
|
@@ -285,23 +289,24 @@ const SHAPES = {
|
|
|
285
289
|
nd_each: (name, said, shape) => {
|
|
286
290
|
if (said.length !== 2)
|
|
287
291
|
return `takes what to run over and the block to run, and was handed ${String(said.length)} thing(s)`;
|
|
288
|
-
const over = spliced(said[0] ?? '', shape
|
|
289
|
-
const block = spliced(said[1] ?? '', shape
|
|
292
|
+
const over = spliced(said[0] ?? '', shape, ROLES.over);
|
|
293
|
+
const block = spliced(said[1] ?? '', shape, ROLES.item);
|
|
290
294
|
if (typeof block === 'string')
|
|
291
295
|
return `runs a block that ${block}`;
|
|
292
296
|
if (typeof over === 'string')
|
|
293
297
|
return `runs over a block that ${over}`;
|
|
298
|
+
const unread = shape.unread.has(name);
|
|
294
299
|
const item = `${OWN}${name}_item`;
|
|
295
300
|
const all = `${OWN}${name}_all`;
|
|
296
301
|
const each = `${OWN}${name}_each`;
|
|
297
302
|
return {
|
|
298
303
|
runs: [
|
|
299
|
-
`const ${all}: unknown[] = []
|
|
304
|
+
...(unread ? [] : [`const ${all}: unknown[] = []`]),
|
|
300
305
|
`const ${each} = (${block.whole}) as (item: unknown, opened: ${shape.all}) => Promise<unknown>`,
|
|
301
306
|
`for (const ${item} of ${over.form === 'expression' ? `(${over.text})` : `((${over.whole}) as () => readonly unknown[])()`} as readonly unknown[]) {`,
|
|
302
|
-
` ${all}.push(await ${each}(${item}, opened))`,
|
|
307
|
+
unread ? ` await ${each}(${item}, opened)` : ` ${all}.push(await ${each}(${item}, opened))`,
|
|
303
308
|
'}',
|
|
304
|
-
`const ${name}: unknown = ${all}
|
|
309
|
+
...(unread ? [] : [`const ${name}: unknown = ${all}`]),
|
|
305
310
|
],
|
|
306
311
|
};
|
|
307
312
|
},
|
|
@@ -316,17 +321,17 @@ const SHAPES = {
|
|
|
316
321
|
return 'was handed no blocks to run at once';
|
|
317
322
|
const calls = [];
|
|
318
323
|
for (const [order, one] of every.entries()) {
|
|
319
|
-
const each = spliced(one, shape
|
|
324
|
+
const each = spliced(one, shape, ROLES.block);
|
|
320
325
|
if (typeof each === 'string')
|
|
321
326
|
return `runs a block at position ${String(order + 1)} that ${each}`;
|
|
322
327
|
calls.push(each.form === 'expression' ? `(async () => ${each.text})()` : `${asFn(each.whole, shape.all)}(opened)`);
|
|
323
328
|
}
|
|
324
|
-
return { runs: [`const ${name}: unknown = await Promise.all([${calls.join(', ')}])`] };
|
|
329
|
+
return { runs: [`${shape.unread.has(name) ? '' : `const ${name}: unknown = `}await Promise.all([${calls.join(', ')}])`] };
|
|
325
330
|
},
|
|
326
331
|
nd_repeat: (name, said, shape) => {
|
|
327
332
|
if (said.length !== 2)
|
|
328
333
|
return `takes a ceiling and the block to repeat, and was handed ${String(said.length)} thing(s)`;
|
|
329
|
-
const block = spliced(said[1] ?? '', shape
|
|
334
|
+
const block = spliced(said[1] ?? '', shape, ROLES.again);
|
|
330
335
|
if (typeof block === 'string')
|
|
331
336
|
return `repeats a block that ${block}`;
|
|
332
337
|
const ceiling = `${OWN}${name}_ceiling`;
|
|
@@ -335,16 +340,17 @@ const SHAPES = {
|
|
|
335
340
|
const done = `${OWN}${name}_done`;
|
|
336
341
|
const pass = `${OWN}${name}_pass`;
|
|
337
342
|
const answer = `${OWN}${name}_said`;
|
|
343
|
+
const unread = shape.unread.has(name);
|
|
338
344
|
return {
|
|
339
345
|
runs: [
|
|
340
|
-
`let ${name}: unknown
|
|
346
|
+
...(unread ? [] : [`let ${name}: unknown`]),
|
|
341
347
|
`const ${ceiling}: number = ${said[0] ?? ''}`,
|
|
342
348
|
`const ${again} = (${block.whole}) as (pass: number, carried: unknown, opened: ${shape.all}) => Promise<{ readonly done: boolean; readonly value: unknown }>`,
|
|
343
349
|
`let ${carried}: unknown`,
|
|
344
350
|
`let ${done} = false`,
|
|
345
351
|
`for (let ${pass} = 0; ${pass} < ${ceiling}; ${pass} += 1) {`,
|
|
346
352
|
` const ${answer} = await ${again}(${pass}, ${carried}, opened)`,
|
|
347
|
-
` if (${answer}.done) { ${name} = ${answer}.value; ${done} = true; break }`,
|
|
353
|
+
` if (${answer}.done) { ${unread ? '' : `${name} = ${answer}.value; `}${done} = true; break }`,
|
|
348
354
|
` ${carried} = ${answer}.value`,
|
|
349
355
|
'}',
|
|
350
356
|
`if (!${done}) throw new Error(\`${name} reached its ceiling of \${String(${ceiling})} passes without answering done\`)`,
|
|
@@ -357,21 +363,22 @@ const STOPS = "Symbol.for('thread.operation.stopped')";
|
|
|
357
363
|
function plainly(name, said, shape, opened, ends) {
|
|
358
364
|
if (said.length !== 1)
|
|
359
365
|
return `takes one block, and was handed ${String(said.length)} thing(s)`;
|
|
360
|
-
const one = spliced(said[0] ?? '', shape
|
|
366
|
+
const one = spliced(said[0] ?? '', shape, ROLES.block);
|
|
361
367
|
if (typeof one === 'string')
|
|
362
368
|
return one;
|
|
369
|
+
const unread = shape.unread.has(name);
|
|
363
370
|
if (!ends)
|
|
364
|
-
return { runs: answered(name, one, opened, 'const') };
|
|
371
|
+
return { runs: answered(name, one, opened, 'const', unread) };
|
|
365
372
|
return {
|
|
366
373
|
runs: [
|
|
367
|
-
...answered(name, one, opened, 'const'),
|
|
374
|
+
...answered(name, one, opened, 'const', unread),
|
|
368
375
|
`throw Object.assign(new Error(${quoted(`${name} ended the operation`)}), { [${STOPS}]: true, value: ${name} })`,
|
|
369
376
|
],
|
|
370
377
|
};
|
|
371
378
|
}
|
|
372
379
|
const saidIn = (held) => held !== undefined && ts.isStringLiteral(held) ? held.text : undefined;
|
|
373
380
|
const sourceIn = (held) => held?.getText();
|
|
374
|
-
function compiled(at, id, one, known) {
|
|
381
|
+
function compiled(at, id, one, known, unread) {
|
|
375
382
|
const because = problem(id, one);
|
|
376
383
|
if (because !== undefined)
|
|
377
384
|
return { because };
|
|
@@ -394,6 +401,7 @@ function compiled(at, id, one, known) {
|
|
|
394
401
|
const mine = new Set(['input', ...one.nodes.map((row) => String(row.id))]);
|
|
395
402
|
const runs = [];
|
|
396
403
|
const names = new Set();
|
|
404
|
+
const heard = new Set();
|
|
397
405
|
let last;
|
|
398
406
|
for (const [order, row] of one.nodes.entries()) {
|
|
399
407
|
const name = row.id;
|
|
@@ -418,7 +426,7 @@ function compiled(at, id, one, known) {
|
|
|
418
426
|
if (held === undefined) {
|
|
419
427
|
return { because: `the node \`${name}\` states no block this can read in ${row.at} — run \`thread seed\` and write it again` };
|
|
420
428
|
}
|
|
421
|
-
const shape = { all: `Opened<${only}>`, reads: `Reads<Opened<${only}>>`, names, mine };
|
|
429
|
+
const shape = { all: `Opened<${only}>`, reads: `Reads<Opened<${only}>>`, names, mine, heard, unread: unread ?? new Set() };
|
|
422
430
|
const made = (SHAPES[from] ?? ownType)(name, parts(held), shape);
|
|
423
431
|
if (typeof made === 'string')
|
|
424
432
|
return { because: `the node \`${name}\` ${made}` };
|
|
@@ -428,12 +436,17 @@ function compiled(at, id, one, known) {
|
|
|
428
436
|
names.add(name);
|
|
429
437
|
last = from === 'nd_stop' ? undefined : name;
|
|
430
438
|
}
|
|
439
|
+
if (unread === undefined) {
|
|
440
|
+
const final = one.nodes.at(-1)?.id;
|
|
441
|
+
return compiled(at, id, one, known, new Set([...mine].filter((name) => name !== final && !heard.has(name))));
|
|
442
|
+
}
|
|
443
|
+
const typed = ['Opened', 'Reads'].filter((word) => runs.some((line) => line.includes(`${word}<`)));
|
|
431
444
|
return {
|
|
432
445
|
text: [
|
|
433
446
|
"import type { z } from 'zod'",
|
|
434
447
|
'',
|
|
435
448
|
`import type { Operation } from ${quoted(runtime(at, '../operations.ts'))}`,
|
|
436
|
-
|
|
449
|
+
...(typed.length === 0 ? [] : [`import type { ${typed.join(', ')} } from ${quoted(runtime(at, '../operations.ts'))}`]),
|
|
437
450
|
'',
|
|
438
451
|
`import { input, resource } from ${quoted(beside(at, takes))}`,
|
|
439
452
|
'',
|
|
@@ -448,7 +461,7 @@ function compiled(at, id, one, known) {
|
|
|
448
461
|
...(quota === undefined ? [] : [` quota: ${quota},`]),
|
|
449
462
|
'',
|
|
450
463
|
' run: async (opened, held) => {',
|
|
451
|
-
' const input = held.input',
|
|
464
|
+
...(unread.has('input') ? [] : [' const input = held.input']),
|
|
452
465
|
` let ${ENTERED} = ''`,
|
|
453
466
|
' try {',
|
|
454
467
|
...runs,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
3
|
import { argv, stdout } from 'node:process'
|
|
4
4
|
|
|
5
5
|
import ts from 'typescript'
|
|
@@ -190,12 +190,8 @@ interface Spliced {
|
|
|
190
190
|
readonly whole: string
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
function spliced(
|
|
194
|
-
|
|
195
|
-
names: ReadonlySet<string>,
|
|
196
|
-
mine: ReadonlySet<string>,
|
|
197
|
-
roles: readonly string[],
|
|
198
|
-
): Spliced | string {
|
|
193
|
+
function spliced(text: string, shape: Shape, roles: readonly string[]): Spliced | string {
|
|
194
|
+
const { names, mine } = shape
|
|
199
195
|
const read = arrowIn(text, roles)
|
|
200
196
|
if (typeof read === 'string') return read
|
|
201
197
|
|
|
@@ -243,6 +239,7 @@ function spliced(
|
|
|
243
239
|
return
|
|
244
240
|
}
|
|
245
241
|
edits.push({ from: up.getStart(), to: up.getEnd(), text: said })
|
|
242
|
+
shape.heard.add(said)
|
|
246
243
|
return
|
|
247
244
|
}
|
|
248
245
|
because = `reaches for \`${read.held}\` itself rather than one thing on it, and once every node is a local there is no such thing to hand it`
|
|
@@ -300,14 +297,19 @@ interface Shape {
|
|
|
300
297
|
readonly reads: string
|
|
301
298
|
readonly names: ReadonlySet<string>
|
|
302
299
|
readonly mine: ReadonlySet<string>
|
|
300
|
+
readonly heard: Set<string>
|
|
301
|
+
readonly unread: ReadonlySet<string>
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
const linesOf = (one: Spliced, name: string, unread: boolean): readonly string[] =>
|
|
305
|
+
(unread ? `${one.text.replace(`${ANSWERS} = `, 'void (')})` : one.text.replace(ANSWERS, name)).split('\n')
|
|
306
|
+
|
|
307
|
+
function answered(name: string, one: Spliced, opened: string, kind: 'const' | 'let', unread: boolean): readonly string[] {
|
|
308
|
+
if (one.form === 'expression') return [unread ? `void (${one.text})` : `${kind} ${name}: unknown = ${one.text}`]
|
|
307
309
|
if (one.form === 'statements') {
|
|
308
|
-
return [`let ${name}: unknown
|
|
310
|
+
return [...(unread ? [] : [`let ${name}: unknown`]), '{', ...linesOf(one, name, unread).map((line) => ` ${line}`), '}']
|
|
309
311
|
}
|
|
310
|
-
return [`${kind} ${name}: unknown = await ${asFn(one.whole, opened)}(opened)`]
|
|
312
|
+
return [`${unread ? '' : `${kind} ${name}: unknown = `}await ${asFn(one.whole, opened)}(opened)`]
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
const asFn = (text: string, opened: string, more: string = ''): string =>
|
|
@@ -316,7 +318,7 @@ const asFn = (text: string, opened: string, more: string = ''): string =>
|
|
|
316
318
|
function forked(name: string, said: readonly string[], shape: Shape): Made | string {
|
|
317
319
|
if (said.length !== 2) return `takes a chooser and the ways it chooses between, and was handed ${String(said.length)} thing(s)`
|
|
318
320
|
|
|
319
|
-
const chose = spliced(said[0] ?? '', shape
|
|
321
|
+
const chose = spliced(said[0] ?? '', shape, ROLES.block)
|
|
320
322
|
if (typeof chose === 'string') return `chooses with a block that ${chose}`
|
|
321
323
|
|
|
322
324
|
const held = inside(said[1] ?? '', '{', '}')
|
|
@@ -324,9 +326,10 @@ function forked(name: string, said: readonly string[], shape: Shape): Made | str
|
|
|
324
326
|
const named = ways?.every((one) => one !== undefined && PLAIN.test(one.key)) === true ? ways : undefined
|
|
325
327
|
if (named === undefined || named.length === 0) return 'names its ways in a way this cannot read — write each one as `name: block`'
|
|
326
328
|
|
|
329
|
+
const unread = shape.unread.has(name)
|
|
327
330
|
const picked = `${OWN}${name}_said`
|
|
328
331
|
const runs = [
|
|
329
|
-
`let ${name}: unknown
|
|
332
|
+
...(unread ? [] : [`let ${name}: unknown`]),
|
|
330
333
|
chose.form === 'expression'
|
|
331
334
|
? `const ${picked}: string = await (${chose.text})`
|
|
332
335
|
: `const ${picked}: string = await ((${said[0] ?? ''}) as (opened: ${shape.reads}) => Promise<string>)(opened)`,
|
|
@@ -334,12 +337,12 @@ function forked(name: string, said: readonly string[], shape: Shape): Made | str
|
|
|
334
337
|
]
|
|
335
338
|
|
|
336
339
|
for (const way of named) {
|
|
337
|
-
const each = spliced(way?.held ?? '', shape
|
|
340
|
+
const each = spliced(way?.held ?? '', shape, ROLES.block)
|
|
338
341
|
if (typeof each === 'string') return `has a way \`${way?.key ?? ''}\` that ${each}`
|
|
339
342
|
runs.push(` case ${(way?.key ?? '').startsWith("'") ? (way?.key ?? '') : quoted(way?.key ?? '')}: {`)
|
|
340
|
-
if (each.form === 'expression') runs.push(` ${name} = ${each.text}`)
|
|
341
|
-
else if (each.form === 'statements') runs.push(...each
|
|
342
|
-
else runs.push(` ${name} = await ${asFn(each.whole, shape.all)}(opened)`)
|
|
343
|
+
if (each.form === 'expression') runs.push(unread ? ` void (${each.text})` : ` ${name} = ${each.text}`)
|
|
344
|
+
else if (each.form === 'statements') runs.push(...linesOf(each, name, unread).map((line) => ` ${line}`))
|
|
345
|
+
else runs.push(` ${unread ? '' : `${name} = `}await ${asFn(each.whole, shape.all)}(opened)`)
|
|
343
346
|
runs.push(' break')
|
|
344
347
|
runs.push(' }')
|
|
345
348
|
}
|
|
@@ -356,21 +359,22 @@ const SHAPES: Readonly<Record<string, (name: string, said: readonly string[], sh
|
|
|
356
359
|
|
|
357
360
|
nd_each: (name, said, shape) => {
|
|
358
361
|
if (said.length !== 2) return `takes what to run over and the block to run, and was handed ${String(said.length)} thing(s)`
|
|
359
|
-
const over = spliced(said[0] ?? '', shape
|
|
360
|
-
const block = spliced(said[1] ?? '', shape
|
|
362
|
+
const over = spliced(said[0] ?? '', shape, ROLES.over)
|
|
363
|
+
const block = spliced(said[1] ?? '', shape, ROLES.item)
|
|
361
364
|
if (typeof block === 'string') return `runs a block that ${block}`
|
|
362
365
|
if (typeof over === 'string') return `runs over a block that ${over}`
|
|
366
|
+
const unread = shape.unread.has(name)
|
|
363
367
|
const item = `${OWN}${name}_item`
|
|
364
368
|
const all = `${OWN}${name}_all`
|
|
365
369
|
const each = `${OWN}${name}_each`
|
|
366
370
|
return {
|
|
367
371
|
runs: [
|
|
368
|
-
`const ${all}: unknown[] = []
|
|
372
|
+
...(unread ? [] : [`const ${all}: unknown[] = []`]),
|
|
369
373
|
`const ${each} = (${block.whole}) as (item: unknown, opened: ${shape.all}) => Promise<unknown>`,
|
|
370
374
|
`for (const ${item} of ${over.form === 'expression' ? `(${over.text})` : `((${over.whole}) as () => readonly unknown[])()`} as readonly unknown[]) {`,
|
|
371
|
-
` ${all}.push(await ${each}(${item}, opened))`,
|
|
375
|
+
unread ? ` await ${each}(${item}, opened)` : ` ${all}.push(await ${each}(${item}, opened))`,
|
|
372
376
|
'}',
|
|
373
|
-
`const ${name}: unknown = ${all}
|
|
377
|
+
...(unread ? [] : [`const ${name}: unknown = ${all}`]),
|
|
374
378
|
],
|
|
375
379
|
}
|
|
376
380
|
},
|
|
@@ -384,16 +388,16 @@ const SHAPES: Readonly<Record<string, (name: string, said: readonly string[], sh
|
|
|
384
388
|
|
|
385
389
|
const calls: string[] = []
|
|
386
390
|
for (const [order, one] of every.entries()) {
|
|
387
|
-
const each = spliced(one, shape
|
|
391
|
+
const each = spliced(one, shape, ROLES.block)
|
|
388
392
|
if (typeof each === 'string') return `runs a block at position ${String(order + 1)} that ${each}`
|
|
389
393
|
calls.push(each.form === 'expression' ? `(async () => ${each.text})()` : `${asFn(each.whole, shape.all)}(opened)`)
|
|
390
394
|
}
|
|
391
|
-
return { runs: [`const ${name}: unknown = await Promise.all([${calls.join(', ')}])`] }
|
|
395
|
+
return { runs: [`${shape.unread.has(name) ? '' : `const ${name}: unknown = `}await Promise.all([${calls.join(', ')}])`] }
|
|
392
396
|
},
|
|
393
397
|
|
|
394
398
|
nd_repeat: (name, said, shape) => {
|
|
395
399
|
if (said.length !== 2) return `takes a ceiling and the block to repeat, and was handed ${String(said.length)} thing(s)`
|
|
396
|
-
const block = spliced(said[1] ?? '', shape
|
|
400
|
+
const block = spliced(said[1] ?? '', shape, ROLES.again)
|
|
397
401
|
if (typeof block === 'string') return `repeats a block that ${block}`
|
|
398
402
|
const ceiling = `${OWN}${name}_ceiling`
|
|
399
403
|
const again = `${OWN}${name}_again`
|
|
@@ -401,16 +405,17 @@ const SHAPES: Readonly<Record<string, (name: string, said: readonly string[], sh
|
|
|
401
405
|
const done = `${OWN}${name}_done`
|
|
402
406
|
const pass = `${OWN}${name}_pass`
|
|
403
407
|
const answer = `${OWN}${name}_said`
|
|
408
|
+
const unread = shape.unread.has(name)
|
|
404
409
|
return {
|
|
405
410
|
runs: [
|
|
406
|
-
`let ${name}: unknown
|
|
411
|
+
...(unread ? [] : [`let ${name}: unknown`]),
|
|
407
412
|
`const ${ceiling}: number = ${said[0] ?? ''}`,
|
|
408
413
|
`const ${again} = (${block.whole}) as (pass: number, carried: unknown, opened: ${shape.all}) => Promise<{ readonly done: boolean; readonly value: unknown }>`,
|
|
409
414
|
`let ${carried}: unknown`,
|
|
410
415
|
`let ${done} = false`,
|
|
411
416
|
`for (let ${pass} = 0; ${pass} < ${ceiling}; ${pass} += 1) {`,
|
|
412
417
|
` const ${answer} = await ${again}(${pass}, ${carried}, opened)`,
|
|
413
|
-
` if (${answer}.done) { ${name} = ${answer}.value; ${done} = true; break }`,
|
|
418
|
+
` if (${answer}.done) { ${unread ? '' : `${name} = ${answer}.value; `}${done} = true; break }`,
|
|
414
419
|
` ${carried} = ${answer}.value`,
|
|
415
420
|
'}',
|
|
416
421
|
`if (!${done}) throw new Error(\`${name} reached its ceiling of \${String(${ceiling})} passes without answering done\`)`,
|
|
@@ -425,14 +430,15 @@ const STOPS = "Symbol.for('thread.operation.stopped')"
|
|
|
425
430
|
|
|
426
431
|
function plainly(name: string, said: readonly string[], shape: Shape, opened: string, ends: boolean): Made | string {
|
|
427
432
|
if (said.length !== 1) return `takes one block, and was handed ${String(said.length)} thing(s)`
|
|
428
|
-
const one = spliced(said[0] ?? '', shape
|
|
433
|
+
const one = spliced(said[0] ?? '', shape, ROLES.block)
|
|
429
434
|
if (typeof one === 'string') return one
|
|
430
435
|
|
|
431
|
-
|
|
436
|
+
const unread = shape.unread.has(name)
|
|
437
|
+
if (!ends) return { runs: answered(name, one, opened, 'const', unread) }
|
|
432
438
|
|
|
433
439
|
return {
|
|
434
440
|
runs: [
|
|
435
|
-
...answered(name, one, opened, 'const'),
|
|
441
|
+
...answered(name, one, opened, 'const', unread),
|
|
436
442
|
`throw Object.assign(new Error(${quoted(`${name} ended the operation`)}), { [${STOPS}]: true, value: ${name} })`,
|
|
437
443
|
],
|
|
438
444
|
}
|
|
@@ -447,6 +453,7 @@ function compiled(
|
|
|
447
453
|
id: string,
|
|
448
454
|
one: ReturnType<typeof operations> extends ReadonlyMap<string, infer W> ? W : never,
|
|
449
455
|
known: ReadonlySet<string>,
|
|
456
|
+
unread?: ReadonlySet<string>,
|
|
450
457
|
): { readonly text?: string; readonly because?: string } {
|
|
451
458
|
const because = problem(id, one)
|
|
452
459
|
if (because !== undefined) return { because }
|
|
@@ -474,6 +481,7 @@ function compiled(
|
|
|
474
481
|
|
|
475
482
|
const runs: string[] = []
|
|
476
483
|
const names = new Set<string>()
|
|
484
|
+
const heard = new Set<string>()
|
|
477
485
|
let last: string | undefined
|
|
478
486
|
|
|
479
487
|
for (const [order, row] of one.nodes.entries()) {
|
|
@@ -500,7 +508,7 @@ function compiled(
|
|
|
500
508
|
return { because: `the node \`${name}\` states no block this can read in ${row.at} — run \`thread seed\` and write it again` }
|
|
501
509
|
}
|
|
502
510
|
|
|
503
|
-
const shape: Shape = { all: `Opened<${only}>`, reads: `Reads<Opened<${only}>>`, names, mine }
|
|
511
|
+
const shape: Shape = { all: `Opened<${only}>`, reads: `Reads<Opened<${only}>>`, names, mine, heard, unread: unread ?? new Set<string>() }
|
|
504
512
|
const made = (SHAPES[from] ?? ownType)(name, parts(held), shape)
|
|
505
513
|
if (typeof made === 'string') return { because: `the node \`${name}\` ${made}` }
|
|
506
514
|
|
|
@@ -510,12 +518,19 @@ function compiled(
|
|
|
510
518
|
last = from === 'nd_stop' ? undefined : name
|
|
511
519
|
}
|
|
512
520
|
|
|
521
|
+
if (unread === undefined) {
|
|
522
|
+
const final = one.nodes.at(-1)?.id
|
|
523
|
+
return compiled(at, id, one, known, new Set([...mine].filter((name) => name !== final && !heard.has(name))))
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const typed = ['Opened', 'Reads'].filter((word) => runs.some((line) => line.includes(`${word}<`)))
|
|
527
|
+
|
|
513
528
|
return {
|
|
514
529
|
text: [
|
|
515
530
|
"import type { z } from 'zod'",
|
|
516
531
|
'',
|
|
517
532
|
`import type { Operation } from ${quoted(runtime(at, '../operations.ts'))}`,
|
|
518
|
-
|
|
533
|
+
...(typed.length === 0 ? [] : [`import type { ${typed.join(', ')} } from ${quoted(runtime(at, '../operations.ts'))}`]),
|
|
519
534
|
'',
|
|
520
535
|
`import { input, resource } from ${quoted(beside(at, takes))}`,
|
|
521
536
|
'',
|
|
@@ -530,7 +545,7 @@ function compiled(
|
|
|
530
545
|
...(quota === undefined ? [] : [` quota: ${quota},`]),
|
|
531
546
|
'',
|
|
532
547
|
' run: async (opened, held) => {',
|
|
533
|
-
' const input = held.input',
|
|
548
|
+
...(unread.has('input') ? [] : [' const input = held.input']),
|
|
534
549
|
` let ${ENTERED} = ''`,
|
|
535
550
|
' try {',
|
|
536
551
|
...runs,
|
|
@@ -61,6 +61,7 @@ if (open.length > 0) {
|
|
|
61
61
|
'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is');
|
|
62
62
|
}
|
|
63
63
|
const said = (held) => JSON.stringify(held);
|
|
64
|
+
const used = new Set();
|
|
64
65
|
function reads(one, grant) {
|
|
65
66
|
if ('value' in one)
|
|
66
67
|
return said(one.value);
|
|
@@ -85,6 +86,7 @@ function reads(one, grant) {
|
|
|
85
86
|
grant.on +
|
|
86
87
|
'" itself, or state the rule as an invariant where the rows are');
|
|
87
88
|
}
|
|
89
|
+
used.add(READS);
|
|
88
90
|
return `at(resource, ${said(one.field)})`;
|
|
89
91
|
}
|
|
90
92
|
stop(`\`${grant.id}\` states an operand this gate cannot read`);
|
|
@@ -98,6 +100,7 @@ function predicate(condition, grant) {
|
|
|
98
100
|
}
|
|
99
101
|
if ('not' in condition)
|
|
100
102
|
return `(!${predicate(condition.not, grant)})`;
|
|
103
|
+
used.add(COMPARES);
|
|
101
104
|
return `held(${said(condition.op)}, ${reads(condition.is, grant)}, ${reads(condition.than, grant)})`;
|
|
102
105
|
}
|
|
103
106
|
function wears(grant) {
|
|
@@ -107,6 +110,8 @@ function wears(grant) {
|
|
|
107
110
|
held.push(`some(actor.roles, ${said(roles)})`);
|
|
108
111
|
if (grant.to?.entitlements !== undefined)
|
|
109
112
|
held.push(`some(actor.entitlements, ${said(grant.to.entitlements)})`);
|
|
113
|
+
if (held.length > 0)
|
|
114
|
+
used.add(WEARS);
|
|
110
115
|
return held;
|
|
111
116
|
}
|
|
112
117
|
const READS = ['const at = (row: Row | null, name: string): unknown => (row === null ? undefined : row[name])'];
|
|
@@ -154,9 +159,7 @@ function permitting() {
|
|
|
154
159
|
`import type { Actor } from '${runtime(PERMITS_AT, '../../core/actor.ts')}'`,
|
|
155
160
|
"import type { Row } from '" + runtime(PERMITS_AT, '../../integrations/target.ts') + "'",
|
|
156
161
|
'',
|
|
157
|
-
...
|
|
158
|
-
...(grants.some((one) => one.to !== undefined) ? [...WEARS, ''] : []),
|
|
159
|
-
...(grants.some((one) => one.when !== undefined) ? [...COMPARES, ''] : []),
|
|
162
|
+
...[READS, WEARS, COMPARES].filter((one) => used.has(one)).flatMap((one) => [...one, '']),
|
|
160
163
|
'export function permits(actor: Actor, action: string, resource: Row | null): { allow: boolean; because?: string } {',
|
|
161
164
|
...(grants.length === 0 ? [' void actor', ' void resource'] : []),
|
|
162
165
|
...body,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
|
|
2
2
|
import { valuesIn } from '../../core/rows.ts'
|
|
3
|
-
import {
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
4
|
import { argv, stderr, stdout } from 'node:process'
|
|
5
|
-
import { pathToFileURL } from 'node:url'
|
|
6
5
|
|
|
7
6
|
import { exit } from '../../core/invoked.ts'
|
|
8
7
|
import { PUBLIC_ROLE as PUBLIC } from '../../core/actor.ts'
|
|
@@ -81,6 +80,8 @@ if (open.length > 0) {
|
|
|
81
80
|
|
|
82
81
|
const said = (held: unknown): string => JSON.stringify(held)
|
|
83
82
|
|
|
83
|
+
const used = new Set<readonly string[]>()
|
|
84
|
+
|
|
84
85
|
function reads(one: Operand, grant: Grant): string {
|
|
85
86
|
if ('value' in one) return said(one.value)
|
|
86
87
|
if ('now' in one) return 'Date.now()'
|
|
@@ -106,6 +107,7 @@ function reads(one: Operand, grant: Grant): string {
|
|
|
106
107
|
'" itself, or state the rule as an invariant where the rows are',
|
|
107
108
|
)
|
|
108
109
|
}
|
|
110
|
+
used.add(READS)
|
|
109
111
|
return `at(resource, ${said(one.field)})`
|
|
110
112
|
}
|
|
111
113
|
stop(`\`${grant.id}\` states an operand this gate cannot read`)
|
|
@@ -119,6 +121,7 @@ function predicate(condition: Condition, grant: Grant): string {
|
|
|
119
121
|
return condition.any.length === 0 ? 'false' : `(${condition.any.map((one) => predicate(one, grant)).join(' || ')})`
|
|
120
122
|
}
|
|
121
123
|
if ('not' in condition) return `(!${predicate(condition.not, grant)})`
|
|
124
|
+
used.add(COMPARES)
|
|
122
125
|
return `held(${said(condition.op)}, ${reads(condition.is, grant)}, ${reads(condition.than, grant)})`
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -127,6 +130,7 @@ function wears(grant: Grant): readonly string[] {
|
|
|
127
130
|
const roles = grant.to?.roles
|
|
128
131
|
if (roles !== undefined && !roles.includes(PUBLIC)) held.push(`some(actor.roles, ${said(roles)})`)
|
|
129
132
|
if (grant.to?.entitlements !== undefined) held.push(`some(actor.entitlements, ${said(grant.to.entitlements)})`)
|
|
133
|
+
if (held.length > 0) used.add(WEARS)
|
|
130
134
|
return held
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -181,9 +185,7 @@ function permitting(): string {
|
|
|
181
185
|
`import type { Actor } from '${runtime(PERMITS_AT, '../../core/actor.ts')}'`,
|
|
182
186
|
"import type { Row } from '" + runtime(PERMITS_AT, '../../integrations/target.ts') + "'",
|
|
183
187
|
'',
|
|
184
|
-
...
|
|
185
|
-
...(grants.some((one) => one.to !== undefined) ? [...WEARS, ''] : []),
|
|
186
|
-
...(grants.some((one) => one.when !== undefined) ? [...COMPARES, ''] : []),
|
|
188
|
+
...[READS, WEARS, COMPARES].filter((one) => used.has(one)).flatMap((one) => [...one, '']),
|
|
187
189
|
|
|
188
190
|
'export function permits(actor: Actor, action: string, resource: Row | null): { allow: boolean; because?: string } {',
|
|
189
191
|
...(grants.length === 0 ? [' void actor', ' void resource'] : []),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, type Answering, type Listed } from '../../integrations/disk.ts'
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
3
|
import { argv, stdout } from 'node:process'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
5
5
|
|