@tschk/moonshine-svelte 0.0.0-stage → 0.4.4

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.
package/README.md CHANGED
@@ -1,3 +1,474 @@
1
- # Temporary Holding Version
1
+ # `@tschk/moonshine-svelte`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Independent stores, native component context/events, lifecycle helpers, and Moonshine signal bridges.
4
+ No Svelte runtime, React, WASM, or parser is required. The framework peer is
5
+ optional at runtime and loaded only when using the compatibility renderer.
6
+
7
+ Strict TypeScript checking of the root entry requires installing
8
+ `@tschk/moonshine-framework`, even when importing only a bridge: the root also
9
+ exports `crepusRenderer` with the framework's `Renderer` contract. The framework
10
+ remains optional for runtime imports and bridge-only browser/Bun/Node bundles.
11
+ This requirement does not apply to the signal-only `@tschk/moonshine` core entry.
12
+
13
+ | Subpath | Provides |
14
+ | ---------- | ------------------------------------------------------------------- |
15
+ | `.` | signal bridges and lazy native SSR compatibility renderer |
16
+ | `./svelte` | lifecycle, context, typed `createContext`, component event dispatch |
17
+ | `./class` | opt-in `classValue` normalization for source class expressions |
18
+ | `./store` | independent `writable`, `readable`, `derived`, and `get` |
19
+
20
+ `classValue` flattens nested class arrays and includes truthy object keys. Empty
21
+ results return `undefined`, omitting the DOM/SSR attribute. Top-level primitive
22
+ values retain Svelte 5.57 stringification; nested falsy values are omitted. This
23
+ helper does not change ordinary native `h()` attributes or implement class/style
24
+ directives.
25
+
26
+ The optional `/runes` entry exports compiler-facing `createState`, `createDerived`
27
+ and `createRuneEffect`.
28
+
29
+ Compiler-facing `/runes` also exports `spreadProps(...layers)` for live component
30
+ props. Layers are objects, nullish values, or object getters; later layers win.
31
+ Source getters keep their receiver and are read lazily. Layer getters are not
32
+ cached: the compiler must memoize call expressions where required. Nullish
33
+ layers are skipped during setter lookup, avoiding the pinned host's null-layer
34
+ assignment error. Pass component
35
+ children through the named props object to preserve source precedence.
36
+
37
+ The separate `/ssr` `spreadServerProps(layers)` copies enumerable own string-key
38
+ descriptors once per render pass. Getters remain lazy and read from the merged
39
+ object; symbols and inherited properties are omitted. DOM and SSR spread helpers
40
+ intentionally have different contracts. The native SFC gate verifies bounded component spread lowering, including
41
+ call-expression caching, live rest props, explicit children and component
42
+ references. Implicit body/legacy slots, element spreads and hydration remain
43
+ unverified.
44
+
45
+ Lifecycle helpers require a live native owner. `onMount` runs untracked in a
46
+ microtask after synchronous DOM insertion; a synchronous returned function is
47
+ owned cleanup. Async callbacks do not register returned promise values as
48
+ cleanup. Disposed roots skip pending mounts, including synchronous SSR roots.
49
+ `onDestroy` runs on owner disposal, including SSR. `tick()` yields, then commits
50
+ pending native rune work across the default effect and opt-in render queues.
51
+ Future promise/timer updates and hydration remain separate contracts.
52
+
53
+ Mount hooks share the post-DOM rune-effect queue, preserving registration order
54
+ across sibling components. By default, pending native component references publish before
55
+ mount/effect callbacks. Opaque exported instances preserve live getters and methods;
56
+ source fixtures now verify named/empty exports and scalar/member `bind:this` in Chromium and SSR. Other export forms and loop-dependent references remain separate acceptance work.
57
+
58
+ Stores implement the subscription protocol, but reads are not automatically
59
+ tracked by native DOM bindings. Subscribe into a native signal and unsubscribe
60
+ on destruction:
61
+
62
+ ```ts
63
+ import { createSignal } from "@tschk/moonshine-dom/reactive";
64
+ import { writable } from "@tschk/moonshine-svelte/store";
65
+ import { onDestroy } from "@tschk/moonshine-svelte/svelte";
66
+
67
+ // During native component initialization:
68
+ const count = writable(0);
69
+ const [value, setValue] = createSignal(0);
70
+ onDestroy(count.subscribe((next) => setValue(next)));
71
+ // Use value as a native DOM child accessor.
72
+ ```
73
+
74
+ Context and dispatch APIs require native function-component initialization through
75
+ `h(Component, props)` or `createComponent` from `@tschk/moonshine-dom/component`.
76
+ Each instance inherits its own context map; `getAllContexts()` returns that live
77
+ map. Mutating it changes subsequent context reads and child inheritance without
78
+ changing sibling or ancestor maps.
79
+ `createContext<T>()` returns typed get/set/has functions.
80
+
81
+ `createEventDispatcher` delivers non-bubbling `CustomEvent`s to the explicit
82
+ listener map on the `componentEvents` symbol from the component module. It
83
+ supports detail, listener arrays and cancellation. Callback props remain separate.
84
+ Disposed instances stop dispatching; listeners have no legacy exported-instance
85
+ `this`. This is a native compiler contract, not Svelte's internal component ABI.
86
+
87
+ This package does not execute existing Svelte compiler instructions or alias
88
+ `svelte`; existing output must be recompiled through Crepus. Authored top-level/imported `$store` reads, replacement and writable expressions have bounded DOM/SSR proof. The tested local compiler supports a bounded authored
89
+ SFC slice: scalar, shallow raw and ordinary deep `$state`, `$state.snapshot`, `$derived`,
90
+ post-DOM `$effect`, and live `$props()` (whole-object or destructured with rename,
91
+ defaults and read-only rest), click handlers, static/dynamic child components,
92
+ keyed blocks, `$effect.tracking()`, manual `$effect.root`, checkbox/value/range
93
+ bindings, named/empty component exports, scalar/member `bind:this` and named context helpers. Reachable plain
94
+ TypeScript context helpers use the native bridge too. Fixtures are verified in
95
+ Chromium and SSR, including skipped effect factories on the server.
96
+ Full source-compatibility remains unfinished; the acceptance surface is
97
+ tracked in [the SFC coverage ledger](../../docs/native-sfc-coverage.md).
98
+
99
+ The `/runes` helpers are compiler targets. `createState` isolates proxy writes
100
+ from ordinary object/array inputs and tracks deep mutations. Unusual property
101
+ descriptors reject explicitly. Reads capture raw properties lazily; proxy writes
102
+ remain isolated from the input. Own properties shadowing inherited names enumerate
103
+ consistently, intentionally differing from Svelte's edge behavior. Complete Svelte
104
+ proxy parity remains unfinished. `stateSnapshot` clones plain data and supported
105
+ platform values, handles `toJSON` and cycles, and retains uncloneable values.
106
+ Map/Set snapshots are shallow, matching Svelte production.
107
+ `createRawState` preserves object/function identity and tracks only replacement,
108
+ with Svelte's `===` equality and the same teardown-history policy as deep cells.
109
+ Nested mutation remains ordinary JavaScript; this helper does not create proxies.
110
+ `createRuneEffect` deduplicates post-DOM microtask effects, owns
111
+ teardown and nested work, and skips synchronous SSR, including bare renders and
112
+ retained foreign owners. It does not implement
113
+ the pre scheduler or asynchronous Svelte boundaries; `/pre` stays separate. `runeTracking()` reports whether
114
+ synchronous dependency collection is active: true inside native render accessors,
115
+ derived computations and rune effects; false during component setup, ordinary
116
+ event callbacks and `untrack`. The tracking source fixture verifies these contexts
117
+ in Chromium and SSR.
118
+
119
+ `createDerived` evaluates on demand and allows optimistic `.value` overrides.
120
+ Dependency invalidation resets an override even when the computed value is equal;
121
+ dynamic dependencies, errors and diamond updates preserve the kernel's pull graph.
122
+ Runtime helper tests do not establish compiler coverage for every rune form.
123
+
124
+ The source gate also admits imported `.svelte.ts` and `.svelte.js` factories with
125
+ function-local state, raw state and derived declarations. Typed and JavaScript
126
+ factories preserve independent instances, cached reads, live getters and SSR
127
+ request output. Top-level exported rune cells remain explicitly unsupported,
128
+ including declarations supplying types without a runtime implementation. Two
129
+ synchronous writes can still cause an extra native derived callback; the current
130
+ source fixture tests writes separately and does not establish full batching
131
+ parity.
132
+
133
+ `createProp` supplies writable compiler prop cells: ordinary writes stay local;
134
+ declared bindable props forward an actual parent setter. Defaults use a lazy,
135
+ reactive derived value, matching the Svelte 5.57.1 reference. A bound undefined prop
136
+ with a fallback rejects in the browser; SSR forwards a defined fallback instead.
137
+ This helper does not validate source binding declarations or implement `bind:this`.
138
+ Its SSR forwarding is not Svelte `$bindable` parity. Source SSR lowering must use
139
+ the optional `/ssr` local prop cells and buffered retries with one-time setup and
140
+ lifecycle callbacks retained only from the accepted render pass.
141
+
142
+ The root `crepusRenderer` is a lazy native SSR compatibility renderer. Pages
143
+ must explicitly use `@tschk/moonshine-dom/ssr`. Source-path loading is supported
144
+ under Bun; bundled deployments use
145
+ `@tschk/moonshine-dom/framework-native` with their bundled module table.
146
+
147
+ The optional `/binding` entry wraps native single-select and radio bindings with
148
+ state-origin comparison: a native state proxy and its original object compare
149
+ equal. Selected values are forwarded unchanged, preserving proxy overlays.
150
+ Multiple selects and checkbox groups retain array `includes` identity semantics.
151
+ Single selects match a typed `undefined` option before adopting browser defaults;
152
+ choosing a duplicate option retains that node when its model value is unchanged.
153
+ Checkbox binding and typed-value metadata helpers forward to native bindings. This entry does not add source binding
154
+ syntax support or recognize proxies created by an installed Svelte runtime.
155
+
156
+ `createRuneRoot` on `/runes` runs untracked setup synchronously and returns an
157
+ idempotent disposer. Its effects have a manual lifetime: enclosing component
158
+ unmounts and effect reruns do not dispose the root. Returned setup cleanup runs
159
+ when explicitly stopped, after owned effects. SSR component frames skip setup
160
+ entirely; server lowering must also erase source `$effect.root` callbacks.
161
+ Svelte `/binding` also maps empty numeric `bindValue` writes to `null`;
162
+ invalid nonempty numeric defaults reset to `NaN`, while text fields still write
163
+ empty strings. Generic native DOM bindings keep their
164
+ existing `undefined` numeric contract. Input type must remain fixed.
165
+
166
+ Teardown history is confined to this adapter. Within a pending flush, cleanup
167
+ reads the first previous values of compiler-created state and derived cells;
168
+ cleanup writes remain immediately readable. Derived historical reads keep a
169
+ separate cache, and synchronous native DOM consumers continue to read live values.
170
+ Proxy property history follows Svelte's lazy sources: previously unread properties
171
+ can read as undefined during cleanup, while key enumeration follows current structure.
172
+ External kernel getters without instrumented writers cannot reconstruct history.
173
+ SSR destruction reads live values. The source acceptance gate checks pending
174
+ teardown, derived values, manual stop, disposal and isolated SSR requests.
175
+
176
+ The optional `/ssr` entry exposes compiler-facing `createServerProp`, `afterRender`
177
+ and `settle` helpers for synchronous server bindings. Server prop assignments stay
178
+ local until successful serialization ends; only a currently undefined parent with
179
+ an own setter receives the final defined child value. `settle` buffers retryable
180
+ template output while setup stays outside the callback. Discarded attempts run
181
+ internal cleanup but suppress Svelte destroy callbacks, including nested accepted
182
+ attempts inside a discarded parent. Only accepted output updates select bookkeeping.
183
+ Runaway invalidation throws after 1,000 attempts. The native source gate now
184
+ verifies scalar component `bind:value` with a child `$bindable(3)` default:
185
+ two-way browser writes, retained nodes, independent instances, all four SSR
186
+ initial/assignment cases and accepted cleanup. Failed undefined browser setup
187
+ cleans the partial native parent owner, differing from the pinned reference.
188
+ The composite source fixtures also verify typed member targets, renamed child
189
+ bindable props, reactive fallback values without premature parent publication,
190
+ and binding precedence over spreads in either attribute order. Named component
191
+ getter/setter pairs also have source acceptance: boolean/string transformations,
192
+ captured member functions called without a receiver, and strict literal-union
193
+ types. Dynamic replacements retain their captured functions, set up the new
194
+ component before old cleanup, and keep both write directions live. Unlike ordinary bindings, SSR function bindings publish an undefined
195
+ getter's child fallback once without retrying setup. Browser undefined values
196
+ still reject when the child has a fallback. Other binding forms, asynchronous
197
+ rendering and hydration remain unverified.
198
+
199
+ The separate `/pre` entry exports `withRuneScheduler(callback)` and
200
+ `createRunePreEffect(callback)`. Enter scheduling under a live native owner:
201
+
202
+ ```ts
203
+ import { h, render } from "@tschk/moonshine-dom/dom";
204
+ import { withRuneScheduler } from "@tschk/moonshine-svelte/pre";
205
+
206
+ const dispose = render(() => withRuneScheduler(() => h(App, {})), target);
207
+ ```
208
+
209
+ Within this tree, pre-effects run initially during setup. Later writes coalesce
210
+ into a microtask: parent pre-effects see current state and old DOM, structural
211
+ work removes hidden descendants before their queued callbacks, retained keyed
212
+ rows follow current index order, and DOM updates precede post-effects. References
213
+ publish before mounts/effects by default; opt-in `publication: "post"` on the
214
+ component reference can register after child mounts/effects and before ancestor
215
+ posts. Scheduled control branches dispose children before parent hooks while
216
+ retained rows survive updates. Child post callbacks precede ancestor callbacks.
217
+ Reference publication can invalidate pre-effects and text bindings; that work
218
+ waits until pending posts finish. Ordinary post-effect cleanup writes still
219
+ render before nested posts. Structural branches follow owner order, so an earlier
220
+ branch invalidated by a later branch waits for the next pass. Render/structural
221
+ job descendants dispose before their component's hooks; pre-effect shutdown
222
+ retains subscription-first error precedence.
223
+ Pure derived values remain pullable. Cleanup history spans the complete cycle.
224
+ Errors drain queued peers, ownership cancels disposed work, and repeated feedback
225
+ is limited per callback rather than per distinct job. Ownerless entry rejects;
226
+ Synchronous SSR bypasses new scheduling and suppresses pre callbacks, including
227
+ bare callbacks and foreign scopes.
228
+
229
+ The ordinary native renderer stays synchronous. `/runes` does not import this
230
+ scheduler. The authored pre-effect pair has bounded DOM/SSR source acceptance,
231
+ including skipped SSR factories and child-first destruction. Other pre-effect
232
+ variants, actions, attachments, async boundaries and hydration need separate
233
+ source acceptance. `tick()` also drains synchronous writes between these native
234
+ queues; this does not establish Svelte's asynchronous compiler semantics.
235
+
236
+ The separate `/directives` entry provides manual `installAction(element, invoke,
237
+ readParameter?)` and `installAttachment(element, readAttachment)` under a live
238
+ `withRuneScheduler` tree. Install them before the element's attribute/text jobs
239
+ (for native `h`, use its first `ref` prop). Factories run after insertion; action
240
+ updates keep their source position before later DOM jobs. Attachment identity
241
+ changes clean up before replacement, while retained callback dependencies rerun
242
+ after DOM updates. Final cleanup sees the element removed.
243
+
244
+ Action parameters observe deep native state, including lazy cyclic aliases;
245
+ merely reading another alias does not trigger an update. Traversal interests
246
+ are removed on rerun/disposal. SSR skips all directive thunks. Native cleanup
247
+ drains peers and reports the first failure, differing from Svelte's early abort.
248
+ These helpers do not establish `use:` or `{@attach}` source lowering, attachment
249
+ spread support, hydration or complete Svelte directive compatibility.
250
+
251
+ The optional `/await` entry exports `awaitBlock(read, { pending, then, catch })`.
252
+ Use it under a live `withRuneScheduler` owner; branch values and errors are
253
+ accessors. Promise completion commits within one microtask, ignores stale or
254
+ disposed generations, and preserves a retained branch's nodes and local state.
255
+ Replacement branches initialize before old cleanup. Plain values, including
256
+ `false` and `null`, select `then`; already resolved promises skip pending output.
257
+
258
+ Synchronous SSR reads once and emits pending output for promises, or `then` for
259
+ plain values. It does not await. Missing rejection branches use native owner
260
+ error handling; unhandled callback failures retain the original error in a
261
+ microtask. This helper does not establish authored `{#await}` lowering,
262
+ asynchronous SSR, hydration, transitions or complete Svelte compatibility.
263
+
264
+ The optional `/store-binding` entry exports `createStoreReference<T>()` for
265
+ compiler-owned component subscriptions. Create one per store variable under a
266
+ live native owner. Its `get(store)` subscribes lazily, caches repeated reads and
267
+ tracks later notifications, including identical object/function values.
268
+ `releaseIfChanged(next)` releases the old subscription immediately and returns
269
+ `next`, without eagerly subscribing to it. Call `finish()` at the end of instance
270
+ setup, after registering authored destruction hooks; this preserves cached store
271
+ reads during teardown and releases child subscriptions before parent hooks. A
272
+ failed-setup fallback still releases acquired subscriptions.
273
+
274
+ Both function and observable-object unsubscriptions retain their protocol and
275
+ receiver. Effect cleanup uses the existing previous-flush history. SSR caches
276
+ subscriptions within the request owner and releases them on disposal. Retained
277
+ reads after disposal use temporary subscriptions; callbacks emitted after
278
+ unsubscription are ignored. Compiled SSR instead selects the separate server
279
+ group described below. Native failures drain cleanup peers. The local Crepuscularity `dafde4ab` compiler verifies authored top-level and
280
+ imported `$store` reads plus simple `$state` store-variable replacement. It
281
+ rejects `$store` assignments and mutations. This compiler patch is applied locally in
282
+ Crepuscularity but is not released; legacy reactive statements, async SSR and hydration
283
+ remain unsupported.
284
+
285
+ `/ssr` also provides `createServerState(initial)` and
286
+ `createServerDerived(compute, cache?)` for server lowering. State is a plain box.
287
+ Derived caching defaults to the render context at creation; component lowering
288
+ passes `true` explicitly. Nullish overrides resume computation, while falsy and
289
+ function values remain literal overrides. This centralizes server runtime policy
290
+ in Moonshine. The verified compiler source gate checks these imports for
291
+ components and both JS and TS companion factories, including a companion sharing
292
+ its importing SFC's basename.
293
+
294
+ The verified local `dafde4ab` compiler installs the rune scheduler in generated DOM
295
+ components. Native handwritten rendering remains synchronous by default. The
296
+ same authored counter built by this compiler is 10,766 gzip bytes versus pinned
297
+ Svelte 5.57.1's 17,389; this is one workload, not a universal size or speed claim.
298
+ The optional adapter-event hook in the shared DOM factory adds 32 gzip bytes to
299
+ this workload; the signal kernel is unchanged.
300
+
301
+ The 340-module source gate includes a self-imported recursive component tree.
302
+ Conditional updates preserve local state, focus and context; removed descendants
303
+ stop before their stale props can be evaluated. Disposal/remounting and fresh
304
+ escaped SSR match the pinned reference. Broader component compatibility remains
305
+ tracked in the coverage ledger.
306
+
307
+ Named component snippets preserve lexical values, consumer arguments, inherited
308
+ context, retained focus/local state and child-first cleanup. Type-only `Snippet`
309
+ imports use Moonshine's host-free contract; both generated graphs pass strict
310
+ checking, with fresh escaped SSR verified.
311
+
312
+ Bare intrinsic attributes preserve empty strings, expression attributes retain
313
+ boolean values, and bare component props remain `true`. The source gate also
314
+ checks boolean controls, empty file values and quoted event expressions in DOM
315
+ and SSR. A bounded form-reset fixture also checks literal versus expression
316
+ defaults, bound text/number/checkbox models, user/programmatic writes, reset after
317
+ tick, independent instances, disposal and fresh SSR. Broader reset variants remain open.
318
+
319
+ Function-local TS and JS companion factories can contain `$effect`. Two factories in
320
+ each of two component instances match the reference post-DOM and cleanup traces;
321
+ SSR erases registration and callback evaluation. Same-basename SFC/JS companions
322
+ are supported through explicit compiler inventory mapping and collision-safe
323
+ component output names. The source gate checks strict TS graphs, emitted JS
324
+ syntax, host-free imports and fresh escaped requests.
325
+
326
+ Textarea source checks cover literal versus expression defaults, unchanged-output
327
+ dirty-edit retention, changed values and independent instances. Raw closing tags,
328
+ component-looking markup and script strings remain text. Literal CR/CRLF, named
329
+ entities, semicolonless ampersands and static/dynamic decoding differences match
330
+ pinned Svelte DOM and parsed SSR; fixtures declare UTF-8 explicitly. Ordinary
331
+ text fragment whitespace and programmatic raw-snippet ownership remain open.
332
+
333
+ The source gate also checks bounded ordinary whitespace, snippet boundaries,
334
+ NBSP edges and pre/textarea indentation against exact DOM and parsed SSR
335
+ references. Leading pre newlines before conditionals, keys, snippets, raw HTML and imported
336
+ components now have exact DOM/parsed SSR checks; SVG and nested snippet
337
+ whitespace remain separate work.
338
+
339
+ Private raw-snippet bridges are available on `/raw-snippet`, `/raw-snippet-dom`
340
+ and `/raw-snippet-ssr`. They separate source callable metadata, DOM getter cells
341
+ and SSR values, with explicit consumer ownership and cleanup. These entries
342
+ require compiler lowering; authored `createRawSnippet` remains outside the source
343
+ acceptance gate. Only one trimmed HTML element root is supported, without
344
+ hydration. See the coverage ledger for the exact runtime and reference checks.
345
+
346
+ The private DOM entry also exports `createRawSnippetSlot`. Lowering creates one
347
+ slot per `@render`, passes `slot.deferRemoved` to `mapArray`, wraps invocation in
348
+ `slot.render`, and returns `slot.commit(mapped())` for every output, including
349
+ ordinary and empty snippets. This commits replacement setup before disposing
350
+ the previous branch and drains canceled work when its owner is disposed.
351
+
352
+ The private raw insertion hook adds 30 gzip bytes to the authored counter;
353
+ the signal-only default bundle remains unchanged.
354
+
355
+ The source gate verifies textarea body/reset-default independence, exact reader
356
+ order and complete SSR defaultValue expression erasure. Coercion/error-order
357
+ variants remain separate reference work.
358
+
359
+ Textarea expression bodies also preserve dirty edits when a forced rerun returns
360
+ the same object, without repeating its conversion. The source gate checks
361
+ conversion failures, DOM Symbol assignment errors, reset/disposal, literal versus
362
+ expression newlines, independent instances and fresh escaped SSR. Mixed body
363
+ expressions and default-value conversion ordering remain separate work.
364
+
365
+ One-level object and fixed-array `$derived` patterns now have authored DOM/SSR
366
+ proof, including renames, lazy defaults, bounded iterator closing, typed tuples,
367
+ independent overrides and error recovery. Nested/rest/computed patterns still
368
+ reject. Imperatively read derived chains remain lazy after source writes.
369
+
370
+ `/ssr` exports `createServerStoreReferences()` for compiler-owned server groups.
371
+ Create per-variable refs through `group.create<T>()`; call `group.finish()` after
372
+ successful serialization and accepted settlement, before bindable prop commits.
373
+ Cleanup follows first-read order and preserves completed same-source caches for
374
+ destruction hooks. Failed/discarded attempts still release resources. Late/new
375
+ destruction reads are balanced one-shot subscriptions, and peer cleanup drains
376
+ after errors; these deliberately avoid reference leaks. The DOM `/store-binding`
377
+ helper retains its separate destruction-before-release contract.
378
+
379
+ Compiler-facing `/store-binding` exports `setStoreValue`, `mutateStoreValue`
380
+ and `updateStoreValue`; `/ssr` exports `setStoreValue`, `mutateServerStore`
381
+ and `updateStoreValue`. Lowering captures the writable source before evaluating
382
+ an assignment and preserves the expression result. DOM member writes and SSR
383
+ member writes deliberately have different read/notification ordering. These
384
+ helpers require compiler-owned evaluation order; they are not general mutation
385
+ utilities. The frozen writable-store compiler passes the 346-module source gate,
386
+ including direct/compound/logical/member writes and prefix/postfix updates.
387
+ Destructuring, delete and iteration write targets remain rejected.
388
+
389
+ Scheduled list cleanup follows current retained keyed row order, children before
390
+ parents, while deferred removed rows remain alive until commit or owner disposal.
391
+ Authored keyed and unkeyed `#each` blocks now pass real CLI source acceptance
392
+ with identifier and static nested object item bindings, live indices,
393
+ arrays/iterables/array-like/nullish inputs,
394
+ key visits interleaved with setup and physical movement, retained row state and
395
+ empty fallbacks. Repeated empty inputs retain the same fallback root and DOM.
396
+ SSR does not evaluate keys. Renamed aliases, nested member bindings and unbound
397
+ callback/tag receivers have minified browser and fresh SSR proof. Defaults,
398
+ rest/array patterns and direct item/index write targets still diagnose;
399
+ transitions and hydration remain open.
400
+
401
+ Plain modern/legacy module scripts preserve module-once execution and named
402
+ exports. Bounded module runes now support state/raw state, snapshots and private
403
+ derived values. Stable exported state is the actual value/proxy; reassigned
404
+ state exports and derived exports reject. Module state persists across SSR
405
+ requests; module derived reads recompute while component derived values cache.
406
+ Module async/generator factories preserve rune creation-time ownership and caching.
407
+ Instance scripts and companion factories keep their placement restrictions.
408
+ The permanent gate covers 784 modules using frozen combined compiler
409
+ `ae8c6f6f`,
410
+ including shared module-rune state, snapshots, export identity, lifetime and SSR
411
+ cache behavior, bounded action/attachment source behavior and eight SSR prop-order
412
+ cases plus finite Vue defaults, explicit Solid source and authored await blocks. All 778 emitted TypeScript modules pass both strict checkers,
413
+ with no type exceptions. Await alias `typeof` queries have bounded source proof,
414
+ including qualified queries, nested defaults and annotations; invalid value-as-type
415
+ uses still fail both strict checkers.
416
+
417
+ The optional `/each` compiler entry reuses native insertion ranges and owners.
418
+ Duplicate or unstable keys diagnose and provisional roots always clean up;
419
+ reference production failure leaks are deliberately not reproduced. Cleanup
420
+ continues through sibling errors. With frozen compiler `c2ae808e` and the
421
+ invalidation/demand runtime, the unchanged authored SFC counter measures
422
+ 34,650 raw / 12,683 gzip9 / 11,482 Brotli11 bytes versus the production
423
+ reference's 46,617 / 17,344 / 15,628. Correct derived overrides and equal-value
424
+ effect suppression add 1,386 browser gzip bytes; SSR gzip stays at 4,361 bytes.
425
+ The signal-only core is 4,041 raw bytes, up 243 raw / 86 gzip bytes from the
426
+ previous runtime. Native full-row list updates are slower in the measured workload;
427
+ smaller output does not establish a speed advantage.
428
+
429
+ A derived value first read after its component stops computes once from current
430
+ inputs, then freezes without subscribing. Failed computations keep and rethrow
431
+ the original error; dependency writes and newly attached owned work still reject.
432
+ This preserves native error/override policies rather than claiming exact Svelte
433
+ behavior for those cases.
434
+
435
+ Authored styles containing only explicit `:global(selector)` rules or a
436
+ `:global { ... }` block now compile to inventoried external CSS assets. Browser
437
+ checks cover scriptless and stateful components, outside matching elements,
438
+ independent instances, retained input edits/focus and disposal. No runtime style
439
+ injector is used, and ordinary counter output stays byte-identical.
440
+
441
+ Bounded local rules now scope authored intrinsic selector participants, including
442
+ ordinary compounds/combinators and global prefixes/suffixes. Sixteen state
443
+ predicates and before/after pseudo elements retain scope across hover, focus,
444
+ checked and disabled changes; unrelated elements and opaque child/raw HTML
445
+ boundaries remain untouched. Generated CSS is external, with matching DOM/SSR
446
+ hashes. Prefix matching memoizes selector/node states to avoid exponential
447
+ ancestor-path enumeration.
448
+
449
+ Bounded first/last/only child and of-type selectors, `:empty`, and four nth
450
+ selectors are supported. Nth arguments remain unscoped, including foreign
451
+ siblings. Other functional selectors, at-rules/keyframes, complex global placement,
452
+ preprocessing, external references, escapes, spreads and uncertain relational
453
+ branch/slot cases remain unsupported. The optional compiler uses the MIT Raffia
454
+ parser; no browser dependency is added. Its safety guard rejects more than 256
455
+ opening delimiters, including those in literals/comments. Full scoped CSS and
456
+ unused-selector diagnostics remain open.
457
+
458
+ Public instance rune fields now compile through Crepus: state/raw/derived/by,
459
+ including typed and zero-argument state fields. Bounded source checks cover
460
+ inheritance, initialization order, nonenumerable DOM accessors, ordinary SSR
461
+ state fields, callable receivers, independent instances and disposal. Private
462
+ identifier-keyed instance state/raw/derived initializers also have browser/SSR
463
+ source proof. Full constructor coverage remains open, including JavaScript SFC computed members.
464
+ Static fields and cast-wrapped state assignments with top-level await/yield
465
+ remain unsupported; full class compatibility is unfinished.
466
+
467
+ The compiler-facing `/runes` entry also exports private-field state/raw cells.
468
+ They reuse native state/history, expose historical tracked `value` and live
469
+ untracked `peek()`, and deep `assign()` returns its resolved proxy before feedback
470
+ or disposal can replace the current value. These runtime contracts have focused
471
+ behavior/type tests and bounded private-class source acceptance in the shared
472
+ shared source gate.
473
+
474
+ The optional `/mount` entry freshly mounts compiler-marked native SFC factories and returns their synchronous typed exports. It supports owned teardown, target-local anchors and copied root contexts; `/server-mount` rejects client mounting on the server. Hydration, transitions/outros, legacy event options and error transforms remain unsupported and reject explicitly. Both boolean `intro` values are accepted for sources without transitions. The erased `ComponentView<View, Exports, Props>` return metadata and `ComponentFactory<Implementation>` callable type preserve required/optional props and export types. Generated public functions must also preserve ESM hoisting. Public mounting checks metadata props rather than an implementation parameter typed `any`. The standalone `scripts/check-native-svelte-mount.ts --compiler <path>` source check verifies 28 generated modules with frozen compiler `703ceb65`: unchanged authored launchers, typed synchronous exports, two independent instances, anchor/context/cleanup behavior, DOM-free server imports and real Bun/Node ESM cycles. The combined await/source gate currently uses a separate compiler snapshot; merging those compiler lineages remains open. Declaration emission for setup-local interfaces is also unfinished.
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@tschk/moonshine-svelte",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.4",
4
+ "description": "Independent Svelte store and native lifecycle APIs with Moonshine signal bridges",
5
+ "license": "ISC",
6
+ "type": "module",
7
+ "main": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": "./src/index.ts",
11
+ "./svelte": "./src/svelte.ts",
12
+ "./store": "./src/store.ts",
13
+ "./runes": "./src/runes.ts",
14
+ "./binding": "./src/binding.ts",
15
+ "./class": "./src/class.ts",
16
+ "./ssr": "./src/ssr.ts",
17
+ "./pre": "./src/pre.ts",
18
+ "./directives": "./src/directives.ts",
19
+ "./await": "./src/await.ts",
20
+ "./store-binding": "./src/store-binding.ts",
21
+ "./raw-snippet": "./src/raw-snippet.ts",
22
+ "./raw-snippet-dom": "./src/raw-snippet-dom.ts",
23
+ "./raw-snippet-ssr": "./src/raw-snippet-ssr.ts",
24
+ "./each": "./src/each.ts",
25
+ "./mount": "./src/mount.ts",
26
+ "./server-mount": "./src/server-mount.ts",
27
+ "./const": "./src/const-derived.ts"
28
+ },
29
+ "dependencies": {
30
+ "@tschk/moonshine": "^0.4.4",
31
+ "@tschk/moonshine-dom": "^0.4.4"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "7.0.2"
35
+ },
36
+ "peerDependencies": {
37
+ "@tschk/moonshine-framework": "^0.4.4"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@tschk/moonshine-framework": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "files": [
45
+ "src",
46
+ "README.md"
47
+ ],
48
+ "keywords": [
49
+ "moonshine",
50
+ "svelte",
51
+ "adapter"
52
+ ],
53
+ "scripts": {
54
+ "format": "bunx prettier --check .",
55
+ "lint": "bunx oxlint .",
56
+ "typecheck": "bunx tsc --noEmit",
57
+ "test": "bun test"
58
+ }
59
+ }
package/src/await.ts ADDED
@@ -0,0 +1,123 @@
1
+ /** Independent await-block control; no async SSR, hydration, or transitions. */
2
+ import {
3
+ createComponentScope,
4
+ createRenderEffect,
5
+ createSignal,
6
+ getOwner,
7
+ getRenderScheduler,
8
+ onCleanup,
9
+ runWithOwner,
10
+ untrack,
11
+ type Accessor,
12
+ } from "@tschk/moonshine-dom/reactive";
13
+ import { mapArray } from "@tschk/moonshine-dom/control";
14
+ import { isSSRRendering } from "@tschk/moonshine-dom/ssr-owner";
15
+ import { peekComponentContext } from "@tschk/moonshine-dom/component";
16
+ import { flushRuneWork } from "./pre";
17
+ /** Compiler-facing awaited source type; erased from runtime output. */
18
+ export type AwaitValue<Read extends () => unknown> = Awaited<ReturnType<Read>>;
19
+ export type AwaitInput<T> = T | PromiseLike<T>;
20
+ export type AwaitBranches<T, R> = {
21
+ pending?: () => R;
22
+ then?: (value: Accessor<T>) => R;
23
+ catch?: (error: Accessor<unknown>) => R;
24
+ };
25
+ function promise(value: unknown): value is PromiseLike<unknown> {
26
+ return (
27
+ value != null &&
28
+ (typeof value === "object" || typeof value === "function") &&
29
+ typeof (value as PromiseLike<unknown>).then === "function"
30
+ );
31
+ }
32
+ export function awaitBlock<T, R>(
33
+ read: () => T | PromiseLike<T>,
34
+ branches: AwaitBranches<T, R>,
35
+ schedule?: (read: () => (R | undefined)[]) => Accessor<(R | undefined)[]>,
36
+ ): Accessor<(R | undefined)[]> {
37
+ if (isSSRRendering() || peekComponentContext()?.mode === "ssr") {
38
+ const input = read();
39
+ let child: R | undefined;
40
+ if (promise(input)) {
41
+ input.then(null, () => {});
42
+ child = branches.pending?.();
43
+ } else child = branches.then?.(() => input);
44
+ const result = child === undefined ? [] : [child];
45
+ return () => result;
46
+ }
47
+ const outer = getOwner();
48
+ if (!outer || outer.disposed || !getRenderScheduler())
49
+ throw Error("Await blocks require a live owner and withRuneScheduler");
50
+ return createComponentScope(() => {
51
+ const owner = getOwner()!;
52
+ let starting = true;
53
+ let generation = 0,
54
+ disposed = false;
55
+ const [state, setState] = createSignal<
56
+ "pending" | "then" | "catch" | undefined
57
+ >(undefined);
58
+ const [value, setValue] = createSignal<T | undefined>(undefined),
59
+ [error, setError] = createSignal<unknown>(undefined);
60
+ onCleanup(() => {
61
+ disposed = true;
62
+ generation++;
63
+ });
64
+ function alive(token: number) {
65
+ return !disposed && !owner.disposed && generation === token;
66
+ }
67
+ createRenderEffect(() => {
68
+ const token = ++generation;
69
+ const input = read();
70
+ if (!promise(input)) {
71
+ setValue(() => input);
72
+ setState("then");
73
+ return;
74
+ }
75
+ let settled = false;
76
+ const publish = (kind: "then" | "catch", next: unknown) => {
77
+ if (!alive(token)) return;
78
+ settled = true;
79
+ try {
80
+ runWithOwner(owner, () =>
81
+ untrack(() => {
82
+ if (kind === "then") setValue(() => next as T);
83
+ else setError(() => next);
84
+ setState(kind);
85
+ if (!starting) flushRuneWork();
86
+ }),
87
+ );
88
+ } catch (failure) {
89
+ queueMicrotask(() => {
90
+ throw failure;
91
+ });
92
+ }
93
+ };
94
+ input.then(
95
+ (next) => publish("then", next),
96
+ (reason) => publish("catch", reason),
97
+ );
98
+ queueMicrotask(() => {
99
+ if (!settled && alive(token))
100
+ runWithOwner(owner, () => {
101
+ setState("pending");
102
+ flushRuneWork();
103
+ });
104
+ });
105
+ });
106
+ const view = mapArray(
107
+ () => {
108
+ const current = state();
109
+ return current === undefined ? [] : [current];
110
+ },
111
+ (kind) => {
112
+ if (kind === "pending") return branches.pending?.();
113
+ if (kind === "then") return branches.then?.(value as Accessor<T>);
114
+ if (!branches.catch) throw untrack(error);
115
+ return branches.catch(error);
116
+ },
117
+ undefined,
118
+ schedule,
119
+ );
120
+ starting = false;
121
+ return view;
122
+ });
123
+ }