@wcstack/fetch 1.4.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/README.ja.md +625 -0
- package/README.md +625 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +3 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.esm.js +380 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# @wcstack/fetch
|
|
2
|
+
|
|
3
|
+
`@wcstack/fetch` is a headless fetch component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is an **I/O node** that connects HTTP requests to reactive state.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-fetch>` can be bound directly through path contracts:
|
|
9
|
+
|
|
10
|
+
- **input / command surface**: `url`, `body`, `trigger`
|
|
11
|
+
- **output state surface**: `value`, `loading`, `error`, `status`
|
|
12
|
+
|
|
13
|
+
This means async communication can be expressed declaratively in HTML, without writing `fetch()`, `async/await`, or loading/error glue code in your UI layer.
|
|
14
|
+
|
|
15
|
+
`@wcstack/fetch` follows the [HAWC](https://github.com/wc-bindable-protocol/wc-bindable-protocol/blob/main/docs/articles/HAWC.md) architecture:
|
|
16
|
+
|
|
17
|
+
- **Core** (`FetchCore`) handles HTTP, abort, and async state
|
|
18
|
+
- **Shell** (`<wcs-fetch>`) connects that state to the DOM
|
|
19
|
+
- frameworks and binding systems consume it through [wc-bindable-protocol](https://github.com/wc-bindable-protocol/wc-bindable-protocol)
|
|
20
|
+
|
|
21
|
+
## Why this exists
|
|
22
|
+
|
|
23
|
+
In many frontend apps, the hardest part to migrate is not the template — it is the async logic:
|
|
24
|
+
HTTP requests, loading flags, errors, retries, and lifecycle cleanup.
|
|
25
|
+
|
|
26
|
+
`@wcstack/fetch` moves that async logic into a reusable component and exposes the result as bindable state.
|
|
27
|
+
|
|
28
|
+
With `@wcstack/state`, the flow becomes:
|
|
29
|
+
|
|
30
|
+
1. state computes `url`
|
|
31
|
+
2. `<wcs-fetch>` executes the request
|
|
32
|
+
3. async results return as `value`, `loading`, `error`, `status`
|
|
33
|
+
4. UI binds to those paths with `data-wcs`
|
|
34
|
+
|
|
35
|
+
This turns async communication into **state transitions**, not imperative UI code.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @wcstack/fetch
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### 1. Reactive fetch from state
|
|
46
|
+
|
|
47
|
+
When `url` changes, `<wcs-fetch>` automatically runs a new request.
|
|
48
|
+
If another request is already in flight, it aborts the previous one.
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
52
|
+
<script type="module" src="https://esm.run/@wcstack/fetch/auto"></script>
|
|
53
|
+
|
|
54
|
+
<wcs-state>
|
|
55
|
+
<script type="module">
|
|
56
|
+
export default {
|
|
57
|
+
users: [],
|
|
58
|
+
get usersUrl() {
|
|
59
|
+
return "/api/users";
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<wcs-fetch data-wcs="url: usersUrl; value: users"></wcs-fetch>
|
|
65
|
+
|
|
66
|
+
<ul>
|
|
67
|
+
<template data-wcs="for: users">
|
|
68
|
+
<li data-wcs="textContent: users.*.name"></li>
|
|
69
|
+
</template>
|
|
70
|
+
</ul>
|
|
71
|
+
</wcs-state>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This is the default mode:
|
|
75
|
+
|
|
76
|
+
- connect `url`
|
|
77
|
+
- receive `value`
|
|
78
|
+
- optionally bind `loading`, `error`, and `status`
|
|
79
|
+
|
|
80
|
+
### 2. Reactive URL example
|
|
81
|
+
|
|
82
|
+
A computed URL can drive data fetching automatically:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<wcs-state>
|
|
86
|
+
<script type="module">
|
|
87
|
+
export default {
|
|
88
|
+
filterRole: "",
|
|
89
|
+
users: [],
|
|
90
|
+
|
|
91
|
+
get usersUrl() {
|
|
92
|
+
const role = this.filterRole;
|
|
93
|
+
return role ? "/api/users?role=" + role : "/api/users";
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<select data-wcs="value: filterRole">
|
|
99
|
+
<option value="">All</option>
|
|
100
|
+
<option value="admin">Admin</option>
|
|
101
|
+
<option value="staff">Staff</option>
|
|
102
|
+
</select>
|
|
103
|
+
|
|
104
|
+
<wcs-fetch
|
|
105
|
+
data-wcs="url: usersUrl; value: users; loading: listLoading; error: listError">
|
|
106
|
+
</wcs-fetch>
|
|
107
|
+
|
|
108
|
+
<template data-wcs="if: listLoading">
|
|
109
|
+
<p>Loading...</p>
|
|
110
|
+
</template>
|
|
111
|
+
<template data-wcs="if: listError">
|
|
112
|
+
<p>Failed to load users.</p>
|
|
113
|
+
</template>
|
|
114
|
+
|
|
115
|
+
<ul>
|
|
116
|
+
<template data-wcs="for: users">
|
|
117
|
+
<li data-wcs="textContent: users.*.name"></li>
|
|
118
|
+
</template>
|
|
119
|
+
</ul>
|
|
120
|
+
</wcs-state>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Manual execution with `trigger`
|
|
124
|
+
|
|
125
|
+
Use `manual` when you want to prepare inputs first and execute later.
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<wcs-state>
|
|
129
|
+
<script type="module">
|
|
130
|
+
export default {
|
|
131
|
+
users: [],
|
|
132
|
+
shouldRefresh: false,
|
|
133
|
+
|
|
134
|
+
reload() {
|
|
135
|
+
this.shouldRefresh = true;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<wcs-fetch
|
|
141
|
+
url="/api/users"
|
|
142
|
+
manual
|
|
143
|
+
data-wcs="trigger: shouldRefresh; value: users; loading: listLoading">
|
|
144
|
+
</wcs-fetch>
|
|
145
|
+
|
|
146
|
+
<button data-wcs="onclick: reload">Refresh</button>
|
|
147
|
+
</wcs-state>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`trigger` is a **one-way command surface**:
|
|
151
|
+
|
|
152
|
+
- writing `true` starts `fetch()`
|
|
153
|
+
- it resets itself to `false` after completion
|
|
154
|
+
- the reset emits `wcs-fetch:trigger-changed`
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
external write: false → true No event (triggers fetch)
|
|
158
|
+
auto-reset: true → false Dispatches wcs-fetch:trigger-changed
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4. POST with reactive body
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<wcs-state>
|
|
165
|
+
<script type="module">
|
|
166
|
+
export default {
|
|
167
|
+
newUser: {
|
|
168
|
+
name: "",
|
|
169
|
+
email: "",
|
|
170
|
+
},
|
|
171
|
+
submitRequest: false,
|
|
172
|
+
submitResult: null,
|
|
173
|
+
submitError: null,
|
|
174
|
+
|
|
175
|
+
submit() {
|
|
176
|
+
this.submitRequest = true;
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<input data-wcs="value: newUser.name" placeholder="Name">
|
|
182
|
+
<input data-wcs="value: newUser.email" placeholder="Email">
|
|
183
|
+
|
|
184
|
+
<button data-wcs="onclick: submit">Create</button>
|
|
185
|
+
|
|
186
|
+
<wcs-fetch
|
|
187
|
+
url="/api/users"
|
|
188
|
+
method="POST"
|
|
189
|
+
manual
|
|
190
|
+
data-wcs="
|
|
191
|
+
body: newUser;
|
|
192
|
+
trigger: submitRequest;
|
|
193
|
+
value: submitResult;
|
|
194
|
+
error: submitError;
|
|
195
|
+
loading: submitLoading
|
|
196
|
+
">
|
|
197
|
+
<wcs-fetch-header name="Content-Type" value="application/json"></wcs-fetch-header>
|
|
198
|
+
</wcs-fetch>
|
|
199
|
+
|
|
200
|
+
<template data-wcs="if: submitLoading">
|
|
201
|
+
<p>Submitting...</p>
|
|
202
|
+
</template>
|
|
203
|
+
<template data-wcs="if: submitError">
|
|
204
|
+
<p>Submit failed.</p>
|
|
205
|
+
</template>
|
|
206
|
+
</wcs-state>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## State Surface vs Command Surface
|
|
210
|
+
|
|
211
|
+
`<wcs-fetch>` exposes two different kinds of properties.
|
|
212
|
+
|
|
213
|
+
### Output state (bindable async state)
|
|
214
|
+
|
|
215
|
+
These properties represent the result of the current request and are the main HAWC surface:
|
|
216
|
+
|
|
217
|
+
| Property | Type | Description |
|
|
218
|
+
|----------|------|-------------|
|
|
219
|
+
| `value` | `any` | Response data |
|
|
220
|
+
| `loading` | `boolean` | `true` while a request is in flight |
|
|
221
|
+
| `error` | `WcsFetchHttpError \| Error \| null` | HTTP or network error |
|
|
222
|
+
| `status` | `number` | HTTP status code |
|
|
223
|
+
|
|
224
|
+
### Input / command surface
|
|
225
|
+
|
|
226
|
+
These properties control request execution from HTML, JS, or `@wcstack/state` bindings:
|
|
227
|
+
|
|
228
|
+
| Property | Type | Description |
|
|
229
|
+
|----------|------|-------------|
|
|
230
|
+
| `url` | `string` | Request URL |
|
|
231
|
+
| `body` | `any` | Request body (resets to `null` after `fetch()`) |
|
|
232
|
+
| `trigger` | `boolean` | One-way execution trigger |
|
|
233
|
+
| `manual` | `boolean` | Disables auto-fetch on connect / URL change |
|
|
234
|
+
|
|
235
|
+
## Architecture
|
|
236
|
+
|
|
237
|
+
`@wcstack/fetch` follows the HAWC architecture.
|
|
238
|
+
|
|
239
|
+
### Core: `FetchCore`
|
|
240
|
+
|
|
241
|
+
`FetchCore` is a pure `EventTarget` class.
|
|
242
|
+
It contains:
|
|
243
|
+
|
|
244
|
+
- HTTP execution
|
|
245
|
+
- abort control
|
|
246
|
+
- async state transitions
|
|
247
|
+
- `wc-bindable-protocol` declaration
|
|
248
|
+
|
|
249
|
+
It can run headlessly in any runtime that supports `EventTarget` and `fetch`.
|
|
250
|
+
|
|
251
|
+
### Shell: `<wcs-fetch>`
|
|
252
|
+
|
|
253
|
+
`<wcs-fetch>` is a thin `HTMLElement` wrapper around `FetchCore`.
|
|
254
|
+
It adds:
|
|
255
|
+
|
|
256
|
+
- attribute / property mapping
|
|
257
|
+
- DOM lifecycle integration
|
|
258
|
+
- declarative execution helpers such as `trigger`
|
|
259
|
+
|
|
260
|
+
This split keeps the async logic portable while allowing DOM-based binding systems such as `@wcstack/state` to interact with it naturally.
|
|
261
|
+
|
|
262
|
+
### Target injection
|
|
263
|
+
|
|
264
|
+
The Core dispatches events directly on the Shell via **target injection**, so no event re-dispatch is needed.
|
|
265
|
+
|
|
266
|
+
## Headless Usage (Core only)
|
|
267
|
+
|
|
268
|
+
`FetchCore` can be used standalone without the DOM. Since it declares `static wcBindable`, you can use `@wc-bindable/core`'s `bind()` to subscribe to its state — the same way framework adapters work:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { FetchCore } from "@wcstack/fetch";
|
|
272
|
+
import { bind } from "@wc-bindable/core";
|
|
273
|
+
|
|
274
|
+
const core = new FetchCore();
|
|
275
|
+
|
|
276
|
+
const unbind = bind(core, (name, value) => {
|
|
277
|
+
console.log(`${name}:`, value);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await core.fetch("/api/users");
|
|
281
|
+
|
|
282
|
+
unbind();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
This works in Node.js, Deno, Cloudflare Workers — anywhere `EventTarget` and `fetch` are available.
|
|
286
|
+
|
|
287
|
+
## URL Observation
|
|
288
|
+
|
|
289
|
+
By default, `<wcs-fetch>` automatically executes a request when:
|
|
290
|
+
|
|
291
|
+
1. it is connected to the DOM and `url` is set
|
|
292
|
+
2. the `url` changes
|
|
293
|
+
|
|
294
|
+
If a request is already in flight when the URL changes, the previous request is automatically aborted before the new one starts.
|
|
295
|
+
|
|
296
|
+
Set the `manual` attribute to disable auto-fetch and control execution explicitly via `fetch()` or `trigger`.
|
|
297
|
+
|
|
298
|
+
## Programmatic Usage
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
const fetchEl = document.querySelector("wcs-fetch");
|
|
302
|
+
|
|
303
|
+
// Set body via JS API (takes priority over <wcs-fetch-body>)
|
|
304
|
+
fetchEl.body = { name: "Tanaka" };
|
|
305
|
+
await fetchEl.fetch();
|
|
306
|
+
// Note: body is automatically reset to null after fetch().
|
|
307
|
+
// Set it again before each call if needed.
|
|
308
|
+
|
|
309
|
+
console.log(fetchEl.value); // response data
|
|
310
|
+
console.log(fetchEl.status); // HTTP status code
|
|
311
|
+
console.log(fetchEl.loading); // boolean
|
|
312
|
+
console.log(fetchEl.error); // error info or null
|
|
313
|
+
console.log(fetchEl.body); // null (reset after fetch)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## HTML Replace Mode
|
|
317
|
+
|
|
318
|
+
`<wcs-fetch>` can also replace a target element's `innerHTML` when `target` is set.
|
|
319
|
+
|
|
320
|
+
```html
|
|
321
|
+
<div id="content">Initial content</div>
|
|
322
|
+
<wcs-fetch url="/api/partial" target="content"></wcs-fetch>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This mode is useful for simple fragment loading, but it is separate from the main **state-driven** usage with `@wcstack/state`.
|
|
326
|
+
|
|
327
|
+
## Optional DOM Triggering
|
|
328
|
+
|
|
329
|
+
If `autoTrigger` is enabled (default), clicking an element with `data-fetchtarget` triggers the corresponding `<wcs-fetch>` element:
|
|
330
|
+
|
|
331
|
+
```html
|
|
332
|
+
<button data-fetchtarget="user-fetch">Load Users</button>
|
|
333
|
+
<wcs-fetch id="user-fetch" url="/api/users"></wcs-fetch>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Event delegation is used — works with dynamically added elements. The `closest()` API handles nested elements (e.g., icon inside a button).
|
|
337
|
+
|
|
338
|
+
If the target id does not match any element, or the matched element is not a `<wcs-fetch>`, the click is silently ignored.
|
|
339
|
+
|
|
340
|
+
This is a convenience feature.
|
|
341
|
+
In wcstack applications, **state-driven triggering via `trigger`** is usually the primary pattern.
|
|
342
|
+
|
|
343
|
+
## Elements
|
|
344
|
+
|
|
345
|
+
### `<wcs-fetch>`
|
|
346
|
+
|
|
347
|
+
| Attribute | Type | Default | Description |
|
|
348
|
+
|-----------|------|---------|-------------|
|
|
349
|
+
| `url` | `string` | — | Request URL |
|
|
350
|
+
| `method` | `string` | `GET` | HTTP method |
|
|
351
|
+
| `target` | `string` | — | DOM element id for HTML replace mode |
|
|
352
|
+
| `manual` | `boolean` | `false` | Disable auto-fetch |
|
|
353
|
+
|
|
354
|
+
| Property | Type | Description |
|
|
355
|
+
|----------|------|-------------|
|
|
356
|
+
| `value` | `any` | Response data |
|
|
357
|
+
| `loading` | `boolean` | `true` while request is in flight |
|
|
358
|
+
| `error` | `WcsFetchHttpError \| Error \| null` | Error info |
|
|
359
|
+
| `status` | `number` | HTTP status code |
|
|
360
|
+
| `body` | `any` | Request body (resets to `null` after `fetch()`) |
|
|
361
|
+
| `trigger` | `boolean` | Set to `true` to execute fetch |
|
|
362
|
+
| `manual` | `boolean` | Explicit execution mode |
|
|
363
|
+
|
|
364
|
+
| Method | Description |
|
|
365
|
+
|--------|-------------|
|
|
366
|
+
| `fetch()` | Execute the HTTP request |
|
|
367
|
+
| `abort()` | Cancel the in-flight request |
|
|
368
|
+
|
|
369
|
+
### `<wcs-fetch-header>`
|
|
370
|
+
|
|
371
|
+
Defines a request header. Place it as a child of `<wcs-fetch>`.
|
|
372
|
+
|
|
373
|
+
| Attribute | Type | Description |
|
|
374
|
+
|-----------|------|-------------|
|
|
375
|
+
| `name` | `string` | Header name |
|
|
376
|
+
| `value` | `string` | Header value |
|
|
377
|
+
|
|
378
|
+
### `<wcs-fetch-body>`
|
|
379
|
+
|
|
380
|
+
Defines the request body. Place it as a child of `<wcs-fetch>`.
|
|
381
|
+
|
|
382
|
+
| Attribute | Type | Default | Description |
|
|
383
|
+
|-----------|------|---------|-------------|
|
|
384
|
+
| `type` | `string` | `application/json` | Content-Type |
|
|
385
|
+
|
|
386
|
+
The body content is taken from the element's text content.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
|
|
390
|
+
```html
|
|
391
|
+
<wcs-fetch url="/api/users" method="POST">
|
|
392
|
+
<wcs-fetch-header name="Authorization" value="Bearer token123"></wcs-fetch-header>
|
|
393
|
+
<wcs-fetch-header name="Accept" value="application/json"></wcs-fetch-header>
|
|
394
|
+
<wcs-fetch-body type="application/json">
|
|
395
|
+
{"name": "Tanaka", "email": "tanaka@example.com"}
|
|
396
|
+
</wcs-fetch-body>
|
|
397
|
+
</wcs-fetch>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## wc-bindable-protocol
|
|
401
|
+
|
|
402
|
+
Both `FetchCore` and `<wcs-fetch>` declare `wc-bindable-protocol` compliance, making them interoperable with any framework or component that supports the protocol.
|
|
403
|
+
|
|
404
|
+
### Core (`FetchCore`)
|
|
405
|
+
|
|
406
|
+
`FetchCore` declares the bindable async state that any runtime can subscribe to:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
static wcBindable = {
|
|
410
|
+
protocol: "wc-bindable",
|
|
411
|
+
version: 1,
|
|
412
|
+
properties: [
|
|
413
|
+
{ name: "value", event: "wcs-fetch:response",
|
|
414
|
+
getter: (e) => e.detail.value },
|
|
415
|
+
{ name: "loading", event: "wcs-fetch:loading-changed" },
|
|
416
|
+
{ name: "error", event: "wcs-fetch:error" },
|
|
417
|
+
{ name: "status", event: "wcs-fetch:response",
|
|
418
|
+
getter: (e) => e.detail.status },
|
|
419
|
+
],
|
|
420
|
+
};
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Headless consumers call `core.fetch(url)` directly — no `trigger` needed.
|
|
424
|
+
|
|
425
|
+
### Shell (`<wcs-fetch>`)
|
|
426
|
+
|
|
427
|
+
The Shell extends the Core declaration with `trigger` so binding systems can execute fetch declaratively:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
static wcBindable = {
|
|
431
|
+
...FetchCore.wcBindable,
|
|
432
|
+
properties: [
|
|
433
|
+
...FetchCore.wcBindable.properties,
|
|
434
|
+
{ name: "trigger", event: "wcs-fetch:trigger-changed" },
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## TypeScript Types
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import type {
|
|
443
|
+
WcsFetchHttpError, WcsFetchCoreValues, WcsFetchValues
|
|
444
|
+
} from "@wcstack/fetch";
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// HTTP error (status >= 400)
|
|
449
|
+
interface WcsFetchHttpError {
|
|
450
|
+
status: number;
|
|
451
|
+
statusText: string;
|
|
452
|
+
body: string;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Core (headless) — 4 async state properties
|
|
456
|
+
// T defaults to unknown; pass a type argument for typed `value`
|
|
457
|
+
interface WcsFetchCoreValues<T = unknown> {
|
|
458
|
+
value: T;
|
|
459
|
+
loading: boolean;
|
|
460
|
+
error: WcsFetchHttpError | Error | null;
|
|
461
|
+
status: number;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Shell (<wcs-fetch>) — extends Core with trigger
|
|
465
|
+
interface WcsFetchValues<T = unknown> extends WcsFetchCoreValues<T> {
|
|
466
|
+
trigger: boolean;
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Why this works well with `@wcstack/state`
|
|
471
|
+
|
|
472
|
+
`@wcstack/state` uses path strings as the only contract between UI and state.
|
|
473
|
+
`<wcs-fetch>` fits this model naturally:
|
|
474
|
+
|
|
475
|
+
- state computes `url`
|
|
476
|
+
- `<wcs-fetch>` executes the request
|
|
477
|
+
- async results return as `value`, `loading`, `error`, `status`
|
|
478
|
+
- UI binds to those paths without writing fetch glue code
|
|
479
|
+
|
|
480
|
+
This makes async processing look like ordinary state updates.
|
|
481
|
+
|
|
482
|
+
## Framework Integration
|
|
483
|
+
|
|
484
|
+
Since `<wcs-fetch>` is HAWC + `wc-bindable-protocol`, it works with any framework through thin adapters from `@wc-bindable/*`.
|
|
485
|
+
|
|
486
|
+
### React
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
import { useWcBindable } from "@wc-bindable/react";
|
|
490
|
+
import type { WcsFetchValues } from "@wcstack/fetch";
|
|
491
|
+
|
|
492
|
+
interface User { id: number; name: string; }
|
|
493
|
+
|
|
494
|
+
function UserList() {
|
|
495
|
+
const [ref, { value: users, loading, error }] =
|
|
496
|
+
useWcBindable<HTMLElement, WcsFetchValues<User[]>>();
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<>
|
|
500
|
+
<wcs-fetch ref={ref} url="/api/users" />
|
|
501
|
+
{loading && <p>Loading...</p>}
|
|
502
|
+
{error && <p>Error</p>}
|
|
503
|
+
<ul>
|
|
504
|
+
{users?.map((user) => (
|
|
505
|
+
<li key={user.id}>{user.name}</li>
|
|
506
|
+
))}
|
|
507
|
+
</ul>
|
|
508
|
+
</>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Vue
|
|
514
|
+
|
|
515
|
+
```vue
|
|
516
|
+
<script setup lang="ts">
|
|
517
|
+
import { useWcBindable } from "@wc-bindable/vue";
|
|
518
|
+
import type { WcsFetchValues } from "@wcstack/fetch";
|
|
519
|
+
|
|
520
|
+
interface User { id: number; name: string; }
|
|
521
|
+
|
|
522
|
+
const { ref, values } = useWcBindable<HTMLElement, WcsFetchValues<User[]>>();
|
|
523
|
+
</script>
|
|
524
|
+
|
|
525
|
+
<template>
|
|
526
|
+
<wcs-fetch :ref="ref" url="/api/users" />
|
|
527
|
+
<p v-if="values.loading">Loading...</p>
|
|
528
|
+
<p v-else-if="values.error">Error</p>
|
|
529
|
+
<ul v-else>
|
|
530
|
+
<li v-for="user in values.value" :key="user.id">{{ user.name }}</li>
|
|
531
|
+
</ul>
|
|
532
|
+
</template>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Svelte
|
|
536
|
+
|
|
537
|
+
```svelte
|
|
538
|
+
<script>
|
|
539
|
+
import { wcBindable } from "@wc-bindable/svelte";
|
|
540
|
+
|
|
541
|
+
let users = $state(null);
|
|
542
|
+
let loading = $state(false);
|
|
543
|
+
</script>
|
|
544
|
+
|
|
545
|
+
<wcs-fetch url="/api/users"
|
|
546
|
+
use:wcBindable={{ onUpdate: (name, v) => {
|
|
547
|
+
if (name === "value") users = v;
|
|
548
|
+
if (name === "loading") loading = v;
|
|
549
|
+
}}} />
|
|
550
|
+
|
|
551
|
+
{#if loading}
|
|
552
|
+
<p>Loading...</p>
|
|
553
|
+
{:else if users}
|
|
554
|
+
<ul>
|
|
555
|
+
{#each users as user (user.id)}
|
|
556
|
+
<li>{user.name}</li>
|
|
557
|
+
{/each}
|
|
558
|
+
</ul>
|
|
559
|
+
{/if}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Solid
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
import { createWcBindable } from "@wc-bindable/solid";
|
|
566
|
+
import type { WcsFetchValues } from "@wcstack/fetch";
|
|
567
|
+
|
|
568
|
+
interface User { id: number; name: string; }
|
|
569
|
+
|
|
570
|
+
function UserList() {
|
|
571
|
+
const [values, directive] = createWcBindable<WcsFetchValues<User[]>>();
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<>
|
|
575
|
+
<wcs-fetch ref={directive} url="/api/users" />
|
|
576
|
+
<Show when={!values.loading} fallback={<p>Loading...</p>}>
|
|
577
|
+
<ul>
|
|
578
|
+
<For each={values.value}>{(user) => <li>{user.name}</li>}</For>
|
|
579
|
+
</ul>
|
|
580
|
+
</Show>
|
|
581
|
+
</>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Vanilla — `bind()` directly
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
import { bind } from "@wc-bindable/core";
|
|
590
|
+
|
|
591
|
+
const fetchEl = document.querySelector("wcs-fetch");
|
|
592
|
+
|
|
593
|
+
bind(fetchEl, (name, value) => {
|
|
594
|
+
console.log(`${name} changed:`, value);
|
|
595
|
+
});
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Configuration
|
|
599
|
+
|
|
600
|
+
```javascript
|
|
601
|
+
import { bootstrapFetch } from "@wcstack/fetch";
|
|
602
|
+
|
|
603
|
+
bootstrapFetch({
|
|
604
|
+
autoTrigger: true,
|
|
605
|
+
triggerAttribute: "data-fetchtarget",
|
|
606
|
+
tagNames: {
|
|
607
|
+
fetch: "wcs-fetch",
|
|
608
|
+
fetchHeader: "wcs-fetch-header",
|
|
609
|
+
fetchBody: "wcs-fetch-body",
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Design Notes
|
|
615
|
+
|
|
616
|
+
- `value`, `loading`, `error`, and `status` are **output state**
|
|
617
|
+
- `url`, `body`, and `trigger` are **input / command surface**
|
|
618
|
+
- `trigger` is intentionally one-way: writing `true` executes, reset emits completion
|
|
619
|
+
- `body` is reset to `null` after each `fetch()` call — set it again before each submission
|
|
620
|
+
- `manual` is useful when execution timing should be controlled explicitly
|
|
621
|
+
- HTML replace mode is optional; the primary wcstack pattern is state-driven binding
|
|
622
|
+
|
|
623
|
+
## License
|
|
624
|
+
|
|
625
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED