@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.
Files changed (120) hide show
  1. package/AUTHORS +8 -0
  2. package/LICENSE +202 -0
  3. package/LICENSING.md +20 -0
  4. package/NOTICE +6 -0
  5. package/README.md +294 -0
  6. package/dist/configuration-validation.d.ts +19 -0
  7. package/dist/configuration-validation.d.ts.map +1 -0
  8. package/dist/configuration-validation.js +298 -0
  9. package/dist/content.d.ts +44 -0
  10. package/dist/content.d.ts.map +1 -0
  11. package/dist/content.js +738 -0
  12. package/dist/contracts.d.ts +10 -0
  13. package/dist/contracts.d.ts.map +1 -0
  14. package/dist/contracts.js +106 -0
  15. package/dist/defaults.d.ts +7 -0
  16. package/dist/defaults.d.ts.map +1 -0
  17. package/dist/defaults.js +6 -0
  18. package/dist/diagnostics.d.ts +25 -0
  19. package/dist/diagnostics.d.ts.map +1 -0
  20. package/dist/diagnostics.js +122 -0
  21. package/dist/direction.d.ts +3 -0
  22. package/dist/direction.d.ts.map +1 -0
  23. package/dist/direction.js +6 -0
  24. package/dist/errors.d.ts +15 -0
  25. package/dist/errors.d.ts.map +1 -0
  26. package/dist/errors.js +53 -0
  27. package/dist/events.d.ts +5 -0
  28. package/dist/events.d.ts.map +1 -0
  29. package/dist/events.js +60 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +3 -0
  33. package/dist/input.d.ts +31 -0
  34. package/dist/input.d.ts.map +1 -0
  35. package/dist/input.js +215 -0
  36. package/dist/json.d.ts +3 -0
  37. package/dist/json.d.ts.map +1 -0
  38. package/dist/json.js +109 -0
  39. package/dist/loader.d.ts +33 -0
  40. package/dist/loader.d.ts.map +1 -0
  41. package/dist/loader.js +477 -0
  42. package/dist/locomotion.d.ts +8 -0
  43. package/dist/locomotion.d.ts.map +1 -0
  44. package/dist/locomotion.js +29 -0
  45. package/dist/normalized.d.ts +5 -0
  46. package/dist/normalized.d.ts.map +1 -0
  47. package/dist/normalized.js +266 -0
  48. package/dist/overrides.d.ts +29 -0
  49. package/dist/overrides.d.ts.map +1 -0
  50. package/dist/overrides.js +296 -0
  51. package/dist/own-data.d.ts +18 -0
  52. package/dist/own-data.d.ts.map +1 -0
  53. package/dist/own-data.js +205 -0
  54. package/dist/pack-api.d.ts +9 -0
  55. package/dist/pack-api.d.ts.map +1 -0
  56. package/dist/pack-api.js +6 -0
  57. package/dist/pack-shared.d.ts +28 -0
  58. package/dist/pack-shared.d.ts.map +1 -0
  59. package/dist/pack-shared.js +408 -0
  60. package/dist/pack.d.ts +6 -0
  61. package/dist/pack.d.ts.map +1 -0
  62. package/dist/pack.js +316 -0
  63. package/dist/peekling.css +1 -0
  64. package/dist/peekling.css.sri +1 -0
  65. package/dist/peekling.js +7469 -0
  66. package/dist/peekling.js.sri +1 -0
  67. package/dist/peekling.min.js +2 -0
  68. package/dist/peekling.min.js.sri +1 -0
  69. package/dist/plan-compiler.d.ts +82 -0
  70. package/dist/plan-compiler.d.ts.map +1 -0
  71. package/dist/plan-compiler.js +724 -0
  72. package/dist/plan.d.ts +21 -0
  73. package/dist/plan.d.ts.map +1 -0
  74. package/dist/plan.js +287 -0
  75. package/dist/preflight-api.d.ts +3 -0
  76. package/dist/preflight-api.d.ts.map +1 -0
  77. package/dist/preflight-api.js +1 -0
  78. package/dist/preflight.d.ts +22 -0
  79. package/dist/preflight.d.ts.map +1 -0
  80. package/dist/preflight.js +158 -0
  81. package/dist/registry.d.ts +9 -0
  82. package/dist/registry.d.ts.map +1 -0
  83. package/dist/registry.js +24 -0
  84. package/dist/renderer.d.ts +16 -0
  85. package/dist/renderer.d.ts.map +1 -0
  86. package/dist/renderer.js +176 -0
  87. package/dist/resolver.d.ts +3 -0
  88. package/dist/resolver.d.ts.map +1 -0
  89. package/dist/resolver.js +30 -0
  90. package/dist/runtime-diagnostics.d.ts +3 -0
  91. package/dist/runtime-diagnostics.d.ts.map +1 -0
  92. package/dist/runtime-diagnostics.js +5 -0
  93. package/dist/runtime-validation.d.ts +6 -0
  94. package/dist/runtime-validation.d.ts.map +1 -0
  95. package/dist/runtime-validation.js +104 -0
  96. package/dist/runtime.d.ts +87 -0
  97. package/dist/runtime.d.ts.map +1 -0
  98. package/dist/runtime.js +1015 -0
  99. package/dist/sections.d.ts +15 -0
  100. package/dist/sections.d.ts.map +1 -0
  101. package/dist/sections.js +221 -0
  102. package/dist/styles.d.ts +18 -0
  103. package/dist/styles.d.ts.map +1 -0
  104. package/dist/styles.js +132 -0
  105. package/dist/tooling-preflight.d.ts +23 -0
  106. package/dist/tooling-preflight.d.ts.map +1 -0
  107. package/dist/tooling-preflight.js +207 -0
  108. package/dist/tooling-validation.d.ts +8 -0
  109. package/dist/tooling-validation.d.ts.map +1 -0
  110. package/dist/tooling-validation.js +94 -0
  111. package/dist/types.d.ts +403 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +29 -0
  114. package/dist/visibility.d.ts +12 -0
  115. package/dist/visibility.d.ts.map +1 -0
  116. package/dist/visibility.js +333 -0
  117. package/dist/web-component.d.ts +31 -0
  118. package/dist/web-component.d.ts.map +1 -0
  119. package/dist/web-component.js +236 -0
  120. package/package.json +74 -0
