@jsenv/navi 0.0.1
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/index.js +51 -0
- package/package.json +38 -0
- package/src/action_private_properties.js +11 -0
- package/src/action_proxy_test.html +353 -0
- package/src/action_run_states.js +5 -0
- package/src/actions.js +1377 -0
- package/src/browser_integration/browser_integration.js +191 -0
- package/src/browser_integration/document_back_and_forward.js +17 -0
- package/src/browser_integration/document_loading_signal.js +100 -0
- package/src/browser_integration/document_state_signal.js +9 -0
- package/src/browser_integration/document_url_signal.js +9 -0
- package/src/browser_integration/use_is_visited.js +19 -0
- package/src/browser_integration/via_history.js +199 -0
- package/src/browser_integration/via_navigation.js +168 -0
- package/src/components/action_execution/form_context.js +8 -0
- package/src/components/action_execution/render_actionable_component.jsx +27 -0
- package/src/components/action_execution/use_action.js +330 -0
- package/src/components/action_execution/use_execute_action.js +161 -0
- package/src/components/action_renderer.jsx +136 -0
- package/src/components/collect_form_element_values.js +79 -0
- package/src/components/demos/0_button_demo.html +155 -0
- package/src/components/demos/1_checkbox_demo.html +257 -0
- package/src/components/demos/2_input_textual_demo.html +354 -0
- package/src/components/demos/3_radio_demo.html +222 -0
- package/src/components/demos/4_select_demo.html +104 -0
- package/src/components/demos/5_list_scrollable_demo.html +153 -0
- package/src/components/demos/action/0_button_demo.html +204 -0
- package/src/components/demos/action/10_shortcuts_demo.html +189 -0
- package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
- package/src/components/demos/action/1_input_text_demo.html +461 -0
- package/src/components/demos/action/2_form_multiple.html +303 -0
- package/src/components/demos/action/3_details_demo.html +172 -0
- package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
- package/src/components/demos/action/7_radio_list_demo.html +217 -0
- package/src/components/demos/action/8_editable_text_demo.html +442 -0
- package/src/components/demos/action/9_link_demo.html +172 -0
- package/src/components/demos/demo.md +0 -0
- package/src/components/demos/route/basic/basic.html +14 -0
- package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
- package/src/components/demos/route/multi/multi.html +14 -0
- package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
- package/src/components/details/details.jsx +248 -0
- package/src/components/details/summary_marker.jsx +141 -0
- package/src/components/editable_text/editable_text.jsx +96 -0
- package/src/components/error_boundary_context.js +9 -0
- package/src/components/form.jsx +144 -0
- package/src/components/input/button.jsx +333 -0
- package/src/components/input/checkbox_list.jsx +294 -0
- package/src/components/input/field.jsx +61 -0
- package/src/components/input/field_css.js +118 -0
- package/src/components/input/input.jsx +15 -0
- package/src/components/input/input_checkbox.jsx +370 -0
- package/src/components/input/input_radio.jsx +299 -0
- package/src/components/input/input_textual.jsx +338 -0
- package/src/components/input/radio_list.jsx +283 -0
- package/src/components/input/select.jsx +273 -0
- package/src/components/input/use_form_event.js +20 -0
- package/src/components/input/use_on_change.js +12 -0
- package/src/components/link/link.jsx +291 -0
- package/src/components/loader/loader_background.jsx +324 -0
- package/src/components/loader/loading_spinner.jsx +68 -0
- package/src/components/loader/network_speed.js +83 -0
- package/src/components/loader/rectangle_loading.jsx +225 -0
- package/src/components/route.jsx +15 -0
- package/src/components/selection/selection.js +5 -0
- package/src/components/selection/selection_context.jsx +262 -0
- package/src/components/shortcut/os.js +9 -0
- package/src/components/shortcut/shortcut_context.jsx +390 -0
- package/src/components/use_action_events.js +37 -0
- package/src/components/use_auto_focus.js +43 -0
- package/src/components/use_debounce_true.js +31 -0
- package/src/components/use_focus_group.js +19 -0
- package/src/components/use_initial_value.js +104 -0
- package/src/components/use_is_visited.js +19 -0
- package/src/components/use_ref_array.js +38 -0
- package/src/components/use_signal_sync.js +50 -0
- package/src/components/use_state_array.js +40 -0
- package/src/docs/actions.md +228 -0
- package/src/docs/demos/resource/action_status.jsx +42 -0
- package/src/docs/demos/resource/demo.md +1 -0
- package/src/docs/demos/resource/resource_demo_0.html +84 -0
- package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
- package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
- package/src/docs/demos/resource/resource_demo_2.html +173 -0
- package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
- package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
- package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
- package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
- package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
- package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
- package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
- package/src/docs/demos/resource/tata.jsx +3 -0
- package/src/docs/demos/resource/toto.jsx +3 -0
- package/src/docs/demos/user_nav/user_nav.html +12 -0
- package/src/docs/demos/user_nav/user_nav.jsx +330 -0
- package/src/docs/resource_dependencies.md +103 -0
- package/src/docs/resource_with_params.md +80 -0
- package/src/notes.md +13 -0
- package/src/route/route.js +518 -0
- package/src/route/route.test.html +228 -0
- package/src/store/array_signal_store.js +537 -0
- package/src/store/local_storage_signal.js +17 -0
- package/src/store/resource_graph.js +1303 -0
- package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
- package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
- package/src/store/tests/resource_graph_dependencies.test.js +95 -0
- package/src/store/value_in_local_storage.js +187 -0
- package/src/symbol_object_signal.js +1 -0
- package/src/use_action_data.js +10 -0
- package/src/use_action_status.js +47 -0
- package/src/utils/add_many_event_listeners.js +15 -0
- package/src/utils/array_add_remove.js +61 -0
- package/src/utils/array_signal.js +15 -0
- package/src/utils/compare_two_js_values.js +172 -0
- package/src/utils/execute_with_cleanup.js +21 -0
- package/src/utils/get_caller_info.js +85 -0
- package/src/utils/iterable_weak_set.js +62 -0
- package/src/utils/js_value_weak_map.js +162 -0
- package/src/utils/js_value_weak_map_demo.html +690 -0
- package/src/utils/merge_two_js_values.js +53 -0
- package/src/utils/stringify_for_display.js +150 -0
- package/src/utils/weak_effect.js +48 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineRoutes,
|
|
3
|
+
resource,
|
|
4
|
+
Route,
|
|
5
|
+
useDocumentState,
|
|
6
|
+
useDocumentUrl,
|
|
7
|
+
} from "@jsenv/navi";
|
|
8
|
+
import { signal } from "@preact/signals";
|
|
9
|
+
import { render } from "preact";
|
|
10
|
+
import { useEffect, useState } from "preact/hooks";
|
|
11
|
+
|
|
12
|
+
import.meta.css = /* css */ `
|
|
13
|
+
body {
|
|
14
|
+
font-family: Arial, sans-serif;
|
|
15
|
+
max-width: 800px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
padding: 20px;
|
|
18
|
+
}
|
|
19
|
+
.nav-links {
|
|
20
|
+
margin-bottom: 20px;
|
|
21
|
+
padding: 10px;
|
|
22
|
+
background: #f5f5f5;
|
|
23
|
+
border-radius: 5px;
|
|
24
|
+
}
|
|
25
|
+
.nav-links a {
|
|
26
|
+
margin-right: 15px;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
color: #007bff;
|
|
29
|
+
padding: 5px 10px;
|
|
30
|
+
border-radius: 3px;
|
|
31
|
+
}
|
|
32
|
+
.nav-links a:hover {
|
|
33
|
+
background: #e9ecef;
|
|
34
|
+
}
|
|
35
|
+
.nav-links a.active {
|
|
36
|
+
background: #007bff;
|
|
37
|
+
color: white;
|
|
38
|
+
}
|
|
39
|
+
.user-details {
|
|
40
|
+
margin: 20px 0;
|
|
41
|
+
padding: 20px;
|
|
42
|
+
border: 1px solid #ddd;
|
|
43
|
+
border-radius: 5px;
|
|
44
|
+
}
|
|
45
|
+
.actions {
|
|
46
|
+
margin-top: 15px;
|
|
47
|
+
}
|
|
48
|
+
button {
|
|
49
|
+
margin-right: 10px;
|
|
50
|
+
margin-bottom: 10px;
|
|
51
|
+
padding: 8px 15px;
|
|
52
|
+
border: none;
|
|
53
|
+
border-radius: 3px;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
background: #007bff;
|
|
56
|
+
color: white;
|
|
57
|
+
}
|
|
58
|
+
button:hover {
|
|
59
|
+
background: #0056b3;
|
|
60
|
+
}
|
|
61
|
+
button:disabled {
|
|
62
|
+
background: #6c757d;
|
|
63
|
+
cursor: not-allowed;
|
|
64
|
+
}
|
|
65
|
+
.current-url {
|
|
66
|
+
margin-top: 10px;
|
|
67
|
+
padding: 10px;
|
|
68
|
+
background: #f8f9fa;
|
|
69
|
+
border-radius: 3px;
|
|
70
|
+
font-family: monospace;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
}
|
|
73
|
+
.loading {
|
|
74
|
+
opacity: 0.6;
|
|
75
|
+
}
|
|
76
|
+
.error {
|
|
77
|
+
color: #dc3545;
|
|
78
|
+
background: #f8d7da;
|
|
79
|
+
padding: 10px;
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
const App = () => {
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<h1>User Navigation Test</h1>
|
|
88
|
+
<Navigation />
|
|
89
|
+
<Route route={USER_ROUTE}>{(user) => <UserPage user={user} />}</Route>
|
|
90
|
+
<DocumentInfo />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const Navigation = () => {
|
|
96
|
+
const users = USER.useArray();
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="nav-links">
|
|
100
|
+
{users.map((user) => {
|
|
101
|
+
const url = USER_ROUTE.buildUrl({ name: user.name });
|
|
102
|
+
return (
|
|
103
|
+
<a key={user.id} href={url}>
|
|
104
|
+
{user.name}
|
|
105
|
+
</a>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const UserPage = ({ user }) => {
|
|
113
|
+
const isRenamed = user.name !== user.originalName;
|
|
114
|
+
|
|
115
|
+
const handleRename = async () => {
|
|
116
|
+
console.log("handleRename called for user:", user.name);
|
|
117
|
+
const renameAction = USER.PUT.bindParams({
|
|
118
|
+
name: user.name,
|
|
119
|
+
property: "name",
|
|
120
|
+
value: `${user.name}_2`,
|
|
121
|
+
});
|
|
122
|
+
console.log("About to call renameAction.load()");
|
|
123
|
+
await renameAction.reload();
|
|
124
|
+
console.log("renameAction.load() completed");
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handleRevert = async () => {
|
|
128
|
+
const revertAction = USER.PUT.bindParams({
|
|
129
|
+
name: user.name,
|
|
130
|
+
property: "name",
|
|
131
|
+
value: user.originalName,
|
|
132
|
+
});
|
|
133
|
+
await revertAction.reload();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className="user-details">
|
|
138
|
+
<h2>User: {user.name}</h2>
|
|
139
|
+
<p>
|
|
140
|
+
<strong>Status:</strong> {isRenamed ? "Renamed" : "Original"}
|
|
141
|
+
</p>
|
|
142
|
+
|
|
143
|
+
<div className="actions">
|
|
144
|
+
<button
|
|
145
|
+
onClick={handleRename}
|
|
146
|
+
disabled={USER.PUT.loadingState === "LOADING"}
|
|
147
|
+
>
|
|
148
|
+
Rename to "{user.name}_2"
|
|
149
|
+
</button>
|
|
150
|
+
<button
|
|
151
|
+
onClick={handleRevert}
|
|
152
|
+
disabled={!isRenamed || USER.PUT.loadingState === "LOADING"}
|
|
153
|
+
>
|
|
154
|
+
Revert to Original Name
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<MutableIdSignalDemo currentUser={user} />
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const MutableIdSignalDemo = ({ currentUser }) => {
|
|
164
|
+
// Create a signal that tracks the ORIGINAL user's name (this should NOT change)
|
|
165
|
+
const [originalName] = useState(currentUser.name);
|
|
166
|
+
const originalUserNameSignal = signal(originalName);
|
|
167
|
+
|
|
168
|
+
// Use signalForMutableIdKey to get a signal that tracks the user by the ORIGINAL name
|
|
169
|
+
const userFromMutableIdSignal = USER.store.signalForMutableIdKey(
|
|
170
|
+
"name",
|
|
171
|
+
originalUserNameSignal,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Get the current value
|
|
175
|
+
const [signalValue, setSignalValue] = useState(userFromMutableIdSignal.value);
|
|
176
|
+
|
|
177
|
+
// Subscribe to changes
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
console.log(
|
|
180
|
+
"MutableIdSignalDemo: Setting up subscription to userFromMutableIdSignal",
|
|
181
|
+
);
|
|
182
|
+
const unsubscribe = userFromMutableIdSignal.subscribe((newValue) => {
|
|
183
|
+
console.log(
|
|
184
|
+
"MutableIdSignalDemo: userFromMutableIdSignal changed to:",
|
|
185
|
+
newValue,
|
|
186
|
+
);
|
|
187
|
+
setSignalValue(newValue);
|
|
188
|
+
});
|
|
189
|
+
return unsubscribe;
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
console.log("MutableIdSignalDemo render:", {
|
|
193
|
+
originalName,
|
|
194
|
+
currentUserName: currentUser.name,
|
|
195
|
+
currentUserId: currentUser.id,
|
|
196
|
+
signalValueName: signalValue?.name,
|
|
197
|
+
signalValueId: signalValue?.id,
|
|
198
|
+
originalUserNameSignalValue: originalUserNameSignal.value,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Check if a rename has occurred
|
|
202
|
+
const hasBeenRenamed = currentUser.name !== originalName;
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
style={{
|
|
207
|
+
marginTop: "20px",
|
|
208
|
+
padding: "15px",
|
|
209
|
+
backgroundColor: "#f0f8ff",
|
|
210
|
+
borderRadius: "5px",
|
|
211
|
+
border: "1px solid #007bff",
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<h3>signalForMutableIdKey Demo</h3>
|
|
215
|
+
<p>
|
|
216
|
+
<strong>Current Route User:</strong> {currentUser.name} (ID:{" "}
|
|
217
|
+
{currentUser.id})
|
|
218
|
+
</p>
|
|
219
|
+
<p>
|
|
220
|
+
<strong>Signal Tracking Original Name:</strong>{" "}
|
|
221
|
+
{originalUserNameSignal.value}
|
|
222
|
+
</p>
|
|
223
|
+
<p>
|
|
224
|
+
<strong>Signal Value:</strong>{" "}
|
|
225
|
+
{signalValue ? `${signalValue.name} (ID: ${signalValue.id})` : "null"}
|
|
226
|
+
</p>
|
|
227
|
+
<p style={{ fontSize: "0.9em", color: "#666" }}>
|
|
228
|
+
💡 This signal tracks by the original name "{originalName}"
|
|
229
|
+
but should return the same user even after renaming, demonstrating the
|
|
230
|
+
caching behavior of signalForMutableIdKey.
|
|
231
|
+
</p>
|
|
232
|
+
{signalValue && signalValue.id === currentUser.id && hasBeenRenamed && (
|
|
233
|
+
<p style={{ color: "green", fontWeight: "bold" }}>
|
|
234
|
+
✅ Signal correctly returns the same user instance even after rename!
|
|
235
|
+
</p>
|
|
236
|
+
)}
|
|
237
|
+
{signalValue && signalValue.id === currentUser.id && !hasBeenRenamed && (
|
|
238
|
+
<p style={{ color: "blue", fontWeight: "bold" }}>
|
|
239
|
+
🔵 Signal correctly returns the user (no rename yet)
|
|
240
|
+
</p>
|
|
241
|
+
)}
|
|
242
|
+
{signalValue && signalValue.id !== currentUser.id && (
|
|
243
|
+
<p style={{ color: "orange", fontWeight: "bold" }}>
|
|
244
|
+
⚠️ Signal returned a different user (this might happen during
|
|
245
|
+
transitions)
|
|
246
|
+
</p>
|
|
247
|
+
)}
|
|
248
|
+
{!signalValue && (
|
|
249
|
+
<p style={{ color: "red", fontWeight: "bold" }}>
|
|
250
|
+
❌ Signal returned null (user not found in store) - Looking for
|
|
251
|
+
original name: "{originalUserNameSignal.value}"
|
|
252
|
+
</p>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const DocumentInfo = () => {
|
|
259
|
+
const documentUrl = useDocumentUrl();
|
|
260
|
+
const documentState = useDocumentState();
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<div>
|
|
264
|
+
<div>
|
|
265
|
+
Current URL: <div className="current-url">{documentUrl}</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="current-state">
|
|
268
|
+
Current State: <pre>{JSON.stringify(documentState, null, 2)}</pre>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const initialUsers = [
|
|
275
|
+
{
|
|
276
|
+
id: 1,
|
|
277
|
+
name: "alice",
|
|
278
|
+
email: "alice@example.com",
|
|
279
|
+
originalName: "alice",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 2,
|
|
283
|
+
name: "bob",
|
|
284
|
+
email: "bob@example.com",
|
|
285
|
+
originalName: "bob",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: 3,
|
|
289
|
+
name: "charlie",
|
|
290
|
+
email: "charlie@example.com",
|
|
291
|
+
originalName: "charlie",
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
const USER = resource("user", {
|
|
295
|
+
idKey: "id",
|
|
296
|
+
mutableIdKeys: ["name"],
|
|
297
|
+
GET_MANY: () => initialUsers,
|
|
298
|
+
GET: async ({ name }) => {
|
|
299
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
300
|
+
return initialUsers.find((user) => user.name === name);
|
|
301
|
+
},
|
|
302
|
+
PUT: ({ name, property, value }) => {
|
|
303
|
+
console.log("PUT action called:", { name, property, value });
|
|
304
|
+
const userIndex = initialUsers.findIndex((user) => user.name === name);
|
|
305
|
+
if (userIndex === -1) {
|
|
306
|
+
throw new Error(`User with name "${name}" not found`);
|
|
307
|
+
}
|
|
308
|
+
const user = initialUsers[userIndex];
|
|
309
|
+
console.log("Found user:", user);
|
|
310
|
+
const updatedUser = {
|
|
311
|
+
...user,
|
|
312
|
+
[property]: value,
|
|
313
|
+
};
|
|
314
|
+
console.log("Updated user:", updatedUser);
|
|
315
|
+
|
|
316
|
+
// Update the initialUsers array so future lookups work
|
|
317
|
+
initialUsers[userIndex] = updatedUser;
|
|
318
|
+
|
|
319
|
+
return updatedUser;
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const [USER_ROUTE] = defineRoutes({
|
|
324
|
+
"/user/:name": USER.GET,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Populate initial users in the store
|
|
328
|
+
USER.store.upsert(initialUsers);
|
|
329
|
+
|
|
330
|
+
render(<App />, document.querySelector("#app"));
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Resource Dependencies Documentation
|
|
2
|
+
|
|
3
|
+
The `withParams` method now supports cross-resource dependencies, allowing you to set up autoreload relationships between different resources.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const role = resource("role", {
|
|
9
|
+
GET_MANY: () => fetchRoles(),
|
|
10
|
+
POST: (data) => createRole(data),
|
|
11
|
+
DELETE: (id) => deleteRole(id),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const database = resource("database", {
|
|
15
|
+
GET_MANY: () => fetchDatabases(),
|
|
16
|
+
POST: (data) => createDatabase(data),
|
|
17
|
+
DELETE: (id) => deleteDatabase(id),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const tables = resource("tables", {
|
|
21
|
+
GET_MANY: () => fetchTables(),
|
|
22
|
+
POST: (data) => createTable(data),
|
|
23
|
+
DELETE: (id) => deleteTable(id),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Create a parameterized resource with cross-resource dependencies
|
|
27
|
+
const ROLE_WITH_OWNERSHIP = role.withParams(
|
|
28
|
+
{ owners: true },
|
|
29
|
+
{
|
|
30
|
+
dependencies: [role, database, tables],
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
When you specify `dependencies`, any non-GET action (POST, PUT, PATCH, DELETE) on the dependency resources will trigger an autoreload of the GET_MANY actions in the parameterized resource.
|
|
38
|
+
|
|
39
|
+
### Autoreload Behavior
|
|
40
|
+
|
|
41
|
+
- **Triggering Actions**: Any POST, PUT, PATCH, or DELETE on dependency resources
|
|
42
|
+
- **Target Actions**: GET_MANY actions in the parameterized resource (same param scope only)
|
|
43
|
+
- **Scope Isolation**: Only actions with the same parameter scope are affected
|
|
44
|
+
|
|
45
|
+
### Example Scenarios
|
|
46
|
+
|
|
47
|
+
1. **Creating a table**: `tables.POST.load(newTableData)` → triggers `ROLE_WITH_OWNERSHIP.GET_MANY` reload
|
|
48
|
+
2. **Deleting a database**: `database.DELETE.load(dbId)` → triggers `ROLE_WITH_OWNERSHIP.GET_MANY` reload
|
|
49
|
+
3. **Updating a role**: `role.PUT.load(roleData)` → triggers `ROLE_WITH_OWNERSHIP.GET_MANY` reload
|
|
50
|
+
|
|
51
|
+
## Advanced Usage
|
|
52
|
+
|
|
53
|
+
### Parameterized Dependencies
|
|
54
|
+
|
|
55
|
+
You can also use parameterized resources as dependencies:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const recentTables = tables.withParams({ recent: true });
|
|
59
|
+
const adminTables = tables.withParams({ owner: "admin" });
|
|
60
|
+
|
|
61
|
+
const ROLE_WITH_RECENT_OWNERSHIP = role.withParams(
|
|
62
|
+
{ owners: true },
|
|
63
|
+
{
|
|
64
|
+
dependencies: [recentTables], // Only affected by recent tables
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const ROLE_WITH_ADMIN_OWNERSHIP = role.withParams(
|
|
69
|
+
{ owners: true },
|
|
70
|
+
{
|
|
71
|
+
dependencies: [adminTables], // Only affected by admin tables
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Custom Autoreload Settings
|
|
77
|
+
|
|
78
|
+
You can also customize the autoreload behavior:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const ROLE_WITH_OWNERSHIP = role.withParams(
|
|
82
|
+
{ owners: true },
|
|
83
|
+
{
|
|
84
|
+
dependencies: [role, database, tables],
|
|
85
|
+
autoreloadGetManyAfter: ["POST", "DELETE", "PUT"], // Custom trigger verbs
|
|
86
|
+
autoreloadGetAfter: false, // Disable GET autoreload
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Implementation Details
|
|
92
|
+
|
|
93
|
+
- **Global Registry**: Dependencies are tracked in a global registry to enable cross-resource communication
|
|
94
|
+
- **Memory Management**: The system uses WeakSets to avoid memory leaks
|
|
95
|
+
- **Async Execution**: Autoreloads are triggered asynchronously with `setTimeout` to ensure proper sequencing
|
|
96
|
+
- **Parameter Isolation**: Each parameter scope maintains its own autoreload behavior
|
|
97
|
+
|
|
98
|
+
## Benefits
|
|
99
|
+
|
|
100
|
+
1. **Reactive Updates**: Automatically keep related data in sync across different resources
|
|
101
|
+
2. **Parameter Isolation**: Avoid unwanted cross-contamination between different parameter sets
|
|
102
|
+
3. **Flexible Dependencies**: Mix and match regular and parameterized resources as dependencies
|
|
103
|
+
4. **Performance**: Only reload what's actually needed based on the dependency graph
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Resource `withParams()` Method
|
|
2
|
+
|
|
3
|
+
Creates a parameterized version of a resource with isolated autoreload behavior. Solves cross-contamination where actions with different parameters incorrectly trigger each other's autoreload.
|
|
4
|
+
|
|
5
|
+
## Problem & Solution
|
|
6
|
+
|
|
7
|
+
Without `withParams()`:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const ROLE = resource("role", {
|
|
11
|
+
GET_MANY: (params) => fetchRoles(params),
|
|
12
|
+
DELETE: (params) => deleteRole(params),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// These actions interfere with each other
|
|
16
|
+
await ROLE.GET_MANY.bindParams({ admin: true });
|
|
17
|
+
await ROLE.DELETE.bindParams({ id: 123 }); // ❌ Reloads both admin and non-admin queries
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
With `withParams()`:
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
const adminRoles = ROLE.withParams({ admin: true });
|
|
24
|
+
const guestRoles = ROLE.withParams({ admin: false });
|
|
25
|
+
|
|
26
|
+
await adminRoles.DELETE({ id: 123 }); // ✅ Only reloads admin queries
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
### `resource.withParams(params)`
|
|
32
|
+
|
|
33
|
+
**Parameters:** `params` (Object, required) - Parameters to bind to all actions
|
|
34
|
+
|
|
35
|
+
**Returns:** New resource instance with parameter-bound actions and isolated autoreload
|
|
36
|
+
|
|
37
|
+
**Throws:** Error if params is empty
|
|
38
|
+
|
|
39
|
+
## Autoreload Hierarchy
|
|
40
|
+
|
|
41
|
+
Actions follow a parent-child hierarchy where child scopes reload their parents:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const ROLE = resource("role", { GET_MANY: () => fetch("/roles") });
|
|
45
|
+
const ROLE_ADMIN = ROLE.withParams({ type: "admin" });
|
|
46
|
+
const ROLE_ADMIN_MALE = ROLE_ADMIN.withParams({ gender: "male" });
|
|
47
|
+
|
|
48
|
+
// Si ROLE_ADMIN_MALE.POST() est exécuté, il recharge :
|
|
49
|
+
// ✅ ROLE_ADMIN_MALE.GET_MANY (même scope: { type: "admin", gender: "male" })
|
|
50
|
+
// ✅ ROLE_ADMIN.GET_MANY (parent scope: { type: "admin" })
|
|
51
|
+
// ✅ ROLE.GET_MANY (root parent: {})
|
|
52
|
+
// ❌ Ne recharge PAS femaleAdmin.GET_MANY ({ type: "admin", gender: "female" })
|
|
53
|
+
|
|
54
|
+
// Si ROLE_ADMIN.POST() est exécuté, il recharge :
|
|
55
|
+
// ✅ ROLE_ADMIN.GET_MANY (même scope: { type: "admin" })
|
|
56
|
+
// ✅ ROLE.GET_MANY (root parent: {})
|
|
57
|
+
// ❌ Ne recharge PAS ROLE_ADMIN_MALE.GET_MANY (enfant, pas parent)
|
|
58
|
+
|
|
59
|
+
// Si ROLE.POST() est exécuté, il recharge :
|
|
60
|
+
// ✅ ROLE.GET_MANY (même scope: {})
|
|
61
|
+
// ❌ Ne recharge PAS ROLE_ADMIN.GET_MANY ni ROLE_ADMIN_MALE.GET_MANY (enfants, pas parents)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Chaining
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const maleAdmins = USER.withParams({ role: "admin" }).withParams({
|
|
68
|
+
gender: "male",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Equivalent to:
|
|
72
|
+
const maleAdmins = USER.withParams({ role: "admin", gender: "male" });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Key Features
|
|
76
|
+
|
|
77
|
+
- **Isolation**: Different parameter sets have separate autoreload behavior
|
|
78
|
+
- **Hierarchy**: Child scopes reload parent scopes automatically
|
|
79
|
+
- **Shared Store**: All parameterized resources use the same data store
|
|
80
|
+
- **Efficient**: Parameter comparison is cached for performance
|
package/src/notes.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- import.meta.css during build should use stylesheet to inject so that it puts an url instead of constructed stylesheet?
|
|
2
|
+
|
|
3
|
+
- La table qui affiche une page, on mettra les details dans un bouton settings en haut a droite
|
|
4
|
+
Qui ouvrira les infos a propos de cette tables et on focus sur les données au lieu des settings de la table
|
|
5
|
+
(surement qu'on aura
|
|
6
|
+
- api/tables/:tablename/settings
|
|
7
|
+
- api/tables/:tablename/columns
|
|
8
|
+
- api/tables/:tablename/rows
|
|
9
|
+
(et api/tables on verra je serais pas encore)
|
|
10
|
+
|
|
11
|
+
- See table columns, ability to add, update, remove table columns
|
|
12
|
+
- Ability to see table rows, ability to add/update/remove rows
|
|
13
|
+
- Pagination of course
|