@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,85 @@
1
+ /**
2
+ * Island boundaries, as comment pairs.
3
+ *
4
+ * ```html
5
+ * <!--lsp {"c":"chart","props":{"title":"Q"},"hydrate":"visible"}-->
6
+ * …content…
7
+ * <!--/lsp-->
8
+ * ```
9
+ *
10
+ * Comments rather than a custom element because comments are legal in **every**
11
+ * parser insertion mode. `<lsp-mount>` inside `<tbody>` is foster-parented out
12
+ * of the table by the HTML parser - confirmed, not theoretical - which becomes
13
+ * a shipped-HTML bug the moment markers appear in static output.
14
+ *
15
+ * A pair also gives three things one element could not hold at once: rendered
16
+ * content, slot content, and a skeleton. Content simply lives *between* the
17
+ * comments as ordinary DOM.
18
+ */
19
+ export interface MarkerPayload {
20
+ /** Component id. */
21
+ c: string;
22
+ /** Static attributes, which become props. */
23
+ props?: Record<string, string | true>;
24
+ /** Static presentational attributes, forwarded to the child's root. */
25
+ attrs?: Record<string, string>;
26
+ /** `@event="handler"`, evaluated in the parent's scope. */
27
+ on?: Record<string, string>;
28
+ /** `$name="expr"` - dynamic props, plus class/style for the root. */
29
+ bind?: Record<string, string>;
30
+ /**
31
+ * `$cell:name="key"` - a prop the **child** subscribes to, by cell key.
32
+ *
33
+ * Written at the usage site like a prop, delivered like a cell: nothing is
34
+ * evaluated in the parent's scope, so the parent is not in the propagation
35
+ * path and can be flattened, unmounted or never mounted at all. See ADR-053.
36
+ */
37
+ cells?: Record<string, string>;
38
+ /**
39
+ * `<Dynamic $is="expr" />` - the component is named by an expression, not by
40
+ * the tag.
41
+ *
42
+ * `c` is empty in this case and filled in at mount, once the expression can
43
+ * be evaluated against the owner's scope. Everything downstream - props,
44
+ * slots, `$hydrate`, `$needs` - behaves exactly as it would for a tag.
45
+ */
46
+ dyn?: string;
47
+ /** `$hydrate` keyword: when the logic runs. */
48
+ hydrate?: string;
49
+ /**
50
+ * `$needs` - the cells this usage waits on. See ADR-010.
51
+ *
52
+ * Independent of `hydrate`, which says *when* the logic runs. This says what
53
+ * has to be true before there is anything to render. The skeleton is the
54
+ * consequence, not the declaration.
55
+ *
56
+ * Present but empty is `$needs` with no value: "wait for me, I fetch
57
+ * internally". It names nothing resolvable, so it suppresses build rendering
58
+ * and imposes no runtime gate.
59
+ */
60
+ needs?: string[];
61
+ /** Set by the build: the content between the markers is rendered output. */
62
+ ssr?: number;
63
+ }
64
+ export declare const MARKER_END = "<!--/lsp-->";
65
+ /**
66
+ * A comment may not contain `--`, so any run of dashes in the payload is
67
+ * escaped. `-` parses back to `-`, so the round trip is lossless.
68
+ */
69
+ export declare function serializeMarker(payload: MarkerPayload): string;
70
+ export declare function isMarkerStart(node: Node): boolean;
71
+ export declare function isMarkerEnd(node: Node): boolean;
72
+ export declare function parseMarker(node: Node): MarkerPayload | null;
73
+ /**
74
+ * Index of the `<!--/lsp-->` closing `nodes[start]`, accounting for nesting.
75
+ * Returns -1 if unterminated.
76
+ */
77
+ export declare function findMarkerEnd(nodes: Node[], start: number): number;
78
+ /**
79
+ * The slot content for a marker.
80
+ *
81
+ * For a server-rendered island the nodes between the comments are *output*,
82
+ * not slot content - the original is stashed in an inert
83
+ * `<template data-lsp-slot>` so the client can rebuild from it.
84
+ */
85
+ export declare function slotNodesFor(payload: MarkerPayload, between: Node[]): Node[];
@@ -0,0 +1,2 @@
1
+ export { parseLSP, validateLSP, inlineStyleSources } from "./lsp.js";
2
+ export type { LSPComponent } from "./lsp.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Layouts - shared document structure for multi-page projects.
3
+ *
4
+ * A multi-page project repeats its doctype, head, stylesheet links, chrome and
5
+ * script tags in every page. A layout holds that once; pages declare which one
6
+ * they use and supply only their content.
7
+ *
8
+ * This is deliberately the *same* vocabulary as component slots - `<slot>`,
9
+ * `<slot name>`, `<template slot>`, fallback content - rather than a second
10
+ * composition concept to learn. The difference is only when it happens:
11
+ * layouts are substituted at build, so the output is what you would have
12
+ * written by hand and nothing about layouts survives into the page.
13
+ */
14
+ export declare const LAYOUT_OPT_OUT = "none";
15
+ /**
16
+ * The layout name a page asks for, or null if it does not ask.
17
+ *
18
+ * Null means "decide by convention" - not "no layout". Only an explicit
19
+ * `none` opts out, so a page can always escape the convention.
20
+ */
21
+ export declare function readLayoutName(html: string): string | null;
22
+ export declare function stripLayoutDeclaration(html: string): string;
23
+ /**
24
+ * Substitute a page into its layout.
25
+ *
26
+ * Unfilled slots keep their fallback content, which is what makes a layout
27
+ * able to carry a sensible default title or empty chrome.
28
+ */
29
+ export declare function applyLayout(page: string, layout: string, context?: {
30
+ page?: string;
31
+ layout?: string;
32
+ }): string;
@@ -0,0 +1,56 @@
1
+ export interface LSPComponent {
2
+ view: string;
3
+ skeleton: string;
4
+ logic: string;
5
+ /** `<style>` - inlined with the page. Anything affecting size or layout. */
6
+ style: string;
7
+ /**
8
+ * `<style deferred>` - shipped with the component and applied when it
9
+ * hydrates. Hover, focus, transitions: nothing can be in those states before
10
+ * hydration, so they cost nothing up front.
11
+ */
12
+ styleDeferred: string;
13
+ /**
14
+ * Files a `<style src="…">` asked for, in the order they were written.
15
+ *
16
+ * Unresolved here: parsing stays pure, and reading a file is the caller's
17
+ * business. `inlineStyleSources` does the joining, given something that can
18
+ * read.
19
+ */
20
+ styleSources: string[];
21
+ styleDeferredSources: string[];
22
+ /**
23
+ * `<needs>` - cell keys this component cannot render without.
24
+ *
25
+ * Declared by the component rather than by every place that uses it. A
26
+ * caller should not have to know what its data dependencies are, and
27
+ * repeating the key at each usage is the same coupling a prop chain has.
28
+ *
29
+ * Read by the compiler, not at runtime: gating decides whether the logic
30
+ * runs at all, so it has to be knowable before the logic exists.
31
+ */
32
+ needs: string[];
33
+ filename: string;
34
+ }
35
+ /**
36
+ * Parse a Lightspeed Single File Component (.lsp)
37
+ */
38
+ export declare function parseLSP(content: string, filename: string): LSPComponent;
39
+ /**
40
+ * Fold every `<style src="…">` into the component's own styles.
41
+ *
42
+ * Shared rules are **inlined per component**, not emitted once: each copy is
43
+ * scoped to the component that asked for it, so sharing a file never means
44
+ * sharing a selector. Two components with the same rules get two scoped
45
+ * copies, which is what stops one of them ever styling the other's markup.
46
+ *
47
+ * Shared text comes first, so a component's own rules win. Everything is
48
+ * scoped with `:where()` and therefore weighs nothing, which means source
49
+ * order is the whole of the cascade here - "mine, after theirs" is the only
50
+ * override rule an author needs to know.
51
+ */
52
+ export declare function inlineStyleSources(component: LSPComponent, read: (specifier: string, from: string) => string): LSPComponent;
53
+ /**
54
+ * Validate that required sections exist
55
+ */
56
+ export declare function validateLSP(component: LSPComponent): string[];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Route patterns - `/blog/:slug`.
3
+ *
4
+ * A page declares the shape of its URL, and the build turns that into real
5
+ * files wherever it can enumerate the values. Nothing here does client-side
6
+ * navigation: links are ordinary links and every navigation is a full page
7
+ * load. The only thing a pattern buys is that data can live in the *path*
8
+ * rather than a query string.
9
+ */
10
+ export interface RouteDeclaration {
11
+ /** Absent when the routes tree supplies it. */
12
+ pattern?: string;
13
+ /**
14
+ * The build-time cell holding the rows to enumerate, if any.
15
+ *
16
+ * With it, the build emits one real file per row. Without it the URLs are
17
+ * not knowable at build - an admin editing arbitrary entries - and the page
18
+ * ships as a single shell the host rewrites onto, with params read from the
19
+ * path at runtime.
20
+ */
21
+ from?: string;
22
+ }
23
+ export interface RouteSegment {
24
+ /** Literal text, or the parameter name when `param` is true. */
25
+ value: string;
26
+ param: boolean;
27
+ }
28
+ export interface ParsedRoute {
29
+ pattern: string;
30
+ segments: RouteSegment[];
31
+ params: string[];
32
+ /**
33
+ * Every URL matching this pattern has the same number of segments.
34
+ *
35
+ * False only for a catch-all. It matters because a rewritten page is one
36
+ * file answering many URLs: with a relative `base`, asset paths resolve
37
+ * against the URL's depth rather than the file's, so a varying depth cannot
38
+ * be made to work.
39
+ */
40
+ fixedDepth: boolean;
41
+ }
42
+ export declare function readRouteDeclaration(html: string): RouteDeclaration | null;
43
+ export declare function stripRouteDeclaration(html: string): string;
44
+ export declare function parseRoute(pattern: string): ParsedRoute;
45
+ /** `/blog/:slug` + `{ slug: "post-1" }` → `/blog/post-1` */
46
+ export declare function buildPath(route: ParsedRoute, params: Record<string, unknown>): string;
47
+ /** `/blog/:slug` + `/blog/post-1` → `{ slug: "post-1" }`, or null. */
48
+ export declare function matchPath(route: ParsedRoute, pathname: string): Record<string, string> | null;
49
+ /**
50
+ * The file a URL is served from: `/blog/post-1` → `blog/post-1/index.html`.
51
+ *
52
+ * Directory output rather than `blog/post-1.html` so the URL carries no
53
+ * extension, which every static host serves without configuration.
54
+ */
55
+ export declare function outputFileFor(path: string): string;
56
+ /**
57
+ * How many directories deep a URL sits, which is what a relative `base`
58
+ * resolves asset paths against.
59
+ */
60
+ export declare function depthOf(path: string): number;
61
+ /** The directory whose tree defines a project's URLs. */
62
+ export declare const ROUTES_DIR = "routes";
63
+ /**
64
+ * The URL a page answers, from where it sits in the routes tree.
65
+ *
66
+ * `[param]` is a segment, `[...rest]` a catch-all, and a trailing `index` is
67
+ * the folder itself. The tree *is* the route map, so finding a page's URL is
68
+ * reading its path rather than opening it.
69
+ *
70
+ * index.html → /
71
+ * about.html → /about
72
+ * blog/index.html → /blog
73
+ * collections/[collection]/index.html → /collections/:collection
74
+ * collections/[collection]/[id].html → /collections/:collection/:id
75
+ * docs/[...rest].html → /docs/*
76
+ */
77
+ export declare function routeFromFile(relative: string): string;
78
+ /** Does this path carry dynamic segments, or is it a fixed page? */
79
+ export declare function isDynamicRoute(relative: string): boolean;
80
+ /**
81
+ * A stable file for a runtime-resolved page.
82
+ *
83
+ * One shell answers many URLs, so it cannot live at any of them. It goes
84
+ * somewhere clearly internal instead, and the host rewrites onto it. Depth is
85
+ * irrelevant because a runtime route already requires an absolute `base`.
86
+ */
87
+ export declare function shellFileFor(pattern: string): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Read a `<style src="…">` from disk, relative to the `.lsp` that asked.
3
+ *
4
+ * Kept out of `parser/index.ts` on purpose: that module is in the graph of
5
+ * `lightspeed/compile`, which a consumer may bundle for a browser, and `fs`
6
+ * has no business travelling there. Anything with a filesystem imports this
7
+ * one directly.
8
+ *
9
+ * A missing file throws, naming both ends. The failure this replaces is a
10
+ * component compiling with its styles silently absent - which is exactly the
11
+ * bug that made shared styles worth having.
12
+ */
13
+ export declare function nodeStyleReader(): (specifier: string, from: string) => string;
14
+ /** Where a `<style src="…">` resolves to, for watching it. */
15
+ export declare function styleSourcePaths(component: {
16
+ filename: string;
17
+ styleSources: string[];
18
+ styleDeferredSources: string[];
19
+ }): string[];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Build-time CSS scoping.
3
+ *
4
+ * Every component's styles are scoped by default: its selectors gain an
5
+ * attribute matching a per-component hash, and the runtime stamps that same
6
+ * attribute on the elements the component owns. `:global(...)` opts a part of a
7
+ * selector back out.
8
+ *
9
+ * No shadow DOM and no runtime cost - which is what let ADR-003 reject custom
10
+ * elements without giving up encapsulation.
11
+ */
12
+ /** A short, stable id for a component. */
13
+ export declare function scopeIdFor(componentId: string): string;
14
+ export declare function scopeStyles(css: string, scopeId: string): string;
15
+ export interface Scopes {
16
+ /** Zero-specificity scope for ordinary rules. */
17
+ normal: string;
18
+ /** Weighted anchor for `:deep()` overrides. */
19
+ deep: string;
20
+ }
@@ -0,0 +1,32 @@
1
+ interface ComponentRegistry {
2
+ [key: string]: {
3
+ template: string;
4
+ style: string;
5
+ logic: string;
6
+ /** Cell keys the component itself declared - see `LSPComponent.needs`. */
7
+ needs?: string[];
8
+ };
9
+ }
10
+ /**
11
+ * Transform Lightspeed template syntax to browser-ready HTML.
12
+ *
13
+ * Child components are NOT inlined here. They become <lsp-mount> placeholders
14
+ * that the runtime replaces with a real, independently-stated instance. Text
15
+ * spliced at build time has no identity, which is what made every usage of a
16
+ * component share one state object.
17
+ */
18
+ export declare function transformTemplate(template: string, componentId: string, componentRegistry?: ComponentRegistry): string;
19
+ /**
20
+ * Extract reactive expressions from template for analysis
21
+ */
22
+ export declare function extractReactiveExpressions(template: string): string[];
23
+ /**
24
+ * Extract import statements from component logic for dependency tracking
25
+ */
26
+ export declare function extractImports(logic: string): Array<{
27
+ variable: string;
28
+ path: string;
29
+ type: "default" | "named" | "star";
30
+ }>;
31
+ export declare function validateTemplate(template: string): string[];
32
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,174 @@
1
+ import type { ComponentInstance } from "../components/ComponentInstance.js";
2
+ import type { CellManager } from "../state/index.js";
3
+ import type { BindingHolder, ForBlock, Row } from "../dom/ForBlock.js";
4
+ export type BindingKind = "text" | "class" | "attr" | "prop" | "model" | "for" | "show" | "html";
5
+ type Compiled = (state: any, props: any, shared: any, pending: (key: string) => boolean, ...locals: any[]) => any;
6
+ interface BaseBinding {
7
+ expr: string;
8
+ compiled: Compiled;
9
+ /** Cell keys this expression read last time it was evaluated. */
10
+ cellKeys?: Set<string>;
11
+ cellUnsubscribes?: Array<() => void>;
12
+ cleanupRegistered?: boolean;
13
+ }
14
+ export interface ElementBinding extends BaseBinding {
15
+ kind: "text" | "class" | "attr" | "show" | "html";
16
+ el: Element;
17
+ /** Target attribute name, for kind "attr". */
18
+ attr?: string;
19
+ }
20
+ /**
21
+ * A binding whose target is a child component's prop rather than an element.
22
+ * Owned by the parent and evaluated in the parent's scope.
23
+ */
24
+ export interface PropBinding extends BaseBinding {
25
+ kind: "prop";
26
+ target: ComponentInstance;
27
+ name: string;
28
+ }
29
+ export interface ModelTarget {
30
+ /** DOM *property* - not an attribute. */
31
+ prop: "value" | "checked";
32
+ event: string;
33
+ coerce: (raw: any) => any;
34
+ radio?: boolean;
35
+ }
36
+ export interface ModelBinding extends BaseBinding {
37
+ kind: "model";
38
+ el: Element;
39
+ target: ModelTarget;
40
+ write: (state: any, props: any, shared: any, pending: (key: string) => boolean, value: any, ...locals: any[]) => void;
41
+ }
42
+ /** Re-evaluates a `$for` collection and hands it to the block to reconcile. */
43
+ export interface ForBinding extends BaseBinding {
44
+ kind: "for";
45
+ block: ForBlock;
46
+ }
47
+ export type Binding = ElementBinding | PropBinding | ModelBinding | ForBinding;
48
+ /**
49
+ * Where an expression is evaluated.
50
+ *
51
+ * Rows share their component's state and props but carry their own loop
52
+ * variables and their own cleanup, so removing a row detaches exactly its
53
+ * listeners and cell subscriptions.
54
+ */
55
+ export declare class BindingScope {
56
+ instance: ComponentInstance;
57
+ locals: any[];
58
+ cleanup: BindingHolder;
59
+ constructor(instance: ComponentInstance, locals: any[], cleanup: BindingHolder);
60
+ get id(): string;
61
+ get state(): any;
62
+ get props(): any;
63
+ }
64
+ export declare function resolveModelTarget(el: Element): ModelTarget;
65
+ /**
66
+ * Compiles and applies template bindings.
67
+ *
68
+ * Bindings are collected once at mount and stored on whatever owns their scope
69
+ * - a component instance, or a single `$for` row. An update touches only that
70
+ * owner's elements. Nothing here queries the document.
71
+ */
72
+ export declare class BindingEvaluator {
73
+ private cells;
74
+ private onPropChanged;
75
+ private compiledCache;
76
+ private writerCache;
77
+ /**
78
+ * @param onPropChanged Queues the child whose prop changed. Routed through
79
+ * the runtime's scheduler so prop cascades batch per frame like any other
80
+ * update, instead of recursing.
81
+ */
82
+ constructor(cells: CellManager, onPropChanged?: (instance: ComponentInstance) => void);
83
+ /**
84
+ * Loop aliases become function parameters, so `{{ item.name }}` resolves
85
+ * without a scope object or `with`. The cache keys on the parameter list too
86
+ * - the same expression means different things inside different blocks.
87
+ */
88
+ /** Compiled by the build, merged as components and pages register. */
89
+ private expressions;
90
+ private writers;
91
+ registerExpressions(expressions?: Record<string, any>, writers?: Record<string, any>): void;
92
+ /**
93
+ * The function the build compiled for this expression.
94
+ *
95
+ * Not compiled here: `new Function` is what forces a page to allow
96
+ * `unsafe-eval`. The build emits a table keyed exactly as this cache was
97
+ * keyed, so a lookup replaces a compile. See [[ADR-016]].
98
+ */
99
+ private compile;
100
+ private compileWriter;
101
+ createBinding(el: Element, kind: "text" | "class" | "attr" | "show" | "html", expr: string, attr?: string, params?: string[]): ElementBinding;
102
+ createPropBinding(target: ComponentInstance, name: string, expr: string, params?: string[]): PropBinding;
103
+ /**
104
+ * `$cell:count="ui:clicks"` - hand a cell to a child as one of its props.
105
+ *
106
+ * Not a binding, deliberately. A binding is owned by whoever wrote the
107
+ * expression, and for a prop that is the *parent* - so the value only
108
+ * arrives while the parent is mounted and still evaluating. Here the
109
+ * subscription belongs to the child, which is the whole point: it survives
110
+ * the parent being flattened to HTML, never mounting, or mounting long
111
+ * after this one did.
112
+ *
113
+ * A pending cell writes nothing, so a declared default still stands. That is
114
+ * the ADR-005 distinction doing its job - "nobody has said yet" is not the
115
+ * same as "the answer is undefined".
116
+ */
117
+ bindCellProp(target: ComponentInstance, name: string, key: string): void;
118
+ createModelBinding(el: Element, expr: string, params?: string[]): ModelBinding;
119
+ createForBinding(block: ForBlock, expr: string, params?: string[]): ForBinding;
120
+ /** A plain read view of the cell namespace, with no dependency recording. */
121
+ readProxy(): any;
122
+ /**
123
+ * `pending('key')` in a template expression.
124
+ *
125
+ * Must record a read exactly like the cell proxy does. Without that a
126
+ * `$if="pending('x')"` would register no dependency, never re-evaluate when
127
+ * the cell fills, and leave a permanent placeholder with no error - which is
128
+ * why this shares `onRead` with the proxy rather than reaching for the cell
129
+ * directly.
130
+ */
131
+ pendingProbe(onRead?: (key: string) => void): (key: string) => boolean;
132
+ /** For `$needs`, which asks about cell readiness rather than values. */
133
+ get cellManager(): CellManager;
134
+ /** Compile a standalone expression, e.g. a `$key`. */
135
+ compileExpression(expr: string, params?: string[]): Compiled;
136
+ evaluate(expr: string, instance: ComponentInstance, locals?: any[]): any;
137
+ applyBinding(binding: Binding, scope: BindingScope): void;
138
+ /** Re-evaluate everything owned by this instance, including its blocks. */
139
+ updateInstance(instance: ComponentInstance): void;
140
+ updateRow(instance: ComponentInstance, row: Row): void;
141
+ private updateBlock;
142
+ /** Re-evaluate an instance and everything below it. */
143
+ updateTree(instance: ComponentInstance): void;
144
+ /** Listen for edits and push them back into state. */
145
+ attachModel(binding: ModelBinding, scope: BindingScope): void;
146
+ private readFromDom;
147
+ private apply;
148
+ private applyModel;
149
+ /**
150
+ * Keep a binding subscribed to precisely the cells it read.
151
+ *
152
+ * Re-derived on every evaluation so conditional reads stay correct - an
153
+ * expression like `shared.a ? shared.b : shared.c` touches different cells
154
+ * depending on the value of the first.
155
+ */
156
+ private syncCellSubscriptions;
157
+ /** What a string `$class` last added, so it can take it back. */
158
+ private appliedClasses;
159
+ private updateClasses;
160
+ }
161
+ /**
162
+ * `$show` - hide without destroying.
163
+ *
164
+ * The counterpart to `$if`, which rebuilds its subtree and takes any DOM state
165
+ * with it (ADR-007). A half-typed input, a scroll position or a focused field
166
+ * inside a toggled region survives this and does not survive `$if`.
167
+ *
168
+ * The element's own display value is remembered the first time it is hidden,
169
+ * so restoring never has to guess - clearing the property outright would
170
+ * discard an inline `display` the author set.
171
+ */
172
+ /** Marks an element hidden by `$show`. Paired with HIDDEN_RULE in the CSS. */
173
+ export declare const HIDDEN_ATTR = "data-lsp-hidden";
174
+ export {};
@@ -0,0 +1,2 @@
1
+ export { BindingEvaluator, BindingScope, resolveModelTarget } from "./BindingEvaluator.js";
2
+ export type { Binding, BindingKind, ElementBinding, PropBinding, ModelBinding, ModelTarget, ForBinding, } from "./BindingEvaluator.js";
@@ -0,0 +1,11 @@
1
+ interface ImportInfo {
2
+ variable: string;
3
+ path: string;
4
+ type: "default" | "named" | "star";
5
+ }
6
+ export declare class ImportTransformer {
7
+ transformImports(logic: string): string;
8
+ private pathToComponentId;
9
+ extractImports(logic: string): ImportInfo[];
10
+ }
11
+ export {};
@@ -0,0 +1 @@
1
+ export { ImportTransformer } from "./ImportTransformer.js";
@@ -0,0 +1,110 @@
1
+ import { SignalManager } from "../signals/index.js";
2
+ import type { Binding } from "../bindings/index.js";
3
+ import type { BindingHolder, ForBlock } from "../dom/ForBlock.js";
4
+ export declare class ComponentInstance implements BindingHolder {
5
+ /** Unique per mount, e.g. "button#2". Keys the runtime's instance map. */
6
+ id: string;
7
+ /** The component definition this was mounted from, e.g. "button". */
8
+ componentId: string;
9
+ parent: ComponentInstance | null;
10
+ children: ComponentInstance[];
11
+ /** Top-level nodes this instance owns in the DOM. */
12
+ rootNodes: Node[];
13
+ /**
14
+ * Elements this component named with `$ref`, by name.
15
+ *
16
+ * Filled while the DOM is collected - which is *after* `setup` runs - so
17
+ * logic reads them in `onMounted` and not before. The object identity is
18
+ * stable, so a component can hold the reference it was handed at setup and
19
+ * find it populated later.
20
+ */
21
+ refs: Record<string, Element>;
22
+ /**
23
+ * `onMounted` callbacks, waiting for this instance's nodes to be in the
24
+ * document.
25
+ *
26
+ * Held rather than run, because logic runs *before* the DOM exists: `setup`
27
+ * happens first, then the template is built or adopted, then the nodes are
28
+ * inserted. A callback that measures something, focuses something or
29
+ * observes a node needs the last of those to have happened.
30
+ */
31
+ mountedCallbacks: Function[];
32
+ /**
33
+ * Cleanups registered by the component's logic - signal listeners, cell
34
+ * subscriptions, `onUnmounted`. See {@link addLogicCleanup}.
35
+ */
36
+ logicCleanup: Function[];
37
+ /** Bindings whose expressions evaluate against THIS instance's scope. */
38
+ bindings: Binding[];
39
+ /** `$for` blocks declared directly in this component's template. */
40
+ blocks: ForBlock[];
41
+ /** Style scope hash for the component this was mounted from. */
42
+ scopeId?: string;
43
+ props: any;
44
+ state: any;
45
+ handlers: Map<string, Function>;
46
+ /**
47
+ * `onUpdate` callbacks, run *before* this instance's bindings re-evaluate.
48
+ *
49
+ * Before rather than after, deliberately: a callback exists to recompute
50
+ * state a binding is about to read, so running it first means the same pass
51
+ * picks it up - and a write from inside it cannot schedule a second pass to
52
+ * do what this one was already going to do.
53
+ */
54
+ updateCallbacks: Function[];
55
+ /**
56
+ * Handler names an event expression is compiled with, fixed at build.
57
+ *
58
+ * Not `handlers.keys()`: that is whatever the instance registered, which the
59
+ * build cannot know for a handler created at runtime. See [[ADR-016]].
60
+ */
61
+ handlerNames: string[];
62
+ signals: SignalManager;
63
+ cleanup: Function[];
64
+ /**
65
+ * Evidence that this instance needs the browser.
66
+ *
67
+ * Recorded while it renders rather than inferred from its source: component
68
+ * logic is arbitrary JavaScript, and what it *did* is knowable where what it
69
+ * *might do* is not. A build that watched an instance mount and saw it
70
+ * register nothing has better grounds than any parser.
71
+ *
72
+ * Not a guarantee - a listener attached from a promise is invisible here -
73
+ * which is why this decides a warning today and not what ships.
74
+ */
75
+ listeners: number;
76
+ models: number;
77
+ /** `onMounted` was asked for. It never runs at build, but wanting it counts. */
78
+ wantsMount: boolean;
79
+ /** Cells it declared that are not fetched at build. */
80
+ runtimeCells: Set<string>;
81
+ constructor(id: string, componentId: string, signals: SignalManager, parent?: ComponentInstance | null);
82
+ addHandler(name: string, fn: Function): void;
83
+ getHandler(name: string): Function | undefined;
84
+ hasHandler(name: string): boolean;
85
+ addCleanup(cleanup: Function): void;
86
+ /**
87
+ * A cleanup that undoes something the component's *logic* set up.
88
+ *
89
+ * Kept apart from the DOM-owned ones because the two have opposite fates on
90
+ * a rebuild. When adoption bails, the rendered nodes are thrown away and
91
+ * everything attached to them has to go with them - but **the logic has
92
+ * already run and will not run again**, so anything it registered is the
93
+ * only copy there will ever be. Running these would silently unsubscribe a
94
+ * component from its own signals and leave it looking alive.
95
+ *
96
+ * Handlers were already protected from exactly this. Subscriptions were not.
97
+ */
98
+ addLogicCleanup(cleanup: Function): void;
99
+ /** Drop everything tied to the DOM being replaced, and only that. */
100
+ releaseDom(): void;
101
+ addBinding(binding: Binding): void;
102
+ addBlock(block: ForBlock): void;
103
+ /**
104
+ * Tear down this instance and everything below it.
105
+ *
106
+ * Children go first so their cleanup runs while their parent is still
107
+ * intact. Does not touch the DOM - the mounter owns node removal.
108
+ */
109
+ destroy(): void;
110
+ }
@@ -0,0 +1,11 @@
1
+ import type { ComponentDefinition } from "./types/index.js";
2
+ export declare class ComponentRegistry {
3
+ private components;
4
+ register(component: ComponentDefinition): void;
5
+ update(id: string, component: ComponentDefinition): void;
6
+ get(id: string): ComponentDefinition | undefined;
7
+ has(id: string): boolean;
8
+ getAllIds(): string[];
9
+ getAll(): ComponentDefinition[];
10
+ clear(): void;
11
+ }
@@ -0,0 +1,3 @@
1
+ export { ComponentRegistry } from "./ComponentRegistry.js";
2
+ export { ComponentInstance } from "./ComponentInstance.js";
3
+ export type { ComponentDefinition } from "./types/index.js";