package/dist/plan.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { type CompiledPlan, type PlanEventEvaluation } from "./plan-compiler.js";
2
+ import { type BehaviorRequest, type Plan, type World } from "./types.js";
3
+ export declare function compiledPlanSectionRequirements(plan: CompiledPlan): {
4
+ selectors: readonly string[];
5
+ thresholds: readonly number[];
6
+ };
7
+ /** The engine-owned default Plan policy. It uses the public Plan shape. */
8
+ export declare function createDefaultPlan(locomotion: boolean): Plan;
9
+ /** Instance-owned evaluator for one immutable compiled Plan. */
10
+ export declare class PlanRuntime {
11
+ #private;
12
+ constructor(plan: CompiledPlan);
13
+ get plan(): CompiledPlan;
14
+ get lastEventStatus(): PlanEventEvaluation["status"] | undefined;
15
+ get lastEventRejections(): PlanEventEvaluation["rejectedSurfaces"];
16
+ evaluate(world: Readonly<World>): BehaviorRequest;
17
+ reset(): void;
18
+ nextWakeAt(): number | undefined;
19
+ rebase(delta: number): void;
20
+ }
21
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,YAAY,EAGjB,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,IAAI,EAGT,KAAK,KAAK,EACX,MAAM,YAAY,CAAC;AAEpB,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG;IACnE,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAgBA;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAsB3D;AAaD,gEAAgE;AAChE,qBAAa,WAAW;;IAatB,YAAY,IAAI,EAAE,YAAY,EAE7B;IAED,IAAI,IAAI,IAAI,YAAY,CAEvB;IAED,IAAI,eAAe,IAAI,mBAAmB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAE/D;IAED,IAAI,mBAAmB,IAAI,mBAAmB,CAAC,kBAAkB,CAAC,CAEjE;IAED,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,eAAe,CAwIhD;IAED,KAAK,IAAI,IAAI,CAQZ;IAED,UAAU,IAAI,MAAM,GAAG,SAAS,CAU/B;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM1B;CAmFF"}
package/dist/plan.js ADDED
@@ -0,0 +1,287 @@
1
+ import { DEFAULT_PERSISTENT_REQUEST_LIMIT, DEFAULT_SPEED } from "./defaults.js";
2
+ import { createPlanEventState, evaluatePlanEvent, selectCompiledEffectChannels, } from "./plan-compiler.js";
3
+ import { browserCompletionEvent } from "./events.js";
4
+ import {} from "./types.js";
5
+ export function compiledPlanSectionRequirements(plan) {
6
+ const selectors = new Set();
7
+ const thresholds = new Set([0, 1]);
8
+ for (const rule of plan.rules) {
9
+ if (rule.when.source === "browser" &&
10
+ rule.when.event === "section.visibility") {
11
+ selectors.add(rule.when.selector);
12
+ thresholds.add(rule.when.threshold ?? 0.25);
13
+ }
14
+ }
15
+ return {
16
+ selectors: [...selectors],
17
+ thresholds: [...thresholds].sort((left, right) => left - right),
18
+ };
19
+ }
20
+ /** The engine-owned default Plan policy. It uses the public Plan shape. */
21
+ export function createDefaultPlan(locomotion) {
22
+ return {
23
+ baseline: {
24
+ channels: ["state"],
25
+ state: { state: "idle" },
26
+ },
27
+ ...(locomotion
28
+ ? {
29
+ rules: [
30
+ {
31
+ id: "follow-pointer",
32
+ when: { source: "browser", event: "pointer.move" },
33
+ effect: {
34
+ channels: ["motion", "state"],
35
+ motion: { type: "follow-pointer", speed: DEFAULT_SPEED },
36
+ state: { capability: "locomotion" },
37
+ },
38
+ },
39
+ ],
40
+ }
41
+ : {}),
42
+ };
43
+ }
44
+ /** Instance-owned evaluator for one immutable compiled Plan. */
45
+ export class PlanRuntime {
46
+ #plan;
47
+ #eventState = createPlanEventState();
48
+ #activeChannels = new Map();
49
+ #activeSurfaces = new Map();
50
+ #activeInterrupts = [];
51
+ #surfaceVersion = 0;
52
+ #lastEventStatus;
53
+ #lastEventRejections = [];
54
+ constructor(plan) {
55
+ this.#plan = plan;
56
+ }
57
+ get plan() {
58
+ return this.#plan;
59
+ }
60
+ get lastEventStatus() {
61
+ return this.#lastEventStatus;
62
+ }
63
+ get lastEventRejections() {
64
+ return this.#lastEventRejections;
65
+ }
66
+ evaluate(world) {
67
+ this.#lastEventStatus = undefined;
68
+ this.#lastEventRejections = [];
69
+ const reaction = world.reaction;
70
+ this.#expireInterrupts(world.now);
71
+ if (reaction) {
72
+ const selected = evaluatePlanEvent(this.#plan, this.#eventState, {
73
+ source: reaction.source,
74
+ name: reaction.name,
75
+ ...(reaction.payload === undefined
76
+ ? {}
77
+ : { payload: reaction.payload }),
78
+ });
79
+ this.#lastEventStatus = selected.status;
80
+ this.#lastEventRejections = selected.rejectedSurfaces;
81
+ if (selected.status !== "invalid" &&
82
+ selected.status !== "stale" &&
83
+ selected.status !== "closed") {
84
+ this.#expireInterrupts(world.now, reaction.source, reaction.name);
85
+ }
86
+ if (selected.status === "matched") {
87
+ const claimed = new Set();
88
+ selected.effects.forEach((effect, index) => {
89
+ const available = effect.channels.filter((channel) => !claimed.has(channel));
90
+ if (!available.length)
91
+ return;
92
+ for (const channel of available)
93
+ claimed.add(channel);
94
+ if (effect.until) {
95
+ this.#startInterrupt(selectCompiledEffectChannels(effect, available), world.now);
96
+ }
97
+ else {
98
+ for (const channel of available) {
99
+ if (channel === "motion" || channel === "state") {
100
+ this.#activeChannels.set(channel, [
101
+ selected.rules[index].effect,
102
+ effect,
103
+ ]);
104
+ }
105
+ }
106
+ for (const surface of effect.surfaces) {
107
+ if (available.includes(surface.channel)) {
108
+ this.#applySurface(surface);
109
+ }
110
+ }
111
+ }
112
+ });
113
+ }
114
+ }
115
+ const channels = new Map();
116
+ for (const interrupt of this.#activeInterrupts) {
117
+ for (const channel of interrupt.effect.channels) {
118
+ channels.set(channel, interrupt.effect);
119
+ }
120
+ }
121
+ for (const rule of this.#plan.rules) {
122
+ const continuous = this.#isContinuous(rule, world);
123
+ for (const channel of ["motion", "state"]) {
124
+ if (channels.has(channel))
125
+ continue;
126
+ const latched = this.#activeChannels.get(channel);
127
+ const effect = continuous && rule.effect.channels.includes(channel)
128
+ ? rule.effect
129
+ : latched?.[0] === rule.effect
130
+ ? latched[1]
131
+ : undefined;
132
+ if (effect)
133
+ channels.set(channel, effect);
134
+ }
135
+ }
136
+ for (const channel of this.#plan.baseline.channels) {
137
+ if (!channels.has(channel))
138
+ channels.set(channel, this.#plan.baseline);
139
+ }
140
+ const output = {};
141
+ const state = channels.get("state")?.state;
142
+ if (state?.kind === "state")
143
+ output.state = state.name;
144
+ if (state?.kind === "capability") {
145
+ output.capability = state.name;
146
+ }
147
+ const motion = channels.get("motion")?.motion;
148
+ if (motion && world.pointer) {
149
+ const dx = world.pointer.x - world.position.x;
150
+ const dy = world.pointer.y - world.position.y;
151
+ const distance = Math.hypot(dx, dy);
152
+ const arrivalRadius = motion.arrivalRadius ?? 0;
153
+ if (distance > arrivalRadius) {
154
+ output.motion = {
155
+ x: dx / distance,
156
+ y: dy / distance,
157
+ speed: motion.speed ?? DEFAULT_SPEED,
158
+ maxDistance: distance - arrivalRadius,
159
+ };
160
+ }
161
+ else if (channels.get("motion") === channels.get("state")) {
162
+ delete output.capability;
163
+ const baseline = this.#baselineState();
164
+ if (!output.state && baseline)
165
+ output.state = baseline;
166
+ }
167
+ }
168
+ const selected = new Map();
169
+ for (const surface of this.#plan.baseline.surfaces) {
170
+ selected.set(surface.channel, { surface, version: 0 });
171
+ }
172
+ for (const active of this.#activeSurfaces.values()) {
173
+ selected.set(active.surface.channel, active);
174
+ }
175
+ for (const interrupt of this.#activeInterrupts) {
176
+ for (const surface of interrupt.effect.surfaces) {
177
+ selected.set(surface.channel, {
178
+ surface,
179
+ version: interrupt.version,
180
+ });
181
+ }
182
+ }
183
+ if (selected.size) {
184
+ output.surfaces = Object.freeze([...selected.values()].map(({ surface, version }) => Object.freeze({
185
+ id: surface.id,
186
+ channel: surface.channel,
187
+ contentId: surface.contentId,
188
+ ...(surface.data === undefined ? {} : { data: surface.data }),
189
+ key: `${surface.channel}:${version}`,
190
+ })));
191
+ }
192
+ if (reaction && this.#lastEventStatus === "matched") {
193
+ output.reactionId = reaction.id;
194
+ }
195
+ return output;
196
+ }
197
+ reset() {
198
+ this.#activeChannels.clear();
199
+ this.#activeSurfaces.clear();
200
+ this.#activeInterrupts = [];
201
+ this.#eventState.ordering.clear();
202
+ this.#eventState.sessions.clear();
203
+ this.#lastEventStatus = undefined;
204
+ this.#lastEventRejections = [];
205
+ }
206
+ nextWakeAt() {
207
+ return this.#activeInterrupts.reduce((earliest, interrupt) => {
208
+ if (!Number.isFinite(interrupt.deadline))
209
+ return earliest;
210
+ return earliest === undefined
211
+ ? interrupt.deadline
212
+ : Math.min(earliest, interrupt.deadline);
213
+ }, undefined);
214
+ }
215
+ rebase(delta) {
216
+ if (!delta)
217
+ return;
218
+ this.#activeInterrupts = this.#activeInterrupts.map((interrupt) => ({
219
+ ...interrupt,
220
+ deadline: interrupt.deadline + delta,
221
+ }));
222
+ }
223
+ #baselineState() {
224
+ const state = this.#plan.baseline.state;
225
+ return state?.kind === "state" ? state.name : undefined;
226
+ }
227
+ #isContinuous(rule, world) {
228
+ const condition = rule.when;
229
+ if (condition.source !== "browser")
230
+ return false;
231
+ if (condition.event === "pointer.move") {
232
+ if (!world.pointer)
233
+ return false;
234
+ const motion = rule.effect.motion;
235
+ if (!motion)
236
+ return true;
237
+ return (Math.hypot(world.pointer.x - world.position.x, world.pointer.y - world.position.y) > (motion.arrivalRadius ?? 0));
238
+ }
239
+ if (condition.event !== "section.visibility")
240
+ return false;
241
+ const section = world.sections?.[condition.selector];
242
+ const threshold = condition.threshold ?? 0.25;
243
+ return (condition.phase === "while-visible" && (section?.ratio ?? 0) >= threshold);
244
+ }
245
+ #applySurface(surface) {
246
+ const current = this.#activeSurfaces.get(surface.channel);
247
+ if (surface.disposition === "ignore")
248
+ return;
249
+ const next = surface.disposition === "update" && current
250
+ ? { ...surface, contentId: current.surface.contentId }
251
+ : surface;
252
+ this.#activeSurfaces.set(surface.channel, {
253
+ surface: Object.freeze(next),
254
+ version: ++this.#surfaceVersion,
255
+ });
256
+ }
257
+ #startInterrupt(effect, now) {
258
+ const lifetime = effect.until;
259
+ const duration = lifetime.type === "duration"
260
+ ? lifetime.ms
261
+ : (lifetime.timeout ?? DEFAULT_PERSISTENT_REQUEST_LIMIT);
262
+ const deadline = now + duration;
263
+ if (!Number.isFinite(deadline))
264
+ return;
265
+ this.#activeInterrupts = this.#activeInterrupts.filter((active) => !active.effect.channels.some((channel) => effect.channels.includes(channel)));
266
+ this.#activeInterrupts.push({
267
+ effect,
268
+ deadline,
269
+ version: ++this.#surfaceVersion,
270
+ });
271
+ }
272
+ #expireInterrupts(now, eventSource, eventName) {
273
+ this.#activeInterrupts = this.#activeInterrupts.filter((active) => {
274
+ if (now >= active.deadline)
275
+ return false;
276
+ const until = active.effect.until;
277
+ return !(eventSource !== undefined &&
278
+ eventName !== undefined &&
279
+ until.type === "event" &&
280
+ completionSource(until.name) === eventSource &&
281
+ until.name === eventName);
282
+ });
283
+ }
284
+ }
285
+ function completionSource(name) {
286
+ return browserCompletionEvent(name) ? "browser" : "application";
287
+ }
@@ -0,0 +1,3 @@
1
+ export { preflight } from "./tooling-preflight.js";
2
+ export type { PreflightDiagnostic, PreflightOptions, PreflightResult, } from "./tooling-preflight.js";
3
+ //# sourceMappingURL=preflight-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight-api.d.ts","sourceRoot":"","sources":["../src/preflight-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,GAChB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1 @@
1
+ export { preflight } from "./tooling-preflight.js";
@@ -0,0 +1,22 @@
1
+ import type { PeeklingOptions } from "./runtime.js";
2
+ export interface PreflightIssue {
3
+ readonly code: string;
4
+ readonly path: string;
5
+ readonly message: string;
6
+ readonly fix: string;
7
+ }
8
+ export interface PreflightResult {
9
+ readonly valid: boolean;
10
+ readonly errors: readonly PreflightIssue[];
11
+ readonly warnings: readonly PreflightIssue[];
12
+ }
13
+ export interface PreflightContext {
14
+ readonly document?: Document;
15
+ readonly baseUrl?: string;
16
+ readonly states?: ReadonlySet<string>;
17
+ readonly capabilities?: ReadonlySet<"locomotion">;
18
+ /** Package-internal context for an externally supplied, validated Pack. */
19
+ readonly externalPackSelection?: boolean;
20
+ }
21
+ export declare function preflight(input: PeeklingOptions, context?: PreflightContext): PreflightResult;
22
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAMpD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;IAClD,2EAA2E;IAC3E,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CA6GjB"}
@@ -0,0 +1,158 @@
1
+ import { validateConfiguration } from "./configuration-validation.js";
2
+ import { isNormalizedPack, validateNormalizedPack } from "./normalized.js";
3
+ import { OwnDataError, snapshotConfiguration } from "./own-data.js";
4
+ import { validateNativePack } from "./pack.js";
5
+ import { preflightPlan } from "./plan-compiler.js";
6
+ import { toolingDiagnostic } from "./tooling-validation.js";
7
+ import { CONTENT_REGIONS } from "./types.js";
8
+ const REMOVED_FIELDS = new Set(["run", "constructor", "behaviors"]);
9
+ export function preflight(input, context = {}) {
10
+ const errors = [];
11
+ const warnings = [];
12
+ let options;
13
+ try {
14
+ options = snapshotConfiguration(input);
15
+ }
16
+ catch (cause) {
17
+ errors.push({
18
+ code: "invalid-options",
19
+ path: "$",
20
+ message: cause instanceof OwnDataError
21
+ ? cause.message
22
+ : "Options cannot be inspected safely",
23
+ fix: "Pass own data in a local ordinary object or null-prototype object",
24
+ });
25
+ return { valid: false, errors, warnings };
26
+ }
27
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
28
+ errors.push({
29
+ code: "invalid-options",
30
+ path: "$",
31
+ message: "Options must be an object",
32
+ fix: "Pass own data in a local ordinary object or null-prototype object",
33
+ });
34
+ return { valid: false, errors, warnings };
35
+ }
36
+ for (const field of REMOVED_FIELDS) {
37
+ if (!Object.hasOwn(options, field))
38
+ continue;
39
+ const guidance = removedFieldDiagnostic(field);
40
+ errors.push({
41
+ code: "removed-api",
42
+ path: `$.${field}`,
43
+ message: guidance.message,
44
+ fix: guidance.fix,
45
+ });
46
+ }
47
+ if (options.pack !== undefined) {
48
+ try {
49
+ if (isNormalizedPack(options.pack))
50
+ validateNormalizedPack(options.pack);
51
+ else
52
+ validateNativePack(options.pack);
53
+ }
54
+ catch (cause) {
55
+ errors.push({
56
+ code: "invalid-pack",
57
+ path: "$.pack",
58
+ message: String(cause),
59
+ fix: "Pass a valid data-only Pack",
60
+ });
61
+ }
62
+ }
63
+ const reportFault = (code, path, detail) => {
64
+ if (context.externalPackSelection && code === "manifest.selection")
65
+ return;
66
+ errors.push(toolingDiagnostic(code, path, detail));
67
+ };
68
+ const checkedOptions = { ...options };
69
+ for (const field of REMOVED_FIELDS)
70
+ Reflect.deleteProperty(checkedOptions, field);
71
+ const content = validateConfiguration(checkedOptions, context.baseUrl ?? "https://peekling.invalid/", reportFault);
72
+ if (options.plan !== undefined) {
73
+ const checkedPlan = preflightPlan(options.plan, {
74
+ ...(context.states ? { states: context.states } : {}),
75
+ ...(context.capabilities ? { capabilities: context.capabilities } : {}),
76
+ contentIds: new Set(Object.keys(content)),
77
+ });
78
+ for (const issue of checkedPlan.errors) {
79
+ errors.push({
80
+ code: issue.code,
81
+ path: issue.path.replace(/^\$plan/, "$.plan"),
82
+ message: issue.message,
83
+ fix: "Fix the Plan field and referenced Pack or content ID",
84
+ });
85
+ }
86
+ inspectSelectors(options, context.document, errors, warnings);
87
+ }
88
+ for (const field of ["onDiagnostic", "logger"]) {
89
+ if (typeof options[field] === "function") {
90
+ warnings.push({
91
+ code: "unverified-callback",
92
+ path: `$.${field}`,
93
+ message: "Preflight cannot prove callback behavior",
94
+ fix: "Keep callback side effects bounded",
95
+ });
96
+ }
97
+ }
98
+ for (const [id, item] of Object.entries(content)) {
99
+ for (const region of CONTENT_REGIONS) {
100
+ const surface = item[region];
101
+ if (surface && typeof surface !== "string" && surface.mountId) {
102
+ warnings.push({
103
+ code: "unverified-host-code",
104
+ path: `$.content.${id}.${region}`,
105
+ message: "Preflight cannot prove host code safe",
106
+ fix: "Keep host code bounded",
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return { valid: errors.length === 0, errors, warnings };
112
+ }
113
+ function inspectSelectors(options, document, errors, warnings) {
114
+ if (!document || !options.plan || !Array.isArray(options.plan.rules))
115
+ return;
116
+ for (const [index, rule] of options.plan.rules.entries()) {
117
+ const when = rule?.when;
118
+ if (!when ||
119
+ when.source !== "browser" ||
120
+ when.event !== "section.visibility" ||
121
+ typeof when.selector !== "string") {
122
+ continue;
123
+ }
124
+ const path = `$.plan.rules[${index}].when.selector`;
125
+ try {
126
+ if (!document.querySelector(when.selector)) {
127
+ warnings.push({
128
+ code: "missing-selector",
129
+ path,
130
+ message: `No element currently matches ${when.selector}`,
131
+ fix: "Ensure the element exists",
132
+ });
133
+ }
134
+ }
135
+ catch {
136
+ errors.push({
137
+ code: "invalid-selector",
138
+ path,
139
+ message: `Invalid selector ${when.selector}`,
140
+ fix: "Use a valid bounded CSS selector",
141
+ });
142
+ }
143
+ }
144
+ }
145
+ function removedFieldDiagnostic(field) {
146
+ if (field === "behaviors") {
147
+ return {
148
+ message: "behaviors is not a Peekling 0.1 configuration field",
149
+ fix: "Express the behavior as Rules in one canonical Plan",
150
+ };
151
+ }
152
+ return {
153
+ message: field === "run"
154
+ ? "run is not part of the Peekling 0.1 API"
155
+ : "Direct construction is not part of the Peekling 0.1 API",
156
+ fix: "Create the instance with hatch(configuration)",
157
+ };
158
+ }
@@ -0,0 +1,9 @@
1
+ interface CharacterPackReference {
2
+ readonly url: string;
3
+ readonly sha256: string;
4
+ }
5
+ export declare function isRegisteredCharacter(character: string): boolean;
6
+ export declare function characterPackReference(character: string): CharacterPackReference;
7
+ export declare function characterManifestUrl(character: string): string;
8
+ export {};
9
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAcA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AASD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAChB,sBAAsB,CASxB;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,24 @@
1
+ import { runtimeMessage } from "./runtime-diagnostics.js";
2
+ const PEEK_REFERENCE_URL = typeof __PEEK_PACK_URL__ === "string"
3
+ ? __PEEK_PACK_URL__
4
+ : "https://cdn.jsdelivr.net/npm/@peekling/pack-peek@0.1.0/character.json";
5
+ const PEEK_REFERENCE_SHA256 = typeof __PEEK_PACK_SHA256__ === "string"
6
+ ? __PEEK_PACK_SHA256__
7
+ : "bc01840e84f72edca4b0421dca4d07623b38d971d851bfc647e1cada03e1cbcc";
8
+ const REGISTRY = {
9
+ peek: Object.freeze({
10
+ url: PEEK_REFERENCE_URL,
11
+ sha256: PEEK_REFERENCE_SHA256,
12
+ }),
13
+ };
14
+ export function isRegisteredCharacter(character) {
15
+ return Object.hasOwn(REGISTRY, character);
16
+ }
17
+ export function characterPackReference(character) {
18
+ if (!isRegisteredCharacter(character))
19
+ throw new Error(runtimeMessage("character.unknown", `Unknown Peekling character: ${character}`));
20
+ return REGISTRY[character];
21
+ }
22
+ export function characterManifestUrl(character) {
23
+ return characterPackReference(character).url;
24
+ }
@@ -0,0 +1,16 @@
1
+ import type { NormalizedPack, Point, StateDefinition } from "./types.js";
2
+ import { type RuntimeStyleAsset } from "./styles.js";
3
+ export declare function frameAt(state: StateDefinition, elapsed: number): number;
4
+ export declare function snapToDevicePixel(value: number, devicePixelRatio?: number): number;
5
+ export declare class AtlasRenderer {
6
+ #private;
7
+ readonly host: HTMLElement;
8
+ readonly ready: Promise<void>;
9
+ constructor(document: Document, pack: NormalizedPack, atlasUrl: string, scale: number, styles: RuntimeStyleAsset);
10
+ swapAtlas(pack: NormalizedPack, atlasUrl: string): void;
11
+ setHidden(hidden: boolean): void;
12
+ nextFrameIn(now: number): number | undefined;
13
+ render(stateName: string, now: number, position: Point, devicePixelRatio?: number, lift?: number): void;
14
+ destroy(): void;
15
+ }
16
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGzE,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAErB,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBvE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,SAAI,GAAG,MAAM,CAM7E;AAED,qBAAa,aAAa;;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAe9B,YACE,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,iBAAiB,EAoB1B;IAED,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGtD;IAoBD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAG/B;IAED,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuB3C;IAED,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,KAAK,EACf,gBAAgB,SAAI,EACpB,IAAI,SAAI,GACP,IAAI,CAkDN;IAED,OAAO,IAAI,IAAI,CAKd;CAkBF"}