@openelement/core 0.41.0-alpha.1

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/package.json +124 -0
  4. package/src/binding-activation.d.ts +2 -0
  5. package/src/binding-activation.js +254 -0
  6. package/src/binding-descriptor.d.ts +79 -0
  7. package/src/binding-descriptor.js +9 -0
  8. package/src/context.d.ts +32 -0
  9. package/src/context.js +76 -0
  10. package/src/csr.d.ts +13 -0
  11. package/src/csr.js +14 -0
  12. package/src/dsd-hydration-events.d.ts +2 -0
  13. package/src/dsd-hydration-events.js +13 -0
  14. package/src/dsd-hydration.d.ts +40 -0
  15. package/src/dsd-hydration.js +48 -0
  16. package/src/errors.d.ts +54 -0
  17. package/src/errors.js +113 -0
  18. package/src/event-hydration.d.ts +12 -0
  19. package/src/event-hydration.js +118 -0
  20. package/src/event-marker.d.ts +7 -0
  21. package/src/event-marker.js +54 -0
  22. package/src/html-escape.d.ts +29 -0
  23. package/src/html-escape.js +126 -0
  24. package/src/hydrate.d.ts +15 -0
  25. package/src/hydrate.js +15 -0
  26. package/src/index.d.ts +58 -0
  27. package/src/index.js +46 -0
  28. package/src/island-transform.d.ts +14 -0
  29. package/src/island-transform.js +60 -0
  30. package/src/island.d.ts +59 -0
  31. package/src/island.js +342 -0
  32. package/src/isr-runtime.d.ts +28 -0
  33. package/src/isr-runtime.js +99 -0
  34. package/src/isr.d.ts +22 -0
  35. package/src/isr.js +41 -0
  36. package/src/jsx-render-dom.d.ts +22 -0
  37. package/src/jsx-render-dom.js +376 -0
  38. package/src/jsx-runtime.d.ts +58 -0
  39. package/src/jsx-runtime.js +99 -0
  40. package/src/jsx-types.d.ts +46 -0
  41. package/src/logger.d.ts +15 -0
  42. package/src/logger.js +24 -0
  43. package/src/prop.d.ts +24 -0
  44. package/src/prop.js +160 -0
  45. package/src/registry.d.ts +17 -0
  46. package/src/registry.js +219 -0
  47. package/src/render-dsd-stream.d.ts +46 -0
  48. package/src/render-dsd-stream.js +86 -0
  49. package/src/render-dsd.d.ts +27 -0
  50. package/src/render-dsd.js +315 -0
  51. package/src/render-ir.d.ts +32 -0
  52. package/src/render-ir.js +245 -0
  53. package/src/runtime.d.ts +9 -0
  54. package/src/runtime.js +16 -0
  55. package/src/security.d.ts +1 -0
  56. package/src/security.js +40 -0
  57. package/src/signal-context.d.ts +15 -0
  58. package/src/signal-context.js +59 -0
  59. package/src/static.d.ts +35 -0
  60. package/src/static.js +34 -0
  61. package/src/style-sheet.d.ts +9 -0
  62. package/src/style-sheet.js +56 -0
  63. package/src/tag-utils.d.ts +11 -0
  64. package/src/tag-utils.js +28 -0
  65. package/src/vnode.d.ts +15 -0
  66. package/src/vnode.js +31 -0
