@hypen-space/core 0.4.45 → 0.4.81
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 +8 -9
- package/dist/app.d.ts +56 -6
- package/dist/app.js +77 -8
- package/dist/app.js.map +4 -4
- package/dist/components/builtin.js +77 -8
- package/dist/components/builtin.js.map +4 -4
- package/dist/datasource.d.ts +4 -4
- package/dist/datasource.js.map +1 -1
- package/dist/engine-base.d.ts +193 -0
- package/dist/engine-base.js +540 -0
- package/dist/engine-base.js.map +12 -0
- package/dist/hypen.d.ts +24 -19
- package/dist/hypen.js +7 -7
- package/dist/hypen.js.map +3 -3
- package/dist/index.browser.js +86 -14
- package/dist/index.browser.js.map +6 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +92 -20
- package/dist/index.js.map +7 -7
- package/dist/persistence.d.ts +21 -0
- package/dist/remote/client.d.ts +2 -0
- package/dist/remote/client.js +3 -2
- package/dist/remote/client.js.map +3 -3
- package/dist/remote/index.js +3 -2
- package/dist/remote/index.js.map +3 -3
- package/dist/remote/types.d.ts +2 -0
- package/dist/router.js +8 -6
- package/dist/router.js.map +3 -3
- package/package.json +7 -1
- package/src/app.ts +177 -29
- package/src/datasource.ts +4 -4
- package/src/engine-base.ts +358 -0
- package/src/hypen.ts +34 -28
- package/src/index.ts +8 -2
- package/src/persistence.ts +25 -0
- package/src/remote/client.ts +3 -0
- package/src/remote/types.ts +2 -0
- package/src/router.ts +13 -6
- package/src/types.ts +1 -0
package/src/hypen.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Enables single-file components with inline UI templates.
|
|
5
5
|
*
|
|
6
|
-
* The `hypen` tagged template preserves
|
|
6
|
+
* The `hypen` tagged template preserves @{state.x} and @{item.x} bindings
|
|
7
7
|
* instead of interpolating them, allowing you to write:
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* })
|
|
18
18
|
* .ui(hypen`
|
|
19
19
|
* Column {
|
|
20
|
-
* Text("Count:
|
|
20
|
+
* Text("Count: @{state.count}")
|
|
21
21
|
* Button { Text("+") }
|
|
22
22
|
* .onClick("@actions.increment")
|
|
23
23
|
* }
|
|
@@ -29,12 +29,13 @@
|
|
|
29
29
|
* Creates a proxy that captures property access as a binding path string.
|
|
30
30
|
*
|
|
31
31
|
* When used in a template literal, the proxy's toString() returns the
|
|
32
|
-
* full binding expression (e.g., "
|
|
32
|
+
* full binding expression (e.g., "@{state.user.name}"). This lets you
|
|
33
|
+
* interpolate JS variables into hypen templates without losing type safety.
|
|
33
34
|
*
|
|
34
35
|
* @example
|
|
35
36
|
* ```typescript
|
|
36
37
|
* const state = createBindingProxy('state');
|
|
37
|
-
*
|
|
38
|
+
* String(state.user.name) // Returns: "@{state.user.name}"
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
41
|
function createBindingProxy(root: string): any {
|
|
@@ -46,7 +47,7 @@ function createBindingProxy(root: string): any {
|
|
|
46
47
|
prop === "toString" ||
|
|
47
48
|
prop === "valueOf"
|
|
48
49
|
) {
|
|
49
|
-
return () =>
|
|
50
|
+
return () => `@{${root}}`;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
// Handle other Symbol properties (e.g., Symbol.toStringTag)
|
|
@@ -56,7 +57,7 @@ function createBindingProxy(root: string): any {
|
|
|
56
57
|
|
|
57
58
|
// Handle JSON.stringify
|
|
58
59
|
if (prop === "toJSON") {
|
|
59
|
-
return () =>
|
|
60
|
+
return () => `@{${root}}`;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// Chain to nested property: state.user -> state.user.name
|
|
@@ -87,12 +88,18 @@ function createBindingProxy(root: string): any {
|
|
|
87
88
|
/**
|
|
88
89
|
* Proxy for state bindings.
|
|
89
90
|
*
|
|
90
|
-
*
|
|
91
|
+
* In hypen templates, prefer the direct `@{state.x}` syntax. This proxy
|
|
92
|
+
* exists for cases where you want to interpolate JS values into templates
|
|
93
|
+
* — e.g., `hypen\`Text("${state.user.name}")\`` produces the same string
|
|
94
|
+
* as `hypen\`Text("@{state.user.name}")\``.
|
|
91
95
|
*
|
|
92
96
|
* @example
|
|
93
97
|
* ```typescript
|
|
98
|
+
* // Preferred: direct syntax
|
|
99
|
+
* hypen`Text("Hello, @{state.user.name}")`
|
|
100
|
+
*
|
|
101
|
+
* // Also works: proxy interpolation
|
|
94
102
|
* hypen`Text("Hello, ${state.user.name}")`
|
|
95
|
-
* // Produces: Text("Hello, ${state.user.name}")
|
|
96
103
|
* ```
|
|
97
104
|
*/
|
|
98
105
|
export const state: any = createBindingProxy("state");
|
|
@@ -106,7 +113,7 @@ export const state: any = createBindingProxy("state");
|
|
|
106
113
|
* ```typescript
|
|
107
114
|
* hypen`
|
|
108
115
|
* List(@state.items) {
|
|
109
|
-
* Text("
|
|
116
|
+
* Text("@{item.name}: @{item.price}")
|
|
110
117
|
* }
|
|
111
118
|
* `
|
|
112
119
|
* ```
|
|
@@ -122,51 +129,50 @@ export const item: any = createBindingProxy("item");
|
|
|
122
129
|
* ```typescript
|
|
123
130
|
* hypen`
|
|
124
131
|
* List(@state.items) {
|
|
125
|
-
* Text("Item
|
|
132
|
+
* Text("Item #@{index}: @{item.name}")
|
|
126
133
|
* }
|
|
127
134
|
* `
|
|
128
135
|
* ```
|
|
129
136
|
*/
|
|
130
137
|
export const index: any = {
|
|
131
|
-
[Symbol.toPrimitive]: () => "
|
|
132
|
-
toString: () => "
|
|
133
|
-
valueOf: () => "
|
|
134
|
-
toJSON: () => "
|
|
138
|
+
[Symbol.toPrimitive]: () => "@{index}",
|
|
139
|
+
toString: () => "@{index}",
|
|
140
|
+
valueOf: () => "@{index}",
|
|
141
|
+
toJSON: () => "@{index}",
|
|
135
142
|
};
|
|
136
143
|
|
|
137
144
|
/**
|
|
138
145
|
* Tagged template literal for Hypen DSL templates.
|
|
139
146
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
147
|
+
* Prefer the direct `@{state.x}` / `@{item.x}` syntax. The `state`, `item`,
|
|
148
|
+
* and `index` proxies are available for interpolating JS variables or
|
|
149
|
+
* mixing dynamic content with bindings.
|
|
142
150
|
*
|
|
143
151
|
* @example
|
|
144
152
|
* ```typescript
|
|
145
153
|
* import { hypen, state, item } from "@hypen-space/core";
|
|
146
154
|
*
|
|
147
|
-
* //
|
|
148
|
-
* const t1 = hypen`Text("Count:
|
|
149
|
-
* // Result: 'Text("Count: ${state.count}")'
|
|
155
|
+
* // Preferred: direct binding syntax
|
|
156
|
+
* const t1 = hypen`Text("Count: @{state.count}")`;
|
|
150
157
|
*
|
|
151
|
-
* // Nested
|
|
152
|
-
* const t2 = hypen`Text("Hello,
|
|
153
|
-
* // Result: 'Text("Hello, ${state.user.profile.name}")'
|
|
158
|
+
* // Nested paths work the same way
|
|
159
|
+
* const t2 = hypen`Text("Hello, @{state.user.profile.name}")`;
|
|
154
160
|
*
|
|
155
161
|
* // List with item binding
|
|
156
162
|
* const t3 = hypen`
|
|
157
163
|
* List(@state.products) {
|
|
158
|
-
* Text("
|
|
164
|
+
* Text("@{item.name} - $@{item.price}")
|
|
159
165
|
* }
|
|
160
166
|
* `;
|
|
161
167
|
*
|
|
162
|
-
* //
|
|
168
|
+
* // Interpolate JS variables alongside bindings
|
|
163
169
|
* const title = "My App";
|
|
164
|
-
* const t4 = hypen`Text("${title}:
|
|
165
|
-
* // Result: 'Text("My App:
|
|
170
|
+
* const t4 = hypen`Text("${title}: @{state.count}")`;
|
|
171
|
+
* // Result: 'Text("My App: @{state.count}")'
|
|
166
172
|
* ```
|
|
167
173
|
*
|
|
168
174
|
* @param strings - Template literal string parts
|
|
169
|
-
* @param expressions - Interpolated
|
|
175
|
+
* @param expressions - Interpolated JS values or binding proxies
|
|
170
176
|
* @returns The template string with bindings preserved
|
|
171
177
|
*/
|
|
172
178
|
export function hypen(
|
|
@@ -179,7 +185,7 @@ export function hypen(
|
|
|
179
185
|
const expr = expressions[i];
|
|
180
186
|
|
|
181
187
|
// Convert expression to string
|
|
182
|
-
// Binding proxies will return "
|
|
188
|
+
// Binding proxies will return "@{state.x}" via their toString()
|
|
183
189
|
result += String(expr);
|
|
184
190
|
result += strings[i + 1]!;
|
|
185
191
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* })
|
|
17
17
|
* .ui(hypen`
|
|
18
18
|
* Column {
|
|
19
|
-
* Text("Count:
|
|
19
|
+
* Text("Count: @{state.count}")
|
|
20
20
|
* Button { Text("+") }
|
|
21
21
|
* .onClick("@actions.increment")
|
|
22
22
|
* }
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* .build();
|
|
38
38
|
*
|
|
39
39
|
* // component.hypen (separate file)
|
|
40
|
-
* // Column { Text("Count:
|
|
40
|
+
* // Column { Text("Count: @{state.count}") ... }
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
|
|
@@ -95,6 +95,12 @@ export type {
|
|
|
95
95
|
DataSourceAccessor,
|
|
96
96
|
} from "./app.js";
|
|
97
97
|
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// STATE PERSISTENCE
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
export type { StateStore } from "./persistence.js";
|
|
103
|
+
|
|
98
104
|
// ============================================================================
|
|
99
105
|
// DATA SOURCE PLUGIN SYSTEM
|
|
100
106
|
// ============================================================================
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Persistence Layer
|
|
3
|
+
*
|
|
4
|
+
* Pluggable persistence interface for Hypen modules.
|
|
5
|
+
* Modules opt in with `.persist(adapter)` on the builder.
|
|
6
|
+
* The runtime handles save/restore lifecycle transparently.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface StateStore<T = unknown> {
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the storage key for this module instance.
|
|
12
|
+
* Called on init and whenever state changes (for withKey).
|
|
13
|
+
* Returns null = no persistence active.
|
|
14
|
+
*/
|
|
15
|
+
resolveKey(state: T, moduleName: string, sessionId: string): string | null;
|
|
16
|
+
|
|
17
|
+
/** Load persisted state. Called when key first resolves to non-null. */
|
|
18
|
+
load(key: string): Promise<T | null>;
|
|
19
|
+
|
|
20
|
+
/** Save state. Called on mutation (debounced by runtime). */
|
|
21
|
+
save(key: string, state: T): Promise<void>;
|
|
22
|
+
|
|
23
|
+
/** Delete persisted state. Called on session expire. */
|
|
24
|
+
delete(key: string): Promise<void>;
|
|
25
|
+
}
|
package/src/remote/client.ts
CHANGED
|
@@ -71,6 +71,8 @@ export interface SessionOptions {
|
|
|
71
71
|
id?: string;
|
|
72
72
|
/** Client metadata (platform, version, userId, etc.) */
|
|
73
73
|
props?: Record<string, unknown>;
|
|
74
|
+
/** Optional persist/routing key for Durable Object routing (withKey) */
|
|
75
|
+
persistKey?: string;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -270,6 +272,7 @@ export class RemoteEngine implements Disposable {
|
|
|
270
272
|
type: "hello",
|
|
271
273
|
sessionId: this.currentSessionId ?? this.sessionOptions?.id,
|
|
272
274
|
props: this.sessionOptions?.props,
|
|
275
|
+
persistKey: this.sessionOptions?.persistKey,
|
|
273
276
|
};
|
|
274
277
|
|
|
275
278
|
this.ws.send(JSON.stringify(hello));
|
package/src/remote/types.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface HelloMessage {
|
|
|
73
73
|
sessionId?: string;
|
|
74
74
|
/** Client metadata (platform, version, userId, etc.) */
|
|
75
75
|
props?: Record<string, any>;
|
|
76
|
+
/** Optional persist/routing key for Durable Object routing (withKey) */
|
|
77
|
+
persistKey?: string;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
package/src/router.ts
CHANGED
|
@@ -184,12 +184,19 @@ export class HypenRouter {
|
|
|
184
184
|
window.history.pushState(null, "", url);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
// Manually trigger hashchange event
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
// Manually trigger hashchange event. Wrap in try-catch because
|
|
188
|
+
// in test environments the global HashChangeEvent class may come
|
|
189
|
+
// from a different realm than the window, causing dispatchEvent to
|
|
190
|
+
// reject it with "parameter 1 is not of type 'Event'".
|
|
191
|
+
try {
|
|
192
|
+
const hashChangeEvent = new HashChangeEvent("hashchange", {
|
|
193
|
+
oldURL: window.location.href.replace(window.location.hash, "#" + oldPath),
|
|
194
|
+
newURL: window.location.href,
|
|
195
|
+
});
|
|
196
|
+
window.dispatchEvent(hashChangeEvent);
|
|
197
|
+
} catch {
|
|
198
|
+
// Ignore cross-realm Event dispatch errors
|
|
199
|
+
}
|
|
193
200
|
}
|
|
194
201
|
} finally {
|
|
195
202
|
this.isUpdating = false;
|
package/src/types.ts
CHANGED