@oomol-lab/open-flow 0.1.0-beta.77 → 0.1.0-beta.79
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 +2 -0
- package/dist/browser/{ContentIcon-VZQO8VZU.js → ContentIcon-CMn0c-kl.js} +93 -3
- package/dist/browser/{FlowCanvas-BNGDMaWr.js → FlowCanvas-BQGBcfoL.js} +1675 -591
- package/dist/browser/{createResourceDialog-BmtakfVg.js → createResourceDialog-DkovGogv.js} +3 -3
- package/dist/browser/{dialog-BcFBfRB1.js → dialog-CFFuRM8s.js} +37 -86
- package/dist/browser/{exportSvg-CG8hEUNk.js → exportSvg-CYwyYiuH.js} +2 -2
- package/dist/browser/{flowChanges-BGo_4Ypo.js → flowChanges-CN2R-m1M.js} +1258 -422
- package/dist/browser/{flowWorkspace-BFHGev7s.js → flowWorkspace-CXVL1FNv.js} +634 -628
- package/dist/browser/licenses.md +1 -1
- package/dist/browser/preview.css +512 -462
- package/dist/browser/preview.js +8183 -6384
- package/dist/browser/{tooltip-BAWSUexx.js → tooltip-BDcVTNpu.js} +65 -2
- package/dist/browser/{typeScriptSession-CRoezJPN.js → typeScriptSession-CoEIptNC.js} +1 -1
- package/dist/browser/ui.css +5 -24
- package/dist/browser/workbench.css +512 -462
- package/dist/browser/workbench.d.ts +2 -3
- package/dist/browser/workbench.js +65 -690
- package/dist/browser/{workbenchSelect-CjK-xdm3.js → workbenchSelect-CkOS95gX.js} +2 -2
- package/dist/command/index.js +31 -31
- package/dist/common/authoring.d.ts +3 -3
- package/dist/common/authoringExamples.d.ts +3 -3
- package/dist/common/authoringSchema.d.ts +14 -22
- package/dist/common/control-api-conformance.js +91 -25
- package/dist/common/control-api.d.ts +8 -3
- package/dist/common/control-api.js +51 -25
- package/dist/common/control-requests.d.ts +9 -3
- package/dist/common/control-requests.js +54 -42
- package/dist/common/flow-authoring.js +32 -19
- package/dist/common/flow-change.d.ts +1 -1
- package/dist/common/flow-change.js +32 -19
- package/dist/common/flow-encoding.js +31 -18
- package/dist/common/flow-semantics.js +41 -26
- package/dist/common/flow-state.d.ts +49 -43
- package/dist/common/flow-state.js +95 -110
- package/dist/common/mcp.d.ts +35 -4
- package/dist/common/mcp.js +51 -26
- package/dist/common/run-events.js +19 -9
- package/dist/common/runtime-contract.d.ts +4 -1
- package/dist/common/runtime-contract.js +66 -17
- package/dist/common/runtime-nodejs-compat.js +11 -12
- package/dist/common/scheduler.d.ts +2 -1
- package/dist/common/scheduler.js +82 -55
- package/dist/common/state-conformance.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/control/common/api.ts +36 -7
- package/src/control/common/authoring.ts +6 -5
- package/src/control/common/authoringExamples.ts +6 -5
- package/src/control/common/authoringSchema.ts +7 -7
- package/src/control/common/conformance.ts +31 -0
- package/src/control/common/mcp.ts +7 -2
- package/src/control/common/requests.ts +10 -1
- package/src/execution/common/events.ts +22 -10
- package/src/execution/common/node-execution.ts +1 -1
- package/src/execution/common/scheduler.ts +28 -14
- package/src/flow/common/change.ts +1 -1
- package/src/flow/common/changeSchema.ts +4 -4
- package/src/flow/common/errorHandling.ts +1 -1
- package/src/flow/common/semantics.ts +12 -6
- package/src/runtime/common/nodejsGuest.generated.json +11 -12
- package/src/runtime/common/runtime.ts +45 -12
- package/src/state/common/conformance.ts +32 -21
- package/src/state/common/state.ts +85 -80
- package/src/types/index.ts +1 -0
|
@@ -27,11 +27,20 @@ export function createState(invoke: (kind: string, payload: JsonValue) => Promis
|
|
|
27
27
|
throw invalid('Identities must be strings or finite numbers.')
|
|
28
28
|
return ids
|
|
29
29
|
}
|
|
30
|
+
function get(key: string): Promise<JsonValue>
|
|
31
|
+
function get(keys: readonly string[]): Promise<JsonValue[]>
|
|
32
|
+
async function get(key: unknown) {
|
|
33
|
+
if (Array.isArray(key)) {
|
|
34
|
+
if (key.length === 0 || key.length > 16) throw invalid('State reads require 1 to 16 keys.')
|
|
35
|
+
const keys = Array.from(key, keyOf)
|
|
36
|
+
if (new Set(keys).size !== keys.length) throw invalid('State keys must be distinct.')
|
|
37
|
+
return ((await invoke('state.get', { key: keys })).body as { values: JsonValue[] }).values
|
|
38
|
+
}
|
|
39
|
+
return ((await invoke('state.get', { key: keyOf(key) })).body as { value: JsonValue }).value
|
|
40
|
+
}
|
|
30
41
|
return {
|
|
31
42
|
state: Object.freeze({
|
|
32
|
-
|
|
33
|
-
return ((await invoke('state.get', { key: keyOf(key) })).body as { value: JsonValue }).value
|
|
34
|
-
},
|
|
43
|
+
get,
|
|
35
44
|
async set(key: unknown, value: unknown) {
|
|
36
45
|
await invoke('state.set', { key: keyOf(key), value: valueOf(value) })
|
|
37
46
|
},
|
|
@@ -443,6 +452,23 @@ async function rejects(operation: Promise<unknown>, pattern: RegExp, message: st
|
|
|
443
452
|
}
|
|
444
453
|
|
|
445
454
|
export const runtimeConformanceCases: readonly RuntimeConformanceCase[] = [
|
|
455
|
+
{
|
|
456
|
+
name: 'reads multiple State keys in one Host call while preserving input order',
|
|
457
|
+
async verify(harness) {
|
|
458
|
+
const calls: { kind: string; payload: JsonValue }[] = []
|
|
459
|
+
const result = await harness.invoke({
|
|
460
|
+
input: null,
|
|
461
|
+
invocationId: 'state-batch-read',
|
|
462
|
+
capability: async ({ kind, payload }) => {
|
|
463
|
+
calls.push({ kind, payload })
|
|
464
|
+
return { status: 200, body: { values: [0, null, false, ''] } }
|
|
465
|
+
},
|
|
466
|
+
program: program(harness, `export default async (_, context) => context.state.get(['zero', 'missing', 'false', 'empty'])`),
|
|
467
|
+
})
|
|
468
|
+
equal(result, [0, null, false, ''], 'State batch values')
|
|
469
|
+
equal(calls, [{ kind: 'state.get', payload: { key: ['zero', 'missing', 'false', 'empty'] } }], 'State batch dispatch')
|
|
470
|
+
},
|
|
471
|
+
},
|
|
446
472
|
{
|
|
447
473
|
name: 'cancels detached State work when its Task finishes',
|
|
448
474
|
async verify(harness) {
|
|
@@ -492,7 +518,15 @@ export const runtimeConformanceCases: readonly RuntimeConformanceCase[] = [
|
|
|
492
518
|
const invalid = [undefined, NaN, Infinity, new Date(), { x: undefined }, cyclic, [undefined], { x: () => 1 }];
|
|
493
519
|
const errors = [];
|
|
494
520
|
for (const value of invalid) { try { await context.state.set('key', value); errors.push('accepted'); } catch (e) { errors.push(e.code); } }
|
|
495
|
-
for (const call of [
|
|
521
|
+
for (const call of [
|
|
522
|
+
() => context.state.get(''), () => context.state.get('x'.repeat(257)),
|
|
523
|
+
() => context.state.get([]), () => context.state.get(['x', 'x']),
|
|
524
|
+
() => context.state.get(Array.from({length: 17}, (_, index) => String(index))),
|
|
525
|
+
() => context.state.get(['']), () => context.state.get(['汉'.repeat(86)]),
|
|
526
|
+
() => context.state.get(new Array(1)), () => context.state.get(null), () => context.state.get(),
|
|
527
|
+
() => context.state.insert('x', null), () => context.dedupe.insert([NaN]),
|
|
528
|
+
() => context.dedupe.has([null]), () => context.dedupe.insert(new Array(1))
|
|
529
|
+
]) {
|
|
496
530
|
try { await call(); errors.push('accepted'); } catch (e) { errors.push(e.code); }
|
|
497
531
|
}
|
|
498
532
|
return errors;
|
|
@@ -501,7 +535,7 @@ export const runtimeConformanceCases: readonly RuntimeConformanceCase[] = [
|
|
|
501
535
|
})
|
|
502
536
|
equal(
|
|
503
537
|
result,
|
|
504
|
-
Array.from({ length:
|
|
538
|
+
Array.from({ length: 22 }, () => 'capability.invalid'),
|
|
505
539
|
'State boundary validation',
|
|
506
540
|
)
|
|
507
541
|
equal(calls, 0, 'State invalid dispatch')
|
|
@@ -1051,14 +1085,13 @@ export function createBase64() {
|
|
|
1051
1085
|
const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0
|
|
1052
1086
|
const bytes = new Uint8Array((text.length / 4) * 3 - padding)
|
|
1053
1087
|
for (let index = 0, offset = 0; index < text.length; index += 4) {
|
|
1054
|
-
|
|
1088
|
+
let value = 0
|
|
1089
|
+
for (let position = 0; position < 4; position++) {
|
|
1055
1090
|
const code = text.charCodeAt(index + position)
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
})
|
|
1061
|
-
const value = (digits[0]! << 18) | (digits[1]! << 12) | (digits[2]! << 6) | digits[3]!
|
|
1091
|
+
const digit = code == 61 && index + position >= text.length - padding ? 0 : code < 128 ? values[code]! : 255
|
|
1092
|
+
if (digit == 255) throw new TypeError('Base64 content is invalid.')
|
|
1093
|
+
value = (value << 6) | digit
|
|
1094
|
+
}
|
|
1062
1095
|
if (offset < bytes.length) bytes[offset++] = value >> 16
|
|
1063
1096
|
if (offset < bytes.length) bytes[offset++] = (value >> 8) & 255
|
|
1064
1097
|
if (offset < bytes.length) bytes[offset++] = value & 255
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonValue } from '../../flow/common/model.ts'
|
|
2
|
-
import type {
|
|
2
|
+
import type { SkipDuplicatesConfig } from './state.ts'
|
|
3
3
|
|
|
4
4
|
import { dequal } from 'dequal/lite'
|
|
5
5
|
|
|
@@ -7,31 +7,42 @@ export interface StateConformanceHarness {
|
|
|
7
7
|
readonly maxIdentities: number
|
|
8
8
|
/** Executes Code on the same initially empty Flow and scope on every call; returns its `result` output. */
|
|
9
9
|
run(source: string): Promise<JsonValue>
|
|
10
|
-
/** Executes the same
|
|
11
|
-
|
|
10
|
+
/** Executes the same Skip Duplicates node across Runs, updating its business config and items. */
|
|
11
|
+
skipDuplicates(config: SkipDuplicatesConfig, items: JsonValue[]): Promise<{ items: JsonValue[]; duplicates: JsonValue[] }>
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const stateConformanceCases: readonly { name: string; verify(harness: StateConformanceHarness): Promise<void> }[] = [
|
|
15
15
|
{
|
|
16
|
-
name: '
|
|
16
|
+
name: 'reads selected State keys in input order with missing, falsy and independent values',
|
|
17
17
|
async verify(harness) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
const result = await harness.run(`export default async (_, context) => {
|
|
19
|
+
await context.state.set('object', { nested: [1] });
|
|
20
|
+
await context.state.set('zero', 0);
|
|
21
|
+
await context.state.set('false', false);
|
|
22
|
+
await context.state.set('empty', '');
|
|
23
|
+
const values = await context.state.get(['false', 'missing', 'object', 'zero', 'empty']);
|
|
24
|
+
values[2].nested.push(2);
|
|
25
|
+
return {result: {values, retained: await context.state.get('object'), zero: await context.state.get('zero')}};
|
|
26
|
+
}`)
|
|
27
|
+
if (!dequal(result, { values: [false, null, { nested: [1, 2] }, 0, ''], retained: { nested: [1] }, zero: 0 }))
|
|
28
|
+
throw new Error('State batch reads must preserve order, missing keys and independent JSON values.')
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'skips composite identities across successful Runs without depending on field order',
|
|
33
|
+
async verify(harness) {
|
|
34
|
+
const items = [
|
|
35
|
+
{ company: 'a', id: 1 },
|
|
36
|
+
{ company: 'b', id: 1 },
|
|
37
|
+
{ company: 'a', id: 1 },
|
|
38
|
+
]
|
|
39
|
+
const first = await harness.skipDuplicates({ fields: ['company', 'id'] }, items)
|
|
40
|
+
if (!dequal(first, { items: items.slice(0, 2), duplicates: [items[2]] }))
|
|
41
|
+
throw new Error('Composite identities must retain the first item in input order.')
|
|
42
|
+
const second = await harness.skipDuplicates({ fields: ['id', 'company'] }, items)
|
|
43
|
+
if (!dequal(second, { items: [], duplicates: items })) throw new Error('Successful Run history must persist independently of field order.')
|
|
44
|
+
const changed = await harness.skipDuplicates({ fields: ['id'] }, items)
|
|
45
|
+
if (!dequal(changed, { items: [items[0]], duplicates: items.slice(1) })) throw new Error('The field definition must participate in identity.')
|
|
35
46
|
},
|
|
36
47
|
},
|
|
37
48
|
{
|
|
@@ -5,32 +5,46 @@ import { z } from 'zod'
|
|
|
5
5
|
export { stateConformanceCases, type StateConformanceHarness } from './conformance.ts'
|
|
6
6
|
|
|
7
7
|
export type StateScope = 'draft' | 'live'
|
|
8
|
-
export const
|
|
9
|
-
|
|
8
|
+
export const stateReadLimits = { maxKeys: 16, maxBytes: 1024 * 1024 }
|
|
9
|
+
const keySchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.min(1)
|
|
12
|
+
.refine((value) => new TextEncoder().encode(value).length <= 256)
|
|
13
|
+
export const stateQuerySchema = z.strictObject({ scope: z.enum(['draft', 'live']), values: z.literal(false).optional() })
|
|
14
|
+
export const stateReadSchema = z.strictObject({
|
|
10
15
|
scope: z.enum(['draft', 'live']),
|
|
11
|
-
|
|
12
|
-
.
|
|
16
|
+
keys: z
|
|
17
|
+
.array(keySchema)
|
|
13
18
|
.min(1)
|
|
14
|
-
.
|
|
15
|
-
.
|
|
19
|
+
.max(stateReadLimits.maxKeys)
|
|
20
|
+
.refine((keys) => new Set(keys).size === keys.length, 'State keys must be distinct.'),
|
|
21
|
+
})
|
|
22
|
+
export type StateRead = z.infer<typeof stateReadSchema>
|
|
23
|
+
export const stateClearSchema = z.strictObject({
|
|
24
|
+
scope: z.enum(['draft', 'live']),
|
|
25
|
+
key: keySchema.optional(),
|
|
16
26
|
})
|
|
17
27
|
export const dedupeClearSchema = z.strictObject({ scope: z.enum(['draft', 'live']), space: z.string().regex(/^(flow|node:.+)$/) })
|
|
18
28
|
export type DedupeIdentity = string | number
|
|
19
|
-
export const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
export const skipDuplicatesConfigSchema = z.strictObject({
|
|
30
|
+
fields: z
|
|
31
|
+
.array(z.string().refine((field) => field.length > 0 && field.split('.').every((part) => part.length > 0), 'Use a nonempty dot-separated field path.'))
|
|
32
|
+
.refine((fields) => new Set(fields).size === fields.length, 'Identity fields must be distinct.')
|
|
33
|
+
.default([]),
|
|
23
34
|
})
|
|
24
|
-
export type
|
|
25
|
-
export interface
|
|
26
|
-
readonly kind: '
|
|
35
|
+
export type SkipDuplicatesConfig = z.infer<typeof skipDuplicatesConfigSchema>
|
|
36
|
+
export interface SkipDuplicatesExecutor extends SkipDuplicatesConfig {
|
|
37
|
+
readonly kind: 'skip-duplicates'
|
|
27
38
|
}
|
|
28
|
-
export const
|
|
39
|
+
export const skipDuplicatesExecutorSchema = skipDuplicatesConfigSchema.extend({ kind: z.literal('skip-duplicates') })
|
|
29
40
|
|
|
30
|
-
export function
|
|
41
|
+
export function skipDuplicatesTask(
|
|
42
|
+
config: SkipDuplicatesConfig = skipDuplicatesConfigSchema.parse({}),
|
|
43
|
+
name = 'Skip Duplicates',
|
|
44
|
+
): ManagedTaskDefinitionOf<SkipDuplicatesExecutor> {
|
|
31
45
|
return {
|
|
32
46
|
name,
|
|
33
|
-
executor: { ...config, kind: '
|
|
47
|
+
executor: { ...config, kind: 'skip-duplicates' },
|
|
34
48
|
inputs: [{ handle: 'items', jsonSchema: { type: 'array' }, nullable: false }],
|
|
35
49
|
outputs: ['items', 'duplicates'].map((handle) => ({ handle, jsonSchema: { type: 'array' }, nullable: false })),
|
|
36
50
|
}
|
|
@@ -39,13 +53,13 @@ export function dedupeTask(config: DedupeConfig = dedupeConfigSchema.parse({}),
|
|
|
39
53
|
export class StateError extends Error {
|
|
40
54
|
readonly retryable = false
|
|
41
55
|
readonly catchable: boolean
|
|
42
|
-
readonly code: 'capability.invalid' | 'capability.denied' | 'dedupe.full' | 'limit-exceeded'
|
|
56
|
+
readonly code: 'capability.invalid' | 'capability.denied' | 'dedupe.full' | 'limit-exceeded' | 'node.input-invalid'
|
|
43
57
|
|
|
44
58
|
constructor(code: StateError['code'], message: string) {
|
|
45
59
|
super(message)
|
|
46
60
|
this.name = 'StateError'
|
|
47
61
|
this.code = code
|
|
48
|
-
this.catchable = code === 'dedupe.full'
|
|
62
|
+
this.catchable = code === 'dedupe.full' || code === 'node.input-invalid'
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
65
|
|
|
@@ -67,63 +81,31 @@ export async function hashIdentities(ids: readonly DedupeIdentity[]): Promise<Ui
|
|
|
67
81
|
)
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function increasingIdentities(ids: readonly DedupeIdentity[], watermark: DedupeIdentity | null) {
|
|
97
|
-
const type = typeof (watermark ?? ids[0])
|
|
98
|
-
if (ids.some((id) => typeof id !== type)) throw new StateError('capability.invalid', 'Watermark types must not be mixed.')
|
|
99
|
-
const initial = watermark == null ? -Infinity : watermarkValue(watermark)
|
|
100
|
-
const seen = new Set<number>()
|
|
101
|
-
let maximum = initial
|
|
102
|
-
let next = watermark
|
|
103
|
-
const inserted = ids.map((id) => {
|
|
104
|
-
const value = watermarkValue(id)
|
|
105
|
-
const fresh = value > initial && !seen.has(value)
|
|
106
|
-
seen.add(value)
|
|
107
|
-
if (value > maximum) {
|
|
108
|
-
maximum = value
|
|
109
|
-
next = id
|
|
110
|
-
}
|
|
111
|
-
return fresh
|
|
84
|
+
/** Canonical identities include the field definition and preserve scalar types. */
|
|
85
|
+
export function skipDuplicatesIdentities(items: JsonValue, fields: readonly string[]): string[] {
|
|
86
|
+
if (!Array.isArray(items)) throw new StateError('node.input-invalid', 'Skip Duplicates requires an array of items.')
|
|
87
|
+
const paths = fields.toSorted()
|
|
88
|
+
return items.map((item, index) => {
|
|
89
|
+
const values = (paths.length === 0 ? [''] : paths).map((field) => {
|
|
90
|
+
const value =
|
|
91
|
+
field === ''
|
|
92
|
+
? item
|
|
93
|
+
: field
|
|
94
|
+
.split('.')
|
|
95
|
+
.reduce<JsonValue | undefined>(
|
|
96
|
+
(current, key) =>
|
|
97
|
+
current != null && typeof current === 'object' && Object.hasOwn(current, key) ? (current as Record<string, JsonValue>)[key] : undefined,
|
|
98
|
+
item,
|
|
99
|
+
)
|
|
100
|
+
if (typeof value !== 'string' && !(typeof value === 'number' && Number.isFinite(value)))
|
|
101
|
+
throw new StateError(
|
|
102
|
+
'node.input-invalid',
|
|
103
|
+
`Item ${index + 1}, ${field === '' ? 'the item itself' : `field ${JSON.stringify(field)}`} must be a string or finite number.`,
|
|
104
|
+
)
|
|
105
|
+
return value
|
|
106
|
+
})
|
|
107
|
+
return JSON.stringify([paths, values])
|
|
112
108
|
})
|
|
113
|
-
return { inserted, watermark: next }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function itemIdentities(items: JsonValue, field: string): DedupeIdentity[] {
|
|
117
|
-
if (!Array.isArray(items)) throw new StateError('capability.invalid', 'Deduplication requires an array of items.')
|
|
118
|
-
const path = field === '' ? [] : field.split('.')
|
|
119
|
-
return dedupeIdentities(
|
|
120
|
-
items.map((item) =>
|
|
121
|
-
path.reduce<JsonValue | undefined>(
|
|
122
|
-
(value, key) => (value != null && typeof value === 'object' && Object.hasOwn(value, key) ? (value as Record<string, JsonValue>)[key] : undefined),
|
|
123
|
-
item,
|
|
124
|
-
),
|
|
125
|
-
),
|
|
126
|
-
)
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
export interface FlowStateView {
|
|
@@ -136,12 +118,12 @@ export interface FlowStateView {
|
|
|
136
118
|
readonly maxIdentities: number
|
|
137
119
|
readonly maxRunWrites: number
|
|
138
120
|
}
|
|
139
|
-
readonly values: readonly { readonly key: string; readonly size: number; readonly updatedAt: number
|
|
121
|
+
readonly values: readonly { readonly key: string; readonly size: number; readonly updatedAt: number }[]
|
|
140
122
|
readonly spaces: readonly {
|
|
141
123
|
readonly space: string
|
|
142
|
-
readonly mode: 'new' | 'increasing'
|
|
143
124
|
readonly count: number
|
|
144
|
-
readonly
|
|
125
|
+
readonly pendingCount: number
|
|
126
|
+
readonly reservedCount: number
|
|
145
127
|
readonly updatedAt: number
|
|
146
128
|
}[]
|
|
147
129
|
}
|
|
@@ -156,14 +138,37 @@ export const flowStateViewSchema = z.object({
|
|
|
156
138
|
maxIdentities: z.number().positive(),
|
|
157
139
|
maxRunWrites: z.number().positive(),
|
|
158
140
|
}),
|
|
159
|
-
values: z.array(z.
|
|
141
|
+
values: z.array(z.strictObject({ key: z.string(), size: z.number().nonnegative(), updatedAt: z.number() })),
|
|
160
142
|
spaces: z.array(
|
|
161
143
|
z.object({
|
|
162
144
|
space: z.string(),
|
|
163
|
-
mode: z.enum(['new', 'increasing']),
|
|
164
145
|
count: z.number().nonnegative(),
|
|
165
|
-
|
|
146
|
+
pendingCount: z.number().nonnegative(),
|
|
147
|
+
reservedCount: z.number().nonnegative(),
|
|
166
148
|
updatedAt: z.number(),
|
|
167
149
|
}),
|
|
168
150
|
),
|
|
169
151
|
})
|
|
152
|
+
|
|
153
|
+
export interface FlowStateValues {
|
|
154
|
+
readonly version: 1
|
|
155
|
+
readonly scope: StateScope
|
|
156
|
+
readonly values: readonly { readonly key: string; readonly value: JsonValue }[]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const flowStateValuesSchema = z
|
|
160
|
+
.object({
|
|
161
|
+
version: z.literal(1),
|
|
162
|
+
scope: z.enum(['draft', 'live']),
|
|
163
|
+
values: z
|
|
164
|
+
.array(z.strictObject({ key: keySchema, value: z.json() }))
|
|
165
|
+
.min(1)
|
|
166
|
+
.max(stateReadLimits.maxKeys),
|
|
167
|
+
})
|
|
168
|
+
.refine((response) => {
|
|
169
|
+
const encoder = new TextEncoder()
|
|
170
|
+
return (
|
|
171
|
+
response.values.reduce((bytes, item) => bytes + (item.value === null ? 0 : encoder.encode(JSON.stringify(item.value)).byteLength), 0) <=
|
|
172
|
+
stateReadLimits.maxBytes
|
|
173
|
+
)
|
|
174
|
+
}, 'State values exceed the 1 MiB read limit.')
|
package/src/types/index.ts
CHANGED
|
@@ -121,6 +121,7 @@ export interface TaskContext<
|
|
|
121
121
|
readonly blockId: string
|
|
122
122
|
readonly state: {
|
|
123
123
|
get(key: string): Promise<JsonValue | null>
|
|
124
|
+
get(keys: readonly string[]): Promise<(JsonValue | null)[]>
|
|
124
125
|
set(key: string, value: JsonValue): Promise<void>
|
|
125
126
|
insert(key: string, value: Exclude<JsonValue, null>): Promise<boolean>
|
|
126
127
|
delete(key: string): Promise<void>
|