@lightspeed-js/core 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 (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/compile.js +1494 -0
  4. package/dist/index.js +3060 -0
  5. package/dist/ssg.js +3620 -0
  6. package/dist/types/build.d.ts +1 -0
  7. package/dist/types/index.d.ts +8 -0
  8. package/dist/types/src/agent/index.d.ts +175 -0
  9. package/dist/types/src/cli/commands/agent.d.ts +9 -0
  10. package/dist/types/src/cli/commands/build.d.ts +2 -0
  11. package/dist/types/src/cli/commands/create.d.ts +2 -0
  12. package/dist/types/src/cli/commands/dev.d.ts +2 -0
  13. package/dist/types/src/cli/commands/index.d.ts +3 -0
  14. package/dist/types/src/cli/index.d.ts +2 -0
  15. package/dist/types/src/cli/types/commands.d.ts +11 -0
  16. package/dist/types/src/cli/types/index.d.ts +1 -0
  17. package/dist/types/src/cli/utils/commands.d.ts +5 -0
  18. package/dist/types/src/cli/utils/index.d.ts +1 -0
  19. package/dist/types/src/compile/expressions.d.ts +76 -0
  20. package/dist/types/src/compile/index.d.ts +44 -0
  21. package/dist/types/src/compile/logic.d.ts +101 -0
  22. package/dist/types/src/manifest.d.ts +148 -0
  23. package/dist/types/src/marker.d.ts +85 -0
  24. package/dist/types/src/parser/index.d.ts +2 -0
  25. package/dist/types/src/parser/layout.d.ts +32 -0
  26. package/dist/types/src/parser/lsp.d.ts +56 -0
  27. package/dist/types/src/parser/route.d.ts +87 -0
  28. package/dist/types/src/parser/style-source-node.d.ts +19 -0
  29. package/dist/types/src/parser/style.d.ts +20 -0
  30. package/dist/types/src/parser/template.d.ts +32 -0
  31. package/dist/types/src/parser/test.d.ts +1 -0
  32. package/dist/types/src/runtime/bindings/BindingEvaluator.d.ts +174 -0
  33. package/dist/types/src/runtime/bindings/index.d.ts +2 -0
  34. package/dist/types/src/runtime/compiler/ImportTransformer.d.ts +11 -0
  35. package/dist/types/src/runtime/compiler/index.d.ts +1 -0
  36. package/dist/types/src/runtime/components/ComponentInstance.d.ts +110 -0
  37. package/dist/types/src/runtime/components/ComponentRegistry.d.ts +11 -0
  38. package/dist/types/src/runtime/components/index.d.ts +3 -0
  39. package/dist/types/src/runtime/components/types/index.d.ts +34 -0
  40. package/dist/types/src/runtime/core.d.ts +251 -0
  41. package/dist/types/src/runtime/diagnostics.d.ts +33 -0
  42. package/dist/types/src/runtime/dom/ForBlock.d.ts +127 -0
  43. package/dist/types/src/runtime/dom/Mounter.d.ts +223 -0
  44. package/dist/types/src/runtime/dom/StyleManager.d.ts +57 -0
  45. package/dist/types/src/runtime/dom/hydration.d.ts +35 -0
  46. package/dist/types/src/runtime/dom/index.d.ts +7 -0
  47. package/dist/types/src/runtime/events/EventBinder.d.ts +18 -0
  48. package/dist/types/src/runtime/events/index.d.ts +1 -0
  49. package/dist/types/src/runtime/globals/GlobalFunctions.d.ts +24 -0
  50. package/dist/types/src/runtime/globals/index.d.ts +1 -0
  51. package/dist/types/src/runtime/helpers.d.ts +141 -0
  52. package/dist/types/src/runtime/index.d.ts +16 -0
  53. package/dist/types/src/runtime/reactive/ReactiveProxy.d.ts +63 -0
  54. package/dist/types/src/runtime/reactive/UpdateScheduler.d.ts +9 -0
  55. package/dist/types/src/runtime/reactive/index.d.ts +2 -0
  56. package/dist/types/src/runtime/signals/SignalManager.d.ts +10 -0
  57. package/dist/types/src/runtime/signals/index.d.ts +3 -0
  58. package/dist/types/src/runtime/signals/signals.d.ts +16 -0
  59. package/dist/types/src/runtime/signals/types/index.d.ts +1 -0
  60. package/dist/types/src/runtime/signals/types/signals.d.ts +7 -0
  61. package/dist/types/src/runtime/state/Cell.d.ts +68 -0
  62. package/dist/types/src/runtime/state/CellManager.d.ts +101 -0
  63. package/dist/types/src/runtime/state/index.d.ts +4 -0
  64. package/dist/types/src/specifiers.d.ts +16 -0
  65. package/dist/types/src/ssg/index.d.ts +21 -0
  66. package/dist/types/src/ssg/rebuild.d.ts +49 -0
  67. package/dist/types/src/ssg/render.d.ts +104 -0
  68. package/dist/types/src/types/lsp.d.ts +22 -0
  69. package/dist/types/src/types/optional.d.ts +14 -0
  70. package/dist/types/src/types/virtual.d.ts +4 -0
  71. package/dist/types/src/vite/csp.d.ts +12 -0
  72. package/dist/types/src/vite/index.d.ts +2 -0
  73. package/dist/types/src/vite/layout.d.ts +17 -0
  74. package/dist/types/src/vite/plugin.d.ts +69 -0
  75. package/dist/types/src/vite/routes.d.ts +139 -0
  76. package/dist/types/test/adopted-styles.test.d.ts +1 -0
  77. package/dist/types/test/adoption.test.d.ts +1 -0
  78. package/dist/types/test/agent.test.d.ts +1 -0
  79. package/dist/types/test/audit.test.d.ts +1 -0
  80. package/dist/types/test/bindings.test.d.ts +1 -0
  81. package/dist/types/test/branches-in-loops.test.d.ts +1 -0
  82. package/dist/types/test/build.test.d.ts +1 -0
  83. package/dist/types/test/buildtime.test.d.ts +1 -0
  84. package/dist/types/test/built.test.d.ts +1 -0
  85. package/dist/types/test/cell-props.test.d.ts +1 -0
  86. package/dist/types/test/comments.test.d.ts +1 -0
  87. package/dist/types/test/compile-expressions.test.d.ts +1 -0
  88. package/dist/types/test/compile-logic.test.d.ts +1 -0
  89. package/dist/types/test/components.test.d.ts +1 -0
  90. package/dist/types/test/composition.test.d.ts +1 -0
  91. package/dist/types/test/declared-needs.test.d.ts +1 -0
  92. package/dist/types/test/diagnostics.test.d.ts +1 -0
  93. package/dist/types/test/dynamic.test.d.ts +1 -0
  94. package/dist/types/test/example.test.d.ts +1 -0
  95. package/dist/types/test/faq.test.d.ts +1 -0
  96. package/dist/types/test/flatten-nested.test.d.ts +1 -0
  97. package/dist/types/test/html-binding.test.d.ts +1 -0
  98. package/dist/types/test/hydration-report.test.d.ts +1 -0
  99. package/dist/types/test/hydration.test.d.ts +1 -0
  100. package/dist/types/test/layout-resolution.test.d.ts +1 -0
  101. package/dist/types/test/layout.test.d.ts +1 -0
  102. package/dist/types/test/lazy.test.d.ts +1 -0
  103. package/dist/types/test/manifest.test.d.ts +1 -0
  104. package/dist/types/test/model-props.test.d.ts +1 -0
  105. package/dist/types/test/model.test.d.ts +1 -0
  106. package/dist/types/test/mounting.test.d.ts +1 -0
  107. package/dist/types/test/needs.test.d.ts +1 -0
  108. package/dist/types/test/nested-rows.test.d.ts +1 -0
  109. package/dist/types/test/on-mounted.test.d.ts +1 -0
  110. package/dist/types/test/on-update.test.d.ts +1 -0
  111. package/dist/types/test/page-island.test.d.ts +1 -0
  112. package/dist/types/test/pages.test.d.ts +1 -0
  113. package/dist/types/test/reactivity.test.d.ts +1 -0
  114. package/dist/types/test/rebuild-keeps-logic.test.d.ts +1 -0
  115. package/dist/types/test/rebuild.test.d.ts +1 -0
  116. package/dist/types/test/reenumerate.test.d.ts +1 -0
  117. package/dist/types/test/refresh.test.d.ts +1 -0
  118. package/dist/types/test/refs.test.d.ts +1 -0
  119. package/dist/types/test/reorder.test.d.ts +1 -0
  120. package/dist/types/test/route.test.d.ts +1 -0
  121. package/dist/types/test/routes-build.test.d.ts +1 -0
  122. package/dist/types/test/routes-dev.test.d.ts +1 -0
  123. package/dist/types/test/routes-emit.test.d.ts +1 -0
  124. package/dist/types/test/routes-runtime.test.d.ts +1 -0
  125. package/dist/types/test/setup.d.ts +59 -0
  126. package/dist/types/test/show.test.d.ts +1 -0
  127. package/dist/types/test/skeleton-adoption.test.d.ts +1 -0
  128. package/dist/types/test/slots.test.d.ts +1 -0
  129. package/dist/types/test/specifiers.test.d.ts +1 -0
  130. package/dist/types/test/ssg.test.d.ts +1 -0
  131. package/dist/types/test/state.test.d.ts +1 -0
  132. package/dist/types/test/static-islands.test.d.ts +1 -0
  133. package/dist/types/test/stats.test.d.ts +1 -0
  134. package/dist/types/test/structural.test.d.ts +1 -0
  135. package/dist/types/test/style-sources.test.d.ts +1 -0
  136. package/dist/types/test/styles.test.d.ts +1 -0
  137. package/dist/types/test/surgical-rebuild.test.d.ts +1 -0
  138. package/dist/types/test/tables.test.d.ts +1 -0
  139. package/dist/types/test/unmount.test.d.ts +1 -0
  140. package/dist/types/test/verbatim.test.d.ts +1 -0
  141. package/dist/vite.js +5655 -0
  142. package/package.json +64 -0
@@ -0,0 +1,57 @@
1
+ import type { ComponentRegistry } from "../components/index.js";
2
+ export declare class StyleManager {
3
+ private ssg;
4
+ private componentRegistry;
5
+ private styleElement;
6
+ private sheet;
7
+ private adopted;
8
+ private pendingStylesUsed;
9
+ /**
10
+ * Components whose CSS is already in the document.
11
+ *
12
+ * Additive rather than rewrite-from-scratch. A page carries critical CSS for
13
+ * everything it can *reach*, including components whose chunk has not loaded
14
+ * - a deferred island's skeleton is on screen long before its code is.
15
+ * Rebuilding this element from the registry would drop exactly those rules
16
+ * the moment anything registered, leaving the skeleton unstyled.
17
+ */
18
+ private covered;
19
+ private deferredCovered;
20
+ /**
21
+ * At build, deferred CSS is emitted separately at the end of the body so it
22
+ * stays off the critical path. On the client it travels with the component.
23
+ */
24
+ constructor(componentRegistry: ComponentRegistry, ssg?: boolean);
25
+ private ensureElement;
26
+ updateStyles(): void;
27
+ /** Called the first time an element opts into `$shimmer`. */
28
+ ensurePendingStyles(): void;
29
+ /** Component ids whose critical CSS this element carries. */
30
+ coveredComponents(): string[];
31
+ removeStyles(): void;
32
+ addComponentStyle(componentId: string, style: string): void;
33
+ removeComponentStyle(componentId: string): void;
34
+ /**
35
+ * Where CSS added at runtime goes.
36
+ *
37
+ * A `<style>` element is inline content, so a strict `style-src` blocks it -
38
+ * and a hash cannot cover CSS that does not exist until a chunk loads. A
39
+ * constructable stylesheet is CSSOM rather than inline content and is not
40
+ * checked at all, so a page with deferred component CSS needs no
41
+ * `unsafe-inline`.
42
+ *
43
+ * The build keeps writing a real `<style>` element: its output has to be
44
+ * serialisable HTML, and that block is hashed.
45
+ */
46
+ private emit;
47
+ /**
48
+ * The document's own `CSSStyleSheet`, not the global one.
49
+ *
50
+ * They are the same in a browser and not under jsdom, where the constructor
51
+ * hangs off the window rather than `globalThis` - so reading the global
52
+ * would silently take the fallback and leave the adopted path untested.
53
+ */
54
+ private sheetConstructor;
55
+ /** Whether this document can adopt a stylesheet. */
56
+ private adopts;
57
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * When a component becomes live.
3
+ *
4
+ * `load` is the default and mounts inline. The others leave a skeleton in
5
+ * place and mount later, which is the point of the whole exercise: a page can
6
+ * paint its shape immediately and pay for interactivity only where it is
7
+ * needed.
8
+ */
9
+ export type HydrationTrigger = "load" | "idle" | "visible" | "interaction"
10
+ /**
11
+ * Never. The build renders it and the client leaves it alone.
12
+ *
13
+ * For markup that is finished once it is drawn - a published section, a
14
+ * footer. The build strips its markers entirely, so the browser does not
15
+ * mount it, does not fetch its chunk, and has nothing to adopt. The whole
16
+ * subtree goes static with it: a marker inside one has no parent instance
17
+ * left to evaluate its props against.
18
+ */
19
+ | "never"
20
+ /**
21
+ * Always. The evidence says it could be static; the author says otherwise.
22
+ *
23
+ * The escape hatch for anything the build cannot see - a listener attached
24
+ * from a promise, a library reaching for the element after mount.
25
+ */
26
+ | "always";
27
+ export declare function parseTrigger(value: string | null): HydrationTrigger;
28
+ /**
29
+ * Arrange for `run` to happen when the trigger fires.
30
+ *
31
+ * Returns a cancel function. It must be registered on whatever owns the
32
+ * placeholder, so a component unmounted before its trigger fires does not
33
+ * leave an observer or a queued callback behind.
34
+ */
35
+ export declare function scheduleHydration(trigger: HydrationTrigger, element: Element, run: () => void): () => void;
@@ -0,0 +1,7 @@
1
+ export { Mounter } from "./Mounter.js";
2
+ export type { InitInstance, ReleaseInstance } from "./Mounter.js";
3
+ export { StyleManager } from "./StyleManager.js";
4
+ export { ForBlock, Row } from "./ForBlock.js";
5
+ export type { BindingHolder, RowRenderer } from "./ForBlock.js";
6
+ export { parseTrigger, scheduleHydration } from "./hydration.js";
7
+ export type { HydrationTrigger } from "./hydration.js";
@@ -0,0 +1,18 @@
1
+ import type { ComponentInstance } from "../components/ComponentInstance.js";
2
+ import type { BindingHolder } from "../dom/ForBlock.js";
3
+ /**
4
+ * Attaches native event listeners at mount time.
5
+ *
6
+ * Handlers resolve against the instance that owns the element's scope, so a
7
+ * handler name means "this component's handler" rather than "whichever
8
+ * component happens to declare it first". Every listener registers its own
9
+ * removal on whatever owns it - the instance, or a single `$for` row - so
10
+ * unmounting that owner detaches it.
11
+ */
12
+ export declare class EventBinder {
13
+ /** `@click="handleClick"` - a bare name. */
14
+ bind(el: Element, eventType: string, handlerName: string, owner: ComponentInstance, cleanupTarget?: BindingHolder): void;
15
+ /** `@click="remove(item)"` - an expression, needed for per-row actions. */
16
+ bindExpression(el: Element, eventType: string, invoke: (event: Event) => void, cleanupTarget: BindingHolder, label: string): void;
17
+ private listen;
18
+ }
@@ -0,0 +1 @@
1
+ export { EventBinder } from "./EventBinder.js";
@@ -0,0 +1,24 @@
1
+ import { ReactiveProxy } from "../reactive/index.js";
2
+ import { SignalManager } from "../signals/index.js";
3
+ export declare class GlobalFunctions {
4
+ private reactiveProxy;
5
+ private globalSignals;
6
+ constructor(reactiveProxy: ReactiveProxy, globalSignals: SignalManager);
7
+ setup(): void;
8
+ private defineProps;
9
+ private reactive;
10
+ private defineSignal;
11
+ /**
12
+ * The global escape hatch, for code that is not inside a component.
13
+ *
14
+ * A timer, and it has to be: there is no instance here and therefore no
15
+ * nodes to wait for. Components do **not** come through this - their
16
+ * `onMounted` is queued against the instance and released when its nodes are
17
+ * in the document, which is a promise this cannot make.
18
+ *
19
+ * Kept deliberately unimproved. Making it look reliable would invite the
20
+ * thing it cannot do.
21
+ */
22
+ private onMounted;
23
+ cleanup(): void;
24
+ }
@@ -0,0 +1 @@
1
+ export { GlobalFunctions } from "./GlobalFunctions.js";
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Runtime helper functions for Lightspeed components
3
+ *
4
+ * These functions are available for import in component logic:
5
+ * ```typescript
6
+ * import { reactive, defineSignal, onMounted } from 'lightspeed'
7
+ * ```
8
+ *
9
+ * Note: These are type definitions for IDE support. At runtime, imports from 'lightspeed'
10
+ * are automatically transformed to use the injected runtime context.
11
+ */
12
+ import type { SignalAPI } from './signals';
13
+ /**
14
+ * Creates a reactive proxy object that triggers updates when properties change
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { reactive } from 'lightspeed'
19
+ *
20
+ * const state = reactive({ count: 0 })
21
+ * state.count = 1 // Triggers reactive update
22
+ * ```
23
+ */
24
+ export declare function reactive<T extends object>(target: T): T;
25
+ /**
26
+ * Creates a signal API for cross-component communication
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { defineSignal } from 'lightspeed'
31
+ *
32
+ * const signal = defineSignal()
33
+ * signal.emit('event:name', { data: 'value' })
34
+ * signal.on('event:name', (data) => console.log(data))
35
+ * ```
36
+ */
37
+ export declare function defineSignal(): SignalAPI;
38
+ /**
39
+ * Registers a callback to run when the component is mounted to the DOM
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { onMounted } from 'lightspeed'
44
+ *
45
+ * onMounted(() => {
46
+ * console.log('Component mounted!')
47
+ * })
48
+ * ```
49
+ */
50
+ export declare function onMounted(callback: () => void): void;
51
+ /**
52
+ * Recompute derived state before this component's bindings read it.
53
+ *
54
+ * Runs on mount and again whenever this instance's state or props change -
55
+ * **props included**, which is the point: a component whose input can change
56
+ * under it derives from `props` here rather than snapshotting them at setup.
57
+ *
58
+ * Before the bindings rather than after, so a template never renders a value
59
+ * one pass behind.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { defineProps, reactive, onUpdate } from 'lightspeed'
64
+ *
65
+ * const props = defineProps({ items: { default: [] } })
66
+ * const state = reactive({ summary: '' })
67
+ *
68
+ * onUpdate(() => {
69
+ * state.summary = props.items.join(', ')
70
+ * })
71
+ * ```
72
+ */
73
+ export declare function onUpdate(callback: () => void): void;
74
+ /**
75
+ * The elements this component named with `$ref`, by name.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * import { defineRefs, onMounted } from 'lightspeed'
80
+ *
81
+ * const refs = defineRefs()
82
+ *
83
+ * onMounted(() => {
84
+ * // Filled by now: refs are collected when the DOM is, which is after the
85
+ * // logic has run.
86
+ * const width = refs.frame.getBoundingClientRect().width
87
+ * })
88
+ * ```
89
+ */
90
+ export declare function defineRefs<T = Record<string, Element>>(): T;
91
+ /**
92
+ * Defines component props with default values
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * import { defineProps } from 'lightspeed'
97
+ *
98
+ * const props = defineProps({
99
+ * title: { default: 'Hello' },
100
+ * count: { default: 0 }
101
+ * })
102
+ * ```
103
+ */
104
+ export declare function defineProps<T = any>(propsDefinition?: any): T;
105
+ /**
106
+ * Get or create a named shared cell.
107
+ *
108
+ * Cells are ownerless - whoever references a key first creates it. A cell that
109
+ * has never been set is `pending`, which is distinct from holding a falsy
110
+ * value, so a late-mounting component can tell "unknown" from "zero".
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * import { shared } from 'lightspeed'
115
+ *
116
+ * const clicks = shared('ui:clicks', 0)
117
+ * clicks.value++
118
+ *
119
+ * // One-shot async initialiser - runs once no matter how many callers pass it
120
+ * const revisions = shared('doc:revisions', async () => fetchRevisionCount())
121
+ *
122
+ * // Survives page loads
123
+ * const theme = shared('ui:theme', 'light', { persist: true })
124
+ * ```
125
+ */
126
+ /**
127
+ * Put back whatever this component did to the world outside itself.
128
+ *
129
+ * A listener on `window`, an observer, a timer - anything the component owns
130
+ * that outlives its own DOM. Runs when the instance is destroyed.
131
+ */
132
+ export declare function onUnmounted(callback: () => void): void;
133
+ export declare function shared<T = any>(key: string, init?: T | (() => T | Promise<T>), options?: {
134
+ persist?: boolean;
135
+ }): {
136
+ key: string;
137
+ value: T | undefined;
138
+ readonly pending: boolean;
139
+ set(next: T | undefined): void;
140
+ subscribe(listener: (value: T | undefined) => void): () => void;
141
+ };
@@ -0,0 +1,16 @@
1
+ export { createLightspeedApp, LightspeedRuntime } from "./core.js";
2
+ export type { MountHandle } from "./core.js";
3
+ export type { ComponentDefinition } from "./components/index.js";
4
+ export { ComponentRegistry, ComponentInstance } from "./components/index.js";
5
+ export { ReactiveProxy, UpdateScheduler } from "./reactive/index.js";
6
+ export { SignalManager, createSignalAPI, createScopedSignalAPI, } from "./signals/index.js";
7
+ export type { SignalAPI } from "./signals/index.js";
8
+ export { ImportTransformer } from "./compiler/index.js";
9
+ export { BindingEvaluator } from "./bindings/index.js";
10
+ export type { Binding, BindingKind } from "./bindings/index.js";
11
+ export { EventBinder } from "./events/index.js";
12
+ export { Cell, CellManager } from "./state/index.js";
13
+ export type { CellListener, CellOptions, CellInitialiser } from "./state/index.js";
14
+ export { GlobalFunctions } from "./globals/index.js";
15
+ export { Mounter, StyleManager } from "./dom/index.js";
16
+ export { registerPageExpressions } from "./core.js";
@@ -0,0 +1,63 @@
1
+ export declare class ReactiveProxy {
2
+ private onChange;
3
+ /**
4
+ * Per-owner cache of raw object -> proxy.
5
+ *
6
+ * Keyed by owner as well as target so two components sharing an object each
7
+ * get a proxy that notifies *them*. Caching also keeps proxy identity
8
+ * stable, which matters because bindings compare values with `Object.is` -
9
+ * a fresh proxy per read would look like a change on every evaluation.
10
+ */
11
+ private caches;
12
+ private ownerless;
13
+ /** proxy -> raw, so assigning a proxy back in does not nest wrappers. */
14
+ private rawTargets;
15
+ /**
16
+ * Every instance holding a proxy for a given object.
17
+ *
18
+ * A form shares one mutable object across components - the editor owns it,
19
+ * each field writes into it - so "notify the owner" is not enough: the
20
+ * owner's own bindings refresh and its siblings keep rendering the previous
21
+ * value. Notifying everyone who ever wrapped this object is what makes a
22
+ * shared object behave like shared state.
23
+ *
24
+ * Weak references, pruned as they die, so a component that has unmounted
25
+ * cannot be kept alive by an object its parent still holds.
26
+ */
27
+ private observers;
28
+ constructor(onChange: (owner: any) => void);
29
+ private cacheFor;
30
+ /**
31
+ * Wrap an object so writes notify the instance that owns it.
32
+ *
33
+ * Nested objects and arrays are wrapped lazily, on read, so `items.push(x)`
34
+ * and `items[0].name = 'x'` both notify. A `get` trap is affordable here
35
+ * because reads happen during binding evaluation, not in a render loop.
36
+ *
37
+ * `owner` lets the scheduler update only that instance's bindings. A null
38
+ * owner (the window-global `reactive()`) falls back to updating everything.
39
+ *
40
+ * `owner` must be an object or null - it keys a WeakMap so instances stay
41
+ * collectable.
42
+ */
43
+ create<T extends object>(target: T, owner?: any): T;
44
+ /**
45
+ * Register an instance as holding this object, wherever it came from.
46
+ *
47
+ * A prop is the case that needs it: a child is handed an object its parent
48
+ * wrapped, so it never calls `create` and would otherwise never hear that
49
+ * the object changed.
50
+ */
51
+ share(value: any, observer: object): void;
52
+ private observe;
53
+ /**
54
+ * Tell everyone holding this object that it changed.
55
+ *
56
+ * The writing owner is told first and always - a null owner is the
57
+ * window-global `reactive()`, whose fallback is to update everything, and
58
+ * that must not be lost just because nobody else happens to hold the object.
59
+ */
60
+ private notify;
61
+ /** The underlying object for a proxy, or the value itself if not one. */
62
+ raw<T>(value: T): T;
63
+ }
@@ -0,0 +1,9 @@
1
+ export declare class UpdateScheduler {
2
+ private queue;
3
+ private scheduled;
4
+ private flushCallback;
5
+ constructor(flushCallback: (owners: any[]) => void);
6
+ queueUpdate(owner: any): void;
7
+ private flushUpdates;
8
+ clear(): void;
9
+ }
@@ -0,0 +1,2 @@
1
+ export { ReactiveProxy } from "./ReactiveProxy.js";
2
+ export { UpdateScheduler } from "./UpdateScheduler.js";
@@ -0,0 +1,10 @@
1
+ export declare class SignalManager {
2
+ private listeners;
3
+ private history;
4
+ emit(event: string, data: any): void;
5
+ on(event: string, listener: Function): void;
6
+ off(event: string, listener: Function): void;
7
+ getHistory(event: string): any[];
8
+ getLatest(event: string): any | undefined;
9
+ clear(): void;
10
+ }
@@ -0,0 +1,3 @@
1
+ export { SignalManager } from "./SignalManager.js";
2
+ export { createSignalAPI, createScopedSignalAPI } from "./signals.js";
3
+ export type { SignalAPI } from "./types/index.js";
@@ -0,0 +1,16 @@
1
+ import type { SignalManager } from "./SignalManager.js";
2
+ import type { SignalAPI } from "./types/index.js";
3
+ export declare function createSignalAPI(signalManager: any): SignalAPI;
4
+ /**
5
+ * Create a signal API whose subscriptions are owned by a single caller.
6
+ *
7
+ * Every listener registered through `on` hands an unsubscribe back via
8
+ * `registerCleanup`, so the owner can drop all of its listeners at once when it
9
+ * is torn down. Without this, re-initialising a component (hot reload, or a
10
+ * manual `updateComponent`) leaves the previous instance's listeners attached to
11
+ * the shared manager and every signal fires once per stale instance.
12
+ *
13
+ * Takes a callback rather than a ComponentInstance so the signal layer stays
14
+ * independent of the component layer.
15
+ */
16
+ export declare function createScopedSignalAPI(signalManager: SignalManager, registerCleanup: (cleanup: Function) => void): SignalAPI;
@@ -0,0 +1 @@
1
+ export * from "./signals.js";
@@ -0,0 +1,7 @@
1
+ export interface SignalAPI {
2
+ emit: (event: string, data: any) => void;
3
+ on: (event: string, listener: Function) => void;
4
+ off: (event: string, listener: Function) => void;
5
+ getHistory: (event: string) => any[];
6
+ getLatest: (event: string) => any | undefined;
7
+ }
@@ -0,0 +1,68 @@
1
+ export type CellListener<T = any> = (value: T | undefined) => void;
2
+ export interface CellOptions {
3
+ /** Mirror the value into localStorage so it survives page loads. */
4
+ persist?: boolean;
5
+ /**
6
+ * When this cell's initialiser runs. See ADR-013.
7
+ *
8
+ * `"client"` (the default) is live state - current at the moment of viewing.
9
+ * `"build"` is *content*: managed elsewhere, published on a cadence, baked
10
+ * into the HTML and shipped to the client so it never fetches it again.
11
+ *
12
+ * Anything baked is public. Content worth caching is content worth serving
13
+ * publicly; anything user-specific or secret is live state by definition.
14
+ */
15
+ fetchAt?: "build" | "client";
16
+ /**
17
+ * Bake it, then go and get a fresh one.
18
+ *
19
+ * Only meaningful with `fetchAt: "build"`. The page paints with the value
20
+ * from the build and the initialiser runs again once, in the browser,
21
+ * replacing it when it lands.
22
+ *
23
+ * For data that changes **without a build** - a comment thread, a feed, a
24
+ * stock level. Published content is not that: it is rebuilt when it is
25
+ * published, so a baked entry is not stale, it is correct, and refreshing it
26
+ * would be a fetch for a value that cannot have changed.
27
+ *
28
+ * Showing a stale value at all is a decision only the author can make, which
29
+ * is why the default is the other one: declare no `fetchAt`, gate on
30
+ * `$needs`, and ship a skeleton until the real thing arrives.
31
+ */
32
+ refresh?: boolean;
33
+ }
34
+ /**
35
+ * A named unit of shared state.
36
+ *
37
+ * Distinct from a signal: a signal says "something happened", a cell holds
38
+ * "what is true now". A component that mounts late reads the current value
39
+ * instead of replaying a log of events.
40
+ *
41
+ * A cell that has never been set is `pending` - not zero, not defaulted.
42
+ * Pending is a real state, so a component can show its skeleton rather than
43
+ * asserting a value it does not have.
44
+ */
45
+ export declare class Cell<T = any> {
46
+ readonly key: string;
47
+ private currentValue;
48
+ private isPending;
49
+ private listeners;
50
+ private persist;
51
+ constructor(key: string, options?: CellOptions);
52
+ get value(): T | undefined;
53
+ set value(next: T | undefined);
54
+ /** True until the first set. Distinguishes "unknown" from a falsy value. */
55
+ get pending(): boolean;
56
+ set(next: T | undefined): void;
57
+ /**
58
+ * Turn on persistence after the cell already exists.
59
+ *
60
+ * Needed because a cell is created by whoever touches the key first, and
61
+ * that is often an implicit *read* - a `$count="shared['x']"` binding on an
62
+ * island that hydrates before the component which declares the key properly.
63
+ * Without this, that declaration's options were silently discarded and the
64
+ * value never reached storage.
65
+ */
66
+ enablePersistence(): void;
67
+ subscribe(listener: CellListener<T>): () => void;
68
+ }
@@ -0,0 +1,101 @@
1
+ import { Cell, type CellOptions } from "./Cell.js";
2
+ export type CellInitialiser<T = any> = () => T | Promise<T>;
3
+ /**
4
+ * Owns every shared cell for a runtime.
5
+ *
6
+ * Cells are ownerless: whoever references a key first creates it, and everyone
7
+ * after gets the same cell. There is no registry to maintain and no central
8
+ * declaration file, which keeps state out of the way of the SFC model and
9
+ * works regardless of the order islands hydrate in.
10
+ */
11
+ export declare class CellManager {
12
+ private ssg;
13
+ private cells;
14
+ private initialised;
15
+ private declaredDefaults;
16
+ /** Keys that refresh in the browser despite being baked. */
17
+ private refreshKeys;
18
+ /** Keys already refreshed this session, so several declarations fetch once. */
19
+ private refreshed;
20
+ /** Keys declared `fetchAt: "build"`, so only those are serialised. */
21
+ private buildKeys;
22
+ /** Outstanding build-time fetches, for the build to await. */
23
+ private buildWork;
24
+ /**
25
+ * At build, initialisers do not run.
26
+ *
27
+ * ADR-010 already says nothing is set at build - no fetches, localStorage
28
+ * stubbed - and an initialiser's value could not be used anyway, because the
29
+ * component gated on that cell ships its skeleton by definition. Running it
30
+ * only means hitting the network during a build and holding the process open
31
+ * until it settles: a 1-4s stand-in API took the example's build from 0.9s
32
+ * to 5.3s.
33
+ */
34
+ constructor(ssg?: boolean);
35
+ /**
36
+ * Get or create a cell.
37
+ *
38
+ * `init` is either a plain default value or a function used as a one-shot
39
+ * initialiser. An initialiser runs exactly once per key no matter how many
40
+ * callers pass one, which is what stops N components from firing N requests
41
+ * for the same data.
42
+ */
43
+ get<T = any>(key: string, init?: T | CellInitialiser<T>, options?: CellOptions): Cell<T>;
44
+ private applyDefault;
45
+ private runInitialiser;
46
+ /** Run an initialiser and put what it produces into the cell. */
47
+ private fetch;
48
+ /**
49
+ * Run `onReady` once every named cell holds a value. Returns a cancel.
50
+ *
51
+ * Fires **synchronously** when they are all resolved already, which is the
52
+ * common case for an island hydrating late: the data landed while it was
53
+ * waiting, so it renders with no skeleton at all.
54
+ *
55
+ * A cell never returns to pending once set, so this resolves at most once.
56
+ */
57
+ whenReady(keys: string[], onReady: () => void): () => void;
58
+ /** Which of these keys have never been set. */
59
+ pendingKeys(keys: string[]): string[];
60
+ /**
61
+ * Wait for every build-time fetch declared so far.
62
+ *
63
+ * Loops, because resolving one can mount a component whose logic declares
64
+ * another - a page's content cell filling in the section that fetches its
65
+ * own. Rejections propagate, which is what fails the build.
66
+ */
67
+ settleBuildData(): Promise<void>;
68
+ /** Whether this key's value came from the build rather than the browser. */
69
+ isBuildKey(key: string): boolean;
70
+ /**
71
+ * Baked and never fetched again.
72
+ *
73
+ * The distinction that matters to anything deciding whether a page needs
74
+ * JavaScript: a baked cell cannot change in the browser, but one that
75
+ * refreshes plainly can.
76
+ */
77
+ isBakedOnly(key: string): boolean;
78
+ /** Whether anything declared `fetchAt: "build"` during this render. */
79
+ hasBuildData(): boolean;
80
+ /** Resolved build-time content, for the page to carry to the client. */
81
+ buildPayload(): Record<string, unknown>;
82
+ /**
83
+ * Seed values the build resolved.
84
+ *
85
+ * Marked initialised as well as set, so the client does not re-fetch content
86
+ * it was handed - which would defeat the point and, worse, race with the
87
+ * rendered HTML.
88
+ */
89
+ seed(values: Record<string, unknown>): void;
90
+ has(key: string): boolean;
91
+ keys(): string[];
92
+ /**
93
+ * A view of every cell's current value, for evaluating template expressions.
94
+ *
95
+ * `onRead` reports which keys an expression actually touched, which is how a
96
+ * binding discovers what to subscribe to without any static analysis.
97
+ * Reading an unknown key creates it pending, consistent with get-or-create.
98
+ */
99
+ createReadProxy(onRead?: (key: string) => void): Record<string, any>;
100
+ clear(): void;
101
+ }
@@ -0,0 +1,4 @@
1
+ export { Cell } from "./Cell.js";
2
+ export type { CellListener, CellOptions } from "./Cell.js";
3
+ export { CellManager } from "./CellManager.js";
4
+ export type { CellInitialiser } from "./CellManager.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The names Lightspeed is imported by.
3
+ *
4
+ * The framework is published as `@lightspeed-js/core`: the unscoped name
5
+ * `lightspeed` on npm belongs to an unrelated package. Code written before the
6
+ * rename imports `'lightspeed'`, so that stays accepted everywhere the
7
+ * framework recognises its own imports, and the Vite plugin maps it to the
8
+ * package for everything else.
9
+ */
10
+ export declare const PACKAGE_NAME = "@lightspeed-js/core";
11
+ /** The name used before publishing. Still accepted; new code uses PACKAGE_NAME. */
12
+ export declare const LEGACY_NAME = "lightspeed";
13
+ /** Both names, for bundler `external` lists. */
14
+ export declare const RUNTIME_SPECIFIERS: string[];
15
+ /** Matches either name inside quotes, for import regexes. */
16
+ export declare const SPECIFIER_PATTERN = "(?:@lightspeed-js\\/core|lightspeed)";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The build-side surface.
3
+ *
4
+ * Separate from the runtime entry because these run in a build agent, not in a
5
+ * browser - a CMS webhook re-rendering pages has no use for the runtime, and a
6
+ * page has no use for jsdom.
7
+ */
8
+ export { renderPage } from "./render.js";
9
+ export type { RenderPageOptions, RenderPageResult } from "./render.js";
10
+ export { rebuildPages } from "./rebuild.js";
11
+ export type { RebuildOptions, RebuildResult } from "./rebuild.js";
12
+ /**
13
+ * Route matching, for a server that has to honour a runtime-resolved route.
14
+ *
15
+ * A page whose URLs are not knowable at build ships one shell and relies on
16
+ * the host to rewrite onto it. A host that serves the output itself - rather
17
+ * than a static host reading `_redirects` - needs the same matcher the build
18
+ * and dev server use, or the three drift apart.
19
+ */
20
+ export { parseRoute, matchPath, outputFileFor } from "../parser/route.js";
21
+ export type { ParsedRoute } from "../parser/route.js";