@peekling/runtime 0.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/AUTHORS +8 -0
- package/LICENSE +202 -0
- package/LICENSING.md +20 -0
- package/NOTICE +6 -0
- package/README.md +294 -0
- package/dist/configuration-validation.d.ts +19 -0
- package/dist/configuration-validation.d.ts.map +1 -0
- package/dist/configuration-validation.js +298 -0
- package/dist/content.d.ts +44 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +738 -0
- package/dist/contracts.d.ts +10 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +106 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +6 -0
- package/dist/diagnostics.d.ts +25 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +122 -0
- package/dist/direction.d.ts +3 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/direction.js +6 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +60 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/input.d.ts +31 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +215 -0
- package/dist/json.d.ts +3 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +109 -0
- package/dist/loader.d.ts +33 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +477 -0
- package/dist/locomotion.d.ts +8 -0
- package/dist/locomotion.d.ts.map +1 -0
- package/dist/locomotion.js +29 -0
- package/dist/normalized.d.ts +5 -0
- package/dist/normalized.d.ts.map +1 -0
- package/dist/normalized.js +266 -0
- package/dist/overrides.d.ts +29 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +296 -0
- package/dist/own-data.d.ts +18 -0
- package/dist/own-data.d.ts.map +1 -0
- package/dist/own-data.js +205 -0
- package/dist/pack-api.d.ts +9 -0
- package/dist/pack-api.d.ts.map +1 -0
- package/dist/pack-api.js +6 -0
- package/dist/pack-shared.d.ts +28 -0
- package/dist/pack-shared.d.ts.map +1 -0
- package/dist/pack-shared.js +408 -0
- package/dist/pack.d.ts +6 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +316 -0
- package/dist/peekling.css +1 -0
- package/dist/peekling.css.sri +1 -0
- package/dist/peekling.js +7469 -0
- package/dist/peekling.js.sri +1 -0
- package/dist/peekling.min.js +2 -0
- package/dist/peekling.min.js.sri +1 -0
- package/dist/plan-compiler.d.ts +82 -0
- package/dist/plan-compiler.d.ts.map +1 -0
- package/dist/plan-compiler.js +724 -0
- package/dist/plan.d.ts +21 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +287 -0
- package/dist/preflight-api.d.ts +3 -0
- package/dist/preflight-api.d.ts.map +1 -0
- package/dist/preflight-api.js +1 -0
- package/dist/preflight.d.ts +22 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +158 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +24 -0
- package/dist/renderer.d.ts +16 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +176 -0
- package/dist/resolver.d.ts +3 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +30 -0
- package/dist/runtime-diagnostics.d.ts +3 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +5 -0
- package/dist/runtime-validation.d.ts +6 -0
- package/dist/runtime-validation.d.ts.map +1 -0
- package/dist/runtime-validation.js +104 -0
- package/dist/runtime.d.ts +87 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1015 -0
- package/dist/sections.d.ts +15 -0
- package/dist/sections.d.ts.map +1 -0
- package/dist/sections.js +221 -0
- package/dist/styles.d.ts +18 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +132 -0
- package/dist/tooling-preflight.d.ts +23 -0
- package/dist/tooling-preflight.d.ts.map +1 -0
- package/dist/tooling-preflight.js +207 -0
- package/dist/tooling-validation.d.ts +8 -0
- package/dist/tooling-validation.d.ts.map +1 -0
- package/dist/tooling-validation.js +94 -0
- package/dist/types.d.ts +403 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/visibility.d.ts +12 -0
- package/dist/visibility.d.ts.map +1 -0
- package/dist/visibility.js +333 -0
- package/dist/web-component.d.ts +31 -0
- package/dist/web-component.d.ts.map +1 -0
- package/dist/web-component.js +236 -0
- package/package.json +74 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { addPackIssue, packIssue, PackValidationError } from "./errors.js";
|
|
2
|
+
import { validateEventPayload } from "./events.js";
|
|
3
|
+
import { selectNativeDensity } from "./pack.js";
|
|
4
|
+
import { ASSET_PATH_PATTERN, NAME_PATTERN, SEMVER_PATTERN, SPDX_LICENSE_PATTERN, } from "./contracts.js";
|
|
5
|
+
import { closed, record as object, validateDensityVariants, validateMotionKeyframes, validateSha256, validateStateMap, } from "./pack-shared.js";
|
|
6
|
+
import { NATIVE_DIRECTIONS, } from "./types.js";
|
|
7
|
+
import { OwnDataError, snapshotPackData } from "./own-data.js";
|
|
8
|
+
import { runtimeMessage } from "./runtime-diagnostics.js";
|
|
9
|
+
const MAX_NORMALIZED_ATLAS_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
function text(value) {
|
|
11
|
+
return (typeof value === "string" &&
|
|
12
|
+
value.length > 0 &&
|
|
13
|
+
value.length <= 512 &&
|
|
14
|
+
!/[\u0000-\u001f\u007f]/.test(value));
|
|
15
|
+
}
|
|
16
|
+
export function isNormalizedPack(input) {
|
|
17
|
+
if (!object(input))
|
|
18
|
+
return false;
|
|
19
|
+
const atlas = Object.getOwnPropertyDescriptor(input, "atlas")?.value;
|
|
20
|
+
const assets = Object.getOwnPropertyDescriptor(input, "assets")?.value;
|
|
21
|
+
return object(atlas) && !object(assets);
|
|
22
|
+
}
|
|
23
|
+
export function validateNormalizedPack(input, geometry, preferredDensity) {
|
|
24
|
+
const issues = [];
|
|
25
|
+
try {
|
|
26
|
+
input = snapshotPackData(input);
|
|
27
|
+
}
|
|
28
|
+
catch (cause) {
|
|
29
|
+
if (cause instanceof OwnDataError) {
|
|
30
|
+
throw new PackValidationError([cause.message], cause.issueCode ? [cause.issueCode] : []);
|
|
31
|
+
}
|
|
32
|
+
throw cause;
|
|
33
|
+
}
|
|
34
|
+
if (!object(input) || !object(input.atlas) || !object(input.states)) {
|
|
35
|
+
throw new PackValidationError([
|
|
36
|
+
packIssue("normalized.structure", "normalized pack needs atlas and states"),
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
closed(input, [
|
|
40
|
+
"name",
|
|
41
|
+
"displayName",
|
|
42
|
+
"version",
|
|
43
|
+
"license",
|
|
44
|
+
"atlas",
|
|
45
|
+
"states",
|
|
46
|
+
"defaultScale",
|
|
47
|
+
"directionalStates",
|
|
48
|
+
"locomotionMotion",
|
|
49
|
+
"reactionStates",
|
|
50
|
+
"source",
|
|
51
|
+
], "normalizedPack", issues);
|
|
52
|
+
for (const field of ["name", "version", "license"]) {
|
|
53
|
+
if (!text(input[field]))
|
|
54
|
+
addPackIssue(issues, `normalized.${field}`, `${field} must be a short safe string`);
|
|
55
|
+
}
|
|
56
|
+
if (!text(input.displayName) || input.displayName.length > 120)
|
|
57
|
+
addPackIssue(issues, "normalized.displayName", "displayName must be 1-120 characters");
|
|
58
|
+
else if (/:\/\//.test(input.displayName))
|
|
59
|
+
addPackIssue(issues, "normalized.displayName", "displayName must not contain a URL");
|
|
60
|
+
if (typeof input.version === "string" && input.version.length > 64)
|
|
61
|
+
addPackIssue(issues, "normalized.version", "version must be at most 64 characters");
|
|
62
|
+
if (text(input.name) && !NAME_PATTERN.test(input.name))
|
|
63
|
+
addPackIssue(issues, "normalized.name", "name must be a lowercase ID");
|
|
64
|
+
if (text(input.version) && !SEMVER_PATTERN.test(input.version))
|
|
65
|
+
addPackIssue(issues, "normalized.version", "version must be SemVer 2.0");
|
|
66
|
+
if (text(input.license) && !SPDX_LICENSE_PATTERN.test(input.license))
|
|
67
|
+
addPackIssue(issues, "normalized.license", "license must be SPDX or LicenseRef");
|
|
68
|
+
const atlas = input.atlas;
|
|
69
|
+
closed(atlas, [
|
|
70
|
+
"src",
|
|
71
|
+
"columns",
|
|
72
|
+
"rows",
|
|
73
|
+
"cellWidth",
|
|
74
|
+
"cellHeight",
|
|
75
|
+
"logicalWidth",
|
|
76
|
+
"logicalHeight",
|
|
77
|
+
"density",
|
|
78
|
+
"lineage",
|
|
79
|
+
"sha256",
|
|
80
|
+
"variants",
|
|
81
|
+
], "atlas", issues);
|
|
82
|
+
const dimensions = [
|
|
83
|
+
["columns", 256],
|
|
84
|
+
["rows", 4_096],
|
|
85
|
+
["cellWidth", 1_024],
|
|
86
|
+
["cellHeight", 1_024],
|
|
87
|
+
];
|
|
88
|
+
for (const [field, maximum] of dimensions) {
|
|
89
|
+
if (!Number.isInteger(atlas[field]) ||
|
|
90
|
+
atlas[field] < 1 ||
|
|
91
|
+
atlas[field] > maximum) {
|
|
92
|
+
addPackIssue(issues, `atlas.${field}`, `atlas.${field} must be integer 1-${maximum}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!text(atlas.src) ||
|
|
96
|
+
!ASSET_PATH_PATTERN.test(atlas.src) ||
|
|
97
|
+
!/\.(?:png|webp)$/i.test(atlas.src)) {
|
|
98
|
+
addPackIssue(issues, "atlas.src", "atlas.src must be a safe relative PNG or WebP path");
|
|
99
|
+
}
|
|
100
|
+
const columns = Number(atlas.columns);
|
|
101
|
+
const rows = Number(atlas.rows);
|
|
102
|
+
const cellWidth = Number(atlas.cellWidth);
|
|
103
|
+
const cellHeight = Number(atlas.cellHeight);
|
|
104
|
+
for (const field of ["logicalWidth", "logicalHeight"]) {
|
|
105
|
+
if (atlas[field] !== undefined &&
|
|
106
|
+
(!Number.isInteger(atlas[field]) ||
|
|
107
|
+
atlas[field] < 1 ||
|
|
108
|
+
atlas[field] > 256))
|
|
109
|
+
addPackIssue(issues, `atlas.${field}`, `atlas.${field} must be integer 1-256`);
|
|
110
|
+
}
|
|
111
|
+
if (atlas.density !== undefined &&
|
|
112
|
+
![1, 2, 4].includes(atlas.density))
|
|
113
|
+
addPackIssue(issues, "atlas.density", "atlas.density must be 1, 2, or 4");
|
|
114
|
+
if (atlas.lineage !== undefined &&
|
|
115
|
+
(!text(atlas.lineage) ||
|
|
116
|
+
!/^[a-z0-9][a-z0-9._-]{2,127}$/.test(atlas.lineage)))
|
|
117
|
+
addPackIssue(issues, "atlas.lineage", "atlas.lineage must be a safe ID");
|
|
118
|
+
const variants = atlas.variants === undefined
|
|
119
|
+
? []
|
|
120
|
+
: validateDensityVariants(atlas.variants, { path: "atlas.variants" }, issues);
|
|
121
|
+
const baseSha256 = atlas.sha256 === undefined
|
|
122
|
+
? undefined
|
|
123
|
+
: validateSha256(atlas.sha256, "atlas.sha256", issues)
|
|
124
|
+
? atlas.sha256
|
|
125
|
+
: undefined;
|
|
126
|
+
if (atlas.variants === undefined && atlas.sha256 === undefined) {
|
|
127
|
+
validateSha256(undefined, "atlas.sha256", issues);
|
|
128
|
+
}
|
|
129
|
+
const baseDensity = [1, 2, 4].includes(atlas.density)
|
|
130
|
+
? atlas.density
|
|
131
|
+
: 1;
|
|
132
|
+
const logicalWidth = Number.isInteger(atlas.logicalWidth)
|
|
133
|
+
? atlas.logicalWidth
|
|
134
|
+
: cellWidth / baseDensity;
|
|
135
|
+
const logicalHeight = Number.isInteger(atlas.logicalHeight)
|
|
136
|
+
? atlas.logicalHeight
|
|
137
|
+
: cellHeight / baseDensity;
|
|
138
|
+
const selectedDensity = variants.length
|
|
139
|
+
? selectNativeDensity(variants.map((item) => item.density), preferredDensity ?? baseDensity)
|
|
140
|
+
: baseDensity;
|
|
141
|
+
const selectedVariant = variants.find((item) => item.density === selectedDensity);
|
|
142
|
+
const selectedSha256 = selectedVariant?.sha256 ?? baseSha256;
|
|
143
|
+
const selectedSrc = selectedVariant?.src ?? atlas.src;
|
|
144
|
+
const selectedCellWidth = selectedVariant?.cellWidth ?? cellWidth;
|
|
145
|
+
const selectedCellHeight = selectedVariant?.cellHeight ?? cellHeight;
|
|
146
|
+
if (!Number.isInteger(logicalWidth) ||
|
|
147
|
+
!Number.isInteger(logicalHeight) ||
|
|
148
|
+
selectedCellWidth !== logicalWidth * selectedDensity ||
|
|
149
|
+
selectedCellHeight !== logicalHeight * selectedDensity) {
|
|
150
|
+
addPackIssue(issues, "atlas.logical-cell", "atlas candidates must preserve integer logical cells");
|
|
151
|
+
}
|
|
152
|
+
const cellCount = columns * rows;
|
|
153
|
+
if (columns * selectedCellWidth > 4096 || rows * selectedCellHeight > 4096) {
|
|
154
|
+
addPackIssue(issues, "atlas.size", "normalized atlas exceeds 4096x4096");
|
|
155
|
+
}
|
|
156
|
+
const states = validateStateMap(input.states, cellCount, issues);
|
|
157
|
+
if (!Number.isInteger(input.defaultScale) ||
|
|
158
|
+
input.defaultScale < 1 ||
|
|
159
|
+
input.defaultScale > 4) {
|
|
160
|
+
addPackIssue(issues, "normalized.defaultScale", "defaultScale must be integer 1-4");
|
|
161
|
+
}
|
|
162
|
+
const directionalStates = {};
|
|
163
|
+
if (input.directionalStates !== undefined) {
|
|
164
|
+
if (!object(input.directionalStates))
|
|
165
|
+
addPackIssue(issues, "directionalStates", "directionalStates must be an object");
|
|
166
|
+
else {
|
|
167
|
+
closed(input.directionalStates, NATIVE_DIRECTIONS, "directionalStates", issues);
|
|
168
|
+
for (const direction of NATIVE_DIRECTIONS) {
|
|
169
|
+
const target = input.directionalStates[direction];
|
|
170
|
+
if (target !== undefined) {
|
|
171
|
+
if (typeof target !== "string" || !states[target]) {
|
|
172
|
+
addPackIssue(issues, `directionalStates.${direction}`, `directionalStates.${direction} must name a state`);
|
|
173
|
+
}
|
|
174
|
+
else
|
|
175
|
+
directionalStates[direction] = target;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const reactionStates = {};
|
|
181
|
+
if (input.reactionStates !== undefined) {
|
|
182
|
+
if (!object(input.reactionStates) ||
|
|
183
|
+
Object.keys(input.reactionStates).length > 32) {
|
|
184
|
+
addPackIssue(issues, "reactionStates.limit", "reactionStates allows up to 32 mappings");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
for (const [semantic, target] of Object.entries(input.reactionStates)) {
|
|
188
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/.test(semantic) ||
|
|
189
|
+
typeof target !== "string" ||
|
|
190
|
+
!states[target]) {
|
|
191
|
+
addPackIssue(issues, `reactionStates.${semantic}`, `reactionStates.${semantic} must name a state`);
|
|
192
|
+
}
|
|
193
|
+
else
|
|
194
|
+
reactionStates[semantic] = target;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let locomotionMotion = [];
|
|
199
|
+
if (input.locomotionMotion !== undefined) {
|
|
200
|
+
locomotionMotion = validateMotionKeyframes(input.locomotionMotion, "locomotionMotion", issues);
|
|
201
|
+
}
|
|
202
|
+
if (geometry &&
|
|
203
|
+
(geometry.width !== columns * selectedCellWidth ||
|
|
204
|
+
geometry.height !== rows * selectedCellHeight)) {
|
|
205
|
+
addPackIssue(issues, "atlas.pixels", `atlas pixels must match ${columns * selectedCellWidth}x${rows * selectedCellHeight}`);
|
|
206
|
+
}
|
|
207
|
+
if (geometry?.byteLength !== undefined &&
|
|
208
|
+
(!Number.isSafeInteger(geometry.byteLength) ||
|
|
209
|
+
geometry.byteLength < 1 ||
|
|
210
|
+
geometry.byteLength > MAX_NORMALIZED_ATLAS_BYTES)) {
|
|
211
|
+
addPackIssue(issues, "atlas.size", "normalized atlas exceeds resource limits");
|
|
212
|
+
}
|
|
213
|
+
let source;
|
|
214
|
+
if (input.source !== undefined) {
|
|
215
|
+
try {
|
|
216
|
+
const checked = validateEventPayload(input.source);
|
|
217
|
+
if (!object(checked))
|
|
218
|
+
throw new TypeError(runtimeMessage("normalized.source", "source must be a JSON object"));
|
|
219
|
+
source = Object.freeze({ ...checked });
|
|
220
|
+
}
|
|
221
|
+
catch (cause) {
|
|
222
|
+
addPackIssue(issues, "normalized.source", `source must be bounded JSON: ${String(cause)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (issues.length || !selectedSha256) {
|
|
226
|
+
throw new PackValidationError(issues.length
|
|
227
|
+
? issues
|
|
228
|
+
: [
|
|
229
|
+
packIssue("atlas.sha256", "atlas.sha256 must be 64 lowercase hexadecimal characters"),
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
name: input.name,
|
|
234
|
+
displayName: text(input.displayName)
|
|
235
|
+
? input.displayName
|
|
236
|
+
: input.name,
|
|
237
|
+
version: input.version,
|
|
238
|
+
license: input.license,
|
|
239
|
+
atlas: {
|
|
240
|
+
src: selectedSrc,
|
|
241
|
+
sha256: selectedSha256,
|
|
242
|
+
columns,
|
|
243
|
+
rows,
|
|
244
|
+
cellWidth: selectedCellWidth,
|
|
245
|
+
cellHeight: selectedCellHeight,
|
|
246
|
+
logicalWidth,
|
|
247
|
+
logicalHeight,
|
|
248
|
+
density: selectedDensity,
|
|
249
|
+
...(typeof atlas.lineage === "string" ? { lineage: atlas.lineage } : {}),
|
|
250
|
+
...(variants.length ? { variants } : {}),
|
|
251
|
+
},
|
|
252
|
+
states,
|
|
253
|
+
defaultScale: input.defaultScale,
|
|
254
|
+
...(Object.keys(directionalStates).length ? { directionalStates } : {}),
|
|
255
|
+
...(locomotionMotion.length ? { locomotionMotion } : {}),
|
|
256
|
+
...(Object.keys(reactionStates).length ? { reactionStates } : {}),
|
|
257
|
+
...(source ? { source } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
export function resolveCapabilityName(requested, pack) {
|
|
261
|
+
if (!requested)
|
|
262
|
+
return undefined;
|
|
263
|
+
return pack.reactionStates && Object.hasOwn(pack.reactionStates, requested)
|
|
264
|
+
? pack.reactionStates[requested]
|
|
265
|
+
: requested;
|
|
266
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type PlanCompileContext } from "./plan-compiler.js";
|
|
2
|
+
import type { BehaviorRequest, OverrideCompletionReason, OverrideHandle, OverrideInput, PlanBrowserEvent, PlanEventSource } from "./types.js";
|
|
3
|
+
interface OverrideWorld {
|
|
4
|
+
pointer?: {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
8
|
+
position?: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Instance-owned temporary channel controller. It never mutates the Plan. */
|
|
14
|
+
export declare class OverrideManager {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(context: PlanCompileContext, now: () => number, diagnostic?: (message: string) => void);
|
|
17
|
+
add(input: OverrideInput): OverrideHandle;
|
|
18
|
+
reject(reason?: OverrideCompletionReason): OverrideHandle;
|
|
19
|
+
browserCompletionFor(id: string): PlanBrowserEvent | undefined;
|
|
20
|
+
/** Complete matching waits but never consume the event from the Plan stream. */
|
|
21
|
+
observeEvent(name: string, source?: PlanEventSource): false;
|
|
22
|
+
compose(base: Readonly<BehaviorRequest>, world: Readonly<OverrideWorld>): BehaviorRequest;
|
|
23
|
+
nextWakeAt(): number | undefined;
|
|
24
|
+
rebase(delta: number): void;
|
|
25
|
+
clear(reason: OverrideCompletionReason): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function rejectedOverrideHandle(id: string, reason: OverrideCompletionReason): OverrideHandle;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=overrides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../src/overrides.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,aAAa,EAIb,gBAAgB,EAChB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAiBpB,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC;AAED,8EAA8E;AAC9E,qBAAa,eAAe;;IAO1B,YACE,OAAO,EAAE,kBAAkB,EAC3B,GAAG,EAAE,MAAM,MAAM,EACjB,UAAU,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,EAKjD;IAED,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAyDxC;IAED,MAAM,CAAC,MAAM,GAAE,wBAAqC,GAAG,cAAc,CAIpE;IAED,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAK7D;IAED,gFAAgF;IAChF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,eAA+B,GAAG,KAAK,CAWzE;IAED,OAAO,CACL,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,EAC/B,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,GAC7B,eAAe,CAmDjB;IAED,UAAU,IAAI,MAAM,GAAG,SAAS,CAQ/B;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK1B;IAED,KAAK,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,CAE5C;CA+CF;AAMD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,wBAAwB,GAC/B,cAAc,CAOhB"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { EVENT_NAME_PATTERN } from "./contracts.js";
|
|
2
|
+
import { browserCompletionEvent, isContinuousBrowserEvent } from "./events.js";
|
|
3
|
+
import { DEFAULT_PERSISTENT_REQUEST_LIMIT, DEFAULT_SPEED } from "./defaults.js";
|
|
4
|
+
import { compileOverrideEffect, } from "./plan-compiler.js";
|
|
5
|
+
import { snapshotOwnData } from "./own-data.js";
|
|
6
|
+
import { runtimeMessage } from "./runtime-diagnostics.js";
|
|
7
|
+
const OVERRIDE_LIMIT = 8;
|
|
8
|
+
const MAX_LIFETIME = DEFAULT_PERSISTENT_REQUEST_LIMIT;
|
|
9
|
+
/** Instance-owned temporary channel controller. It never mutates the Plan. */
|
|
10
|
+
export class OverrideManager {
|
|
11
|
+
#context;
|
|
12
|
+
#now;
|
|
13
|
+
#diagnostic;
|
|
14
|
+
#active = [];
|
|
15
|
+
#nextId = 1;
|
|
16
|
+
constructor(context, now, diagnostic = () => { }) {
|
|
17
|
+
this.#context = context;
|
|
18
|
+
this.#now = now;
|
|
19
|
+
this.#diagnostic = diagnostic;
|
|
20
|
+
}
|
|
21
|
+
add(input) {
|
|
22
|
+
const item = this.#create();
|
|
23
|
+
let checked;
|
|
24
|
+
let effect;
|
|
25
|
+
try {
|
|
26
|
+
checked = validateOverride(input);
|
|
27
|
+
effect = compileOverrideEffect(checked.effect, this.#context);
|
|
28
|
+
}
|
|
29
|
+
catch (cause) {
|
|
30
|
+
this.#diagnostic(runtimeMessage("override.invalid", () => `Override ${item.id} rejected: ${errorMessage(cause)}`));
|
|
31
|
+
item.finish("rejected");
|
|
32
|
+
return item.handle;
|
|
33
|
+
}
|
|
34
|
+
const conflicts = this.#active.filter((active) => active.effect.channels.some((channel) => effect.channels.includes(channel)));
|
|
35
|
+
if (conflicts.length && checked.mode !== "replace") {
|
|
36
|
+
this.#diagnostic(runtimeMessage("override.conflict", () => `Override ${item.id} conflict on ${conflictingChannels(conflicts, effect).join(", ")}`));
|
|
37
|
+
item.finish("conflict");
|
|
38
|
+
return item.handle;
|
|
39
|
+
}
|
|
40
|
+
for (const conflict of conflicts)
|
|
41
|
+
this.#finish(conflict, "replaced");
|
|
42
|
+
if (this.#active.length >= OVERRIDE_LIMIT) {
|
|
43
|
+
this.#diagnostic(runtimeMessage("override.limit", () => `Override ${item.id} rejected at the ${OVERRIDE_LIMIT}-override limit`));
|
|
44
|
+
item.finish("overflow");
|
|
45
|
+
return item.handle;
|
|
46
|
+
}
|
|
47
|
+
const startedAt = this.#now();
|
|
48
|
+
Object.assign(item, {
|
|
49
|
+
input: checked,
|
|
50
|
+
effect,
|
|
51
|
+
startedAt,
|
|
52
|
+
deadline: startedAt + lifetimeMs(checked),
|
|
53
|
+
status: "active",
|
|
54
|
+
});
|
|
55
|
+
this.#active.push(item);
|
|
56
|
+
return item.handle;
|
|
57
|
+
}
|
|
58
|
+
reject(reason = "rejected") {
|
|
59
|
+
const item = this.#create();
|
|
60
|
+
item.finish(reason);
|
|
61
|
+
return item.handle;
|
|
62
|
+
}
|
|
63
|
+
browserCompletionFor(id) {
|
|
64
|
+
const until = this.#active.find((item) => item.id === id)?.input.until;
|
|
65
|
+
return until?.type === "event"
|
|
66
|
+
? browserCompletionEvent(until.name)
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Complete matching waits but never consume the event from the Plan stream. */
|
|
70
|
+
observeEvent(name, source = "application") {
|
|
71
|
+
for (const item of [...this.#active]) {
|
|
72
|
+
if (item.input.until?.type === "event" &&
|
|
73
|
+
item.input.until.name === name &&
|
|
74
|
+
completionSource(item.input.until.name) === source) {
|
|
75
|
+
this.#finish(item, "event");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
compose(base, world) {
|
|
81
|
+
const now = this.#now();
|
|
82
|
+
for (const item of [...this.#active]) {
|
|
83
|
+
if (now >= item.deadline)
|
|
84
|
+
this.#finish(item, "timeout");
|
|
85
|
+
}
|
|
86
|
+
const output = { ...base };
|
|
87
|
+
const surfaces = new Map();
|
|
88
|
+
for (const surface of base.surfaces ?? []) {
|
|
89
|
+
surfaces.set(surface.channel, surface);
|
|
90
|
+
}
|
|
91
|
+
for (const item of this.#active) {
|
|
92
|
+
const state = item.effect.state;
|
|
93
|
+
if (state?.kind === "state") {
|
|
94
|
+
output.state = state.name;
|
|
95
|
+
delete output.capability;
|
|
96
|
+
}
|
|
97
|
+
else if (state?.kind === "capability") {
|
|
98
|
+
output.capability = state.name;
|
|
99
|
+
delete output.state;
|
|
100
|
+
}
|
|
101
|
+
const motion = item.effect.motion;
|
|
102
|
+
if (motion && world.pointer && world.position) {
|
|
103
|
+
const dx = world.pointer.x - world.position.x;
|
|
104
|
+
const dy = world.pointer.y - world.position.y;
|
|
105
|
+
const distance = Math.hypot(dx, dy);
|
|
106
|
+
const arrivalRadius = motion.arrivalRadius ?? 0;
|
|
107
|
+
if (distance > arrivalRadius) {
|
|
108
|
+
output.motion = {
|
|
109
|
+
x: dx / distance,
|
|
110
|
+
y: dy / distance,
|
|
111
|
+
speed: motion.speed ?? DEFAULT_SPEED,
|
|
112
|
+
maxDistance: distance - arrivalRadius,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
delete output.motion;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const surface of item.effect.surfaces) {
|
|
120
|
+
surfaces.set(surface.channel, Object.freeze({
|
|
121
|
+
id: surface.id,
|
|
122
|
+
channel: surface.channel,
|
|
123
|
+
contentId: surface.contentId,
|
|
124
|
+
...(surface.data === undefined ? {} : { data: surface.data }),
|
|
125
|
+
key: item.id,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
output.surfaces = Object.freeze([...surfaces.values()]);
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
nextWakeAt() {
|
|
133
|
+
return this.#active.reduce((earliest, item) => earliest === undefined
|
|
134
|
+
? item.deadline
|
|
135
|
+
: Math.min(earliest, item.deadline), undefined);
|
|
136
|
+
}
|
|
137
|
+
rebase(delta) {
|
|
138
|
+
for (const item of this.#active) {
|
|
139
|
+
item.startedAt += delta;
|
|
140
|
+
item.deadline += delta;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
clear(reason) {
|
|
144
|
+
for (const item of [...this.#active])
|
|
145
|
+
this.#finish(item, reason);
|
|
146
|
+
}
|
|
147
|
+
#create() {
|
|
148
|
+
const id = `override:${this.#nextId++}`;
|
|
149
|
+
let resolve;
|
|
150
|
+
let done = false;
|
|
151
|
+
const finished = new Promise((accept) => {
|
|
152
|
+
resolve = accept;
|
|
153
|
+
});
|
|
154
|
+
const item = {};
|
|
155
|
+
const finish = (reason) => {
|
|
156
|
+
if (done)
|
|
157
|
+
return;
|
|
158
|
+
done = true;
|
|
159
|
+
item.status = rejectedReason(reason) ? "rejected" : "finished";
|
|
160
|
+
resolve({ id, reason });
|
|
161
|
+
};
|
|
162
|
+
const handle = Object.freeze({
|
|
163
|
+
id,
|
|
164
|
+
get status() {
|
|
165
|
+
return item.status;
|
|
166
|
+
},
|
|
167
|
+
finished,
|
|
168
|
+
cancel: () => {
|
|
169
|
+
if (item.status === "active")
|
|
170
|
+
this.#finish(item, "cancelled");
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
Object.assign(item, {
|
|
174
|
+
id,
|
|
175
|
+
effect: Object.freeze({ channels: [], surfaces: [] }),
|
|
176
|
+
input: Object.freeze({
|
|
177
|
+
effect: Object.freeze({ channels: [] }),
|
|
178
|
+
until: Object.freeze({ type: "manual" }),
|
|
179
|
+
}),
|
|
180
|
+
startedAt: 0,
|
|
181
|
+
deadline: 0,
|
|
182
|
+
status: "rejected",
|
|
183
|
+
handle,
|
|
184
|
+
finish,
|
|
185
|
+
});
|
|
186
|
+
return item;
|
|
187
|
+
}
|
|
188
|
+
#finish(item, reason) {
|
|
189
|
+
const index = this.#active.indexOf(item);
|
|
190
|
+
if (index >= 0)
|
|
191
|
+
this.#active.splice(index, 1);
|
|
192
|
+
item.finish(reason);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function completionSource(name) {
|
|
196
|
+
return browserCompletionEvent(name) ? "browser" : "application";
|
|
197
|
+
}
|
|
198
|
+
export function rejectedOverrideHandle(id, reason) {
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
id,
|
|
201
|
+
status: "rejected",
|
|
202
|
+
finished: Promise.resolve({ id, reason }),
|
|
203
|
+
cancel: () => { },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function validateOverride(input) {
|
|
207
|
+
const value = snapshotOwnData(input);
|
|
208
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
209
|
+
throw new TypeError(runtimeMessage("override.object", "override must be an object"));
|
|
210
|
+
}
|
|
211
|
+
closed(value, ["effect", "until", "mode"], "override");
|
|
212
|
+
if (!value.effect)
|
|
213
|
+
throw new TypeError(runtimeMessage("override.effect", "override.effect is required"));
|
|
214
|
+
if (value.mode !== undefined &&
|
|
215
|
+
value.mode !== "reject" &&
|
|
216
|
+
value.mode !== "replace") {
|
|
217
|
+
throw new TypeError(runtimeMessage("override.mode", "override.mode must be reject or replace"));
|
|
218
|
+
}
|
|
219
|
+
const until = value.until === undefined
|
|
220
|
+
? Object.freeze({ type: "manual" })
|
|
221
|
+
: value.until;
|
|
222
|
+
if (!until || typeof until !== "object" || Array.isArray(until)) {
|
|
223
|
+
throw new TypeError(runtimeMessage("override.until", "override.until must be an object"));
|
|
224
|
+
}
|
|
225
|
+
if (until.type === "duration") {
|
|
226
|
+
closed(until, ["type", "ms"], "override.until");
|
|
227
|
+
boundedLifetime(until.ms, "override.until.ms");
|
|
228
|
+
}
|
|
229
|
+
else if (until.type === "event") {
|
|
230
|
+
closed(until, ["type", "name", "timeout"], "override.until");
|
|
231
|
+
if (typeof until.name !== "string" ||
|
|
232
|
+
!EVENT_NAME_PATTERN.test(until.name)) {
|
|
233
|
+
throw new TypeError(runtimeMessage("override.until.name", "override.until.name is invalid"));
|
|
234
|
+
}
|
|
235
|
+
if (isContinuousBrowserEvent(until.name)) {
|
|
236
|
+
throw new TypeError(runtimeMessage("override.until.name", "continuous browser Events cannot be completion Events"));
|
|
237
|
+
}
|
|
238
|
+
if (until.timeout !== undefined) {
|
|
239
|
+
boundedLifetime(until.timeout, "override.until.timeout");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (until.type === "manual") {
|
|
243
|
+
closed(until, ["type", "maxMs"], "override.until");
|
|
244
|
+
if (until.maxMs !== undefined) {
|
|
245
|
+
boundedLifetime(until.maxMs, "override.until.maxMs");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
throw new TypeError(runtimeMessage("override.until.type", "override.until.type is invalid"));
|
|
250
|
+
}
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
effect: value.effect,
|
|
253
|
+
until,
|
|
254
|
+
...(value.mode === undefined ? {} : { mode: value.mode }),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function lifetimeMs(input) {
|
|
258
|
+
const until = input.until;
|
|
259
|
+
if (until?.type === "duration")
|
|
260
|
+
return until.ms;
|
|
261
|
+
if (until?.type === "event" && until.timeout !== undefined)
|
|
262
|
+
return until.timeout;
|
|
263
|
+
if (until?.type === "manual" && until.maxMs !== undefined)
|
|
264
|
+
return until.maxMs;
|
|
265
|
+
return MAX_LIFETIME;
|
|
266
|
+
}
|
|
267
|
+
function boundedLifetime(value, path) {
|
|
268
|
+
if (typeof value !== "number" ||
|
|
269
|
+
!Number.isFinite(value) ||
|
|
270
|
+
value < 1 ||
|
|
271
|
+
value > MAX_LIFETIME) {
|
|
272
|
+
throw new TypeError(runtimeMessage("override.lifetime", () => `${path} must be 1-${MAX_LIFETIME}`));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function closed(value, allowed, path) {
|
|
276
|
+
for (const key of Object.keys(value)) {
|
|
277
|
+
if (!allowed.includes(key))
|
|
278
|
+
throw new TypeError(runtimeMessage("override.unknown-field", () => `${path}.${key} is unknown`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function conflictingChannels(active, effect) {
|
|
282
|
+
const channels = new Set();
|
|
283
|
+
for (const item of active) {
|
|
284
|
+
for (const channel of item.effect.channels) {
|
|
285
|
+
if (effect.channels.includes(channel))
|
|
286
|
+
channels.add(channel);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return [...channels];
|
|
290
|
+
}
|
|
291
|
+
function rejectedReason(reason) {
|
|
292
|
+
return (reason === "rejected" || reason === "conflict" || reason === "overflow");
|
|
293
|
+
}
|
|
294
|
+
function errorMessage(cause) {
|
|
295
|
+
return cause instanceof Error ? cause.message : "invalid input";
|
|
296
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class OwnDataError extends TypeError {
|
|
2
|
+
readonly issueCode: string | undefined;
|
|
3
|
+
constructor(path: string, reason: string, issueCode?: string);
|
|
4
|
+
}
|
|
5
|
+
export interface OwnDataSnapshotPolicy {
|
|
6
|
+
readonly json?: boolean;
|
|
7
|
+
readonly key?: (value: string) => boolean;
|
|
8
|
+
readonly maxArrayLength?: number;
|
|
9
|
+
readonly maxDepth?: number;
|
|
10
|
+
readonly maxObjectKeys?: number;
|
|
11
|
+
readonly maxStringLength?: number;
|
|
12
|
+
readonly maxValues?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function snapshotOwnData<T>(input: T, policy?: Readonly<OwnDataSnapshotPolicy>): T;
|
|
15
|
+
/** Shared package-family boundary for data-only Pack and adapter input. */
|
|
16
|
+
export declare function snapshotPackData<T>(input: T): T;
|
|
17
|
+
export declare function snapshotConfiguration<T>(input: T): T;
|
|
18
|
+
//# sourceMappingURL=own-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"own-data.d.ts","sourceRoot":"","sources":["../src/own-data.ts"],"names":[],"mappings":"AAEA,qBAAa,YAAa,SAAQ,SAAS;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAEvC,YAAY,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAI3D;CACF;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AA8OD,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,CAAC,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACvC,CAAC,CAEH;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEpD"}
|