package/src/island.js ADDED
@@ -0,0 +1,342 @@
1
+ import { ERROR_PREFIX } from '@openelement/core';
2
+ import { formatError } from './errors.js';
3
+ /**
4
+ * @openelement/core - defineIsland() wrapper
5
+ *
6
+ * v0.6.2: defineIsland() wraps any Custom Element class to provide:
7
+ * - Automatic registration via customElements.define()
8
+ * - Hydration strategy support (load, idle, visible, only)
9
+ * - __island / __tagName / __layer metadata markers
10
+ * - data-ssr-props restoration on client upgrade
11
+ * - DSD opt-out via `dsd: false` (Pure Island / Layer 3)
12
+ *
13
+ * Framework-agnostic: works with Lit, vanilla Custom Elements,
14
+ * FAST, or any Web Component library. bindSsrProps() sets props
15
+ * directly; adapters handle framework-specific update triggers.
16
+ *
17
+ * v0.29.1: defineCustomElement helper inlined from custom-element.ts.
18
+ */ import { createLogger } from './logger.js';
19
+ /** WeakSet to track elements that have already had SSR props bound (idempotent). */ const ssrPropsBoundSet = new WeakSet();
20
+ const log = createLogger('core');
21
+ /**
22
+ * SSR-safe custom element registration helper.
23
+ * v0.29.1: Merged from custom-element.ts.
24
+ */ export function defineCustomElement(tag, ctor) {
25
+ if (typeof globalThis.customElements === 'undefined') return;
26
+ if (!globalThis.customElements.get(tag)) {
27
+ globalThis.customElements.define(tag, ctor);
28
+ }
29
+ }
30
+ const VALID_STRATEGIES = new Set([
31
+ 'load',
32
+ 'idle',
33
+ 'visible',
34
+ 'only'
35
+ ]);
36
+ // Module-level store of active visibility strategy timeout IDs.
37
+ // Used for test cleanup - tests can call _clearAllVisibilityTimeouts()
38
+ // to prevent timer leaks.
39
+ const _visibilityTimeouts = new Set();
40
+ const _islandMeta = new WeakMap();
41
+ export function getIslandMeta(ctor) {
42
+ return _islandMeta.get(ctor);
43
+ }
44
+ /** Clear all active visibility strategy timeouts (for test cleanup). */ export function _clearAllVisibilityTimeouts() {
45
+ for (const id of _visibilityTimeouts){
46
+ clearTimeout(id);
47
+ }
48
+ _visibilityTimeouts.clear();
49
+ }
50
+ /**
51
+ * Get the value of the data-ssr-props attribute from a host element.
52
+ * Used to reconstruct SSR props on client upgrade.
53
+ *
54
+ * @param el - The custom element host element
55
+ * @returns Parsed props object, or null if no data-ssr-props attribute
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * connectedCallback() {
60
+ * super.connectedCallback();
61
+ * const props = getSsrProps(this);
62
+ * if (props) {
63
+ * this.count = props.count ?? 0;
64
+ * }
65
+ * }
66
+ * ```
67
+ */ export function getSsrProps(el) {
68
+ const raw = el.getAttribute('data-ssr-props');
69
+ if (!raw) return null;
70
+ try {
71
+ return JSON.parse(raw);
72
+ } catch {
73
+ log.warn(`Failed to parse data-ssr-props on <${el.tagName.toLowerCase()}>`);
74
+ return null;
75
+ }
76
+ }
77
+ /**
78
+ * Apply SSR props from data-ssr-props to a component instance.
79
+ * This restores server-rendered property values to the
80
+ * client-side component on upgrade, ensuring consistency between
81
+ * SSR and client state.
82
+ *
83
+ * v0.6.2: Framework-agnostic. No Lit-specific detection.
84
+ * Props are set directly on the instance. DSD hydration and VNode event
85
+ * markers are handled at the component level via DsdElement.
86
+ *
87
+ * v0.14.3: Prototype pollution fix - filters dangerous keys
88
+ * (__proto__, constructor, prototype) from parsed SSR props.
89
+ *
90
+ * @param el - The upgraded custom element
91
+ */ /** Keys that could cause prototype pollution if assigned to an object instance.
92
+ * v0.14.7: Extended to cover all Object.prototype methods that could be
93
+ * exploited via arbitrary property assignment (C-03 fix).
94
+ */ import { DANGEROUS_KEYS } from './security.js';
95
+ export function bindSsrProps(el) {
96
+ const props = getSsrProps(el);
97
+ if (!props) return;
98
+ for (const [key, value] of Object.entries(props)){
99
+ // v0.14.3: Prevent prototype pollution - skip dangerous keys
100
+ if (DANGEROUS_KEYS.has(key)) {
101
+ log.warn(`Skipping dangerous key "${key}" in data-ssr-props on <${el.tagName.toLowerCase()}>`);
102
+ continue;
103
+ }
104
+ try {
105
+ el[key] = value;
106
+ } catch (e) {
107
+ // Some properties may be read-only - safe to skip, but log for debuggability
108
+ log.debug(`Cannot set read-only property "${key}" on <${el.tagName.toLowerCase()}>: ${formatError(e)}`);
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Create an IntersectionObserver-based upgrade strategy.
114
+ * v0.6': Uses querySelectorAll to observe ALL instances of the tag,
115
+ * not just the first one. Per WHATWG IntersectionObserver spec.
116
+ */ function createVisibleStrategy(tagName, registerFn) {
117
+ let registered = false;
118
+ const observer = new IntersectionObserver((entries)=>{
119
+ for (const entry of entries){
120
+ if (entry.isIntersecting) {
121
+ observer.disconnect();
122
+ if (!registered) {
123
+ registered = true;
124
+ clearTimeout(timeoutId);
125
+ _visibilityTimeouts.delete(timeoutId);
126
+ registerFn();
127
+ }
128
+ return;
129
+ }
130
+ }
131
+ }, {
132
+ rootMargin: '200px'
133
+ });
134
+ // We need to wait for elements to be in the DOM first
135
+ const observeAll = ()=>{
136
+ const elements = document.querySelectorAll(tagName);
137
+ if (elements.length > 0) {
138
+ elements.forEach((el)=>observer.observe(el));
139
+ return true;
140
+ }
141
+ return false;
142
+ };
143
+ const mo = new MutationObserver((_mutations, mutObs)=>{
144
+ if (observeAll()) {
145
+ mutObs.disconnect();
146
+ } else {
147
+ // v0.14.5: If elements were removed from DOM, disconnect all observers
148
+ const elements = document.querySelectorAll(tagName);
149
+ if (elements.length === 0 && !registered) {
150
+ mutObs.disconnect();
151
+ observer.disconnect();
152
+ clearTimeout(timeoutId);
153
+ _visibilityTimeouts.delete(timeoutId);
154
+ }
155
+ }
156
+ });
157
+ // v0.14.3: Timeout guard - if the target element never appears
158
+ // (e.g., route changed after island was registered), disconnect
159
+ // both observers after 30 seconds to prevent memory/perf leaks.
160
+ const VISIBILITY_TIMEOUT = 30_000; // 30s
161
+ const timeoutId = setTimeout(()=>{
162
+ _visibilityTimeouts.delete(timeoutId);
163
+ if (!registered) {
164
+ mo.disconnect();
165
+ observer.disconnect();
166
+ log.debug(`Visibility strategy for <${tagName}> timed out after ${VISIBILITY_TIMEOUT}ms`);
167
+ }
168
+ }, VISIBILITY_TIMEOUT);
169
+ _visibilityTimeouts.add(timeoutId);
170
+ // Start observing after DOM content loaded
171
+ if (document.readyState === 'loading') {
172
+ document.addEventListener('DOMContentLoaded', ()=>{
173
+ if (!observeAll()) {
174
+ mo.observe(document.body, {
175
+ childList: true,
176
+ subtree: true
177
+ });
178
+ }
179
+ });
180
+ } else {
181
+ if (!observeAll()) {
182
+ mo.observe(document.body, {
183
+ childList: true,
184
+ subtree: true
185
+ });
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * Create an idle (requestIdleCallback-based) hydration strategy.
191
+ * v0.6': Improved fallback chain:
192
+ * 1. requestIdleCallback (optimal, progressive)
193
+ * 2. requestAnimationFrame (next frame, good for interaction)
194
+ * 3. setTimeout(fn, 50) (final fallback, shorter than old 200ms)
195
+ */ function createIdleStrategy(registerFn) {
196
+ const g = globalThis;
197
+ if (typeof g.requestIdleCallback === 'function') {
198
+ g.requestIdleCallback(registerFn);
199
+ } else if (typeof g.requestAnimationFrame === 'function') {
200
+ g.requestAnimationFrame(()=>registerFn());
201
+ } else {
202
+ setTimeout(registerFn, 50);
203
+ }
204
+ }
205
+ /**
206
+ * Wrap a component class as a openElement Island.
207
+ *
208
+ * Handles:
209
+ * - Automatic customElements.define() registration
210
+ * - Strategy-based upgrade timing
211
+ * - data-ssr-props binding (open:bind)
212
+ * - __island / __tagName export markers
213
+ * - __layer metadata (dsd-static, dsd-interactive, or pure-island)
214
+ * - Idempotent registration (safe for SSR with multiple routes)
215
+ *
216
+ * v0.6.2: Added `dsd` option. When false, the island is a Pure Island
217
+ * (Layer 3) - no DSD template is emitted, framework fully owns shadow root.
218
+ *
219
+ * @param tagName - Custom element tag name (must contain hyphen)
220
+ * @param componentClass - Custom Element constructor (framework-agnostic)
221
+ * @param options - Island options
222
+ * @returns The component class (for chaining / re-export)
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * // Basic usage (DSD enabled by default)
227
+ * export default defineIsland('my-counter', MyCounter);
228
+ *
229
+ * // Pure Island - no DSD, full framework reactivity
230
+ * export default defineIsland('my-counter', MyCounter, { dsd: false });
231
+ *
232
+ * // With visible strategy (IntersectionObserver)
233
+ * export default defineIsland('my-counter', MyCounter, { strategy: 'visible' });
234
+ *
235
+ * // With load strategy (immediate upgrade)
236
+ * export default defineIsland('my-counter', MyCounter, { strategy: 'load' });
237
+ * ```
238
+ */ export function defineIsland(tagName, componentClass, options = {}) {
239
+ const strategy = options.strategy || 'idle';
240
+ if (!VALID_STRATEGIES.has(strategy)) {
241
+ throw new Error(`${ERROR_PREFIX} Invalid island hydration strategy "${String(strategy)}". ` + 'Use one of: load, idle, visible, only.');
242
+ }
243
+ const useDsd = strategy === 'only' ? false : options.dsd !== false; // default true
244
+ const useSsr = strategy === 'only' ? false : options.ssr !== false; // default true
245
+ // Validate tag name per WHATWG Custom Element name rules
246
+ // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
247
+ if (!tagName || !tagName.includes('-')) {
248
+ throw new Error(`${ERROR_PREFIX} defineIsland() requires a hyphenated tag name, got "${tagName}". ` + 'Custom Element names must contain a hyphen per the HTML spec.');
249
+ }
250
+ // WHATWG: must start with lowercase letter, only lowercase/digits/hyphens,
251
+ // must not start with a reserved prefix, no uppercase
252
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(tagName)) {
253
+ throw new Error(`${ERROR_PREFIX} defineIsland() tag name "${tagName}" is not a valid custom element name. ` + 'Must start with a lowercase ASCII letter, contain only lowercase ASCII ' + 'letters, digits, and hyphens, and not use reserved names.');
254
+ }
255
+ // Reserved names per WHATWG (partial list)
256
+ const reservedPrefixes = [
257
+ 'annotation-',
258
+ 'color-profile',
259
+ 'font-face',
260
+ 'font-face-',
261
+ 'missing-glyph'
262
+ ];
263
+ for (const prefix of reservedPrefixes){
264
+ if (tagName.startsWith(prefix)) {
265
+ throw new Error(`${ERROR_PREFIX} defineIsland() tag name "${tagName}" uses a reserved prefix "${prefix}".`);
266
+ }
267
+ }
268
+ _islandMeta.set(componentClass, {
269
+ isIsland: true,
270
+ tagName,
271
+ layer: useDsd ? 'dsd-interactive' : 'pure-island',
272
+ ssr: useSsr,
273
+ dsd: useDsd
274
+ });
275
+ // v0.6': Mixin pattern for connectedCallback - replaces monkey-patch.
276
+ // Instead of modifying the prototype directly, we create a wrapper
277
+ // that calls the original callback + auto-binds SSR props.
278
+ // This is safer than monkey-patching because it doesn't interfere
279
+ // with Lit's own connectedCallback chain.
280
+ //
281
+ // v0.14.3: Added __ssrPropsBound idempotency guard to prevent
282
+ // double bindSsrProps() calls when a subclass island inherits from a
283
+ // parent island (both registered via defineIsland()). Without this guard,
284
+ // the parent's wrapped connectedCallback and the subclass's both
285
+ // call bindSsrProps on the same element.
286
+ const origConnected = componentClass.prototype.connectedCallback;
287
+ if (!componentClass.prototype.__openIslandWrapped) {
288
+ componentClass.prototype.__openIslandWrapped = true;
289
+ componentClass.prototype.connectedCallback = function() {
290
+ // Call original connectedCallback first (super.connectedCallback)
291
+ if (typeof origConnected === 'function') {
292
+ origConnected.call(this);
293
+ }
294
+ // Auto-bind SSR props on upgrade (idempotent - only once per element)
295
+ if (this.hasAttribute('data-ssr-props') && !ssrPropsBoundSet.has(this)) {
296
+ ssrPropsBoundSet.add(this);
297
+ Promise.resolve().then(()=>bindSsrProps(this));
298
+ }
299
+ };
300
+ }
301
+ // Define a registration function that's idempotent
302
+ const register = ()=>{
303
+ const registry = globalThis.customElements;
304
+ if (!registry) return;
305
+ if (!registry.get(tagName)) {
306
+ try {
307
+ registry.define(tagName, componentClass);
308
+ } catch (e) {
309
+ // Already defined - safe to ignore in SSR contexts
310
+ log.debug(`customElements.define("${tagName}") skipped: ${formatError(e)}`);
311
+ }
312
+ }
313
+ };
314
+ // SSR guard: browser-specific strategy scheduling is a no-op during SSR.
315
+ // IntersectionObserver, MutationObserver etc. are browser-only APIs.
316
+ // During SSR we just define the custom element and let the generated
317
+ // client entry handle strategy dispatch in the browser.
318
+ const isBrowser = typeof IntersectionObserver !== 'undefined';
319
+ if (isBrowser) {
320
+ switch(strategy){
321
+ case 'load':
322
+ case 'only':
323
+ register();
324
+ break;
325
+ case 'idle':
326
+ createIdleStrategy(register);
327
+ break;
328
+ case 'visible':
329
+ createVisibleStrategy(tagName, register);
330
+ break;
331
+ }
332
+ } else {
333
+ // SSR path: define the element idempotently, strategy runs on client.
334
+ register();
335
+ }
336
+ return componentClass;
337
+ }
338
+ /**
339
+ * Exports the `island` function as default for convenience imports.
340
+ * Tree-shakable: bundlers can eliminate unused named exports from the same module.
341
+ */ export default defineIsland;
342
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9jb3JlL3NyYy9pc2xhbmQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRVJST1JfUFJFRklYIH0gZnJvbSAnQG9wZW5lbGVtZW50L2NvcmUnO1xuaW1wb3J0IHsgZm9ybWF0RXJyb3IgfSBmcm9tICcuL2Vycm9ycy5qcyc7XG4vKipcbiAqIEBvcGVuZWxlbWVudC9jb3JlIC0gZGVmaW5lSXNsYW5kKCkgd3JhcHBlclxuICpcbiAqIHYwLjYuMjogZGVmaW5lSXNsYW5kKCkgd3JhcHMgYW55IEN1c3RvbSBFbGVtZW50IGNsYXNzIHRvIHByb3ZpZGU6XG4gKiAgIC0gQXV0b21hdGljIHJlZ2lzdHJhdGlvbiB2aWEgY3VzdG9tRWxlbWVudHMuZGVmaW5lKClcbiAqICAgLSBIeWRyYXRpb24gc3RyYXRlZ3kgc3VwcG9ydCAobG9hZCwgaWRsZSwgdmlzaWJsZSwgb25seSlcbiAqICAgLSBfX2lzbGFuZCAvIF9fdGFnTmFtZSAvIF9fbGF5ZXIgbWV0YWRhdGEgbWFya2Vyc1xuICogICAtIGRhdGEtc3NyLXByb3BzIHJlc3RvcmF0aW9uIG9uIGNsaWVudCB1cGdyYWRlXG4gKiAgIC0gRFNEIG9wdC1vdXQgdmlhIGBkc2Q6IGZhbHNlYCAoUHVyZSBJc2xhbmQgLyBMYXllciAzKVxuICpcbiAqIEZyYW1ld29yay1hZ25vc3RpYzogd29ya3Mgd2l0aCBMaXQsIHZhbmlsbGEgQ3VzdG9tIEVsZW1lbnRzLFxuICogRkFTVCwgb3IgYW55IFdlYiBDb21wb25lbnQgbGlicmFyeS4gYmluZFNzclByb3BzKCkgc2V0cyBwcm9wc1xuICogZGlyZWN0bHk7IGFkYXB0ZXJzIGhhbmRsZSBmcmFtZXdvcmstc3BlY2lmaWMgdXBkYXRlIHRyaWdnZXJzLlxuICpcbiAqIHYwLjI5LjE6IGRlZmluZUN1c3RvbUVsZW1lbnQgaGVscGVyIGlubGluZWQgZnJvbSBjdXN0b20tZWxlbWVudC50cy5cbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuL2xvZ2dlci5qcyc7XG5pbXBvcnQgdHlwZSB7IEh5ZHJhdGlvblN0cmF0ZWd5IH0gZnJvbSAnQG9wZW5lbGVtZW50L3Byb3RvY29sL2ZyYW1ld29yayc7XG5pbXBvcnQgdHlwZSB7IElzbGFuZE1ldGEsIElzbGFuZE9wdGlvbnMgfSBmcm9tICdAb3BlbmVsZW1lbnQvcHJvdG9jb2wvaXNsYW5kJztcbmV4cG9ydCB0eXBlIHsgSXNsYW5kTWV0YSwgSXNsYW5kT3B0aW9ucyB9O1xuXG4vKiogV2Vha1NldCB0byB0cmFjayBlbGVtZW50cyB0aGF0IGhhdmUgYWxyZWFkeSBoYWQgU1NSIHByb3BzIGJvdW5kIChpZGVtcG90ZW50KS4gKi9cbmNvbnN0IHNzclByb3BzQm91bmRTZXQgPSBuZXcgV2Vha1NldDxIVE1MRWxlbWVudD4oKTtcbmNvbnN0IGxvZyA9IGNyZWF0ZUxvZ2dlcignY29yZScpO1xuXG4vKipcbiAqIFNTUi1zYWZlIGN1c3RvbSBlbGVtZW50IHJlZ2lzdHJhdGlvbiBoZWxwZXIuXG4gKiB2MC4yOS4xOiBNZXJnZWQgZnJvbSBjdXN0b20tZWxlbWVudC50cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlZmluZUN1c3RvbUVsZW1lbnQoXG4gIHRhZzogc3RyaW5nLFxuICBjdG9yOiBDdXN0b21FbGVtZW50Q29uc3RydWN0b3IsXG4pOiB2b2lkIHtcbiAgaWYgKHR5cGVvZiBnbG9iYWxUaGlzLmN1c3RvbUVsZW1lbnRzID09PSAndW5kZWZpbmVkJykgcmV0dXJuO1xuICBpZiAoIWdsb2JhbFRoaXMuY3VzdG9tRWxlbWVudHMuZ2V0KHRhZykpIHtcbiAgICBnbG9iYWxUaGlzLmN1c3RvbUVsZW1lbnRzLmRlZmluZSh0YWcsIGN0b3IpO1xuICB9XG59XG5cbmNvbnN0IFZBTElEX1NUUkFURUdJRVMgPSBuZXcgU2V0PEh5ZHJhdGlvblN0cmF0ZWd5PihbJ2xvYWQnLCAnaWRsZScsICd2aXNpYmxlJywgJ29ubHknXSk7XG5cbi8vIE1vZHVsZS1sZXZlbCBzdG9yZSBvZiBhY3RpdmUgdmlzaWJpbGl0eSBzdHJhdGVneSB0aW1lb3V0IElEcy5cbi8vIFVzZWQgZm9yIHRlc3QgY2xlYW51cCAtIHRlc3RzIGNhbiBjYWxsIF9jbGVhckFsbFZpc2liaWxpdHlUaW1lb3V0cygpXG4vLyB0byBwcmV2ZW50IHRpbWVyIGxlYWtzLlxuY29uc3QgX3Zpc2liaWxpdHlUaW1lb3V0cyA9IG5ldyBTZXQ8UmV0dXJuVHlwZTx0eXBlb2Ygc2V0VGltZW91dD4+KCk7XG5cbmNvbnN0IF9pc2xhbmRNZXRhID0gbmV3IFdlYWtNYXA8Q3VzdG9tRWxlbWVudENvbnN0cnVjdG9yLCBJc2xhbmRNZXRhPigpO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0SXNsYW5kTWV0YShjdG9yOiBDdXN0b21FbGVtZW50Q29uc3RydWN0b3IpOiBJc2xhbmRNZXRhIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIF9pc2xhbmRNZXRhLmdldChjdG9yKTtcbn1cblxuLyoqIENsZWFyIGFsbCBhY3RpdmUgdmlzaWJpbGl0eSBzdHJhdGVneSB0aW1lb3V0cyAoZm9yIHRlc3QgY2xlYW51cCkuICovXG5leHBvcnQgZnVuY3Rpb24gX2NsZWFyQWxsVmlzaWJpbGl0eVRpbWVvdXRzKCk6IHZvaWQge1xuICBmb3IgKGNvbnN0IGlkIG9mIF92aXNpYmlsaXR5VGltZW91dHMpIHtcbiAgICBjbGVhclRpbWVvdXQoaWQpO1xuICB9XG4gIF92aXNpYmlsaXR5VGltZW91dHMuY2xlYXIoKTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIHZhbHVlIG9mIHRoZSBkYXRhLXNzci1wcm9wcyBhdHRyaWJ1dGUgZnJvbSBhIGhvc3QgZWxlbWVudC5cbiAqIFVzZWQgdG8gcmVjb25zdHJ1Y3QgU1NSIHByb3BzIG9uIGNsaWVudCB1cGdyYWRlLlxuICpcbiAqIEBwYXJhbSBlbCAtIFRoZSBjdXN0b20gZWxlbWVudCBob3N0IGVsZW1lbnRcbiAqIEByZXR1cm5zIFBhcnNlZCBwcm9wcyBvYmplY3QsIG9yIG51bGwgaWYgbm8gZGF0YS1zc3ItcHJvcHMgYXR0cmlidXRlXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHRzXG4gKiBjb25uZWN0ZWRDYWxsYmFjaygpIHtcbiAqICAgc3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKTtcbiAqICAgY29uc3QgcHJvcHMgPSBnZXRTc3JQcm9wcyh0aGlzKTtcbiAqICAgaWYgKHByb3BzKSB7XG4gKiAgICAgdGhpcy5jb3VudCA9IHByb3BzLmNvdW50ID8/IDA7XG4gKiAgIH1cbiAqIH1cbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0U3NyUHJvcHMoZWw6IEhUTUxFbGVtZW50KTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfCBudWxsIHtcbiAgY29uc3QgcmF3ID0gZWwuZ2V0QXR0cmlidXRlKCdkYXRhLXNzci1wcm9wcycpO1xuICBpZiAoIXJhdykgcmV0dXJuIG51bGw7XG4gIHRyeSB7XG4gICAgcmV0dXJuIEpTT04ucGFyc2UocmF3KSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgfSBjYXRjaCB7XG4gICAgbG9nLndhcm4oYEZhaWxlZCB0byBwYXJzZSBkYXRhLXNzci1wcm9wcyBvbiA8JHtlbC50YWdOYW1lLnRvTG93ZXJDYXNlKCl9PmApO1xuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbi8qKlxuICogQXBwbHkgU1NSIHByb3BzIGZyb20gZGF0YS1zc3ItcHJvcHMgdG8gYSBjb21wb25lbnQgaW5zdGFuY2UuXG4gKiBUaGlzIHJlc3RvcmVzIHNlcnZlci1yZW5kZXJlZCBwcm9wZXJ0eSB2YWx1ZXMgdG8gdGhlXG4gKiBjbGllbnQtc2lkZSBjb21wb25lbnQgb24gdXBncmFkZSwgZW5zdXJpbmcgY29uc2lzdGVuY3kgYmV0d2VlblxuICogU1NSIGFuZCBjbGllbnQgc3RhdGUuXG4gKlxuICogdjAuNi4yOiBGcmFtZXdvcmstYWdub3N0aWMuIE5vIExpdC1zcGVjaWZpYyBkZXRlY3Rpb24uXG4gKiBQcm9wcyBhcmUgc2V0IGRpcmVjdGx5IG9uIHRoZSBpbnN0YW5jZS4gRFNEIGh5ZHJhdGlvbiBhbmQgVk5vZGUgZXZlbnRcbiAqIG1hcmtlcnMgYXJlIGhhbmRsZWQgYXQgdGhlIGNvbXBvbmVudCBsZXZlbCB2aWEgRHNkRWxlbWVudC5cbiAqXG4gKiB2MC4xNC4zOiBQcm90b3R5cGUgcG9sbHV0aW9uIGZpeCAtIGZpbHRlcnMgZGFuZ2Vyb3VzIGtleXNcbiAqIChfX3Byb3RvX18sIGNvbnN0cnVjdG9yLCBwcm90b3R5cGUpIGZyb20gcGFyc2VkIFNTUiBwcm9wcy5cbiAqXG4gKiBAcGFyYW0gZWwgLSBUaGUgdXBncmFkZWQgY3VzdG9tIGVsZW1lbnRcbiAqL1xuXG4vKiogS2V5cyB0aGF0IGNvdWxkIGNhdXNlIHByb3RvdHlwZSBwb2xsdXRpb24gaWYgYXNzaWduZWQgdG8gYW4gb2JqZWN0IGluc3RhbmNlLlxuICogdjAuMTQuNzogRXh0ZW5kZWQgdG8gY292ZXIgYWxsIE9iamVjdC5wcm90b3R5cGUgbWV0aG9kcyB0aGF0IGNvdWxkIGJlXG4gKiBleHBsb2l0ZWQgdmlhIGFyYml0cmFyeSBwcm9wZXJ0eSBhc3NpZ25tZW50IChDLTAzIGZpeCkuXG4gKi9cbmltcG9ydCB7IERBTkdFUk9VU19LRVlTIH0gZnJvbSAnLi9zZWN1cml0eS5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBiaW5kU3NyUHJvcHMoZWw6IEhUTUxFbGVtZW50KTogdm9pZCB7XG4gIGNvbnN0IHByb3BzID0gZ2V0U3NyUHJvcHMoZWwpO1xuICBpZiAoIXByb3BzKSByZXR1cm47XG5cbiAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMocHJvcHMpKSB7XG4gICAgLy8gdjAuMTQuMzogUHJldmVudCBwcm90b3R5cGUgcG9sbHV0aW9uIC0gc2tpcCBkYW5nZXJvdXMga2V5c1xuICAgIGlmIChEQU5HRVJPVVNfS0VZUy5oYXMoa2V5KSkge1xuICAgICAgbG9nLndhcm4oXG4gICAgICAgIGBTa2lwcGluZyBkYW5nZXJvdXMga2V5IFwiJHtrZXl9XCIgaW4gZGF0YS1zc3ItcHJvcHMgb24gPCR7ZWwudGFnTmFtZS50b0xvd2VyQ2FzZSgpfT5gLFxuICAgICAgKTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICB0cnkge1xuICAgICAgKGVsIGFzIHVua25vd24gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pW2tleV0gPSB2YWx1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAvLyBTb21lIHByb3BlcnRpZXMgbWF5IGJlIHJlYWQtb25seSAtIHNhZmUgdG8gc2tpcCwgYnV0IGxvZyBmb3IgZGVidWdnYWJpbGl0eVxuICAgICAgbG9nLmRlYnVnKFxuICAgICAgICBgQ2Fubm90IHNldCByZWFkLW9ubHkgcHJvcGVydHkgXCIke2tleX1cIiBvbiA8JHtlbC50YWdOYW1lLnRvTG93ZXJDYXNlKCl9PjogJHtcbiAgICAgICAgICBmb3JtYXRFcnJvcihlKVxuICAgICAgICB9YCxcbiAgICAgICk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogQ3JlYXRlIGFuIEludGVyc2VjdGlvbk9ic2VydmVyLWJhc2VkIHVwZ3JhZGUgc3RyYXRlZ3kuXG4gKiB2MC42JzogVXNlcyBxdWVyeVNlbGVjdG9yQWxsIHRvIG9ic2VydmUgQUxMIGluc3RhbmNlcyBvZiB0aGUgdGFnLFxuICogbm90IGp1c3QgdGhlIGZpcnN0IG9uZS4gUGVyIFdIQVRXRyBJbnRlcnNlY3Rpb25PYnNlcnZlciBzcGVjLlxuICovXG5mdW5jdGlvbiBjcmVhdGVWaXNpYmxlU3RyYXRlZ3koXG4gIHRhZ05hbWU6IHN0cmluZyxcbiAgcmVnaXN0ZXJGbjogKCkgPT4gdm9pZCxcbik6IHZvaWQge1xuICBsZXQgcmVnaXN0ZXJlZCA9IGZhbHNlO1xuICBjb25zdCBvYnNlcnZlciA9IG5ldyBJbnRlcnNlY3Rpb25PYnNlcnZlcihcbiAgICAoZW50cmllcykgPT4ge1xuICAgICAgZm9yIChjb25zdCBlbnRyeSBvZiBlbnRyaWVzKSB7XG4gICAgICAgIGlmIChlbnRyeS5pc0ludGVyc2VjdGluZykge1xuICAgICAgICAgIG9ic2VydmVyLmRpc2Nvbm5lY3QoKTtcbiAgICAgICAgICBpZiAoIXJlZ2lzdGVyZWQpIHtcbiAgICAgICAgICAgIHJlZ2lzdGVyZWQgPSB0cnVlO1xuICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXRJZCk7XG4gICAgICAgICAgICBfdmlzaWJpbGl0eVRpbWVvdXRzLmRlbGV0ZSh0aW1lb3V0SWQpO1xuICAgICAgICAgICAgcmVnaXN0ZXJGbigpO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHsgcm9vdE1hcmdpbjogJzIwMHB4JyB9LCAvLyBTdGFydCBsb2FkaW5nIDIwMHB4IGJlZm9yZSB2aXNpYmxlXG4gICk7XG5cbiAgLy8gV2UgbmVlZCB0byB3YWl0IGZvciBlbGVtZW50cyB0byBiZSBpbiB0aGUgRE9NIGZpcnN0XG4gIGNvbnN0IG9ic2VydmVBbGwgPSAoKSA9PiB7XG4gICAgY29uc3QgZWxlbWVudHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHRhZ05hbWUpO1xuICAgIGlmIChlbGVtZW50cy5sZW5ndGggPiAwKSB7XG4gICAgICBlbGVtZW50cy5mb3JFYWNoKChlbCkgPT4gb2JzZXJ2ZXIub2JzZXJ2ZShlbCkpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfTtcblxuICBjb25zdCBtbyA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKChfbXV0YXRpb25zLCBtdXRPYnMpID0+IHtcbiAgICBpZiAob2JzZXJ2ZUFsbCgpKSB7XG4gICAgICBtdXRPYnMuZGlzY29ubmVjdCgpO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyB2MC4xNC41OiBJZiBlbGVtZW50cyB3ZXJlIHJlbW92ZWQgZnJvbSBET00sIGRpc2Nvbm5lY3QgYWxsIG9ic2VydmVyc1xuICAgICAgY29uc3QgZWxlbWVudHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHRhZ05hbWUpO1xuICAgICAgaWYgKGVsZW1lbnRzLmxlbmd0aCA9PT0gMCAmJiAhcmVnaXN0ZXJlZCkge1xuICAgICAgICBtdXRPYnMuZGlzY29ubmVjdCgpO1xuICAgICAgICBvYnNlcnZlci5kaXNjb25uZWN0KCk7XG4gICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xuICAgICAgICBfdmlzaWJpbGl0eVRpbWVvdXRzLmRlbGV0ZSh0aW1lb3V0SWQpO1xuICAgICAgfVxuICAgIH1cbiAgfSk7XG5cbiAgLy8gdjAuMTQuMzogVGltZW91dCBndWFyZCAtIGlmIHRoZSB0YXJnZXQgZWxlbWVudCBuZXZlciBhcHBlYXJzXG4gIC8vIChlLmcuLCByb3V0ZSBjaGFuZ2VkIGFmdGVyIGlzbGFuZCB3YXMgcmVnaXN0ZXJlZCksIGRpc2Nvbm5lY3RcbiAgLy8gYm90aCBvYnNlcnZlcnMgYWZ0ZXIgMzAgc2Vjb25kcyB0byBwcmV2ZW50IG1lbW9yeS9wZXJmIGxlYWtzLlxuICBjb25zdCBWSVNJQklMSVRZX1RJTUVPVVQgPSAzMF8wMDA7IC8vIDMwc1xuICBjb25zdCB0aW1lb3V0SWQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICBfdmlzaWJpbGl0eVRpbWVvdXRzLmRlbGV0ZSh0aW1lb3V0SWQpO1xuICAgIGlmICghcmVnaXN0ZXJlZCkge1xuICAgICAgbW8uZGlzY29ubmVjdCgpO1xuICAgICAgb2JzZXJ2ZXIuZGlzY29ubmVjdCgpO1xuICAgICAgbG9nLmRlYnVnKGBWaXNpYmlsaXR5IHN0cmF0ZWd5IGZvciA8JHt0YWdOYW1lfT4gdGltZWQgb3V0IGFmdGVyICR7VklTSUJJTElUWV9USU1FT1VUfW1zYCk7XG4gICAgfVxuICB9LCBWSVNJQklMSVRZX1RJTUVPVVQpO1xuICBfdmlzaWJpbGl0eVRpbWVvdXRzLmFkZCh0aW1lb3V0SWQpO1xuXG4gIC8vIFN0YXJ0IG9ic2VydmluZyBhZnRlciBET00gY29udGVudCBsb2FkZWRcbiAgaWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJykge1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG4gICAgICBpZiAoIW9ic2VydmVBbGwoKSkge1xuICAgICAgICBtby5vYnNlcnZlKGRvY3VtZW50LmJvZHksIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pO1xuICAgICAgfVxuICAgIH0pO1xuICB9IGVsc2Uge1xuICAgIGlmICghb2JzZXJ2ZUFsbCgpKSB7XG4gICAgICBtby5vYnNlcnZlKGRvY3VtZW50LmJvZHksIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIENyZWF0ZSBhbiBpZGxlIChyZXF1ZXN0SWRsZUNhbGxiYWNrLWJhc2VkKSBoeWRyYXRpb24gc3RyYXRlZ3kuXG4gKiB2MC42JzogSW1wcm92ZWQgZmFsbGJhY2sgY2hhaW46XG4gKiAgIDEuIHJlcXVlc3RJZGxlQ2FsbGJhY2sgKG9wdGltYWwsIHByb2dyZXNzaXZlKVxuICogICAyLiByZXF1ZXN0QW5pbWF0aW9uRnJhbWUgKG5leHQgZnJhbWUsIGdvb2QgZm9yIGludGVyYWN0aW9uKVxuICogICAzLiBzZXRUaW1lb3V0KGZuLCA1MCkgKGZpbmFsIGZhbGxiYWNrLCBzaG9ydGVyIHRoYW4gb2xkIDIwMG1zKVxuICovXG5mdW5jdGlvbiBjcmVhdGVJZGxlU3RyYXRlZ3kocmVnaXN0ZXJGbjogKCkgPT4gdm9pZCk6IHZvaWQge1xuICBjb25zdCBnID0gZ2xvYmFsVGhpcyBhcyB7XG4gICAgcmVxdWVzdElkbGVDYWxsYmFjaz86IChmbjogKCkgPT4gdm9pZCkgPT4gdm9pZDtcbiAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWU/OiAoZm46ICgpID0+IHZvaWQpID0+IG51bWJlcjtcbiAgfTtcblxuICBpZiAodHlwZW9mIGcucmVxdWVzdElkbGVDYWxsYmFjayA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIGcucmVxdWVzdElkbGVDYWxsYmFjayhyZWdpc3RlckZuKTtcbiAgfSBlbHNlIGlmICh0eXBlb2YgZy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUgPT09ICdmdW5jdGlvbicpIHtcbiAgICBnLnJlcXVlc3RBbmltYXRpb25GcmFtZSgoKSA9PiByZWdpc3RlckZuKCkpO1xuICB9IGVsc2Uge1xuICAgIHNldFRpbWVvdXQocmVnaXN0ZXJGbiwgNTApO1xuICB9XG59XG5cbi8qKlxuICogV3JhcCBhIGNvbXBvbmVudCBjbGFzcyBhcyBhIG9wZW5FbGVtZW50IElzbGFuZC5cbiAqXG4gKiBIYW5kbGVzOlxuICogICAtIEF1dG9tYXRpYyBjdXN0b21FbGVtZW50cy5kZWZpbmUoKSByZWdpc3RyYXRpb25cbiAqICAgLSBTdHJhdGVneS1iYXNlZCB1cGdyYWRlIHRpbWluZ1xuICogICAtIGRhdGEtc3NyLXByb3BzIGJpbmRpbmcgKG9wZW46YmluZClcbiAqICAgLSBfX2lzbGFuZCAvIF9fdGFnTmFtZSBleHBvcnQgbWFya2Vyc1xuICogICAtIF9fbGF5ZXIgbWV0YWRhdGEgKGRzZC1zdGF0aWMsIGRzZC1pbnRlcmFjdGl2ZSwgb3IgcHVyZS1pc2xhbmQpXG4gKiAgIC0gSWRlbXBvdGVudCByZWdpc3RyYXRpb24gKHNhZmUgZm9yIFNTUiB3aXRoIG11bHRpcGxlIHJvdXRlcylcbiAqXG4gKiB2MC42LjI6IEFkZGVkIGBkc2RgIG9wdGlvbi4gV2hlbiBmYWxzZSwgdGhlIGlzbGFuZCBpcyBhIFB1cmUgSXNsYW5kXG4gKiAoTGF5ZXIgMykgLSBubyBEU0QgdGVtcGxhdGUgaXMgZW1pdHRlZCwgZnJhbWV3b3JrIGZ1bGx5IG93bnMgc2hhZG93IHJvb3QuXG4gKlxuICogQHBhcmFtIHRhZ05hbWUgLSBDdXN0b20gZWxlbWVudCB0YWcgbmFtZSAobXVzdCBjb250YWluIGh5cGhlbilcbiAqIEBwYXJhbSBjb21wb25lbnRDbGFzcyAtIEN1c3RvbSBFbGVtZW50IGNvbnN0cnVjdG9yIChmcmFtZXdvcmstYWdub3N0aWMpXG4gKiBAcGFyYW0gb3B0aW9ucyAtIElzbGFuZCBvcHRpb25zXG4gKiBAcmV0dXJucyBUaGUgY29tcG9uZW50IGNsYXNzIChmb3IgY2hhaW5pbmcgLyByZS1leHBvcnQpXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHRzXG4gKiAvLyBCYXNpYyB1c2FnZSAoRFNEIGVuYWJsZWQgYnkgZGVmYXVsdClcbiAqIGV4cG9ydCBkZWZhdWx0IGRlZmluZUlzbGFuZCgnbXktY291bnRlcicsIE15Q291bnRlcik7XG4gKlxuICogLy8gUHVyZSBJc2xhbmQgLSBubyBEU0QsIGZ1bGwgZnJhbWV3b3JrIHJlYWN0aXZpdHlcbiAqIGV4cG9ydCBkZWZhdWx0IGRlZmluZUlzbGFuZCgnbXktY291bnRlcicsIE15Q291bnRlciwgeyBkc2Q6IGZhbHNlIH0pO1xuICpcbiAqIC8vIFdpdGggdmlzaWJsZSBzdHJhdGVneSAoSW50ZXJzZWN0aW9uT2JzZXJ2ZXIpXG4gKiBleHBvcnQgZGVmYXVsdCBkZWZpbmVJc2xhbmQoJ215LWNvdW50ZXInLCBNeUNvdW50ZXIsIHsgc3RyYXRlZ3k6ICd2aXNpYmxlJyB9KTtcbiAqXG4gKiAvLyBXaXRoIGxvYWQgc3RyYXRlZ3kgKGltbWVkaWF0ZSB1cGdyYWRlKVxuICogZXhwb3J0IGRlZmF1bHQgZGVmaW5lSXNsYW5kKCdteS1jb3VudGVyJywgTXlDb3VudGVyLCB7IHN0cmF0ZWd5OiAnbG9hZCcgfSk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlZmluZUlzbGFuZDxUIGV4dGVuZHMgQ3VzdG9tRWxlbWVudENvbnN0cnVjdG9yPihcbiAgdGFnTmFtZTogc3RyaW5nLFxuICBjb21wb25lbnRDbGFzczogVCxcbiAgb3B0aW9uczogSXNsYW5kT3B0aW9ucyA9IHt9LFxuKTogVCB7XG4gIGNvbnN0IHN0cmF0ZWd5ID0gb3B0aW9ucy5zdHJhdGVneSB8fCAnaWRsZSc7XG4gIGlmICghVkFMSURfU1RSQVRFR0lFUy5oYXMoc3RyYXRlZ3kpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYCR7RVJST1JfUFJFRklYfSBJbnZhbGlkIGlzbGFuZCBoeWRyYXRpb24gc3RyYXRlZ3kgXCIke1N0cmluZyhzdHJhdGVneSl9XCIuIGAgK1xuICAgICAgICAnVXNlIG9uZSBvZjogbG9hZCwgaWRsZSwgdmlzaWJsZSwgb25seS4nLFxuICAgICk7XG4gIH1cbiAgY29uc3QgdXNlRHNkID0gc3RyYXRlZ3kgPT09ICdvbmx5JyA/IGZhbHNlIDogb3B0aW9ucy5kc2QgIT09IGZhbHNlOyAvLyBkZWZhdWx0IHRydWVcbiAgY29uc3QgdXNlU3NyID0gc3RyYXRlZ3kgPT09ICdvbmx5JyA/IGZhbHNlIDogb3B0aW9ucy5zc3IgIT09IGZhbHNlOyAvLyBkZWZhdWx0IHRydWVcblxuICAvLyBWYWxpZGF0ZSB0YWcgbmFtZSBwZXIgV0hBVFdHIEN1c3RvbSBFbGVtZW50IG5hbWUgcnVsZXNcbiAgLy8gaHR0cHM6Ly9odG1sLnNwZWMud2hhdHdnLm9yZy9tdWx0aXBhZ2UvY3VzdG9tLWVsZW1lbnRzLmh0bWwjdmFsaWQtY3VzdG9tLWVsZW1lbnQtbmFtZVxuICBpZiAoIXRhZ05hbWUgfHwgIXRhZ05hbWUuaW5jbHVkZXMoJy0nKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGAke0VSUk9SX1BSRUZJWH0gZGVmaW5lSXNsYW5kKCkgcmVxdWlyZXMgYSBoeXBoZW5hdGVkIHRhZyBuYW1lLCBnb3QgXCIke3RhZ05hbWV9XCIuIGAgK1xuICAgICAgICAnQ3VzdG9tIEVsZW1lbnQgbmFtZXMgbXVzdCBjb250YWluIGEgaHlwaGVuIHBlciB0aGUgSFRNTCBzcGVjLicsXG4gICAgKTtcbiAgfVxuICAvLyBXSEFUV0c6IG11c3Qgc3RhcnQgd2l0aCBsb3dlcmNhc2UgbGV0dGVyLCBvbmx5IGxvd2VyY2FzZS9kaWdpdHMvaHlwaGVucyxcbiAgLy8gbXVzdCBub3Qgc3RhcnQgd2l0aCBhIHJlc2VydmVkIHByZWZpeCwgbm8gdXBwZXJjYXNlXG4gIGlmICghL15bYS16XVthLXowLTldKigtW2EtejAtOV0rKSokLy50ZXN0KHRhZ05hbWUpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYCR7RVJST1JfUFJFRklYfSBkZWZpbmVJc2xhbmQoKSB0YWcgbmFtZSBcIiR7dGFnTmFtZX1cIiBpcyBub3QgYSB2YWxpZCBjdXN0b20gZWxlbWVudCBuYW1lLiBgICtcbiAgICAgICAgJ011c3Qgc3RhcnQgd2l0aCBhIGxvd2VyY2FzZSBBU0NJSSBsZXR0ZXIsIGNvbnRhaW4gb25seSBsb3dlcmNhc2UgQVNDSUkgJyArXG4gICAgICAgICdsZXR0ZXJzLCBkaWdpdHMsIGFuZCBoeXBoZW5zLCBhbmQgbm90IHVzZSByZXNlcnZlZCBuYW1lcy4nLFxuICAgICk7XG4gIH1cbiAgLy8gUmVzZXJ2ZWQgbmFtZXMgcGVyIFdIQVRXRyAocGFydGlhbCBsaXN0KVxuICBjb25zdCByZXNlcnZlZFByZWZpeGVzID0gW1xuICAgICdhbm5vdGF0aW9uLScsXG4gICAgJ2NvbG9yLXByb2ZpbGUnLFxuICAgICdmb250LWZhY2UnLFxuICAgICdmb250LWZhY2UtJyxcbiAgICAnbWlzc2luZy1nbHlwaCcsXG4gIF07XG4gIGZvciAoY29uc3QgcHJlZml4IG9mIHJlc2VydmVkUHJlZml4ZXMpIHtcbiAgICBpZiAodGFnTmFtZS5zdGFydHNXaXRoKHByZWZpeCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYCR7RVJST1JfUFJFRklYfSBkZWZpbmVJc2xhbmQoKSB0YWcgbmFtZSBcIiR7dGFnTmFtZX1cIiB1c2VzIGEgcmVzZXJ2ZWQgcHJlZml4IFwiJHtwcmVmaXh9XCIuYCxcbiAgICAgICk7XG4gICAgfVxuICB9XG5cbiAgX2lzbGFuZE1ldGEuc2V0KGNvbXBvbmVudENsYXNzLCB7XG4gICAgaXNJc2xhbmQ6IHRydWUsXG4gICAgdGFnTmFtZSxcbiAgICBsYXllcjogdXNlRHNkID8gJ2RzZC1pbnRlcmFjdGl2ZScgOiAncHVyZS1pc2xhbmQnLFxuICAgIHNzcjogdXNlU3NyLFxuICAgIGRzZDogdXNlRHNkLFxuICB9KTtcblxuICAvLyB2MC42JzogTWl4aW4gcGF0dGVybiBmb3IgY29ubmVjdGVkQ2FsbGJhY2sgLSByZXBsYWNlcyBtb25rZXktcGF0Y2guXG4gIC8vIEluc3RlYWQgb2YgbW9kaWZ5aW5nIHRoZSBwcm90b3R5cGUgZGlyZWN0bHksIHdlIGNyZWF0ZSBhIHdyYXBwZXJcbiAgLy8gdGhhdCBjYWxscyB0aGUgb3JpZ2luYWwgY2FsbGJhY2sgKyBhdXRvLWJpbmRzIFNTUiBwcm9wcy5cbiAgLy8gVGhpcyBpcyBzYWZlciB0aGFuIG1vbmtleS1wYXRjaGluZyBiZWNhdXNlIGl0IGRvZXNuJ3QgaW50ZXJmZXJlXG4gIC8vIHdpdGggTGl0J3Mgb3duIGNvbm5lY3RlZENhbGxiYWNrIGNoYWluLlxuICAvL1xuICAvLyB2MC4xNC4zOiBBZGRlZCBfX3NzclByb3BzQm91bmQgaWRlbXBvdGVuY3kgZ3VhcmQgdG8gcHJldmVudFxuICAvLyBkb3VibGUgYmluZFNzclByb3BzKCkgY2FsbHMgd2hlbiBhIHN1YmNsYXNzIGlzbGFuZCBpbmhlcml0cyBmcm9tIGFcbiAgLy8gcGFyZW50IGlzbGFuZCAoYm90aCByZWdpc3RlcmVkIHZpYSBkZWZpbmVJc2xhbmQoKSkuIFdpdGhvdXQgdGhpcyBndWFyZCxcbiAgLy8gdGhlIHBhcmVudCdzIHdyYXBwZWQgY29ubmVjdGVkQ2FsbGJhY2sgYW5kIHRoZSBzdWJjbGFzcydzIGJvdGhcbiAgLy8gY2FsbCBiaW5kU3NyUHJvcHMgb24gdGhlIHNhbWUgZWxlbWVudC5cbiAgY29uc3Qgb3JpZ0Nvbm5lY3RlZCA9IGNvbXBvbmVudENsYXNzLnByb3RvdHlwZS5jb25uZWN0ZWRDYWxsYmFjaztcbiAgaWYgKCFjb21wb25lbnRDbGFzcy5wcm90b3R5cGUuX19vcGVuSXNsYW5kV3JhcHBlZCkge1xuICAgIGNvbXBvbmVudENsYXNzLnByb3RvdHlwZS5fX29wZW5Jc2xhbmRXcmFwcGVkID0gdHJ1ZTtcbiAgICBjb21wb25lbnRDbGFzcy5wcm90b3R5cGUuY29ubmVjdGVkQ2FsbGJhY2sgPSBmdW5jdGlvbiAodGhpczogSFRNTEVsZW1lbnQpIHtcbiAgICAgIC8vIENhbGwgb3JpZ2luYWwgY29ubmVjdGVkQ2FsbGJhY2sgZmlyc3QgKHN1cGVyLmNvbm5lY3RlZENhbGxiYWNrKVxuICAgICAgaWYgKHR5cGVvZiBvcmlnQ29ubmVjdGVkID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIG9yaWdDb25uZWN0ZWQuY2FsbCh0aGlzKTtcbiAgICAgIH1cbiAgICAgIC8vIEF1dG8tYmluZCBTU1IgcHJvcHMgb24gdXBncmFkZSAoaWRlbXBvdGVudCAtIG9ubHkgb25jZSBwZXIgZWxlbWVudClcbiAgICAgIGlmIChcbiAgICAgICAgdGhpcy5oYXNBdHRyaWJ1dGUoJ2RhdGEtc3NyLXByb3BzJykgJiZcbiAgICAgICAgIXNzclByb3BzQm91bmRTZXQuaGFzKHRoaXMpXG4gICAgICApIHtcbiAgICAgICAgc3NyUHJvcHNCb3VuZFNldC5hZGQodGhpcyk7XG4gICAgICAgIFByb21pc2UucmVzb2x2ZSgpLnRoZW4oKCkgPT4gYmluZFNzclByb3BzKHRoaXMpKTtcbiAgICAgIH1cbiAgICB9IGFzIHVua25vd24gYXMgdHlwZW9mIGNvbXBvbmVudENsYXNzLnByb3RvdHlwZS5jb25uZWN0ZWRDYWxsYmFjaztcbiAgfVxuXG4gIC8vIERlZmluZSBhIHJlZ2lzdHJhdGlvbiBmdW5jdGlvbiB0aGF0J3MgaWRlbXBvdGVudFxuICBjb25zdCByZWdpc3RlciA9ICgpID0+IHtcbiAgICBjb25zdCByZWdpc3RyeSA9IGdsb2JhbFRoaXMuY3VzdG9tRWxlbWVudHM7XG4gICAgaWYgKCFyZWdpc3RyeSkgcmV0dXJuO1xuICAgIGlmICghcmVnaXN0cnkuZ2V0KHRhZ05hbWUpKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZWdpc3RyeS5kZWZpbmUodGFnTmFtZSwgY29tcG9uZW50Q2xhc3MpO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAvLyBBbHJlYWR5IGRlZmluZWQgLSBzYWZlIHRvIGlnbm9yZSBpbiBTU1IgY29udGV4dHNcbiAgICAgICAgbG9nLmRlYnVnKFxuICAgICAgICAgIGBjdXN0b21FbGVtZW50cy5kZWZpbmUoXCIke3RhZ05hbWV9XCIpIHNraXBwZWQ6ICR7Zm9ybWF0RXJyb3IoZSl9YCxcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9XG4gIH07XG5cbiAgLy8gU1NSIGd1YXJkOiBicm93c2VyLXNwZWNpZmljIHN0cmF0ZWd5IHNjaGVkdWxpbmcgaXMgYSBuby1vcCBkdXJpbmcgU1NSLlxuICAvLyBJbnRlcnNlY3Rpb25PYnNlcnZlciwgTXV0YXRpb25PYnNlcnZlciBldGMuIGFyZSBicm93c2VyLW9ubHkgQVBJcy5cbiAgLy8gRHVyaW5nIFNTUiB3ZSBqdXN0IGRlZmluZSB0aGUgY3VzdG9tIGVsZW1lbnQgYW5kIGxldCB0aGUgZ2VuZXJhdGVkXG4gIC8vIGNsaWVudCBlbnRyeSBoYW5kbGUgc3RyYXRlZ3kgZGlzcGF0Y2ggaW4gdGhlIGJyb3dzZXIuXG4gIGNvbnN0IGlzQnJvd3NlciA9IHR5cGVvZiBJbnRlcnNlY3Rpb25PYnNlcnZlciAhPT0gJ3VuZGVmaW5lZCc7XG5cbiAgaWYgKGlzQnJvd3Nlcikge1xuICAgIHN3aXRjaCAoc3RyYXRlZ3kpIHtcbiAgICAgIGNhc2UgJ2xvYWQnOiAvLyBwb255dGFpbDogZmFsbHRocm91Z2gsIGJvdGggJ2xvYWQnIGFuZCAnb25seScgY2FsbCByZWdpc3RlcigpXG4gICAgICBjYXNlICdvbmx5JzpcbiAgICAgICAgcmVnaXN0ZXIoKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdpZGxlJzpcbiAgICAgICAgY3JlYXRlSWRsZVN0cmF0ZWd5KHJlZ2lzdGVyKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICd2aXNpYmxlJzpcbiAgICAgICAgY3JlYXRlVmlzaWJsZVN0cmF0ZWd5KHRhZ05hbWUsIHJlZ2lzdGVyKTtcbiAgICAgICAgYnJlYWs7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIC8vIFNTUiBwYXRoOiBkZWZpbmUgdGhlIGVsZW1lbnQgaWRlbXBvdGVudGx5LCBzdHJhdGVneSBydW5zIG9uIGNsaWVudC5cbiAgICByZWdpc3RlcigpO1xuICB9XG5cbiAgcmV0dXJuIGNvbXBvbmVudENsYXNzO1xufVxuXG4vKipcbiAqIEV4cG9ydHMgdGhlIGBpc2xhbmRgIGZ1bmN0aW9uIGFzIGRlZmF1bHQgZm9yIGNvbnZlbmllbmNlIGltcG9ydHMuXG4gKiBUcmVlLXNoYWthYmxlOiBidW5kbGVycyBjYW4gZWxpbWluYXRlIHVudXNlZCBuYW1lZCBleHBvcnRzIGZyb20gdGhlIHNhbWUgbW9kdWxlLlxuICovXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVJc2xhbmQ7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsU0FBUyxZQUFZLFFBQVEsb0JBQW9CO0FBQ2pELFNBQVMsV0FBVyxRQUFRLGNBQWM7QUFDMUM7Ozs7Ozs7Ozs7Ozs7OztDQWVDLEdBRUQsU0FBUyxZQUFZLFFBQVEsY0FBYztBQUszQyxrRkFBa0YsR0FDbEYsTUFBTSxtQkFBbUIsSUFBSTtBQUM3QixNQUFNLE1BQU0sYUFBYTtBQUV6Qjs7O0NBR0MsR0FDRCxPQUFPLFNBQVMsb0JBQ2QsR0FBVyxFQUNYLElBQThCO0VBRTlCLElBQUksT0FBTyxXQUFXLGNBQWMsS0FBSyxhQUFhO0VBQ3RELElBQUksQ0FBQyxXQUFXLGNBQWMsQ0FBQyxHQUFHLENBQUMsTUFBTTtJQUN2QyxXQUFXLGNBQWMsQ0FBQyxNQUFNLENBQUMsS0FBSztFQUN4QztBQUNGO0FBRUEsTUFBTSxtQkFBbUIsSUFBSSxJQUF1QjtFQUFDO0VBQVE7RUFBUTtFQUFXO0NBQU87QUFFdkYsZ0VBQWdFO0FBQ2hFLHVFQUF1RTtBQUN2RSwwQkFBMEI7QUFDMUIsTUFBTSxzQkFBc0IsSUFBSTtBQUVoQyxNQUFNLGNBQWMsSUFBSTtBQUV4QixPQUFPLFNBQVMsY0FBYyxJQUE4QjtFQUMxRCxPQUFPLFlBQVksR0FBRyxDQUFDO0FBQ3pCO0FBRUEsc0VBQXNFLEdBQ3RFLE9BQU8sU0FBUztFQUNkLEtBQUssTUFBTSxNQUFNLG9CQUFxQjtJQUNwQyxhQUFhO0VBQ2Y7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
@@ -0,0 +1,28 @@
1
+ import type { IsrCacheResult } from '@openelement/protocol/isr';
2
+ import type { IsrManifestEntry } from '@openelement/protocol/framework';
3
+ import type { MemoryIsrCache } from "./isr.js";
4
+ export type IsrRuntimeState = IsrCacheResult['state'] | 'not-found';
5
+ export interface IsrRuntimeRenderResult {
6
+ html: string;
7
+ headers?: Record<string, string>;
8
+ }
9
+ export interface IsrRuntimeRenderContext {
10
+ entry: IsrManifestEntry;
11
+ request?: Request;
12
+ }
13
+ export interface IsrRuntimeOptions {
14
+ manifest: IsrManifestEntry[];
15
+ cache: MemoryIsrCache;
16
+ render: (path: string, context: IsrRuntimeRenderContext) => Promise<IsrRuntimeRenderResult> | IsrRuntimeRenderResult;
17
+ now?: () => number;
18
+ regenerate?: 'blocking' | 'background';
19
+ onRegenerateError?: (error: unknown, entry: IsrManifestEntry) => void;
20
+ schedule?: (task: Promise<void>) => void;
21
+ }
22
+ export interface IsrRuntimeResult {
23
+ state: IsrRuntimeState;
24
+ entry?: IsrManifestEntry;
25
+ response: Response;
26
+ }
27
+ export declare function findIsrManifestEntry(manifest: IsrManifestEntry[], path: string, params?: Record<string, string>): IsrManifestEntry | undefined;
28
+ export declare function renderIsrResponse(path: string, params: Record<string, string>, options: IsrRuntimeOptions, request?: Request): Promise<IsrRuntimeResult>;
@@ -0,0 +1,99 @@
1
+ export function findIsrManifestEntry(manifest, path, params = {}) {
2
+ const paramEntries = Object.entries(params).sort(([a], [b])=>a.localeCompare(b));
3
+ return manifest.find((entry)=>{
4
+ if (entry.path !== path) return false;
5
+ const entryParams = Object.entries(entry.params).sort(([a], [b])=>a.localeCompare(b));
6
+ if (entryParams.length !== paramEntries.length) return false;
7
+ return entryParams.every(([key, value], index)=>{
8
+ const [otherKey, otherValue] = paramEntries[index];
9
+ return key === otherKey && value === otherValue;
10
+ });
11
+ });
12
+ }
13
+ export async function renderIsrResponse(path, params, options, request) {
14
+ const entry = findIsrManifestEntry(options.manifest, path, params);
15
+ if (!entry) {
16
+ return {
17
+ state: 'not-found',
18
+ response: new Response('Not found', {
19
+ status: 404
20
+ })
21
+ };
22
+ }
23
+ const now = options.now?.() ?? Date.now();
24
+ const cached = await options.cache.get(entry.cacheKey, now);
25
+ if (cached.state === 'hit' && cached.entry) {
26
+ return {
27
+ state: 'hit',
28
+ entry,
29
+ response: responseFromCacheEntry(cached.entry, 'hit')
30
+ };
31
+ }
32
+ if (cached.state === 'stale' && cached.entry) {
33
+ const regenerate = regenerateEntry(entry, options, request, now);
34
+ if (options.regenerate === 'background') {
35
+ options.schedule?.(regenerate);
36
+ if (!options.schedule) regenerate.catch((error)=>options.onRegenerateError?.(error, entry));
37
+ return {
38
+ state: 'stale',
39
+ entry,
40
+ response: responseFromCacheEntry(cached.entry, 'stale')
41
+ };
42
+ }
43
+ await regenerate;
44
+ const refreshed = await options.cache.get(entry.cacheKey, options.now?.() ?? Date.now());
45
+ return {
46
+ state: 'stale',
47
+ entry,
48
+ response: refreshed.entry ? responseFromCacheEntry(refreshed.entry, 'stale') : responseFromCacheEntry(cached.entry, 'stale')
49
+ };
50
+ }
51
+ if (cached.state === 'error') {
52
+ return {
53
+ state: 'error',
54
+ entry,
55
+ response: new Response('ISR cache error', {
56
+ status: 500
57
+ })
58
+ };
59
+ }
60
+ const rendered = await renderAndStore(entry, options, request, now);
61
+ return {
62
+ state: 'miss',
63
+ entry,
64
+ response: responseFromCacheEntry(rendered, 'miss')
65
+ };
66
+ }
67
+ async function regenerateEntry(entry, options, request, now) {
68
+ try {
69
+ await renderAndStore(entry, options, request, now);
70
+ } catch (error) {
71
+ options.onRegenerateError?.(error, entry);
72
+ throw error;
73
+ }
74
+ }
75
+ async function renderAndStore(entry, options, request, now) {
76
+ const result = await options.render(entry.path, {
77
+ entry,
78
+ request
79
+ });
80
+ const cacheEntry = {
81
+ html: result.html,
82
+ headers: result.headers,
83
+ createdAt: now,
84
+ revalidate: entry.revalidate
85
+ };
86
+ await options.cache.set(entry.cacheKey, cacheEntry);
87
+ return cacheEntry;
88
+ }
89
+ function responseFromCacheEntry(entry, state) {
90
+ const headers = new Headers(entry.headers);
91
+ if (!headers.has('content-type')) {
92
+ headers.set('content-type', 'text/html; charset=utf-8');
93
+ }
94
+ headers.set('x-openelement-isr', state);
95
+ return new Response(entry.html, {
96
+ headers
97
+ });
98
+ }
99
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9jb3JlL3NyYy9pc3ItcnVudGltZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IElzckNhY2hlRW50cnksIElzckNhY2hlUmVzdWx0IH0gZnJvbSAnQG9wZW5lbGVtZW50L3Byb3RvY29sL2lzcic7XG5pbXBvcnQgdHlwZSB7IElzck1hbmlmZXN0RW50cnkgfSBmcm9tICdAb3BlbmVsZW1lbnQvcHJvdG9jb2wvZnJhbWV3b3JrJztcbmltcG9ydCB0eXBlIHsgTWVtb3J5SXNyQ2FjaGUgfSBmcm9tICcuL2lzci5qcyc7XG5cbmV4cG9ydCB0eXBlIElzclJ1bnRpbWVTdGF0ZSA9IElzckNhY2hlUmVzdWx0WydzdGF0ZSddIHwgJ25vdC1mb3VuZCc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgSXNyUnVudGltZVJlbmRlclJlc3VsdCB7XG4gIGh0bWw6IHN0cmluZztcbiAgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSXNyUnVudGltZVJlbmRlckNvbnRleHQge1xuICBlbnRyeTogSXNyTWFuaWZlc3RFbnRyeTtcbiAgcmVxdWVzdD86IFJlcXVlc3Q7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSXNyUnVudGltZU9wdGlvbnMge1xuICBtYW5pZmVzdDogSXNyTWFuaWZlc3RFbnRyeVtdO1xuICBjYWNoZTogTWVtb3J5SXNyQ2FjaGU7XG4gIHJlbmRlcjogKFxuICAgIHBhdGg6IHN0cmluZyxcbiAgICBjb250ZXh0OiBJc3JSdW50aW1lUmVuZGVyQ29udGV4dCxcbiAgKSA9PiBQcm9taXNlPElzclJ1bnRpbWVSZW5kZXJSZXN1bHQ+IHwgSXNyUnVudGltZVJlbmRlclJlc3VsdDtcbiAgbm93PzogKCkgPT4gbnVtYmVyO1xuICByZWdlbmVyYXRlPzogJ2Jsb2NraW5nJyB8ICdiYWNrZ3JvdW5kJztcbiAgb25SZWdlbmVyYXRlRXJyb3I/OiAoZXJyb3I6IHVua25vd24sIGVudHJ5OiBJc3JNYW5pZmVzdEVudHJ5KSA9PiB2b2lkO1xuICBzY2hlZHVsZT86ICh0YXNrOiBQcm9taXNlPHZvaWQ+KSA9PiB2b2lkO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIElzclJ1bnRpbWVSZXN1bHQge1xuICBzdGF0ZTogSXNyUnVudGltZVN0YXRlO1xuICBlbnRyeT86IElzck1hbmlmZXN0RW50cnk7XG4gIHJlc3BvbnNlOiBSZXNwb25zZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZpbmRJc3JNYW5pZmVzdEVudHJ5KFxuICBtYW5pZmVzdDogSXNyTWFuaWZlc3RFbnRyeVtdLFxuICBwYXRoOiBzdHJpbmcsXG4gIHBhcmFtczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9LFxuKTogSXNyTWFuaWZlc3RFbnRyeSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHBhcmFtRW50cmllcyA9IE9iamVjdC5lbnRyaWVzKHBhcmFtcykuc29ydCgoW2FdLCBbYl0pID0+IGEubG9jYWxlQ29tcGFyZShiKSk7XG4gIHJldHVybiBtYW5pZmVzdC5maW5kKChlbnRyeSkgPT4ge1xuICAgIGlmIChlbnRyeS5wYXRoICE9PSBwYXRoKSByZXR1cm4gZmFsc2U7XG4gICAgY29uc3QgZW50cnlQYXJhbXMgPSBPYmplY3QuZW50cmllcyhlbnRyeS5wYXJhbXMpLnNvcnQoKFthXSwgW2JdKSA9PiBhLmxvY2FsZUNvbXBhcmUoYikpO1xuICAgIGlmIChlbnRyeVBhcmFtcy5sZW5ndGggIT09IHBhcmFtRW50cmllcy5sZW5ndGgpIHJldHVybiBmYWxzZTtcbiAgICByZXR1cm4gZW50cnlQYXJhbXMuZXZlcnkoKFtrZXksIHZhbHVlXSwgaW5kZXgpID0+IHtcbiAgICAgIGNvbnN0IFtvdGhlcktleSwgb3RoZXJWYWx1ZV0gPSBwYXJhbUVudHJpZXNbaW5kZXhdO1xuICAgICAgcmV0dXJuIGtleSA9PT0gb3RoZXJLZXkgJiYgdmFsdWUgPT09IG90aGVyVmFsdWU7XG4gICAgfSk7XG4gIH0pO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVuZGVySXNyUmVzcG9uc2UoXG4gIHBhdGg6IHN0cmluZyxcbiAgcGFyYW1zOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+LFxuICBvcHRpb25zOiBJc3JSdW50aW1lT3B0aW9ucyxcbiAgcmVxdWVzdD86IFJlcXVlc3QsXG4pOiBQcm9taXNlPElzclJ1bnRpbWVSZXN1bHQ+IHtcbiAgY29uc3QgZW50cnkgPSBmaW5kSXNyTWFuaWZlc3RFbnRyeShvcHRpb25zLm1hbmlmZXN0LCBwYXRoLCBwYXJhbXMpO1xuICBpZiAoIWVudHJ5KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHN0YXRlOiAnbm90LWZvdW5kJyxcbiAgICAgIHJlc3BvbnNlOiBuZXcgUmVzcG9uc2UoJ05vdCBmb3VuZCcsIHsgc3RhdHVzOiA0MDQgfSksXG4gICAgfTtcbiAgfVxuXG4gIGNvbnN0IG5vdyA9IG9wdGlvbnMubm93Py4oKSA/PyBEYXRlLm5vdygpO1xuICBjb25zdCBjYWNoZWQgPSBhd2FpdCBvcHRpb25zLmNhY2hlLmdldChlbnRyeS5jYWNoZUtleSwgbm93KTtcbiAgaWYgKGNhY2hlZC5zdGF0ZSA9PT0gJ2hpdCcgJiYgY2FjaGVkLmVudHJ5KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHN0YXRlOiAnaGl0JyxcbiAgICAgIGVudHJ5LFxuICAgICAgcmVzcG9uc2U6IHJlc3BvbnNlRnJvbUNhY2hlRW50cnkoY2FjaGVkLmVudHJ5LCAnaGl0JyksXG4gICAgfTtcbiAgfVxuXG4gIGlmIChjYWNoZWQuc3RhdGUgPT09ICdzdGFsZScgJiYgY2FjaGVkLmVudHJ5KSB7XG4gICAgY29uc3QgcmVnZW5lcmF0ZSA9IHJlZ2VuZXJhdGVFbnRyeShlbnRyeSwgb3B0aW9ucywgcmVxdWVzdCwgbm93KTtcbiAgICBpZiAob3B0aW9ucy5yZWdlbmVyYXRlID09PSAnYmFja2dyb3VuZCcpIHtcbiAgICAgIG9wdGlvbnMuc2NoZWR1bGU/LihyZWdlbmVyYXRlKTtcbiAgICAgIGlmICghb3B0aW9ucy5zY2hlZHVsZSkgcmVnZW5lcmF0ZS5jYXRjaCgoZXJyb3IpID0+IG9wdGlvbnMub25SZWdlbmVyYXRlRXJyb3I/LihlcnJvciwgZW50cnkpKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN0YXRlOiAnc3RhbGUnLFxuICAgICAgICBlbnRyeSxcbiAgICAgICAgcmVzcG9uc2U6IHJlc3BvbnNlRnJvbUNhY2hlRW50cnkoY2FjaGVkLmVudHJ5LCAnc3RhbGUnKSxcbiAgICAgIH07XG4gICAgfVxuICAgIGF3YWl0IHJlZ2VuZXJhdGU7XG4gICAgY29uc3QgcmVmcmVzaGVkID0gYXdhaXQgb3B0aW9ucy5jYWNoZS5nZXQoZW50cnkuY2FjaGVLZXksIG9wdGlvbnMubm93Py4oKSA/PyBEYXRlLm5vdygpKTtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdGU6ICdzdGFsZScsXG4gICAgICBlbnRyeSxcbiAgICAgIHJlc3BvbnNlOiByZWZyZXNoZWQuZW50cnlcbiAgICAgICAgPyByZXNwb25zZUZyb21DYWNoZUVudHJ5KHJlZnJlc2hlZC5lbnRyeSwgJ3N0YWxlJylcbiAgICAgICAgOiByZXNwb25zZUZyb21DYWNoZUVudHJ5KGNhY2hlZC5lbnRyeSwgJ3N0YWxlJyksXG4gICAgfTtcbiAgfVxuXG4gIGlmIChjYWNoZWQuc3RhdGUgPT09ICdlcnJvcicpIHtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdGU6ICdlcnJvcicsXG4gICAgICBlbnRyeSxcbiAgICAgIHJlc3BvbnNlOiBuZXcgUmVzcG9uc2UoJ0lTUiBjYWNoZSBlcnJvcicsIHsgc3RhdHVzOiA1MDAgfSksXG4gICAgfTtcbiAgfVxuXG4gIGNvbnN0IHJlbmRlcmVkID0gYXdhaXQgcmVuZGVyQW5kU3RvcmUoZW50cnksIG9wdGlvbnMsIHJlcXVlc3QsIG5vdyk7XG4gIHJldHVybiB7XG4gICAgc3RhdGU6ICdtaXNzJyxcbiAgICBlbnRyeSxcbiAgICByZXNwb25zZTogcmVzcG9uc2VGcm9tQ2FjaGVFbnRyeShyZW5kZXJlZCwgJ21pc3MnKSxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVnZW5lcmF0ZUVudHJ5KFxuICBlbnRyeTogSXNyTWFuaWZlc3RFbnRyeSxcbiAgb3B0aW9uczogSXNyUnVudGltZU9wdGlvbnMsXG4gIHJlcXVlc3Q6IFJlcXVlc3QgfCB1bmRlZmluZWQsXG4gIG5vdzogbnVtYmVyLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIHRyeSB7XG4gICAgYXdhaXQgcmVuZGVyQW5kU3RvcmUoZW50cnksIG9wdGlvbnMsIHJlcXVlc3QsIG5vdyk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgb3B0aW9ucy5vblJlZ2VuZXJhdGVFcnJvcj8uKGVycm9yLCBlbnRyeSk7XG4gICAgdGhyb3cgZXJyb3I7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVuZGVyQW5kU3RvcmUoXG4gIGVudHJ5OiBJc3JNYW5pZmVzdEVudHJ5LFxuICBvcHRpb25zOiBJc3JSdW50aW1lT3B0aW9ucyxcbiAgcmVxdWVzdDogUmVxdWVzdCB8IHVuZGVmaW5lZCxcbiAgbm93OiBudW1iZXIsXG4pOiBQcm9taXNlPElzckNhY2hlRW50cnk+IHtcbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgb3B0aW9ucy5yZW5kZXIoZW50cnkucGF0aCwgeyBlbnRyeSwgcmVxdWVzdCB9KTtcbiAgY29uc3QgY2FjaGVFbnRyeTogSXNyQ2FjaGVFbnRyeSA9IHtcbiAgICBodG1sOiByZXN1bHQuaHRtbCxcbiAgICBoZWFkZXJzOiByZXN1bHQuaGVhZGVycyxcbiAgICBjcmVhdGVkQXQ6IG5vdyxcbiAgICByZXZhbGlkYXRlOiBlbnRyeS5yZXZhbGlkYXRlLFxuICB9O1xuICBhd2FpdCBvcHRpb25zLmNhY2hlLnNldChlbnRyeS5jYWNoZUtleSwgY2FjaGVFbnRyeSk7XG4gIHJldHVybiBjYWNoZUVudHJ5O1xufVxuXG5mdW5jdGlvbiByZXNwb25zZUZyb21DYWNoZUVudHJ5KGVudHJ5OiBJc3JDYWNoZUVudHJ5LCBzdGF0ZTogSXNyQ2FjaGVSZXN1bHRbJ3N0YXRlJ10pOiBSZXNwb25zZSB7XG4gIGNvbnN0IGhlYWRlcnMgPSBuZXcgSGVhZGVycyhlbnRyeS5oZWFkZXJzKTtcbiAgaWYgKCFoZWFkZXJzLmhhcygnY29udGVudC10eXBlJykpIHtcbiAgICBoZWFkZXJzLnNldCgnY29udGVudC10eXBlJywgJ3RleHQvaHRtbDsgY2hhcnNldD11dGYtOCcpO1xuICB9XG4gIGhlYWRlcnMuc2V0KCd4LW9wZW5lbGVtZW50LWlzcicsIHN0YXRlKTtcbiAgcmV0dXJuIG5ldyBSZXNwb25zZShlbnRyeS5odG1sLCB7IGhlYWRlcnMgfSk7XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBbUNBLE9BQU8sU0FBUyxxQkFDZCxRQUE0QixFQUM1QixJQUFZLEVBQ1osU0FBaUMsQ0FBQyxDQUFDO0VBRW5DLE1BQU0sZUFBZSxPQUFPLE9BQU8sQ0FBQyxRQUFRLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxHQUFLLEVBQUUsYUFBYSxDQUFDO0VBQy9FLE9BQU8sU0FBUyxJQUFJLENBQUMsQ0FBQztJQUNwQixJQUFJLE1BQU0sSUFBSSxLQUFLLE1BQU0sT0FBTztJQUNoQyxNQUFNLGNBQWMsT0FBTyxPQUFPLENBQUMsTUFBTSxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEdBQUssRUFBRSxhQUFhLENBQUM7SUFDcEYsSUFBSSxZQUFZLE1BQU0sS0FBSyxhQUFhLE1BQU0sRUFBRSxPQUFPO0lBQ3ZELE9BQU8sWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssTUFBTSxFQUFFO01BQ3RDLE1BQU0sQ0FBQyxVQUFVLFdBQVcsR0FBRyxZQUFZLENBQUMsTUFBTTtNQUNsRCxPQUFPLFFBQVEsWUFBWSxVQUFVO0lBQ3ZDO0VBQ0Y7QUFDRjtBQUVBLE9BQU8sZUFBZSxrQkFDcEIsSUFBWSxFQUNaLE1BQThCLEVBQzlCLE9BQTBCLEVBQzFCLE9BQWlCO0VBRWpCLE1BQU0sUUFBUSxxQkFBcUIsUUFBUSxRQUFRLEVBQUUsTUFBTTtFQUMzRCxJQUFJLENBQUMsT0FBTztJQUNWLE9BQU87TUFDTCxPQUFPO01BQ1AsVUFBVSxJQUFJLFNBQVMsYUFBYTtRQUFFLFFBQVE7TUFBSTtJQUNwRDtFQUNGO0VBRUEsTUFBTSxNQUFNLFFBQVEsR0FBRyxRQUFRLEtBQUssR0FBRztFQUN2QyxNQUFNLFNBQVMsTUFBTSxRQUFRLEtBQUssQ0FBQyxHQUFHLENBQUMsTUFBTSxRQUFRLEVBQUU7RUFDdkQsSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTLE9BQU8sS0FBSyxFQUFFO0lBQzFDLE9BQU87TUFDTCxPQUFPO01BQ1A7TUFDQSxVQUFVLHVCQUF1QixPQUFPLEtBQUssRUFBRTtJQUNqRDtFQUNGO0VBRUEsSUFBSSxPQUFPLEtBQUssS0FBSyxXQUFXLE9BQU8sS0FBSyxFQUFFO0lBQzVDLE1BQU0sYUFBYSxnQkFBZ0IsT0FBTyxTQUFTLFNBQVM7SUFDNUQsSUFBSSxRQUFRLFVBQVUsS0FBSyxjQUFjO01BQ3ZDLFFBQVEsUUFBUSxHQUFHO01BQ25CLElBQUksQ0FBQyxRQUFRLFFBQVEsRUFBRSxXQUFXLEtBQUssQ0FBQyxDQUFDLFFBQVUsUUFBUSxpQkFBaUIsR0FBRyxPQUFPO01BQ3RGLE9BQU87UUFDTCxPQUFPO1FBQ1A7UUFDQSxVQUFVLHVCQUF1QixPQUFPLEtBQUssRUFBRTtNQUNqRDtJQUNGO0lBQ0EsTUFBTTtJQUNOLE1BQU0sWUFBWSxNQUFNLFFBQVEsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLFFBQVEsRUFBRSxRQUFRLEdBQUcsUUFBUSxLQUFLLEdBQUc7SUFDckYsT0FBTztNQUNMLE9BQU87TUFDUDtNQUNBLFVBQVUsVUFBVSxLQUFLLEdBQ3JCLHVCQUF1QixVQUFVLEtBQUssRUFBRSxXQUN4Qyx1QkFBdUIsT0FBTyxLQUFLLEVBQUU7SUFDM0M7RUFDRjtFQUVBLElBQUksT0FBTyxLQUFLLEtBQUssU0FBUztJQUM1QixPQUFPO01BQ0wsT0FBTztNQUNQO01BQ0EsVUFBVSxJQUFJLFNBQVMsbUJBQW1CO1FBQUUsUUFBUTtNQUFJO0lBQzFEO0VBQ0Y7RUFFQSxNQUFNLFdBQVcsTUFBTSxlQUFlLE9BQU8sU0FBUyxTQUFTO0VBQy9ELE9BQU87SUFDTCxPQUFPO0lBQ1A7SUFDQSxVQUFVLHVCQUF1QixVQUFVO0VBQzdDO0FBQ0Y7QUFFQSxlQUFlLGdCQUNiLEtBQXVCLEVBQ3ZCLE9BQTBCLEVBQzFCLE9BQTRCLEVBQzVCLEdBQVc7RUFFWCxJQUFJO0lBQ0YsTUFBTSxlQUFlLE9BQU8sU0FBUyxTQUFTO0VBQ2hELEVBQUUsT0FBTyxPQUFPO0lBQ2QsUUFBUSxpQkFBaUIsR0FBRyxPQUFPO0lBQ25DLE1BQU07RUFDUjtBQUNGO0FBRUEsZUFBZSxlQUNiLEtBQXVCLEVBQ3ZCLE9BQTBCLEVBQzFCLE9BQTRCLEVBQzVCLEdBQVc7RUFFWCxNQUFNLFNBQVMsTUFBTSxRQUFRLE1BQU0sQ0FBQyxNQUFNLElBQUksRUFBRTtJQUFFO0lBQU87RUFBUTtFQUNqRSxNQUFNLGFBQTRCO0lBQ2hDLE1BQU0sT0FBTyxJQUFJO0lBQ2pCLFNBQVMsT0FBTyxPQUFPO0lBQ3ZCLFdBQVc7SUFDWCxZQUFZLE1BQU0sVUFBVTtFQUM5QjtFQUNBLE1BQU0sUUFBUSxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQU0sUUFBUSxFQUFFO0VBQ3hDLE9BQU87QUFDVDtBQUVBLFNBQVMsdUJBQXVCLEtBQW9CLEVBQUUsS0FBOEI7RUFDbEYsTUFBTSxVQUFVLElBQUksUUFBUSxNQUFNLE9BQU87RUFDekMsSUFBSSxDQUFDLFFBQVEsR0FBRyxDQUFDLGlCQUFpQjtJQUNoQyxRQUFRLEdBQUcsQ0FBQyxnQkFBZ0I7RUFDOUI7RUFDQSxRQUFRLEdBQUcsQ0FBQyxxQkFBcUI7RUFDakMsT0FBTyxJQUFJLFNBQVMsTUFBTSxJQUFJLEVBQUU7SUFBRTtFQUFRO0FBQzVDIn0=
package/src/isr.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @openelement/core - route-level ISR cache primitives.
3
+ *
4
+ * v0.22: Platform adapters (CF Workers KV, Deno KV).
5
+ *
6
+ * Architecture:
7
+ * 1. Build: SSG produces static HTML + isr-manifest.json
8
+ * 2. Runtime: Edge handler checks cache before serving static
9
+ * 4. Stale: serve cached HTML + async background regeneration
10
+ *
11
+ * MemoryIsrCache is the reference in-memory implementation. Production adapters
12
+ * (Cloudflare Workers KV, Deno KV) are v0.22 scope.
13
+ */ import type { CacheAdapter, CacheEntry, IsrCacheEntry, IsrCacheResult, IsrCacheState, IsrRouteConfig } from '@openelement/protocol/isr';
14
+ import type { IsrManifestEntry } from '@openelement/protocol/framework';
15
+ export type { CacheAdapter, CacheEntry, IsrCacheEntry, IsrCacheResult, IsrCacheState, IsrManifestEntry, IsrRouteConfig };
16
+ export declare function isIsrRouteConfig(value: unknown): value is IsrRouteConfig;
17
+ export declare function createIsrCacheKey(routePath: string, params?: Record<string, string>): string;
18
+ export declare class MemoryIsrCache {
19
+ get(key: string, now?: number): IsrCacheResult;
20
+ set(key: string, entry: IsrCacheEntry): void;
21
+ delete(key: string): void;
22
+ }
package/src/isr.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @openelement/core - route-level ISR cache primitives.
3
+ *
4
+ * v0.22: Platform adapters (CF Workers KV, Deno KV).
5
+ *
6
+ * Architecture:
7
+ * 1. Build: SSG produces static HTML + isr-manifest.json
8
+ * 2. Runtime: Edge handler checks cache before serving static
9
+ * 4. Stale: serve cached HTML + async background regeneration
10
+ *
11
+ * MemoryIsrCache is the reference in-memory implementation. Production adapters
12
+ * (Cloudflare Workers KV, Deno KV) are v0.22 scope.
13
+ */ export function isIsrRouteConfig(value) {
14
+ return typeof value === 'object' && value !== null && typeof value.revalidate === 'number' && Number.isFinite(value.revalidate) && value.revalidate > 0;
15
+ }
16
+ export function createIsrCacheKey(routePath, params = {}) {
17
+ const sortedParams = Object.entries(params).sort(([a], [b])=>a.localeCompare(b));
18
+ const suffix = sortedParams.length === 0 ? '' : '?' + sortedParams.map(([key, value])=>`${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&');
19
+ return `openelement:isr:${routePath}${suffix}`;
20
+ }
21
+ export class MemoryIsrCache {
22
+ #entries = new Map();
23
+ get(key, now = Date.now()) {
24
+ const entry = this.#entries.get(key);
25
+ if (!entry) return {
26
+ state: 'miss'
27
+ };
28
+ const ageSeconds = Math.max(0, Math.floor((now - entry.createdAt) / 1000));
29
+ return {
30
+ state: ageSeconds >= entry.revalidate ? 'stale' : 'hit',
31
+ entry
32
+ };
33
+ }
34
+ set(key, entry) {
35
+ this.#entries.set(key, entry);
36
+ }
37
+ delete(key) {
38
+ this.#entries.delete(key);
39
+ }
40
+ }
41
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9jb3JlL3NyYy9pc3IudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAb3BlbmVsZW1lbnQvY29yZSAtIHJvdXRlLWxldmVsIElTUiBjYWNoZSBwcmltaXRpdmVzLlxuICpcbiAqIHYwLjIyOiBQbGF0Zm9ybSBhZGFwdGVycyAoQ0YgV29ya2VycyBLViwgRGVubyBLVikuXG4gKlxuICogQXJjaGl0ZWN0dXJlOlxuICogICAxLiBCdWlsZDogU1NHIHByb2R1Y2VzIHN0YXRpYyBIVE1MICsgaXNyLW1hbmlmZXN0Lmpzb25cbiAqICAgMi4gUnVudGltZTogRWRnZSBoYW5kbGVyIGNoZWNrcyBjYWNoZSBiZWZvcmUgc2VydmluZyBzdGF0aWNcbiAqICAgNC4gU3RhbGU6IHNlcnZlIGNhY2hlZCBIVE1MICsgYXN5bmMgYmFja2dyb3VuZCByZWdlbmVyYXRpb25cbiAqXG4gKiBNZW1vcnlJc3JDYWNoZSBpcyB0aGUgcmVmZXJlbmNlIGluLW1lbW9yeSBpbXBsZW1lbnRhdGlvbi4gUHJvZHVjdGlvbiBhZGFwdGVyc1xuICogKENsb3VkZmxhcmUgV29ya2VycyBLViwgRGVubyBLVikgYXJlIHYwLjIyIHNjb3BlLlxuICovXG5cbmltcG9ydCB0eXBlIHtcbiAgQ2FjaGVBZGFwdGVyLFxuICBDYWNoZUVudHJ5LFxuICBJc3JDYWNoZUVudHJ5LFxuICBJc3JDYWNoZVJlc3VsdCxcbiAgSXNyQ2FjaGVTdGF0ZSxcbiAgSXNyUm91dGVDb25maWcsXG59IGZyb20gJ0BvcGVuZWxlbWVudC9wcm90b2NvbC9pc3InO1xuaW1wb3J0IHR5cGUgeyBJc3JNYW5pZmVzdEVudHJ5IH0gZnJvbSAnQG9wZW5lbGVtZW50L3Byb3RvY29sL2ZyYW1ld29yayc7XG5leHBvcnQgdHlwZSB7XG4gIENhY2hlQWRhcHRlcixcbiAgQ2FjaGVFbnRyeSxcbiAgSXNyQ2FjaGVFbnRyeSxcbiAgSXNyQ2FjaGVSZXN1bHQsXG4gIElzckNhY2hlU3RhdGUsXG4gIElzck1hbmlmZXN0RW50cnksXG4gIElzclJvdXRlQ29uZmlnLFxufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGlzSXNyUm91dGVDb25maWcodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBJc3JSb3V0ZUNvbmZpZyB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICE9PSBudWxsICYmXG4gICAgdHlwZW9mICh2YWx1ZSBhcyBJc3JSb3V0ZUNvbmZpZykucmV2YWxpZGF0ZSA9PT0gJ251bWJlcicgJiZcbiAgICBOdW1iZXIuaXNGaW5pdGUoKHZhbHVlIGFzIElzclJvdXRlQ29uZmlnKS5yZXZhbGlkYXRlKSAmJlxuICAgICh2YWx1ZSBhcyBJc3JSb3V0ZUNvbmZpZykucmV2YWxpZGF0ZSA+IDA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVJc3JDYWNoZUtleShcbiAgcm91dGVQYXRoOiBzdHJpbmcsXG4gIHBhcmFtczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9LFxuKTogc3RyaW5nIHtcbiAgY29uc3Qgc29ydGVkUGFyYW1zID0gT2JqZWN0LmVudHJpZXMocGFyYW1zKS5zb3J0KChbYV0sIFtiXSkgPT4gYS5sb2NhbGVDb21wYXJlKGIpKTtcbiAgY29uc3Qgc3VmZml4ID0gc29ydGVkUGFyYW1zLmxlbmd0aCA9PT0gMCA/ICcnIDogJz8nICtcbiAgICBzb3J0ZWRQYXJhbXMubWFwKChba2V5LCB2YWx1ZV0pID0+IGAke2VuY29kZVVSSUNvbXBvbmVudChrZXkpfT0ke2VuY29kZVVSSUNvbXBvbmVudCh2YWx1ZSl9YClcbiAgICAgIC5qb2luKCcmJyk7XG4gIHJldHVybiBgb3BlbmVsZW1lbnQ6aXNyOiR7cm91dGVQYXRofSR7c3VmZml4fWA7XG59XG5cbmV4cG9ydCBjbGFzcyBNZW1vcnlJc3JDYWNoZSB7XG4gIHJlYWRvbmx5ICNlbnRyaWVzID0gbmV3IE1hcDxzdHJpbmcsIElzckNhY2hlRW50cnk+KCk7XG5cbiAgZ2V0KGtleTogc3RyaW5nLCBub3c6IG51bWJlciA9IERhdGUubm93KCkpOiBJc3JDYWNoZVJlc3VsdCB7XG4gICAgY29uc3QgZW50cnkgPSB0aGlzLiNlbnRyaWVzLmdldChrZXkpO1xuICAgIGlmICghZW50cnkpIHJldHVybiB7IHN0YXRlOiAnbWlzcycgfTtcbiAgICBjb25zdCBhZ2VTZWNvbmRzID0gTWF0aC5tYXgoMCwgTWF0aC5mbG9vcigobm93IC0gZW50cnkuY3JlYXRlZEF0KSAvIDEwMDApKTtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdGU6IGFnZVNlY29uZHMgPj0gZW50cnkucmV2YWxpZGF0ZSA/ICdzdGFsZScgOiAnaGl0JyxcbiAgICAgIGVudHJ5LFxuICAgIH07XG4gIH1cblxuICBzZXQoa2V5OiBzdHJpbmcsIGVudHJ5OiBJc3JDYWNoZUVudHJ5KTogdm9pZCB7XG4gICAgdGhpcy4jZW50cmllcy5zZXQoa2V5LCBlbnRyeSk7XG4gIH1cblxuICBkZWxldGUoa2V5OiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLiNlbnRyaWVzLmRlbGV0ZShrZXkpO1xuICB9XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7OztDQVlDLEdBcUJELE9BQU8sU0FBUyxpQkFBaUIsS0FBYztFQUM3QyxPQUFPLE9BQU8sVUFBVSxZQUFZLFVBQVUsUUFDNUMsT0FBTyxBQUFDLE1BQXlCLFVBQVUsS0FBSyxZQUNoRCxPQUFPLFFBQVEsQ0FBQyxBQUFDLE1BQXlCLFVBQVUsS0FDcEQsQUFBQyxNQUF5QixVQUFVLEdBQUc7QUFDM0M7QUFFQSxPQUFPLFNBQVMsa0JBQ2QsU0FBaUIsRUFDakIsU0FBaUMsQ0FBQyxDQUFDO0VBRW5DLE1BQU0sZUFBZSxPQUFPLE9BQU8sQ0FBQyxRQUFRLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxHQUFLLEVBQUUsYUFBYSxDQUFDO0VBQy9FLE1BQU0sU0FBUyxhQUFhLE1BQU0sS0FBSyxJQUFJLEtBQUssTUFDOUMsYUFBYSxHQUFHLENBQUMsQ0FBQyxDQUFDLEtBQUssTUFBTSxHQUFLLEdBQUcsbUJBQW1CLEtBQUssQ0FBQyxFQUFFLG1CQUFtQixRQUFRLEVBQ3pGLElBQUksQ0FBQztFQUNWLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxZQUFZLFFBQVE7QUFDaEQ7QUFFQSxPQUFPLE1BQU07RUFDRixDQUFBLE9BQVEsR0FBRyxJQUFJLE1BQTZCO0VBRXJELElBQUksR0FBVyxFQUFFLE1BQWMsS0FBSyxHQUFHLEVBQUUsRUFBa0I7SUFDekQsTUFBTSxRQUFRLElBQUksQ0FBQyxDQUFBLE9BQVEsQ0FBQyxHQUFHLENBQUM7SUFDaEMsSUFBSSxDQUFDLE9BQU8sT0FBTztNQUFFLE9BQU87SUFBTztJQUNuQyxNQUFNLGFBQWEsS0FBSyxHQUFHLENBQUMsR0FBRyxLQUFLLEtBQUssQ0FBQyxDQUFDLE1BQU0sTUFBTSxTQUFTLElBQUk7SUFDcEUsT0FBTztNQUNMLE9BQU8sY0FBYyxNQUFNLFVBQVUsR0FBRyxVQUFVO01BQ2xEO0lBQ0Y7RUFDRjtFQUVBLElBQUksR0FBVyxFQUFFLEtBQW9CLEVBQVE7SUFDM0MsSUFBSSxDQUFDLENBQUEsT0FBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLO0VBQ3pCO0VBRUEsT0FBTyxHQUFXLEVBQVE7SUFDeEIsSUFBSSxDQUFDLENBQUEsT0FBUSxDQUFDLE1BQU0sQ0FBQztFQUN2QjtBQUNGIn0=
@@ -0,0 +1,22 @@
1
+ import type { Signal } from '@openelement/protocol/signal';
2
+ import type { BindingDescriptor, BindingLifecycle } from "./binding-descriptor.js";
3
+ /**
4
+ * Collect BindingDescriptor objects from a JSX props object.
5
+ *
6
+ * @param el - Target element the descriptors will apply to.
7
+ * @param props - VNode props.
8
+ * @param signalRegistry - Optional registry used to name signals for hydration markers.
9
+ * @returns Array of binding descriptors.
10
+ */ export declare function collectPropBindings(el: Element, props: Record<string, unknown>, signalRegistry?: Map<string, Signal<unknown>>): BindingDescriptor[];
11
+ /**
12
+ * Apply a props object to a real DOM element.
13
+ */ export declare function applyProps(el: Element, props: Record<string, unknown>, lifecycle?: BindingLifecycle, signalRegistry?: Map<string, Signal<unknown>>): void;
14
+ /**
15
+ * Render a VNode tree to a real DOM node.
16
+ *
17
+ * @param node - VNode, string, number, or null/undefined
18
+ * @param lifecycle - Optional BindingLifecycle for automatic cleanup
19
+ * @param disposers - Optional Set to collect effect dispose fns (backward compat)
20
+ * @param signalRegistry - Optional registry used to resolve signal names for markers
21
+ * @returns DOM Node (Element, Text, or DocumentFragment)
22
+ */ export declare function renderToDom(node: unknown, lifecycle?: BindingLifecycle, disposers?: Set<() => void>, signalRegistry?: Map<string, Signal<unknown>>): Node;