@protoyard/blueprint 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +1149 -2
- package/dist/aggregate.d.ts +83 -0
- package/dist/aggregate.js +268 -0
- package/dist/app-model.d.ts +76 -0
- package/dist/app-model.js +287 -0
- package/dist/archetypes/index.d.ts +1 -0
- package/dist/archetypes/index.js +1 -0
- package/dist/archetypes/schema.d.ts +126 -0
- package/dist/archetypes/schema.js +77 -0
- package/dist/audit.d.ts +52 -0
- package/dist/audit.js +149 -0
- package/dist/authoring/deck-lint.d.ts +32 -0
- package/dist/authoring/deck-lint.js +567 -0
- package/dist/authoring/index.d.ts +1 -0
- package/dist/authoring/index.js +1 -0
- package/dist/authoring/select-deck-layouts.d.ts +50 -0
- package/dist/authoring/select-deck-layouts.js +213 -0
- package/dist/authoring-apis.d.ts +13 -0
- package/dist/authoring-apis.js +13 -0
- package/dist/authoring-validation.d.ts +65 -0
- package/dist/authoring-validation.js +267 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +30 -0
- package/dist/brand.d.ts +70 -0
- package/dist/brand.js +294 -0
- package/dist/brief/archetypes.d.ts +28 -0
- package/dist/brief/archetypes.js +798 -0
- package/dist/brief/content-synthesize.d.ts +31 -0
- package/dist/brief/content-synthesize.js +210 -0
- package/dist/brief/deck-outlines.d.ts +96 -0
- package/dist/brief/deck-outlines.js +561 -0
- package/dist/brief/draft-bodies.d.ts +29 -0
- package/dist/brief/draft-bodies.js +224 -0
- package/dist/brief/fields.d.ts +19 -0
- package/dist/brief/fields.js +229 -0
- package/dist/brief/index.d.ts +8 -0
- package/dist/brief/index.js +8 -0
- package/dist/brief/kind-detection.d.ts +69 -0
- package/dist/brief/kind-detection.js +353 -0
- package/dist/brief/limits.d.ts +117 -0
- package/dist/brief/limits.js +217 -0
- package/dist/brief/page-doc-outlines.d.ts +80 -0
- package/dist/brief/page-doc-outlines.js +485 -0
- package/dist/brief/synthesize.d.ts +59 -0
- package/dist/brief/synthesize.js +511 -0
- package/dist/brief/text.d.ts +13 -0
- package/dist/brief/text.js +59 -0
- package/dist/canvas-screen.d.ts +31 -0
- package/dist/canvas-screen.js +33 -0
- package/dist/canvas-seed.d.ts +58 -0
- package/dist/canvas-seed.js +82 -0
- package/dist/computed/evaluate.d.ts +11 -0
- package/dist/computed/evaluate.js +101 -0
- package/dist/computed/index.d.ts +4 -0
- package/dist/computed/index.js +3 -0
- package/dist/computed/public.d.ts +14 -0
- package/dist/computed/public.js +19 -0
- package/dist/computed/schema.d.ts +10 -0
- package/dist/computed/schema.js +140 -0
- package/dist/computed/types.d.ts +70 -0
- package/dist/computed/types.js +1 -0
- package/dist/computed/validate.d.ts +6 -0
- package/dist/computed/validate.js +146 -0
- package/dist/csv-guard.d.ts +15 -0
- package/dist/csv-guard.js +29 -0
- package/dist/csv.d.ts +85 -0
- package/dist/csv.js +288 -0
- package/dist/files.d.ts +35 -0
- package/dist/files.js +246 -0
- package/dist/form-schema.d.ts +124 -0
- package/dist/form-schema.js +300 -0
- package/dist/generator-version.d.ts +11 -0
- package/dist/generator-version.js +51 -0
- package/dist/generator.d.ts +34 -0
- package/dist/generator.js +445 -0
- package/dist/identity.d.ts +34 -0
- package/dist/identity.js +91 -0
- package/dist/import/csv-shape.d.ts +40 -0
- package/dist/import/csv-shape.js +265 -0
- package/dist/import/json-shape.d.ts +55 -0
- package/dist/import/json-shape.js +491 -0
- package/dist/import/limits.d.ts +13 -0
- package/dist/import/limits.js +14 -0
- package/dist/import/sqlite-schema.d.ts +36 -0
- package/dist/import/sqlite-schema.js +729 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +58 -0
- package/dist/kinds/anchors.d.ts +18 -0
- package/dist/kinds/anchors.js +46 -0
- package/dist/kinds/bodies.d.ts +6 -0
- package/dist/kinds/bodies.js +32 -0
- package/dist/kinds/brand-runtime.d.ts +19 -0
- package/dist/kinds/brand-runtime.js +35 -0
- package/dist/kinds/deck.d.ts +8 -0
- package/dist/kinds/deck.js +34 -0
- package/dist/kinds/doc.d.ts +19 -0
- package/dist/kinds/doc.js +41 -0
- package/dist/kinds/embed/index.d.ts +39 -0
- package/dist/kinds/embed/index.js +91 -0
- package/dist/kinds/envelope.d.ts +159 -0
- package/dist/kinds/envelope.js +78 -0
- package/dist/kinds/index.d.ts +11 -0
- package/dist/kinds/index.js +11 -0
- package/dist/kinds/kind.d.ts +36 -0
- package/dist/kinds/kind.js +55 -0
- package/dist/kinds/limits.d.ts +64 -0
- package/dist/kinds/limits.js +190 -0
- package/dist/kinds/page-doc-runtime.d.ts +37 -0
- package/dist/kinds/page-doc-runtime.js +55 -0
- package/dist/kinds/page.d.ts +16 -0
- package/dist/kinds/page.js +67 -0
- package/dist/kinds/parse.d.ts +18 -0
- package/dist/kinds/parse.js +27 -0
- package/dist/kinds/runtime.d.ts +128 -0
- package/dist/kinds/runtime.js +206 -0
- package/dist/kinds/services.d.ts +49 -0
- package/dist/kinds/services.js +249 -0
- package/dist/kinds/testing.d.ts +9 -0
- package/dist/kinds/testing.js +16 -0
- package/dist/migrations.d.ts +97 -0
- package/dist/migrations.js +351 -0
- package/dist/motion.d.ts +50 -0
- package/dist/motion.js +52 -0
- package/dist/notifications.d.ts +19 -0
- package/dist/notifications.js +177 -0
- package/dist/patch.d.ts +122 -0
- package/dist/patch.js +322 -0
- package/dist/policy.d.ts +23 -0
- package/dist/policy.js +105 -0
- package/dist/runtime-definition.d.ts +39 -0
- package/dist/runtime-definition.js +74 -0
- package/dist/scenarios.d.ts +9 -0
- package/dist/scenarios.js +49 -0
- package/dist/schema/blueprint.schema.json +10191 -0
- package/dist/schema-json.d.ts +6 -0
- package/dist/schema-json.js +36 -0
- package/dist/schema.d.ts +6348 -0
- package/dist/schema.js +1269 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.js +96 -0
- package/dist/services.d.ts +89 -0
- package/dist/services.js +898 -0
- package/dist/share.d.ts +78 -0
- package/dist/share.js +234 -0
- package/dist/sheet-screen.d.ts +49 -0
- package/dist/sheet-screen.js +91 -0
- package/dist/storage.d.ts +43 -0
- package/dist/storage.js +179 -0
- package/dist/stream.d.ts +26 -0
- package/dist/stream.js +173 -0
- package/dist/untrusted.d.ts +63 -0
- package/dist/untrusted.js +502 -0
- package/dist/validation-tasks.d.ts +178 -0
- package/dist/validation-tasks.js +199 -0
- package/dist/validation.d.ts +35 -0
- package/dist/validation.js +312 -0
- package/dist/variants.d.ts +112 -0
- package/dist/variants.js +283 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/schema.d.ts +50 -0
- package/dist/workflows/schema.js +34 -0
- package/dist/workflows/transition.d.ts +15 -0
- package/dist/workflows/transition.js +25 -0
- package/dist/workflows/validate.d.ts +17 -0
- package/dist/workflows/validate.js +140 -0
- package/package.json +120 -3
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnyBlueprint } from './kinds/envelope.ts';
|
|
2
|
+
import { type UntrustedDocumentLimits } from './kinds/limits.ts';
|
|
3
|
+
/** Candidates validated per text, deepest first. */
|
|
4
|
+
export declare const MAX_STREAM_CANDIDATES = 12;
|
|
5
|
+
export interface StreamedBlueprint {
|
|
6
|
+
blueprint: AnyBlueprint;
|
|
7
|
+
/** `true` when the text was the whole blueprint; `false` for the part that has arrived. */
|
|
8
|
+
complete: boolean;
|
|
9
|
+
/** The JSON text that validated: the whole text, or the candidate cut from it. */
|
|
10
|
+
json: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Candidate JSON texts for a truncated JSON document, deepest first: each is
|
|
14
|
+
* the prefix up to the last complete member of one open container, with the
|
|
15
|
+
* open containers closed. Empty when the text is not truncated JSON (a
|
|
16
|
+
* mismatched bracket, or nothing open). A complete document yields nothing:
|
|
17
|
+
* parse it directly.
|
|
18
|
+
*/
|
|
19
|
+
export declare function truncatedJsonCandidates(text: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The blueprint a streamed text paints: the whole blueprint when the text is
|
|
22
|
+
* complete and valid, else (content kinds only) the largest valid part that
|
|
23
|
+
* has arrived, else `null`. Every candidate goes through
|
|
24
|
+
* `loadUntrustedDocument`, with its limits.
|
|
25
|
+
*/
|
|
26
|
+
export declare function paintStreamedBlueprint(text: string, limits?: Partial<UntrustedDocumentLimits>): Promise<StreamedBlueprint | null>;
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { isContentKind } from "./kinds/kind.js";
|
|
2
|
+
import { DEFAULT_UNTRUSTED_DOCUMENT_LIMITS } from "./kinds/limits.js";
|
|
3
|
+
import { loadUntrustedDocument } from "./untrusted.js";
|
|
4
|
+
/**
|
|
5
|
+
* Progressive painting of a streamed blueprint: while an agent is still
|
|
6
|
+
* writing a deck, page or doc blueprint (its file grows, or a tool streams
|
|
7
|
+
* it), the text on hand is usually truncated JSON. This module finds the
|
|
8
|
+
* largest part of it that is already a valid blueprint, so a player can paint
|
|
9
|
+
* the slides, sections or doc blocks that have arrived instead of waiting for
|
|
10
|
+
* the last byte.
|
|
11
|
+
*
|
|
12
|
+
* It never guesses content. A candidate is the text up to the end of a value
|
|
13
|
+
* that already closed, with the containers still open closed after it; the
|
|
14
|
+
* deepest candidate is tried first, then each shallower one (dropping the
|
|
15
|
+
* member that is still arriving), and the first one the ordinary untrusted
|
|
16
|
+
* loader accepts wins. The loader applies every limit and schema check, so a
|
|
17
|
+
* partial blueprint is held to exactly the rules of a complete one.
|
|
18
|
+
*
|
|
19
|
+
* Only content kinds paint partially. An app's entities shape its database,
|
|
20
|
+
* so half an app would open (and migrate) worlds that the finished blueprint
|
|
21
|
+
* then drops: an app paints only once its JSON is complete and valid.
|
|
22
|
+
*
|
|
23
|
+
* Linear in the text, no recursion; the depth and the number of candidates
|
|
24
|
+
* validated are capped.
|
|
25
|
+
*/
|
|
26
|
+
/** Containers deeper than this stop the scan (the loader's own depth cap is lower). */
|
|
27
|
+
const MAX_STREAM_DEPTH = 128;
|
|
28
|
+
/** Candidates validated per text, deepest first. */
|
|
29
|
+
export const MAX_STREAM_CANDIDATES = 12;
|
|
30
|
+
/**
|
|
31
|
+
* Candidate JSON texts for a truncated JSON document, deepest first: each is
|
|
32
|
+
* the prefix up to the last complete member of one open container, with the
|
|
33
|
+
* open containers closed. Empty when the text is not truncated JSON (a
|
|
34
|
+
* mismatched bracket, or nothing open). A complete document yields nothing:
|
|
35
|
+
* parse it directly.
|
|
36
|
+
*/
|
|
37
|
+
export function truncatedJsonCandidates(text) {
|
|
38
|
+
const stack = [];
|
|
39
|
+
let inString = false;
|
|
40
|
+
let escaped = false;
|
|
41
|
+
let stringIsKey = false;
|
|
42
|
+
let scalar = false;
|
|
43
|
+
const completeValue = (end) => {
|
|
44
|
+
const top = stack[stack.length - 1];
|
|
45
|
+
if (!top)
|
|
46
|
+
return;
|
|
47
|
+
if (top.kind === 'array')
|
|
48
|
+
top.cut = end;
|
|
49
|
+
else if (top.awaitingValue) {
|
|
50
|
+
top.cut = end;
|
|
51
|
+
top.awaitingValue = false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
for (let i = 0; i < text.length; i++) {
|
|
55
|
+
const ch = text[i];
|
|
56
|
+
if (inString) {
|
|
57
|
+
if (escaped)
|
|
58
|
+
escaped = false;
|
|
59
|
+
else if (ch === '\\')
|
|
60
|
+
escaped = true;
|
|
61
|
+
else if (ch === '"') {
|
|
62
|
+
inString = false;
|
|
63
|
+
if (!stringIsKey)
|
|
64
|
+
completeValue(i + 1);
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (scalar) {
|
|
69
|
+
if (ch === ',' ||
|
|
70
|
+
ch === '}' ||
|
|
71
|
+
ch === ']' ||
|
|
72
|
+
ch === ' ' ||
|
|
73
|
+
ch === '\n' ||
|
|
74
|
+
ch === '\r' ||
|
|
75
|
+
ch === '\t') {
|
|
76
|
+
scalar = false;
|
|
77
|
+
completeValue(i);
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
switch (ch) {
|
|
83
|
+
case '"': {
|
|
84
|
+
const top = stack[stack.length - 1];
|
|
85
|
+
inString = true;
|
|
86
|
+
stringIsKey = top?.kind === 'object' && !top.awaitingValue;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case '{':
|
|
90
|
+
case '[':
|
|
91
|
+
if (stack.length >= MAX_STREAM_DEPTH)
|
|
92
|
+
return [];
|
|
93
|
+
stack.push({ kind: ch === '{' ? 'object' : 'array', cut: i + 1, awaitingValue: false });
|
|
94
|
+
break;
|
|
95
|
+
case '}':
|
|
96
|
+
case ']': {
|
|
97
|
+
const closed = stack.pop();
|
|
98
|
+
if (!closed || closed.kind !== (ch === '}' ? 'object' : 'array'))
|
|
99
|
+
return [];
|
|
100
|
+
completeValue(i + 1);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case ':': {
|
|
104
|
+
const top = stack[stack.length - 1];
|
|
105
|
+
if (top?.kind !== 'object')
|
|
106
|
+
return [];
|
|
107
|
+
top.awaitingValue = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case ',':
|
|
111
|
+
case ' ':
|
|
112
|
+
case '\n':
|
|
113
|
+
case '\r':
|
|
114
|
+
case '\t':
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
// A number or a literal (true, false, null): it ends at the next delimiter.
|
|
118
|
+
scalar = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const candidates = [];
|
|
122
|
+
for (let depth = stack.length - 1; depth >= 0; depth--) {
|
|
123
|
+
const open = stack[depth];
|
|
124
|
+
let closers = '';
|
|
125
|
+
for (let i = depth; i >= 0; i--)
|
|
126
|
+
closers += stack[i].kind === 'object' ? '}' : ']';
|
|
127
|
+
candidates.push(text.slice(0, open.cut) + closers);
|
|
128
|
+
}
|
|
129
|
+
return candidates;
|
|
130
|
+
}
|
|
131
|
+
function contentKindOf(json) {
|
|
132
|
+
try {
|
|
133
|
+
const value = JSON.parse(json);
|
|
134
|
+
return (value !== null &&
|
|
135
|
+
typeof value === 'object' &&
|
|
136
|
+
isContentKind(value.kind));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The blueprint a streamed text paints: the whole blueprint when the text is
|
|
144
|
+
* complete and valid, else (content kinds only) the largest valid part that
|
|
145
|
+
* has arrived, else `null`. Every candidate goes through
|
|
146
|
+
* `loadUntrustedDocument`, with its limits.
|
|
147
|
+
*/
|
|
148
|
+
export async function paintStreamedBlueprint(text, limits = {}) {
|
|
149
|
+
const maxBytes = limits.maxBytes ?? DEFAULT_UNTRUSTED_DOCUMENT_LIMITS.maxBytes;
|
|
150
|
+
// A UTF-16 length over the byte cap is always over it in UTF-8 too.
|
|
151
|
+
if (text.length > maxBytes)
|
|
152
|
+
return null;
|
|
153
|
+
let complete = true;
|
|
154
|
+
try {
|
|
155
|
+
JSON.parse(text);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
complete = false;
|
|
159
|
+
}
|
|
160
|
+
if (complete) {
|
|
161
|
+
const loaded = await loadUntrustedDocument(text, limits);
|
|
162
|
+
return loaded.success ? { blueprint: loaded.blueprint, complete: true, json: text } : null;
|
|
163
|
+
}
|
|
164
|
+
const candidates = truncatedJsonCandidates(text).slice(0, MAX_STREAM_CANDIDATES);
|
|
165
|
+
for (const candidate of candidates) {
|
|
166
|
+
if (!contentKindOf(candidate))
|
|
167
|
+
continue;
|
|
168
|
+
const loaded = await loadUntrustedDocument(candidate, limits);
|
|
169
|
+
if (loaded.success)
|
|
170
|
+
return { blueprint: loaded.blueprint, complete: false, json: candidate };
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type AnyBlueprint } from './kinds/envelope.ts';
|
|
2
|
+
import { type UntrustedDocumentLimits } from './kinds/limits.ts';
|
|
3
|
+
import { type Blueprint } from './schema.ts';
|
|
4
|
+
/** Limits for data entering a player from a link, paste, file, or message. */
|
|
5
|
+
export interface UntrustedBlueprintLimits {
|
|
6
|
+
maxBytes: number;
|
|
7
|
+
maxDepth: number;
|
|
8
|
+
maxEntities: number;
|
|
9
|
+
maxFieldsPerEntity: number;
|
|
10
|
+
maxScreens: number;
|
|
11
|
+
maxRoles: number;
|
|
12
|
+
maxNotifications: number;
|
|
13
|
+
maxHistory: number;
|
|
14
|
+
maxWorldRows: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_UNTRUSTED_BLUEPRINT_LIMITS: Readonly<UntrustedBlueprintLimits>;
|
|
17
|
+
export type UntrustedBlueprintIssue = {
|
|
18
|
+
code: 'invalid-input' | 'too-large' | 'too-deep' | 'too-many' | 'unsafe-key' | 'invalid-json' | 'invalid-blueprint';
|
|
19
|
+
path: (string | number)[];
|
|
20
|
+
message: string;
|
|
21
|
+
fix: string;
|
|
22
|
+
};
|
|
23
|
+
export type UntrustedBlueprintResult = {
|
|
24
|
+
success: true;
|
|
25
|
+
blueprint: Blueprint;
|
|
26
|
+
issues: [];
|
|
27
|
+
} | {
|
|
28
|
+
success: false;
|
|
29
|
+
blueprint: null;
|
|
30
|
+
issues: UntrustedBlueprintIssue[];
|
|
31
|
+
};
|
|
32
|
+
/** Validate and sanitize data before any player derives or renders a blueprint. */
|
|
33
|
+
export declare function loadUntrustedBlueprint(input: unknown, limits?: Partial<UntrustedBlueprintLimits>): UntrustedBlueprintResult;
|
|
34
|
+
export type UntrustedDocumentResult = {
|
|
35
|
+
success: true;
|
|
36
|
+
blueprint: AnyBlueprint;
|
|
37
|
+
issues: [];
|
|
38
|
+
} | {
|
|
39
|
+
success: false;
|
|
40
|
+
blueprint: null;
|
|
41
|
+
issues: UntrustedBlueprintIssue[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Validates and sanitizes a blueprint of any kind before a player derives or
|
|
45
|
+
* renders it (ADR-036). Apps keep their exact `loadUntrustedBlueprint`
|
|
46
|
+
* contract and limits. Content kinds (deck, page, doc):
|
|
47
|
+
*
|
|
48
|
+
* - size and depth are checked before parsing (`maxBytes`, `maxDepth`);
|
|
49
|
+
* - relative block media is refused at level 1 (generated level-2 schemas
|
|
50
|
+
* continue to accept deck-local paths);
|
|
51
|
+
* - strings lose C0/C1 controls and bidi overrides, except `\n` in the
|
|
52
|
+
* schema-declared multiline fields (markdown, speaker notes, quotes, list
|
|
53
|
+
* items) and `\t` in code; `\r\n` becomes `\n`;
|
|
54
|
+
* - slides, blocks per slide, blocks in total (nested blocks count), chart
|
|
55
|
+
* points and embedded apps are capped (`too-many`);
|
|
56
|
+
* - each embedded app must be an app (no kind or `"app"`), may not embed
|
|
57
|
+
* another app (`too-deep`), and passes `loadUntrustedBlueprint` with the
|
|
58
|
+
* app caps;
|
|
59
|
+
* - then the kind's body schema (loaded on demand) validates the document.
|
|
60
|
+
*
|
|
61
|
+
* Never throws.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadUntrustedDocument(input: unknown, limits?: Partial<UntrustedDocumentLimits & UntrustedBlueprintLimits>): Promise<UntrustedDocumentResult>;
|