@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 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ /// <reference path="./src/types/virtual.d.ts" />
2
+ /// <reference path="./src/types/optional.d.ts" />
3
+ /// <reference path="./src/types/lsp.d.ts" />
4
+ export { createLightspeedApp, LightspeedRuntime } from "./src/runtime/core.js";
5
+ export { registerPageExpressions } from "./src/runtime/core.js";
6
+ export { reactive, defineSignal, onMounted, onUnmounted, defineProps, shared, } from "./src/runtime/helpers.js";
7
+ export type { ComponentDefinition } from "./src/runtime/components/index.js";
8
+ export type { SignalAPI } from "./src/runtime/signals/index.js";
@@ -0,0 +1,175 @@
1
+ import type { ComponentDefinition } from "../runtime/components/index.js";
2
+ /**
3
+ * The build agent: rebuilds a site when its content changes.
4
+ *
5
+ * It exists because **incremental rebuilds need persistent state**. A CI job
6
+ * starts from a clean checkout every time, so it can only ever do a full
7
+ * build. The agent keeps the previous `dist` and manifest between runs, which
8
+ * is what lets a changed entry re-render one page instead of a thousand.
9
+ *
10
+ * It runs *in the site's repo*, because rebuilding needs the site's own
11
+ * components and toolchain - and it holds a repo checkout but never database
12
+ * credentials, which is the boundary the whole architecture is drawn around.
13
+ */
14
+ export interface RebuildRequest {
15
+ /**
16
+ * Build-time cell keys whose content changed - the CMS's publish payload.
17
+ *
18
+ * Each is passed to `rebuildPages` separately, so only the pages that read
19
+ * it are re-rendered. Empty means "rebuild everything with build-time
20
+ * content", which is the honest fallback when a caller does not know.
21
+ */
22
+ cells?: string[];
23
+ /** Where the site lives. */
24
+ root: string;
25
+ /** Build output, relative to root. */
26
+ dist?: string;
27
+ /** Pull before rebuilding. Off for tests. */
28
+ pull?: boolean;
29
+ /**
30
+ * A separate content checkout to build against.
31
+ *
32
+ * Content and code want different version control: code branches and merges,
33
+ * content is a single moving line owned by the CMS. Keeping them in one repo
34
+ * forces content to follow code's branching model, which means resolving
35
+ * content conflicts by hand when a feature branch merges.
36
+ *
37
+ * The agent joins them rather than git doing it - no submodules, no pointer
38
+ * commits, and each history stays clean. A path rather than a repo URL, so
39
+ * the source can later be a sync target or an API dump without this
40
+ * changing.
41
+ */
42
+ content?: string;
43
+ /** Commit and push the rebuilt output. */
44
+ push?: boolean;
45
+ }
46
+ export interface RebuildReport {
47
+ /** Output paths re-rendered, in order. */
48
+ rebuilt: string[];
49
+ /** Pages considered and skipped, with why. */
50
+ skipped: Array<{
51
+ page: string;
52
+ reason: string;
53
+ }>;
54
+ cells: string[];
55
+ pushed: boolean;
56
+ }
57
+ /** Drop the cache, for a caller that knows the source moved. */
58
+ export declare function forgetDefinitions(): void;
59
+ /** Compile every `.lsp` under the site's source directory. */
60
+ export declare function siteDefinitions(root: string, dir?: string): Promise<ComponentDefinition[]>;
61
+ /**
62
+ * Rebuild what the given content keys affect.
63
+ *
64
+ * One `rebuildPages` call per key rather than one for everything: the manifest
65
+ * records which cells each *output* read, so a per-entry key resolves to a
66
+ * single page. Passing them together would lose that precision.
67
+ */
68
+ export declare function rebuild(request: RebuildRequest): Promise<RebuildReport>;
69
+ /**
70
+ * Verify a webhook before doing any work.
71
+ *
72
+ * A shared secret compared in constant time. The agent is never public, but
73
+ * "never public" is a deployment claim rather than a property, and this is the
74
+ * one endpoint that can make it push to a repo.
75
+ */
76
+ export declare function authorised(header: string | null, secret: string): boolean;
77
+ /**
78
+ * The rebuild keys a commit message records.
79
+ *
80
+ * The CMS writes them into the commit body when it publishes, so **the commit
81
+ * is the record and a webhook is only a nudge**. A missed or duplicated
82
+ * webhook then costs nothing: the work is still described in git, and the
83
+ * agent finds it on the next wake.
84
+ *
85
+ * The alternative - the webhook carrying the payload - makes delivery
86
+ * critical. Lose one and the site is silently stale with nothing recording
87
+ * that a rebuild was owed.
88
+ */
89
+ export declare function cellsFromCommits(log: string): string[];
90
+ export interface Pending {
91
+ /** Commit the site is at now. */
92
+ head: string;
93
+ /** Keys to rebuild. Empty means nothing to do. */
94
+ cells: string[];
95
+ /** No recorded state: everything with build-time content is stale. */
96
+ full: boolean;
97
+ /** Whether the range changed content, source, or nothing. */
98
+ kind: ChangeKind;
99
+ /** Dependencies moved, so a build needs an install first. */
100
+ install: boolean;
101
+ /** Commit the content repo is at, when it is separate. */
102
+ contentHead?: string;
103
+ }
104
+ /**
105
+ * What has happened since the agent last built.
106
+ *
107
+ * Reads every commit between the recorded state and `HEAD`, so a run that
108
+ * covers three missed publishes rebuilds the union of their keys rather than
109
+ * only the newest.
110
+ */
111
+ export declare function pendingWork(root: string, content?: string): Promise<Pending>;
112
+ export interface AgentState {
113
+ /** Last site commit built. */
114
+ lastCommit: string | null;
115
+ /** Last content commit built, when content is a separate repo. */
116
+ lastContentCommit?: string | null;
117
+ }
118
+ export declare function readState(root: string): Promise<AgentState>;
119
+ export declare function writeState(root: string, commit: string, contentCommit?: string | null): Promise<void>;
120
+ /** What a range of commits requires. */
121
+ export type ChangeKind = "none" | "content" | "source";
122
+ /**
123
+ * Decide from the changed paths what kind of rebuild is owed.
124
+ *
125
+ * The two are not interchangeable. `rebuildPages` deliberately skips the
126
+ * bundler and re-renders page shells that already carry hashed asset tags -
127
+ * exactly right for content, and **wrong for a component change**, which
128
+ * produces new bundles the shells do not reference. Doing the cheap thing
129
+ * after a source change yields fresh HTML pointing at stale JavaScript, which
130
+ * looks like it worked.
131
+ *
132
+ * Deciding here rather than at the trigger means a developer push, a CMS
133
+ * publish and a manual run all reach the right answer without anyone choosing
134
+ * a mode - and without the CMS needing any part in deploying source.
135
+ */
136
+ export declare function classifyChange(files: string[]): ChangeKind;
137
+ /** Whether dependencies moved, and a build needs an install first. */
138
+ export declare function needsInstall(files: string[]): boolean;
139
+ /** Paths touched between the recorded state and HEAD. */
140
+ export declare function changedFiles(root: string, from: string, to: string): Promise<string[]>;
141
+ /**
142
+ * Run the site's own build.
143
+ *
144
+ * `bun run build` rather than a hardcoded `vite build`: the site owns its
145
+ * toolchain, and the agent is that site building itself.
146
+ */
147
+ export declare function fullBuild(root: string, install: boolean, content?: string): Promise<void>;
148
+ export interface PreviewRequest {
149
+ /** The cell holding this entry's content, e.g. `cms:exhibitions:my-show`. */
150
+ entryCell: string;
151
+ /** The cell the route enumerates from, e.g. `cms:exhibitions`. */
152
+ indexCell: string;
153
+ /** The entry's slug, which decides its URL. */
154
+ slug: string;
155
+ /** The content to render - the editor's current values, saved or not. */
156
+ data: Record<string, unknown>;
157
+ }
158
+ /**
159
+ * Render a page with unpublished content, without publishing it.
160
+ *
161
+ * The CMS sends the content **in the request**, so the agent still never talks
162
+ * to a database - the boundary the whole architecture rests on holds even for
163
+ * previewing drafts.
164
+ *
165
+ * Deliberately the same `renderPage` the build uses. A preview rendered by any
166
+ * other path would drift from what publishing actually produces, and a preview
167
+ * that lies is worse than no preview.
168
+ */
169
+ export declare function preview(request: PreviewRequest & {
170
+ root: string;
171
+ dist?: string;
172
+ }): Promise<{
173
+ html: string;
174
+ path: string;
175
+ }>;
@@ -0,0 +1,9 @@
1
+ import type { Command } from "../types/index.js";
2
+ /**
3
+ * Run the build agent.
4
+ *
5
+ * Not a library call but a process, which is why it is a CLI command rather
6
+ * than an export: it holds a repo checkout and the previous build between
7
+ * runs, and that persistence is the whole reason it exists.
8
+ */
9
+ export declare const agentCommand: Command;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../types/index.js";
2
+ export declare const buildCommand: Command;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../types/index.js";
2
+ export declare const createCommand: Command;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../types/index.js";
2
+ export declare const devCommand: Command;
@@ -0,0 +1,3 @@
1
+ export { buildCommand } from "./build.js";
2
+ export { createCommand } from "./create.js";
3
+ export { devCommand } from "./dev.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,11 @@
1
+ export interface CommandConfig {
2
+ name: string;
3
+ description: string;
4
+ usage?: string;
5
+ examples?: string[];
6
+ }
7
+ export interface Command {
8
+ config: CommandConfig;
9
+ enabled?: boolean;
10
+ handler: (args: string[]) => void;
11
+ }
@@ -0,0 +1 @@
1
+ export * from "./commands.js";
@@ -0,0 +1,5 @@
1
+ import type { Command } from "../types/index.js";
2
+ /**
3
+ * Auto-discover and register commands from the commands directory
4
+ */
5
+ export declare function loadCommands(commandsDir: string): Promise<Record<string, Command>>;
@@ -0,0 +1 @@
1
+ export { loadCommands } from "./commands.js";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Every expression a template evaluates, with the scope it evaluates in.
3
+ *
4
+ * The runtime compiles these with `new Function` today, which is why a page
5
+ * needs a CSP with `unsafe-eval`. Compiling them at build instead needs one
6
+ * thing the flat extractor in `parser/template.ts` cannot give: **the loop
7
+ * aliases in scope at each expression**, because `item` in `{{ item.name }}`
8
+ * is a parameter of the compiled function, not a free variable.
9
+ *
10
+ * Those aliases are static - they come from `$for="item in items"` in the
11
+ * markup - so the build can know them exactly.
12
+ *
13
+ * The key format matches `BindingEvaluator`'s compile cache deliberately, so
14
+ * the runtime can look a compiled function up by the same key it would have
15
+ * cached it under. That keeps expression *source* in the DOM, which is what
16
+ * adoption re-binds from - so nothing about the emitted markup changes.
17
+ */
18
+ export interface TemplateExpression {
19
+ /** The expression source, exactly as the DOM carries it. */
20
+ expr: string;
21
+ /** Loop aliases in scope, outermost first. */
22
+ params: string[];
23
+ /** Which binding produced it, for diagnostics. */
24
+ kind: string;
25
+ /** True for `$model`, which also needs a writer. */
26
+ writable: boolean;
27
+ }
28
+ /** Matches `BindingEvaluator`'s compile cache key. */
29
+ export declare function expressionKey(expr: string, params: string[]): string;
30
+ /** `item in items` → alias and the iterable expression. */
31
+ export declare function parseForExpression(value: string): {
32
+ alias?: string;
33
+ items: string;
34
+ };
35
+ export interface ExtractOptions {
36
+ /**
37
+ * The component's handler names, in declaration order.
38
+ *
39
+ * An **expression** event handler - `@click="toggle(item)"` rather than
40
+ * `@click="toggle"` - is compiled with the component's handlers as
41
+ * parameters, so it can call them. The order matches the order `setup`
42
+ * registers them, which is the order they are declared.
43
+ */
44
+ handlers?: string[];
45
+ }
46
+ export declare function extractExpressions(template: string, options?: ExtractOptions): TemplateExpression[];
47
+ /**
48
+ * The expression table a component ships.
49
+ *
50
+ * Keyed the way `BindingEvaluator` keys its compile cache, so the runtime can
51
+ * look up what it would otherwise have compiled. A `$model` binding also emits
52
+ * a writer under the same key.
53
+ *
54
+ * The table is global at runtime rather than per component: a compiled
55
+ * expression depends only on its source and its scope, so two components
56
+ * sharing an expression share a function, and merging tables cannot conflict.
57
+ */
58
+ export declare function expressionTableSource(found: TemplateExpression[]): string;
59
+ /** The same table, built as real functions - for the build and for tests. */
60
+ export declare function buildExpressionTable(found: TemplateExpression[]): {
61
+ expressions: Record<string, Function>;
62
+ writers: Record<string, Function>;
63
+ };
64
+ /**
65
+ * Handlers a template binds that its logic never declares.
66
+ *
67
+ * `@click="publish"` compiles to a direct handler lookup, so a name that was
68
+ * never declared fails at *click time* with nothing wrong at build. The
69
+ * compiler already knows both halves - every `data-on-*` value, and every
70
+ * declaration - so the mismatch is detectable before anyone opens the page.
71
+ *
72
+ * Only bare names are checked. An expression handler like `remove(todo)` is
73
+ * compiled with the declared handlers as parameters, so an unknown name there
74
+ * is a different failure with a different message.
75
+ */
76
+ export declare function missingHandlers(template: string, declared: string[]): string[];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Compile a `.lsp` source into the definition the runtime mounts.
3
+ *
4
+ * The Vite plugin does this during a build, which is fine until someone wants
5
+ * to test a component *outside* a build - a consumer's test suite has no Vite,
6
+ * so without this there is no way to exercise a component at all. Tela hit this
7
+ * trying to test its field registry.
8
+ *
9
+ * Deliberately the same path the plugin uses rather than a parallel one: a test
10
+ * that compiles differently to the build is not testing the build.
11
+ */
12
+ import { parseLSP } from "../parser/index.js";
13
+ import { transformTemplate } from "../parser/template.js";
14
+ import { scopeStyles, scopeIdFor } from "../parser/style.js";
15
+ import type { ComponentDefinition } from "../runtime/components/index.js";
16
+ export interface CompileSource {
17
+ /** Component id, normally the filename without its extension, lowercased. */
18
+ id: string;
19
+ source: string;
20
+ }
21
+ /**
22
+ * Compile a set of components together.
23
+ *
24
+ * Together, because a component's template can only be compiled once the ids of
25
+ * the components it mounts are known - a tag is only a component tag if
26
+ * something answers to that name.
27
+ */
28
+ export declare function compileComponents(sources: CompileSource[], transpile?: (logic: string) => string,
29
+ /**
30
+ * Resolve a module a component's logic imports.
31
+ *
32
+ * Optional: components that import nothing need no loader. Supplied by the
33
+ * build and by the agent, which both have a filesystem to resolve against.
34
+ */
35
+ load?: (specifier: string) => Promise<Record<string, unknown>>,
36
+ /**
37
+ * Read a `<style src="…">`, relative to the component that asked for it.
38
+ *
39
+ * Optional, like `load`: a component sharing no styles needs none. Anything
40
+ * with a filesystem passes `nodeStyleReader()`.
41
+ */
42
+ readStyle?: (specifier: string, from: string) => string): Promise<Record<string, ComponentDefinition>>;
43
+ export { parseLSP, transformTemplate, scopeStyles, scopeIdFor };
44
+ export { compileLogic, buildSetup, setupSource } from "./logic.js";
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Compile a component's `<logic>` into the body of a real function.
3
+ *
4
+ * Logic is currently a *string* executed with `new Function` once per
5
+ * instance, which is why a component cannot import anything: an `import`
6
+ * statement inside `new Function` is a syntax error, so the two specifiers
7
+ * Lightspeed understands are rewritten and the rest are dropped.
8
+ *
9
+ * Compiling it at build instead means:
10
+ *
11
+ * - **project modules import normally**, resolved and chunked by the bundler
12
+ * - **no `new Function` in the browser**, so a page can run under a CSP with
13
+ * no `unsafe-eval` (see [[ADR-016]])
14
+ * - stack traces point at the `.lsp` file rather than `<anonymous>`
15
+ *
16
+ * This module only produces source. Evaluating it is the build's business, and
17
+ * the build may use `eval` freely - CSP constrains the browser, not the
18
+ * toolchain.
19
+ */
20
+ export interface ImportBinding {
21
+ /** The module specifier. */
22
+ source: string;
23
+ /** Local name → exported name, with `default` and `*` as themselves. */
24
+ names: Array<{
25
+ local: string;
26
+ imported: string;
27
+ }>;
28
+ /** The original statement, for the emitted module. */
29
+ statement: string;
30
+ }
31
+ export interface CompiledLogic {
32
+ /** Real import statements, hoisted to module scope. */
33
+ imports: string[];
34
+ /**
35
+ * The same imports, decomposed.
36
+ *
37
+ * The body references these as free variables, so the client gets them from
38
+ * module scope while the build supplies them as function parameters - one
39
+ * body, two ways of binding it, and no divergence between the paths.
40
+ */
41
+ bindings: ImportBinding[];
42
+ /** The body of `setup(__ctx)`. */
43
+ body: string;
44
+ /** Component ids this logic imports, for the island graph. */
45
+ components: string[];
46
+ }
47
+ /** The context keys `setup` destructures. Must match `createExecutionContext`. */
48
+ export declare const CONTEXT_KEYS: readonly ["_componentInstance", "__lightspeedRuntime", "_getComponent", "_addHandler"];
49
+ /** `./Button.lsp` → `button` */
50
+ export declare function componentIdFrom(path: string): string;
51
+ export declare function compileLogic(logic: string): CompiledLogic;
52
+ /**
53
+ * Top-level declarations, so the template can bind to them.
54
+ *
55
+ * Regex rather than a parser because the shapes that matter are declarations
56
+ * at the start of a line, and the epilogue guards every name with a `typeof`
57
+ * check - a name matched here that never reached scope costs nothing.
58
+ */
59
+ export declare function declaredNames(logic: string): string[];
60
+ /** The full `setup` function source, for a module or for build-time eval. */
61
+ export declare function setupSource(compiled: CompiledLogic): string;
62
+ /** Decompose an import statement into the names it binds. */
63
+ export declare function parseImport(statement: string): ImportBinding;
64
+ /**
65
+ * Build a runnable `setup` from logic, for the build and for tests.
66
+ *
67
+ * Uses `new Function`, which is exactly what the shipped runtime must stop
68
+ * doing - but a CSP governs the browser, not the toolchain. Build-time
69
+ * rendering and the test suite both need a callable function, and neither ends
70
+ * up in anyone's page.
71
+ */
72
+ export declare function buildSetup(logic: string, options?: {
73
+ /** Transpile TypeScript before compiling, if needed. */
74
+ transpile?: (source: string) => string;
75
+ /** Resolve a specifier to a module namespace, for project imports. */
76
+ load?: (source: string) => Promise<Record<string, unknown>>;
77
+ }): Promise<((ctx: Record<string, unknown>) => void) | undefined>;
78
+ /**
79
+ * Declarations that are *functions*, which is what becomes a handler.
80
+ *
81
+ * The epilogue registers a name only when `typeof x === "function"`, so this
82
+ * has to agree with it - an event expression is compiled with the handler list
83
+ * as parameters, and a list that disagrees compiles the wrong signature.
84
+ *
85
+ * Statically undecidable in one case: `const f = makeHandler()` is a function
86
+ * at runtime and unknowable here. See the `$handlers` note in [[ADR-016]].
87
+ */
88
+ export declare function declaredHandlers(logic: string): string[];
89
+ /**
90
+ * The handler names an event expression is compiled with, in order.
91
+ *
92
+ * Shipped with the component so the build and the runtime cannot disagree.
93
+ * Reading them from the instance at runtime - `[...owner.handlers.keys()]` -
94
+ * makes the compiled signature depend on what happened to be registered,
95
+ * which is not knowable at build for `const f = makeHandler()`.
96
+ *
97
+ * Every declaration is included rather than only the ones that look like
98
+ * functions: a name that turns out not to be one is simply `undefined`, which
99
+ * costs a parameter, while a name that is missing breaks the expression.
100
+ */
101
+ export declare function handlerParams(logic: string): string[];
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Where the build manifest lives.
3
+ *
4
+ * Outside `dist`, because it is build metadata rather than site output and
5
+ * `dist` is served publicly. Read by `rebuildPages` and by any server that has
6
+ * to honour a runtime-resolved route.
7
+ */
8
+ export declare const MANIFEST_DIR = ".lightspeed";
9
+ export declare const MANIFEST_FILE = "manifest.json";
10
+ /**
11
+ * The one manifest file that *does* belong in `dist`.
12
+ *
13
+ * A server hosting the output has to resolve runtime routes, so it needs the
14
+ * route list at request time. Nothing else is serving data - page shells,
15
+ * skeletons and the component graph are build metadata - so only this travels
16
+ * with the site.
17
+ */
18
+ export declare const ROUTES_FILE = "lightspeed-routes.json";
19
+ /** Ships with every page: `$show` hides via attribute, not inline style. */
20
+ export declare const HIDDEN_RULE = "[data-lsp-hidden]{display:none!important}";
21
+ import type { ComponentDefinition } from "./runtime/components/index.js";
22
+ /**
23
+ * What the build knows about each component, and what the page needs to load it.
24
+ *
25
+ * Deliberately keyed by **component**, not by island or by page. A component's
26
+ * compiled output is a function of its own source, so a page addition or a
27
+ * content change can never invalidate it - which is what makes incremental
28
+ * rebuilds possible. Rendering a page reads this; it never has to rebuild it.
29
+ *
30
+ * Internal for now. The `version` field is what makes it safe to change.
31
+ */
32
+ export declare const MANIFEST_VERSION = 1;
33
+ export interface ComponentEntry {
34
+ /** Chunk URL. Absent until per-component chunking exists. */
35
+ url?: string;
36
+ /** Other chunks this one needs, for precise preload hints. */
37
+ imports?: string[];
38
+ /** Critical CSS, already scoped. Content at build time; a URL once emitted. */
39
+ css?: string;
40
+ /** Deferred CSS, applied when the component hydrates. */
41
+ cssDeferred?: string;
42
+ /** Style scope hash, so a skeleton can be stamped without loading the chunk. */
43
+ scope?: string;
44
+ /**
45
+ * Components this one's template mounts.
46
+ *
47
+ * Distinct from `imports`: that is the chunk graph, this is the island graph.
48
+ * Preloading has to be transitive over *this* - an island hydrating on load
49
+ * needs its children's chunks immediately, even though they are separate
50
+ * chunks.
51
+ */
52
+ uses: string[];
53
+ /** Mounts a component named by an expression, so it can reach anything. */
54
+ dynamic?: boolean;
55
+ /**
56
+ * Placeholder markup. Needed client-side because a deferred island must
57
+ * render its skeleton *without* fetching the component - otherwise deferral
58
+ * defeats itself. Only matters where there is no build-rendered HTML: dev,
59
+ * and imperative mounts.
60
+ */
61
+ skeleton?: string;
62
+ }
63
+ /** What a page needs to be re-rendered without running the bundler. */
64
+ export interface PageEntry {
65
+ /**
66
+ * The page's markup *before* islands were rendered, with the build's asset
67
+ * tags already injected.
68
+ *
69
+ * Kept because `dist/page.html` is finished output - re-rendering that would
70
+ * render already-rendered content. The shell is the input the build itself
71
+ * used, so a rebuild is the same operation with fresher data.
72
+ */
73
+ shell: string;
74
+ /** Build-time cell keys this page read, so a rebuild can be targeted. */
75
+ content: string[];
76
+ }
77
+ export interface Manifest {
78
+ version: number;
79
+ runtime?: {
80
+ url: string;
81
+ };
82
+ components: Record<string, ComponentEntry>;
83
+ /** Keyed by output filename, e.g. `blog.html`. */
84
+ pages?: Record<string, PageEntry>;
85
+ /** Every URL the build emitted. */
86
+ routes?: RouteEntry[];
87
+ /** Per page, the `style-src` hashes a host must allow. */
88
+ styleHashes?: Record<string, string[]>;
89
+ }
90
+ export interface RouteEntry {
91
+ page: string;
92
+ pattern?: string;
93
+ path: string;
94
+ /** Route params this output was rendered with, for re-rendering just it. */
95
+ params?: Record<string, string>;
96
+ /** Build-time cells this specific output read. */
97
+ content?: string[];
98
+ /**
99
+ * The cell this route enumerated from.
100
+ *
101
+ * An **enumeration** dependency, not a content one. The index decides *which
102
+ * pages exist*; it does not decide what an existing page renders. Treating
103
+ * it as content meant renaming one entry re-rendered every page in the
104
+ * collection - measured at 1000 pages as costing the same as a full build.
105
+ */
106
+ from?: string;
107
+ /** Where a runtime-resolved route is served from, when not the path itself. */
108
+ file?: string;
109
+ }
110
+ /**
111
+ * Whether a template mounts a component named by an expression.
112
+ *
113
+ * The build cannot know what `<Dynamic $is="field.type" />` will resolve to,
114
+ * so a page containing one can reach *any* component. That is what makes it
115
+ * the exception to page-scoping below.
116
+ */
117
+ export declare function hasDynamicMarker(template: string): boolean;
118
+ /** Component ids referenced by a compiled template. */
119
+ export declare function componentsUsedIn(template: string): string[];
120
+ /** Every island marker in a template, with the trigger it declares. */
121
+ export declare function markersIn(template: string): Array<{
122
+ c: string;
123
+ hydrate?: string;
124
+ }>;
125
+ /**
126
+ * Components a page needs *immediately*.
127
+ *
128
+ * Seeded from islands the page hydrates on load, then followed through their
129
+ * templates - a child with no trigger of its own is needed as soon as its
130
+ * parent is. Anything left over is only ever fetched on a trigger.
131
+ *
132
+ * The point of separating them: components that always load together should be
133
+ * bundled together, because splitting things that are always fetched as a set
134
+ * costs compression and buys nothing.
135
+ */
136
+ export declare function eagerComponents(pageHtml: string, templates: Record<string, string>): string[];
137
+ export declare function buildManifest(definitions: ComponentDefinition[]): Manifest;
138
+ /**
139
+ * Everything a page can reach, following `uses` transitively.
140
+ *
141
+ * A page carries only this much of the manifest, so a landing page with one
142
+ * island does not ship a map of the whole project.
143
+ */
144
+ export declare function componentsForPage(pageHtml: string, manifest: Manifest): string[];
145
+ /** The subset of the manifest a page inlines for its runtime. */
146
+ export declare function manifestForPage(pageHtml: string, manifest: Manifest): Manifest;
147
+ /** Critical CSS for one page, in dependency order. */
148
+ export declare function criticalCssForPage(pageHtml: string, manifest: Manifest): string;