@excom/provider-fetch 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/.rush/temp/chunked-rush-logs/provider-fetch.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-fetch.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/provider-fetch.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +5 -0
- package/index.ts +17 -0
- package/package.json +44 -0
- package/provider-fetch.ts +59 -0
- package/rush-logs/provider-fetch.apply-exports.cache.log +1 -0
- package/rush-logs/provider-fetch.apply-exports.log +1 -0
- package/rush-logs/provider-fetch.build_docs.cache.log +1 -0
- package/rush-logs/provider-fetch.build_docs.log +1 -0
- package/rush-logs/provider-fetch.build_package-metas.cache.log +1 -0
- package/rush-logs/provider-fetch.build_package-metas.log +1 -0
- package/support/custom-elements.json +91 -0
- package/support/demos/refetch.html +35 -0
- package/support/demos/simple.html +13 -0
- package/support/dist-docs/provider-fetch.md +172 -0
- package/support/docs/README.md +52 -0
- package/support/package-meta.json +214 -0
- package/support/tests/__snapshots__/refetch.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/simple.view.test.ts.snap +23 -0
- package/support/tests/provider-fetch.test.ts +215 -0
- package/support/tests/refetch.view.test.ts +56 -0
- package/support/tests/simple.view.test.ts +43 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
package/config/rig.json
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ProviderFetch } from "./provider-fetch";
|
|
2
|
+
|
|
3
|
+
ProviderFetch.define();
|
|
4
|
+
|
|
5
|
+
export { ProviderFetch };
|
|
6
|
+
|
|
7
|
+
type T_HTMLProviderFetchElement = typeof ProviderFetch.CustomElement;
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLProviderFetchElement extends T_HTMLProviderFetchElement {}
|
|
10
|
+
interface Window {
|
|
11
|
+
HTMLProviderFetchElement: HTMLProviderFetchElement;
|
|
12
|
+
}
|
|
13
|
+
interface HTMLElementTagNameMap {
|
|
14
|
+
"provider-fetch": HTMLProviderFetchElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export type { HTMLProviderFetchElement };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@excom/provider-fetch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "<provider-fetch> custom element",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.13.0"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@excom/fetchable-element": "^0.1.0",
|
|
12
|
+
"@excom/neutron": "^0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@excom/heft-rig": "^0.1.0",
|
|
17
|
+
"@excom/kit-logger": "^0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"url": "excom-dev/nucleus",
|
|
21
|
+
"directory": "packages/provider-fetch"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/provider-fetch/support/docs/README.md",
|
|
24
|
+
"bugs": "https://github.com/excom-dev/nucleus/issues",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"provider-fetch",
|
|
27
|
+
"neutron",
|
|
28
|
+
"custom-elements"
|
|
29
|
+
],
|
|
30
|
+
"excom": {
|
|
31
|
+
"packageType": "kit-element"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
|
|
35
|
+
"build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
|
|
36
|
+
"format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
|
|
37
|
+
"test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
|
|
38
|
+
"coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
|
|
39
|
+
"dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
|
|
40
|
+
"preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
|
|
41
|
+
"build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
|
|
42
|
+
"build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FetchableElement } from "@excom/fetchable-element";
|
|
2
|
+
import { Neutron, TEvent } from "@excom/neutron";
|
|
3
|
+
|
|
4
|
+
export type FetchArgs = [url: string, requestInit: RequestInit];
|
|
5
|
+
|
|
6
|
+
export type ProviderFetchSubmitEvent = TEvent & {
|
|
7
|
+
type: "provider-fetch-submit";
|
|
8
|
+
detail: FetchArgs;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Zero-JS `fetch()` — composes `FetchableElement` and auto-fetches
|
|
13
|
+
* whenever `api-url` is set (or changes) while not `is-paused`. Re-fetch
|
|
14
|
+
* on demand with the `--fetch` command: a `<button command="--fetch"
|
|
15
|
+
* commandfor="…">`, or `<event-handler command-name="--fetch"
|
|
16
|
+
* target-ref="…">` to relay any event.
|
|
17
|
+
*
|
|
18
|
+
* All request-building attributes (`api-method`, headers, `form-ref`,
|
|
19
|
+
* …) and lifecycle state (`is-loading` / `is-success` / `is-error` /
|
|
20
|
+
* `provision`) are inherited from `FetchableElement` — see that package's
|
|
21
|
+
* docs for the full attribute list.
|
|
22
|
+
*
|
|
23
|
+
* @fires provider-fetch-submit - Internal — dispatched whenever a fetch
|
|
24
|
+
* is about to run (auto-fetch or `--fetch`). Built by
|
|
25
|
+
* `getFetchArgs()`. Cancelable; default action calls `doFetch()`.
|
|
26
|
+
* @type ProviderFetchSubmitEvent
|
|
27
|
+
* @command --fetch - Re-runs the request with the current attributes,
|
|
28
|
+
* even while `is-paused`.
|
|
29
|
+
* @default-action provider-fetch-submit - Calls `doFetch(url,
|
|
30
|
+
* requestInit)` with the event's detail.
|
|
31
|
+
*/
|
|
32
|
+
export const ProviderFetch = Neutron.compose([
|
|
33
|
+
FetchableElement,
|
|
34
|
+
Neutron({
|
|
35
|
+
tag: "provider-fetch",
|
|
36
|
+
props: {
|
|
37
|
+
/**
|
|
38
|
+
* @option
|
|
39
|
+
* Pause auto-fetch. While set, `api-url` changes are ignored;
|
|
40
|
+
* the `--fetch` command still works.
|
|
41
|
+
*/
|
|
42
|
+
isPaused: Boolean,
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
])
|
|
46
|
+
.onPropChanged(
|
|
47
|
+
["apiUrl", "isPaused"],
|
|
48
|
+
({ apiUrl, isPaused, getFetchArgs }) =>
|
|
49
|
+
apiUrl &&
|
|
50
|
+
!isPaused && {
|
|
51
|
+
emit: ["provider-fetch-submit", { detail: [getFetchArgs()] }],
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
.onCommand("--fetch", ({ getFetchArgs }) => ({
|
|
55
|
+
emit: ["provider-fetch-submit", { detail: [getFetchArgs()] }],
|
|
56
|
+
}))
|
|
57
|
+
.onEventDefault("provider-fetch-submit", (_, { detail }) => ({
|
|
58
|
+
doFetch: [detail[0], detail[1]],
|
|
59
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Caching has been disabled for this project's "apply-exports" command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "provider-fetch.ts",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "class",
|
|
10
|
+
"name": "ProviderFetch",
|
|
11
|
+
"customElement": true,
|
|
12
|
+
"tagName": "provider-fetch",
|
|
13
|
+
"mixins": [
|
|
14
|
+
{
|
|
15
|
+
"name": "FetchableElement",
|
|
16
|
+
"package": "@excom/fetchable-element"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"description": "Zero-JS `fetch()` — composes `FetchableElement` and auto-fetches whenever `api-url` is set (or changes) while not `is-paused`. Re-fetch on demand with the `--fetch` command: a `<button command=\"--fetch\" commandfor=\"…\">`, or `<event-handler command-name=\"--fetch\" target-ref=\"…\">` to relay any event. All request-building attributes (`api-method`, headers, `form-ref`, …) and lifecycle state (`is-loading` / `is-success` / `is-error` / `provision`) are inherited from `FetchableElement` — see that package's docs for the full attribute list.",
|
|
20
|
+
"attributes": [
|
|
21
|
+
{
|
|
22
|
+
"name": "is-paused",
|
|
23
|
+
"type": {
|
|
24
|
+
"text": "boolean"
|
|
25
|
+
},
|
|
26
|
+
"description": "Pause auto-fetch. While set, `api-url` changes are ignored; the `--fetch` command still works.",
|
|
27
|
+
"fieldName": "isPaused"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"members": [
|
|
31
|
+
{
|
|
32
|
+
"kind": "field",
|
|
33
|
+
"name": "isPaused",
|
|
34
|
+
"type": {
|
|
35
|
+
"text": "boolean"
|
|
36
|
+
},
|
|
37
|
+
"privacy": "public",
|
|
38
|
+
"readonly": false,
|
|
39
|
+
"description": "Pause auto-fetch. While set, `api-url` changes are ignored; the `--fetch` command still works.",
|
|
40
|
+
"_neutron": {
|
|
41
|
+
"surface": "option"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"events": [
|
|
46
|
+
{
|
|
47
|
+
"name": "provider-fetch-submit",
|
|
48
|
+
"description": "Internal — dispatched whenever a fetch is about to run (auto-fetch or `--fetch`). Built by `getFetchArgs()`. Cancelable; default action calls `doFetch()`.",
|
|
49
|
+
"type": {
|
|
50
|
+
"text": "ProviderFetchSubmitEvent",
|
|
51
|
+
"expanded": "CustomEvent & { type: \"provider-fetch-submit\"; detail: [url: string, requestInit: RequestInit]; bubbles: true; cancelable: true; composed: true }"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"_neutron": {
|
|
56
|
+
"commands": [
|
|
57
|
+
{
|
|
58
|
+
"name": "--fetch",
|
|
59
|
+
"description": "Re-runs the request with the current attributes, even while `is-paused`."
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"defaultActions": [
|
|
63
|
+
{
|
|
64
|
+
"name": "provider-fetch-submit",
|
|
65
|
+
"description": "Calls `doFetch(url, requestInit)` with the event's detail."
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"exports": [
|
|
72
|
+
{
|
|
73
|
+
"kind": "js",
|
|
74
|
+
"name": "ProviderFetch",
|
|
75
|
+
"declaration": {
|
|
76
|
+
"name": "ProviderFetch",
|
|
77
|
+
"module": "provider-fetch.ts"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"kind": "custom-element-definition",
|
|
82
|
+
"name": "provider-fetch",
|
|
83
|
+
"declaration": {
|
|
84
|
+
"name": "ProviderFetch",
|
|
85
|
+
"module": "provider-fetch.ts"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<provider-fetch id="demo-refetch-provider" api-url="/api/todos/1" class="tag-article grid">
|
|
3
|
+
<ul></ul>
|
|
4
|
+
<ul></ul>
|
|
5
|
+
<template id="demo-refetch-item">
|
|
6
|
+
<li class="tag-code"></li>
|
|
7
|
+
</template>
|
|
8
|
+
</provider-fetch>
|
|
9
|
+
<button type="button" command="--fetch" commandfor="demo-refetch-provider">Refetch</button>
|
|
10
|
+
<style>
|
|
11
|
+
#demo-provider-fetch-refetch { max-width: none; }
|
|
12
|
+
#demo-provider-fetch-refetch provider-fetch {
|
|
13
|
+
&[is-loading] {
|
|
14
|
+
opacity: 0.5;
|
|
15
|
+
border: 1px dashed yellow;
|
|
16
|
+
}
|
|
17
|
+
&[is-success] { border: 1px solid green; }
|
|
18
|
+
&[is-error] {
|
|
19
|
+
border: 1px solid red;
|
|
20
|
+
&::before { content: "An error occurred."; }
|
|
21
|
+
}
|
|
22
|
+
ul:first-of-type::before { content: "Todo data:"; }
|
|
23
|
+
ul:last-of-type::before { content: "Response data:"; }
|
|
24
|
+
li { display: block; }
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
<quark-sheet>
|
|
28
|
+
provider-fetch[is-success] {
|
|
29
|
+
$res: prop("provision");
|
|
30
|
+
li { content: "#{index}: #{item}"; }
|
|
31
|
+
ul:first-of-type { content: iterate($res.body, "#demo-refetch-item"); }
|
|
32
|
+
ul:last-of-type { content: iterate($res, "#demo-refetch-item"); }
|
|
33
|
+
}
|
|
34
|
+
</quark-sheet>
|
|
35
|
+
</section>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<provider-fetch api-url="/api/todos/1">
|
|
3
|
+
<h4>Fetching todo...</h4>
|
|
4
|
+
<span></span>
|
|
5
|
+
</provider-fetch>
|
|
6
|
+
<quark-sheet>
|
|
7
|
+
provider-fetch[is-success] {
|
|
8
|
+
$todo: prop("provision").body;
|
|
9
|
+
h4 { content: "Todo ##{$todo.id}: #{$todo.title}"; }
|
|
10
|
+
span { content: "Completed: #{$todo.completed}"; }
|
|
11
|
+
}
|
|
12
|
+
</quark-sheet>
|
|
13
|
+
</section>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# provider-fetch
|
|
2
|
+
|
|
3
|
+
Fetch data with HTML. Pair it with Quark to render the data. Zero app JS.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<section>
|
|
8
|
+
<provider-fetch api-url="/api/todos/1">
|
|
9
|
+
<h4>Fetching todo...</h4>
|
|
10
|
+
<span></span>
|
|
11
|
+
</provider-fetch>
|
|
12
|
+
<quark-sheet>
|
|
13
|
+
provider-fetch[is-success] {
|
|
14
|
+
$todo: prop("provision").body;
|
|
15
|
+
h4 { content: "Todo ##{$todo.id}: #{$todo.title}"; }
|
|
16
|
+
span { content: "Completed: #{$todo.completed}"; }
|
|
17
|
+
}
|
|
18
|
+
</quark-sheet>
|
|
19
|
+
</section>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **Provides data** Use Quark to render that data
|
|
26
|
+
- **Auto-fetch** Fetches whenever `api-url` is set or changes
|
|
27
|
+
- **Re-fetch on demand** The `--fetch` command (`<button command="--fetch" commandfor="…">`) forces a re-fetch
|
|
28
|
+
- **Pausable** `is-paused` holds off auto-fetch without removing state
|
|
29
|
+
- **Highly configurable** Headers, method, `form-ref`,
|
|
30
|
+
credentials, redirect, etc
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
`@excom/provider-fetch` v0.1.0
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add @excom/provider-fetch
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @excom/provider-fetch
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
yarn add @excom/provider-fetch
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Import
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import "@excom/provider-fetch";
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<provider-fetch api-url="/api/todos"></provider-fetch>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Hook the lifecycle state with CSS:
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
provider-fetch[is-loading] { /* show loading spinner */ }
|
|
67
|
+
provider-fetch[is-error]::before { content: "An error occurred." }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or Quark:
|
|
71
|
+
|
|
72
|
+
```quark
|
|
73
|
+
provider-fetch[is-success] {
|
|
74
|
+
$todo: prop("provision").body;
|
|
75
|
+
span { content: $todo.title; }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### API Reference
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
#### Attributes
|
|
83
|
+
|
|
84
|
+
| Name | Surface | Type | Default | Values | Description | Inherited from |
|
|
85
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
86
|
+
| `is-paused` | option | `boolean` | | | Pause auto-fetch. While set, `api-url` changes are ignored; the `--fetch` command still works. | |
|
|
87
|
+
| `form-ref` | option | `string` | | `<CSS Selector>` | CSS selector for a `<form>` to source the request from — its `action` (URL), `method`, `enctype` (Content-Type), and field values (as the JSON payload) all take priority over the matching attributes below. Omit to build the request entirely from attributes / custom `doFetch()` args. | `@excom/fetchable-element` |
|
|
88
|
+
| `has-body` | option | `boolean` | | | Force a request body even for methods that don't imply one (`GET` / `HEAD`). Already implied for `POST` / `PUT` / `PATCH`. | `@excom/fetchable-element` |
|
|
89
|
+
| `api-url` | option | `string` | `""` | | Endpoint URL. When the request has no body, the JSON payload (from `form-ref` or custom `doFetch()` args) is merged in as query params instead. | `@excom/fetchable-element` |
|
|
90
|
+
| `api-method` | option | `string` | `"GET"` | | HTTP method. Always uppercased before the request is sent. | `@excom/fetchable-element` |
|
|
91
|
+
| `header-accept` | option | `string` | `"application/json"` | | `Accept` request header. | `@excom/fetchable-element` |
|
|
92
|
+
| `header-content-type` | option | `string` | `"application/json"` | | `Content-Type` request header. Dropped entirely when the request has no body. | `@excom/fetchable-element` |
|
|
93
|
+
| `header-cache-control` | option | `string` | | | `Cache-Control` request header. Unset by default (browser default caching applies). | `@excom/fetchable-element` |
|
|
94
|
+
| `fetch-redirect` | option | `string` | | `"follow"` \| `"error"` \| `"manual"` | `RequestInit.redirect` mode. Unset defers to the browser default (`follow`). | `@excom/fetchable-element` |
|
|
95
|
+
| `fetch-credentials` | option | `string` | `"include"` | `"omit"` \| `"same-origin"` \| `"include"` | `RequestInit.credentials` mode. | `@excom/fetchable-element` |
|
|
96
|
+
| `is-loading` | state | `boolean` | | | A request is currently in flight. | `@excom/fetchable-element` |
|
|
97
|
+
| `is-success` | state | `boolean` | | | The most recent request resolved successfully. Mutually exclusive with `is-error`. | `@excom/fetchable-element` |
|
|
98
|
+
| `is-error` | state | `boolean` | | | The most recent request failed (non-2xx status, network error, or a thrown error other than `AbortError`). Fires with the `error` event. | `@excom/fetchable-element` |
|
|
99
|
+
|
|
100
|
+
#### Provision
|
|
101
|
+
|
|
102
|
+
| Name | Type | Description | Inherited from |
|
|
103
|
+
| --- | --- | --- | --- |
|
|
104
|
+
| `provision` | `FetchResponse` (`{ bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }`) | Response payload on success, or error payload on failure. Success shape: `{ status, statusText, ok, headers, url, redirected, bodyUsed, type, body }`. Failure shape is either that same response shape (server responded with an error status) or `{ message, stack }` (request never completed). Not reflected as an attribute. | `@excom/fetchable-element` |
|
|
105
|
+
|
|
106
|
+
#### Fires
|
|
107
|
+
|
|
108
|
+
| Name | Type | Description | Inherited from |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| `provider-fetch-submit` | `ProviderFetchSubmitEvent` (`CustomEvent & { type: "provider-fetch-submit"; detail: [url: string, requestInit: RequestInit]; bubbles: true; cancelable: true; composed: true }`) | Internal — dispatched whenever a fetch is about to run (auto-fetch or `--fetch`). Built by `getFetchArgs()`. Cancelable; default action calls `doFetch()`. | |
|
|
111
|
+
| `provider-fetch-loading` | `FetchableLoadingEvent` (`CustomEvent & { type: "{tag}-loading"; detail: void; bubbles: true; cancelable: true; composed: true }`) | Dispatched immediately before the request is sent. | `@excom/fetchable-element` |
|
|
112
|
+
| `provider-fetch-success` | `FetchableSuccessEvent` (`CustomEvent & { type: "{tag}-success"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched when the request resolves successfully. `event.detail` is the parsed response (see `provision`). | `@excom/fetchable-element` |
|
|
113
|
+
| `provider-fetch-error` | `FetchableErrorEvent` (`CustomEvent & { type: "{tag}-error"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; } \| { message: string; stack?: string }; bubbles: true; cancelable: true; composed: true }`) | Dispatched when the request fails — non-2xx status, network error, or a thrown error. `event.detail` is the error payload (see `provision`). Not dispatched for aborted requests. | `@excom/fetchable-element` |
|
|
114
|
+
|
|
115
|
+
#### Commands
|
|
116
|
+
|
|
117
|
+
| Command | Action |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `--fetch` | Re-runs the request with the current attributes, even while `is-paused`. |
|
|
120
|
+
|
|
121
|
+
#### Default actions
|
|
122
|
+
|
|
123
|
+
| Event | Default behavior (unless preventDefault() is called) |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `provider-fetch-submit` | Calls `doFetch(url, requestInit)` with the event's detail. |
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
### Examples
|
|
130
|
+
|
|
131
|
+
#### Comprehensive
|
|
132
|
+
|
|
133
|
+
This example shows loading state, error state, rendering, and refetching.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<section>
|
|
138
|
+
<provider-fetch id="demo-refetch-provider" api-url="/api/todos/1" class="tag-article grid">
|
|
139
|
+
<ul></ul>
|
|
140
|
+
<ul></ul>
|
|
141
|
+
<template id="demo-refetch-item">
|
|
142
|
+
<li class="tag-code"></li>
|
|
143
|
+
</template>
|
|
144
|
+
</provider-fetch>
|
|
145
|
+
<button type="button" command="--fetch" commandfor="demo-refetch-provider">Refetch</button>
|
|
146
|
+
<style>
|
|
147
|
+
#demo-provider-fetch-refetch { max-width: none; }
|
|
148
|
+
#demo-provider-fetch-refetch provider-fetch {
|
|
149
|
+
&[is-loading] {
|
|
150
|
+
opacity: 0.5;
|
|
151
|
+
border: 1px dashed yellow;
|
|
152
|
+
}
|
|
153
|
+
&[is-success] { border: 1px solid green; }
|
|
154
|
+
&[is-error] {
|
|
155
|
+
border: 1px solid red;
|
|
156
|
+
&::before { content: "An error occurred."; }
|
|
157
|
+
}
|
|
158
|
+
ul:first-of-type::before { content: "Todo data:"; }
|
|
159
|
+
ul:last-of-type::before { content: "Response data:"; }
|
|
160
|
+
li { display: block; }
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
163
|
+
<quark-sheet>
|
|
164
|
+
provider-fetch[is-success] {
|
|
165
|
+
$res: prop("provision");
|
|
166
|
+
li { content: "#{index}: #{item}"; }
|
|
167
|
+
ul:first-of-type { content: iterate($res.body, "#demo-refetch-item"); }
|
|
168
|
+
ul:last-of-type { content: iterate($res, "#demo-refetch-item"); }
|
|
169
|
+
}
|
|
170
|
+
</quark-sheet>
|
|
171
|
+
</section>
|
|
172
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# provider-fetch
|
|
2
|
+
|
|
3
|
+
Fetch data with HTML. Pair it with Quark to render the data. Zero app JS.
|
|
4
|
+
|
|
5
|
+
<include-content data-demo="simple"></include-content>
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Provides data** Use Quark to render that data
|
|
10
|
+
- **Auto-fetch** Fetches whenever `api-url` is set or changes
|
|
11
|
+
- **Re-fetch on demand** The `--fetch` command (`<button command="--fetch" commandfor="…">`) forces a re-fetch
|
|
12
|
+
- **Pausable** `is-paused` holds off auto-fetch without removing state
|
|
13
|
+
- **Highly configurable** Headers, method, `form-ref`,
|
|
14
|
+
credentials, redirect, etc
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
<include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<provider-fetch api-url="/api/todos"></provider-fetch>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Hook the lifecycle state with CSS:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
provider-fetch[is-loading] { /* show loading spinner */ }
|
|
30
|
+
provider-fetch[is-error]::before { content: "An error occurred." }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or Quark:
|
|
34
|
+
|
|
35
|
+
```quark
|
|
36
|
+
provider-fetch[is-success] {
|
|
37
|
+
$todo: prop("provision").body;
|
|
38
|
+
span { content: $todo.title; }
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### API Reference
|
|
43
|
+
|
|
44
|
+
<include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
|
|
45
|
+
|
|
46
|
+
### Examples
|
|
47
|
+
|
|
48
|
+
#### Comprehensive
|
|
49
|
+
|
|
50
|
+
This example shows loading state, error state, rendering, and refetching.
|
|
51
|
+
|
|
52
|
+
<include-content data-demo="refetch"></include-content>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
{
|
|
2
|
+
"shortName": "provider-fetch",
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@excom/provider-fetch",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "<provider-fetch> custom element",
|
|
7
|
+
"peerDependencies": {},
|
|
8
|
+
"excom": {
|
|
9
|
+
"packageType": "kit-element"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"demos": {
|
|
13
|
+
"refetch": "<section>\n <provider-fetch id=\"demo-refetch-provider\" api-url=\"/api/todos/1\" class=\"tag-article grid\">\n <ul></ul>\n <ul></ul>\n <template id=\"demo-refetch-item\">\n <li class=\"tag-code\"></li>\n </template>\n </provider-fetch>\n <button type=\"button\" command=\"--fetch\" commandfor=\"demo-refetch-provider\">Refetch</button>\n <style>\n #demo-provider-fetch-refetch { max-width: none; }\n #demo-provider-fetch-refetch provider-fetch {\n &[is-loading] {\n opacity: 0.5;\n border: 1px dashed yellow;\n }\n &[is-success] { border: 1px solid green; }\n &[is-error] {\n border: 1px solid red;\n &::before { content: \"An error occurred.\"; }\n }\n ul:first-of-type::before { content: \"Todo data:\"; }\n ul:last-of-type::before { content: \"Response data:\"; }\n li { display: block; }\n }\n </style>\n <quark-sheet>\n provider-fetch[is-success] {\n $res: prop(\"provision\");\n li { content: \"#{index}: #{item}\"; }\n ul:first-of-type { content: iterate($res.body, \"#demo-refetch-item\"); }\n ul:last-of-type { content: iterate($res, \"#demo-refetch-item\"); }\n }\n </quark-sheet>\n</section>\n",
|
|
14
|
+
"simple": "<section>\n <provider-fetch api-url=\"/api/todos/1\">\n <h4>Fetching todo...</h4>\n <span></span>\n </provider-fetch>\n <quark-sheet>\n provider-fetch[is-success] {\n $todo: prop(\"provision\").body;\n h4 { content: \"Todo ##{$todo.id}: #{$todo.title}\"; }\n span { content: \"Completed: #{$todo.completed}\"; }\n }\n </quark-sheet>\n</section>\n"
|
|
15
|
+
},
|
|
16
|
+
"readme": "<h1 id=\"md-provider-fetch\">provider-fetch</h1>\n<p>Fetch data with HTML. Pair it with Quark to render the data. Zero app JS.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Provides data</strong> Use Quark to render that data</li>\n<li><strong>Auto-fetch</strong> Fetches whenever <code>api-url</code> is set or changes</li>\n<li><strong>Re-fetch on demand</strong> The <code>--fetch</code> command (<code><button command="--fetch" commandfor="…"></code>) forces a re-fetch</li>\n<li><strong>Pausable</strong> <code>is-paused</code> holds off auto-fetch without removing state</li>\n<li><strong>Highly configurable</strong> Headers, method, <code>form-ref</code>,\ncredentials, redirect, etc</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<include-content data-language=\"html\"><template><provider-fetch api-url=\"/api/todos\"></provider-fetch></template></include-content>\n<p>Hook the lifecycle state with CSS:</p>\n<include-content data-language=\"css\"><template>provider-fetch[is-loading] { /* show loading spinner */ }\nprovider-fetch[is-error]::before { content: \"An error occurred.\" }</template></include-content>\n<p>Or Quark:</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-success] {\n $todo: prop(\"provision\").body;\n span { content: $todo.title; }\n}</template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-comprehensive\">Comprehensive</h4>\n<p>This example shows loading state, error state, rendering, and refetching.</p>\n<p><include-content data-demo=\"refetch\"></include-content></p>\n",
|
|
17
|
+
"docs": {
|
|
18
|
+
"readme": "<h1 id=\"md-provider-fetch\">provider-fetch</h1>\n<p>Fetch data with HTML. Pair it with Quark to render the data. Zero app JS.</p>\n<p><include-content data-demo=\"simple\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Provides data</strong> Use Quark to render that data</li>\n<li><strong>Auto-fetch</strong> Fetches whenever <code>api-url</code> is set or changes</li>\n<li><strong>Re-fetch on demand</strong> The <code>--fetch</code> command (<code><button command="--fetch" commandfor="…"></code>) forces a re-fetch</li>\n<li><strong>Pausable</strong> <code>is-paused</code> holds off auto-fetch without removing state</li>\n<li><strong>Highly configurable</strong> Headers, method, <code>form-ref</code>,\ncredentials, redirect, etc</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<include-content data-language=\"html\"><template><provider-fetch api-url=\"/api/todos\"></provider-fetch></template></include-content>\n<p>Hook the lifecycle state with CSS:</p>\n<include-content data-language=\"css\"><template>provider-fetch[is-loading] { /* show loading spinner */ }\nprovider-fetch[is-error]::before { content: \"An error occurred.\" }</template></include-content>\n<p>Or Quark:</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-success] {\n $todo: prop(\"provision\").body;\n span { content: $todo.title; }\n}</template></include-content>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-comprehensive\">Comprehensive</h4>\n<p>This example shows loading state, error state, rendering, and refetching.</p>\n<p><include-content data-demo=\"refetch\"></include-content></p>\n"
|
|
19
|
+
},
|
|
20
|
+
"installation": {
|
|
21
|
+
"name": "@excom/provider-fetch",
|
|
22
|
+
"shortName": "provider-fetch",
|
|
23
|
+
"version": "0.1.0",
|
|
24
|
+
"description": "<provider-fetch> custom element",
|
|
25
|
+
"packageType": "kit-element",
|
|
26
|
+
"cdn": "<script src=\"https://unpkg.com/@excom/kit-utils/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/neutron/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/provider-fetch@0.1.0/dist/index.umd.min.js\"></script>",
|
|
27
|
+
"install": {
|
|
28
|
+
"npm": "npm install @excom/provider-fetch"
|
|
29
|
+
},
|
|
30
|
+
"imports": {
|
|
31
|
+
"js": "import \"@excom/provider-fetch\";",
|
|
32
|
+
"html": "<!-- import path to `node_modules` will depend on your build setup -->\n<script type=\"module\" src=\"/node_modules/@excom/provider-fetch\"></script>\n<link rel=\"stylesheet\" href=\"/node_modules/@excom/provider-fetch\">"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": []
|
|
35
|
+
},
|
|
36
|
+
"elementApis": [
|
|
37
|
+
{
|
|
38
|
+
"tag": "provider-fetch",
|
|
39
|
+
"kind": "class",
|
|
40
|
+
"attributes": [
|
|
41
|
+
{
|
|
42
|
+
"name": "api-method",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "HTTP method. Always uppercased before the request is sent.",
|
|
45
|
+
"fieldName": "apiMethod",
|
|
46
|
+
"surface": "option",
|
|
47
|
+
"default": "\"GET\"",
|
|
48
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "api-url",
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Endpoint URL. When the request has no body, the JSON payload (from <code>form-ref</code> or custom <code>doFetch()</code> args) is merged in as query params instead.",
|
|
54
|
+
"fieldName": "apiUrl",
|
|
55
|
+
"surface": "option",
|
|
56
|
+
"default": "\"\"",
|
|
57
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "fetch-credentials",
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "<code>RequestInit.credentials</code> mode.",
|
|
63
|
+
"fieldName": "fetchCredentials",
|
|
64
|
+
"surface": "option",
|
|
65
|
+
"default": "\"include\"",
|
|
66
|
+
"values": "\"omit\" | \"same-origin\" | \"include\"",
|
|
67
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "fetch-redirect",
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "<code>RequestInit.redirect</code> mode. Unset defers to the browser default (<code>follow</code>).",
|
|
73
|
+
"fieldName": "fetchRedirect",
|
|
74
|
+
"surface": "option",
|
|
75
|
+
"values": "\"follow\" | \"error\" | \"manual\"",
|
|
76
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "form-ref",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "CSS selector for a <code><form></code> to source the request from — its <code>action</code> (URL), <code>method</code>, <code>enctype</code> (Content-Type), and field values (as the JSON payload) all take priority over the matching attributes below. Omit to build the request entirely from attributes / custom <code>doFetch()</code> args.",
|
|
82
|
+
"fieldName": "formRef",
|
|
83
|
+
"surface": "option",
|
|
84
|
+
"values": "<CSS Selector>",
|
|
85
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "has-body",
|
|
89
|
+
"type": "boolean",
|
|
90
|
+
"description": "Force a request body even for methods that don't imply one (<code>GET</code> / <code>HEAD</code>). Already implied for <code>POST</code> / <code>PUT</code> / <code>PATCH</code>.",
|
|
91
|
+
"fieldName": "hasBody",
|
|
92
|
+
"surface": "option",
|
|
93
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "header-accept",
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "<code>Accept</code> request header.",
|
|
99
|
+
"fieldName": "headerAccept",
|
|
100
|
+
"surface": "option",
|
|
101
|
+
"default": "\"application/json\"",
|
|
102
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "header-cache-control",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "<code>Cache-Control</code> request header. Unset by default (browser default caching applies).",
|
|
108
|
+
"fieldName": "headerCacheControl",
|
|
109
|
+
"surface": "option",
|
|
110
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "header-content-type",
|
|
114
|
+
"type": "string",
|
|
115
|
+
"description": "<code>Content-Type</code> request header. Dropped entirely when the request has no body.",
|
|
116
|
+
"fieldName": "headerContentType",
|
|
117
|
+
"surface": "option",
|
|
118
|
+
"default": "\"application/json\"",
|
|
119
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "is-paused",
|
|
123
|
+
"type": "boolean",
|
|
124
|
+
"description": "Pause auto-fetch. While set, <code>api-url</code> changes are ignored; the <code>--fetch</code> command still works.",
|
|
125
|
+
"fieldName": "isPaused",
|
|
126
|
+
"surface": "option"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "is-error",
|
|
130
|
+
"type": "boolean",
|
|
131
|
+
"description": "The most recent request failed (non-2xx status, network error, or a thrown error other than <code>AbortError</code>). Fires with the <code>error</code> event.",
|
|
132
|
+
"fieldName": "isError",
|
|
133
|
+
"surface": "state",
|
|
134
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "is-loading",
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"description": "A request is currently in flight.",
|
|
140
|
+
"fieldName": "isLoading",
|
|
141
|
+
"surface": "state",
|
|
142
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "is-success",
|
|
146
|
+
"type": "boolean",
|
|
147
|
+
"description": "The most recent request resolved successfully. Mutually exclusive with <code>is-error</code>.",
|
|
148
|
+
"fieldName": "isSuccess",
|
|
149
|
+
"surface": "state",
|
|
150
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"events": [
|
|
154
|
+
{
|
|
155
|
+
"name": "provider-fetch-error",
|
|
156
|
+
"description": "Dispatched when the request fails — non-2xx status, network error, or a thrown error. <code>event.detail</code> is the error payload (see <code>provision</code>). Not dispatched for aborted requests.",
|
|
157
|
+
"type": "FetchableErrorEvent",
|
|
158
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-error\"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; } | { message: string; stack?: string }; bubbles: true; cancelable: true; composed: true }",
|
|
159
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "provider-fetch-loading",
|
|
163
|
+
"description": "Dispatched immediately before the request is sent.",
|
|
164
|
+
"type": "FetchableLoadingEvent",
|
|
165
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-loading\"; detail: void; bubbles: true; cancelable: true; composed: true }",
|
|
166
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "provider-fetch-submit",
|
|
170
|
+
"description": "Internal — dispatched whenever a fetch is about to run (auto-fetch or <code>--fetch</code>). Built by <code>getFetchArgs()</code>. Cancelable; default action calls <code>doFetch()</code>.",
|
|
171
|
+
"type": "ProviderFetchSubmitEvent",
|
|
172
|
+
"typeExpanded": "CustomEvent & { type: \"provider-fetch-submit\"; detail: [url: string, requestInit: RequestInit]; bubbles: true; cancelable: true; composed: true }",
|
|
173
|
+
"defaultAction": "Calls <code>doFetch(url, requestInit)</code> with the event's detail."
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "provider-fetch-success",
|
|
177
|
+
"description": "Dispatched when the request resolves successfully. <code>event.detail</code> is the parsed response (see <code>provision</code>).",
|
|
178
|
+
"type": "FetchableSuccessEvent",
|
|
179
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-success\"; detail: { bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }; bubbles: true; cancelable: true; composed: true }",
|
|
180
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"slots": [],
|
|
184
|
+
"cssProperties": [],
|
|
185
|
+
"cssClasses": [],
|
|
186
|
+
"cssAliases": [],
|
|
187
|
+
"listens": [],
|
|
188
|
+
"commands": [
|
|
189
|
+
{
|
|
190
|
+
"name": "--fetch",
|
|
191
|
+
"description": "Re-runs the request with the current attributes, even while <code>is-paused</code>."
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"defaultActions": [
|
|
195
|
+
{
|
|
196
|
+
"name": "provider-fetch-submit",
|
|
197
|
+
"description": "Calls <code>doFetch(url, requestInit)</code> with the event's detail."
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"expectedChildren": [],
|
|
201
|
+
"provisions": [
|
|
202
|
+
{
|
|
203
|
+
"name": "provision",
|
|
204
|
+
"type": "FetchResponse",
|
|
205
|
+
"typeExpanded": "{ bodyUsed: boolean; headers: [string, string][]; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; body: unknown; }",
|
|
206
|
+
"description": "Response payload on success, or error payload on failure. Success shape: <code>{ status, statusText, ok, headers, url, redirected, bodyUsed, type, body }</code>. Failure shape is either that same response shape (server responded with an error status) or <code>{ message, stack }</code> (request never completed). Not reflected as an attribute.",
|
|
207
|
+
"fieldName": "provision",
|
|
208
|
+
"inheritedFrom": "@excom/fetchable-element"
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
"exportedFiles": {}
|
|
214
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`refetch view > loads then refetches > complexity 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"attributeRuns": 11,
|
|
6
|
+
"closest": 22,
|
|
7
|
+
"getVar": 0,
|
|
8
|
+
"importNode": 0,
|
|
9
|
+
"listenerRuns": 0,
|
|
10
|
+
"matches": 5,
|
|
11
|
+
"parentElement": 12,
|
|
12
|
+
"quarkRuns": 2,
|
|
13
|
+
"queryScopeCost": 42,
|
|
14
|
+
"querySelectorAll": 3,
|
|
15
|
+
"removeAttribute": 2,
|
|
16
|
+
"ruleRuns": 5,
|
|
17
|
+
"schedulePaint": 11,
|
|
18
|
+
"setAttribute": 2,
|
|
19
|
+
"setVar": 1,
|
|
20
|
+
"textContent": 0,
|
|
21
|
+
"variableRuns": 1,
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`simple view > renders the fetched todo > complexity 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"attributeRuns": 0,
|
|
6
|
+
"closest": 0,
|
|
7
|
+
"getVar": 0,
|
|
8
|
+
"importNode": 0,
|
|
9
|
+
"listenerRuns": 0,
|
|
10
|
+
"matches": 0,
|
|
11
|
+
"parentElement": 0,
|
|
12
|
+
"quarkRuns": 0,
|
|
13
|
+
"queryScopeCost": 0,
|
|
14
|
+
"querySelectorAll": 0,
|
|
15
|
+
"removeAttribute": 0,
|
|
16
|
+
"ruleRuns": 0,
|
|
17
|
+
"schedulePaint": 0,
|
|
18
|
+
"setAttribute": 0,
|
|
19
|
+
"setVar": 0,
|
|
20
|
+
"textContent": 0,
|
|
21
|
+
"variableRuns": 0,
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
fixture,
|
|
7
|
+
it,
|
|
8
|
+
spyFetch,
|
|
9
|
+
vi,
|
|
10
|
+
waitForEvent,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
import { KitLogger } from "@excom/kit-logger";
|
|
13
|
+
import "../../index";
|
|
14
|
+
|
|
15
|
+
const fetchStubs = {
|
|
16
|
+
success: () => ({
|
|
17
|
+
status: 200,
|
|
18
|
+
body: JSON.stringify({ foo: "bar" }),
|
|
19
|
+
}),
|
|
20
|
+
error: () => ({
|
|
21
|
+
status: 500,
|
|
22
|
+
body: JSON.stringify({ error: "bar" }),
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("provider-fetch", () => {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
document.body.innerHTML = "";
|
|
29
|
+
vi.restoreAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("sets state attributes and fetches data", async () => {
|
|
33
|
+
const providerFetch = fixture<HTMLProviderFetchElement>(
|
|
34
|
+
`<provider-fetch></provider-fetch>`,
|
|
35
|
+
);
|
|
36
|
+
const fetchSpy = spyFetch(fetchStubs.success() as any);
|
|
37
|
+
|
|
38
|
+
providerFetch.addEventListener("provider-fetch-loading", () => {
|
|
39
|
+
expect(providerFetch).dom.to.equalTag(
|
|
40
|
+
`<provider-fetch api-url="/api/test" is-loading></provider-fetch>`,
|
|
41
|
+
);
|
|
42
|
+
expect(providerFetch.provision).toEqual(null);
|
|
43
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
44
|
+
"http://localhost:3000/api/test",
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
method: "GET",
|
|
47
|
+
credentials: "include",
|
|
48
|
+
headers: {
|
|
49
|
+
Accept: "application/json",
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
providerFetch.addEventListener("provider-fetch-success", () => {
|
|
56
|
+
expect(providerFetch).dom.to.equalTag(
|
|
57
|
+
`<provider-fetch api-url="/api/test" is-success></provider-fetch>`,
|
|
58
|
+
);
|
|
59
|
+
expect(providerFetch.provision?.body).toEqual({ foo: "bar" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await waitForEvent(providerFetch, "provider-fetch-success", () => {
|
|
63
|
+
providerFetch.setAttribute("api-url", "/api/test");
|
|
64
|
+
});
|
|
65
|
+
expect.assertions(5);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("sets state attributes and fetches data with formRef", async () => {
|
|
69
|
+
const providerFetch = fixture<HTMLProviderFetchElement>(
|
|
70
|
+
`<provider-fetch form-ref="form" api-method="post">
|
|
71
|
+
<form>
|
|
72
|
+
<input type="text" name="name" value="John Doe">
|
|
73
|
+
<input type="email" name="email" value="j@doe.com">
|
|
74
|
+
<input type="number" name="age" value="50">
|
|
75
|
+
<input type="range" name="volume" min="0" max="100" value="20">
|
|
76
|
+
<input type="tel" name="phone" value="123-456-7890">
|
|
77
|
+
<input type="url" name="website" value="https://example.com">
|
|
78
|
+
</form>
|
|
79
|
+
</provider-fetch>`,
|
|
80
|
+
);
|
|
81
|
+
const fetchSpy = spyFetch(fetchStubs.success() as any);
|
|
82
|
+
|
|
83
|
+
providerFetch.addEventListener("provider-fetch-loading", () => {
|
|
84
|
+
expect(providerFetch).dom.to.equalTag(
|
|
85
|
+
`<provider-fetch is-loading form-ref="form" api-method="post" api-url="/api/test"></provider-fetch>`,
|
|
86
|
+
);
|
|
87
|
+
expect(providerFetch.provision).toEqual(null);
|
|
88
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
89
|
+
"http://localhost:3000/api/test",
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
method: "POST",
|
|
92
|
+
credentials: "include",
|
|
93
|
+
headers: {
|
|
94
|
+
Accept: "application/json",
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
},
|
|
97
|
+
body: expect.toSatisfy((body: string) => {
|
|
98
|
+
expect(JSON.parse(body)).toEqual(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
name: "John Doe",
|
|
101
|
+
email: "j@doe.com",
|
|
102
|
+
age: 50,
|
|
103
|
+
volume: 20,
|
|
104
|
+
phone: "123-456-7890",
|
|
105
|
+
website: "https://example.com",
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
return true;
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
providerFetch.addEventListener("provider-fetch-success", () => {
|
|
115
|
+
expect(providerFetch).dom.to.equalTag(
|
|
116
|
+
`<provider-fetch is-success form-ref="form" api-method="post" api-url="/api/test"></provider-fetch>`,
|
|
117
|
+
);
|
|
118
|
+
expect(providerFetch.provision?.body).toEqual({ foo: "bar" });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await waitForEvent(providerFetch, "provider-fetch-success", () => {
|
|
122
|
+
providerFetch.setAttribute("api-url", "/api/test");
|
|
123
|
+
});
|
|
124
|
+
expect.assertions(6);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("handles fetch error", async () => {
|
|
128
|
+
const providerFetch = fixture<HTMLProviderFetchElement>(
|
|
129
|
+
`<provider-fetch></provider-fetch>`,
|
|
130
|
+
);
|
|
131
|
+
const fetchSpy = spyFetch(fetchStubs.error as any);
|
|
132
|
+
|
|
133
|
+
KitLogger.suppress();
|
|
134
|
+
providerFetch.addEventListener("provider-fetch-loading", () => {
|
|
135
|
+
expect(providerFetch).dom.to.equalTag(
|
|
136
|
+
`<provider-fetch api-url="/api/test" is-loading></provider-fetch>`,
|
|
137
|
+
);
|
|
138
|
+
expect(providerFetch.provision).toEqual(null);
|
|
139
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
140
|
+
"http://localhost:3000/api/test",
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
method: "GET",
|
|
143
|
+
credentials: "include",
|
|
144
|
+
headers: {
|
|
145
|
+
Accept: "application/json",
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
providerFetch.addEventListener("provider-fetch-error", () => {
|
|
152
|
+
KitLogger.unsuppress();
|
|
153
|
+
expect(providerFetch).dom.to.equalTag(
|
|
154
|
+
`<provider-fetch api-url="/api/test" is-error></provider-fetch>`,
|
|
155
|
+
);
|
|
156
|
+
expect(providerFetch.provision?.body).toEqual({ error: "bar" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await waitForEvent(providerFetch, "provider-fetch-error", () => {
|
|
160
|
+
providerFetch.setAttribute("api-url", "/api/test");
|
|
161
|
+
});
|
|
162
|
+
expect.assertions(5);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("interpolates params from its own attributes", async () => {
|
|
166
|
+
const providerFetch = fixture<HTMLProviderFetchElement>(
|
|
167
|
+
`<provider-fetch></provider-fetch>`,
|
|
168
|
+
);
|
|
169
|
+
const fetchSpy = spyFetch(fetchStubs.success as any);
|
|
170
|
+
|
|
171
|
+
providerFetch.addEventListener("provider-fetch-loading", () => {
|
|
172
|
+
expect(providerFetch).dom.to.equalTag(
|
|
173
|
+
`<provider-fetch api-url="/api/users/123" is-loading></provider-fetch>`,
|
|
174
|
+
);
|
|
175
|
+
expect(providerFetch.provision).toEqual(null);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
providerFetch.addEventListener("provider-fetch-success", () => {
|
|
179
|
+
expect(providerFetch).dom.to.equalTag(
|
|
180
|
+
`<provider-fetch api-url="/api/users/123" is-success></provider-fetch>`,
|
|
181
|
+
);
|
|
182
|
+
expect(providerFetch.provision?.body).toEqual({ foo: "bar" });
|
|
183
|
+
const calledUrl = fetchSpy.mock.calls[0][0] as string;
|
|
184
|
+
expect(calledUrl).toBe("http://localhost:3000/api/users/123");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await waitForEvent(providerFetch, "provider-fetch-success", () => {
|
|
188
|
+
providerFetch.setAttribute("api-url", "/api/users/123");
|
|
189
|
+
});
|
|
190
|
+
expect.assertions(5);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("aborts existing fetch when new one is triggered", async () => {
|
|
194
|
+
const providerFetch = fixture<HTMLProviderFetchElement>(
|
|
195
|
+
`<provider-fetch></provider-fetch>`,
|
|
196
|
+
);
|
|
197
|
+
spyFetch(fetchStubs.success as any);
|
|
198
|
+
|
|
199
|
+
providerFetch.addEventListener("provider-fetch-loading", () => {
|
|
200
|
+
expect(providerFetch).dom.to.equalTag(
|
|
201
|
+
`<provider-fetch api-url="/api/test" is-loading></provider-fetch>`,
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
let secondFetch = false;
|
|
205
|
+
providerFetch.addEventListener("provider-fetch-success", () => {
|
|
206
|
+
expect(secondFetch).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
await waitForEvent(providerFetch, "provider-fetch-success", () => {
|
|
209
|
+
providerFetch.setAttribute("api-url", "/api/test");
|
|
210
|
+
secondFetch = true;
|
|
211
|
+
invokeCommand(providerFetch, "--fetch");
|
|
212
|
+
});
|
|
213
|
+
expect.assertions(3);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import "@excom/event-handler";
|
|
3
|
+
import "@excom/quark-sheet";
|
|
4
|
+
import "../../index";
|
|
5
|
+
import {
|
|
6
|
+
afterEach,
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
it,
|
|
10
|
+
spyFetch,
|
|
11
|
+
vi,
|
|
12
|
+
waitForEvent,
|
|
13
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
14
|
+
import {
|
|
15
|
+
expectComplexity,
|
|
16
|
+
flush,
|
|
17
|
+
measureComplexity,
|
|
18
|
+
mountView,
|
|
19
|
+
readDemo,
|
|
20
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
21
|
+
|
|
22
|
+
describe("refetch view", () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.innerHTML = "";
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("loads then refetches", async () => {
|
|
29
|
+
spyFetch({
|
|
30
|
+
status: 200,
|
|
31
|
+
body: JSON.stringify({ id: 1, title: "Once" }),
|
|
32
|
+
});
|
|
33
|
+
const { root, quark } = await mountView(readDemo(import.meta.url, "refetch"));
|
|
34
|
+
const provider = root.querySelector("provider-fetch")!;
|
|
35
|
+
if (!provider.hasAttribute("is-success")) {
|
|
36
|
+
await waitForEvent(provider, "provider-fetch-success");
|
|
37
|
+
}
|
|
38
|
+
await flush();
|
|
39
|
+
expect([...root.querySelectorAll("li")].some((li) => /Once/.test(li.textContent || ""))).toBe(
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
42
|
+
const button = root.querySelector<HTMLButtonElement>("button")!;
|
|
43
|
+
expect(button.getAttribute("command")).toBe("--fetch");
|
|
44
|
+
expect(button.getAttribute("commandfor")).toBe(provider.id);
|
|
45
|
+
const meter = measureComplexity(quark!);
|
|
46
|
+
// happy-dom has no Command API: dispatch what the button would have
|
|
47
|
+
await waitForEvent(provider, "provider-fetch-success", () => {
|
|
48
|
+
invokeCommand(provider, "--fetch", button);
|
|
49
|
+
});
|
|
50
|
+
await flush();
|
|
51
|
+
const budget = meter.take();
|
|
52
|
+
meter.stop();
|
|
53
|
+
expect(provider.hasAttribute("is-success")).toBe(true);
|
|
54
|
+
expectComplexity(budget);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "@excom/quark-sheet";
|
|
2
|
+
import "../../index";
|
|
3
|
+
import {
|
|
4
|
+
afterEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
it,
|
|
8
|
+
spyFetch,
|
|
9
|
+
waitForEvent,
|
|
10
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
11
|
+
import {
|
|
12
|
+
expectComplexity,
|
|
13
|
+
flush,
|
|
14
|
+
measureComplexity,
|
|
15
|
+
mountView,
|
|
16
|
+
readDemo,
|
|
17
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
18
|
+
|
|
19
|
+
describe("simple view", () => {
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
document.body.innerHTML = "";
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("renders the fetched todo", async () => {
|
|
25
|
+
spyFetch({
|
|
26
|
+
status: 200,
|
|
27
|
+
body: JSON.stringify({ id: 7, title: "Ship it", completed: false }),
|
|
28
|
+
});
|
|
29
|
+
const { root, quark } = await mountView(readDemo(import.meta.url, "simple"));
|
|
30
|
+
const provider = root.querySelector("provider-fetch")!;
|
|
31
|
+
if (!provider.hasAttribute("is-success")) {
|
|
32
|
+
await waitForEvent(provider, "provider-fetch-success");
|
|
33
|
+
await flush();
|
|
34
|
+
}
|
|
35
|
+
const meter = measureComplexity(quark!);
|
|
36
|
+
await flush();
|
|
37
|
+
const budget = meter.take();
|
|
38
|
+
meter.stop();
|
|
39
|
+
expect(root.querySelector("h4")?.textContent).toMatch(/Todo #7: Ship it/);
|
|
40
|
+
expect(root.querySelector("span")?.textContent).toMatch(/Completed: false/);
|
|
41
|
+
expectComplexity(budget);
|
|
42
|
+
});
|
|
43
|
+
});
|