@wentools/simmer-svelte 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wentools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @wentools/simmer-svelte
2
+
3
+ Svelte 5 adapters for [@wentools/simmer](https://jsr.io/@wentools/simmer).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # JSR
9
+ deno add jsr:@wentools/simmer-svelte
10
+
11
+ # npm
12
+ npx jsr add @wentools/simmer-svelte
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### `createEventualState`
18
+
19
+ Bridges simmer's `EventualResult` to Svelte 5 reactive state. Returns the
20
+ current (cached) value immediately, then automatically updates when fresh data
21
+ arrives.
22
+
23
+ ```svelte
24
+ <script lang="ts">
25
+ import { createEventualState } from '@wentools/simmer-svelte'
26
+
27
+ let { data } = $props()
28
+
29
+ // data.patronShowcase is EventualResult<ShowcaseData>
30
+ const showcase = createEventualState(() => data.patronShowcase)
31
+ </script>
32
+
33
+ <!-- Renders immediately with cached data -->
34
+ <PatronShowcase data={showcase.value} />
35
+
36
+ {#if showcase.isRefreshing}
37
+ <LoadingSpinner />
38
+ {/if}
39
+
40
+ {#if showcase.error}
41
+ <p>Failed to refresh: {showcase.error.message}</p>
42
+ {/if}
43
+ ```
44
+
45
+ ### How it works
46
+
47
+ `EventualResult<T>` contains `{ current: T, fresh: Promise<T> | null }`.
48
+
49
+ - `current` is served immediately (from cache or SSR)
50
+ - `fresh` resolves when updated data arrives from the API
51
+
52
+ `createEventualState` wraps this in a reactive rune that:
53
+
54
+ 1. Returns `current` as `value` immediately
55
+ 2. Sets `isRefreshing: true` while `fresh` is pending
56
+ 3. Swaps `value` to the resolved fresh data automatically
57
+ 4. Handles race conditions when navigation triggers new requests
58
+
59
+ ## License
60
+
61
+ MIT
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@wentools/simmer-svelte",
3
+ "version": "0.1.0",
4
+ "description": "Svelte 5 adapters for @wentools/simmer",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://gitlab.com/wentools/simmer-svelte"
9
+ },
10
+ "keywords": [
11
+ "svelte",
12
+ "runes",
13
+ "eventual",
14
+ "simmer",
15
+ "reactive",
16
+ "typescript"
17
+ ],
18
+ "type": "module",
19
+ "svelte": "./src/mod.ts",
20
+ "exports": {
21
+ ".": {
22
+ "svelte": "./src/mod.ts",
23
+ "types": "./src/mod.ts",
24
+ "default": "./src/mod.ts"
25
+ }
26
+ },
27
+ "peerDependencies": {
28
+ "svelte": ">=5.0.0",
29
+ "@wentools/simmer": ">=0.1.0"
30
+ }
31
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Svelte 5 rune for handling EventualResult on the client side.
3
+ *
4
+ * Automatically switches from current to fresh data when the promise resolves,
5
+ * with proper handling of race conditions during navigation.
6
+ */
7
+
8
+ import type { EventualResult } from '@wentools/simmer'
9
+
10
+ type EventualInput<TData> = EventualResult<TData>
11
+
12
+ type EventualState<TData> = {
13
+ /** The current value - starts with cached data, updates when fresh arrives */
14
+ readonly value: TData
15
+ /** True while waiting for fresh data */
16
+ readonly isRefreshing: boolean
17
+ /** Error if the fresh promise rejected */
18
+ readonly error: Error | null
19
+ }
20
+
21
+ /**
22
+ * Creates reactive state from an EventualResult.
23
+ *
24
+ * Returns the current value immediately, then automatically updates
25
+ * when fresh data arrives. Handles race conditions from navigation
26
+ * and errors gracefully.
27
+ *
28
+ * @example
29
+ * ```svelte
30
+ * <script lang="ts">
31
+ * import { createEventualState } from '@wentools/simmer-svelte'
32
+ *
33
+ * let { data } = $props()
34
+ *
35
+ * const showcase = createEventualState(() => data.patronShowcase)
36
+ * </script>
37
+ *
38
+ * <Footer patronShowcase={showcase.value} />
39
+ *
40
+ * {#if showcase.isRefreshing}
41
+ * <span class="loading-indicator" />
42
+ * {/if}
43
+ * ```
44
+ *
45
+ * @param getEventual - Getter returning EventualResult (or compatible { current, fresh })
46
+ * @returns Reactive state with value, isRefreshing, and error
47
+ */
48
+ const createEventualState = <TData>(
49
+ getEventual: () => EventualInput<TData>,
50
+ ): EventualState<TData> => {
51
+ // Initialize with current value
52
+ const initial = getEventual()
53
+ let currentValue = $state<TData>(initial.current)
54
+ let freshValue = $state<TData | undefined>(undefined)
55
+ let hasFreshValue = $state(false)
56
+ let hasFreshPromise = $state(initial.fresh !== null)
57
+ let error = $state<Error | null>(null)
58
+
59
+ // Counter to handle race conditions - stale promises are ignored
60
+ let promiseCounter = 0
61
+
62
+ $effect(() => {
63
+ const { current, fresh } = getEventual()
64
+
65
+ // Update tracked state
66
+ currentValue = current
67
+ hasFreshPromise = fresh !== null
68
+
69
+ // Reset fresh state for new input
70
+ const myCounter = ++promiseCounter
71
+ freshValue = undefined
72
+ hasFreshValue = false
73
+ error = null
74
+
75
+ if (fresh) {
76
+ fresh
77
+ .then((result) => {
78
+ // Only update if this is still the active promise
79
+ if (myCounter === promiseCounter) {
80
+ freshValue = result
81
+ hasFreshValue = true
82
+ }
83
+ })
84
+ .catch((err) => {
85
+ if (myCounter === promiseCounter) {
86
+ error = err instanceof Error ? err : new Error(String(err))
87
+ hasFreshValue = true // Mark as done even on error
88
+ }
89
+ })
90
+ }
91
+ })
92
+
93
+ return {
94
+ get value() {
95
+ // Return fresh value if available and no error, otherwise current
96
+ return hasFreshValue && !error ? (freshValue as TData) : currentValue
97
+ },
98
+ get isRefreshing() {
99
+ return hasFreshPromise && !hasFreshValue
100
+ },
101
+ get error() {
102
+ return error
103
+ },
104
+ }
105
+ }
106
+
107
+ export { createEventualState }
108
+ export type { EventualInput, EventualState }
@@ -0,0 +1,2 @@
1
+ export { createEventualState } from './eventual_state.svelte.ts'
2
+ export type { EventualInput, EventualState } from './eventual_state.svelte.ts'
package/src/mod.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createEventualState } from './eventual_state/mod.ts'
2
+ export type { EventualInput, EventualState } from './eventual_state/mod.ts'