@loadbare/app 0.20.0 → 0.22.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.md +18 -0
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +8 -3
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/build.d.ts +62 -0
- package/dist/build/build.d.ts.map +1 -0
- package/dist/build/build.js +148 -0
- package/dist/build/build.js.map +1 -0
- package/dist/build/cli.d.ts +6 -1
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +25 -78
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.d.ts.map +1 -1
- package/dist/build/declared.js +3 -0
- package/dist/build/declared.js.map +1 -1
- package/dist/build/skills-cli.d.ts +2 -1
- package/dist/build/skills-cli.d.ts.map +1 -1
- package/dist/build/skills-cli.js +8 -1
- package/dist/build/skills-cli.js.map +1 -1
- package/dist/build/skills.d.ts +2 -0
- package/dist/build/skills.d.ts.map +1 -1
- package/dist/build/skills.js +2 -0
- package/dist/build/skills.js.map +1 -1
- package/dist/core/lb-constants.d.ts +14 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +25 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/dev/lb-dev.d.ts +128 -0
- package/dist/dev/lb-dev.d.ts.map +1 -0
- package/dist/dev/lb-dev.js +289 -0
- package/dist/dev/lb-dev.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +10 -2
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +8 -2
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +127 -19
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/comparison.md +4 -3
- package/docs/reference/builder.md +12 -4
- package/docs/reference/chrome.md +52 -1
- package/docs/reference/data-binding.md +27 -12
- package/docs/reference/dev-server.md +171 -0
- package/docs/reference/names.md +14 -1
- package/docs/reference/overview.md +1 -0
- package/docs/reference/server.md +8 -3
- package/docs/reference/widgets.md +10 -277
- package/docs/testing.md +2 -1
- package/docs/tutorials/000-getting-started.md +1 -1
- package/docs/tutorials/040-displaying-data.md +1 -1
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +4 -2
- package/skills/loadbare-app/SKILL.md +30 -12
- package/skills/loadbare-app/references/builder.md +12 -4
- package/skills/loadbare-app/references/chrome.md +52 -1
- package/skills/loadbare-app/references/data-binding.md +27 -12
- package/skills/loadbare-app/references/dev-server.md +171 -0
- package/skills/loadbare-app/references/names.md +14 -1
- package/skills/loadbare-app/references/overview.md +1 -0
- package/skills/loadbare-app/references/server.md +8 -3
- package/skills/loadbare-app/references/widgets.md +10 -277
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import { ATTR_COLUMN, ATTR_COLUMN_VALUE, ATTR_KEY_VALUE, ATTR_PAGE, ATTR_PAGE_TITLE, ATTR_QUERY, ATTR_REQUEST, ATTR_REQUEST_ERROR, ATTR_REQUEST_PENDING, ATTR_REQUEST_REFUSED, ATTR_ROW_EDITING, ATTR_SELECT, ATTR_URL_LINK, ATTR_URL_PUSH, ATTR_URL_UNKNOWN, CHOICES_QUERY, COMMAND_EDIT, COMMAND_NEW, COMMAND_RELEASE, COMMAND_SORT, HUB_TAG_NAME, KIND_ROW, LB_DONE_EVENT_NAME, LB_ENDPOINT, LB_EVENT_NAME, LB_REFUSAL_STATUS, LB_REQUEST_TIMEOUT_MS, LB_COMMAND_PREFIX, LB_RESERVED_PREFIX, LIVE_ROW, REQUEST_ROW_DELETE, REQUEST_ROW_INSERT, REQUEST_ROW_UPDATE, URL_COLUMN_PAGE_LABEL, URL_COLUMN_PAGE_UNKNOWN, URL_COLUMN_PATH, URL_QUERY, } from "../core/lb-constants.js";
|
|
2
|
+
import { ATTR_COLUMN, ATTR_COLUMN_VALUE, ATTR_KEY_VALUE, ATTR_PAGE, ATTR_PAGE_TITLE, ATTR_QUERY, ATTR_REQUEST, ATTR_REQUEST_ERROR, ATTR_REQUEST_PENDING, ATTR_REQUEST_REFUSED, ATTR_ROW_EDITING, ATTR_SELECT, ATTR_URL_LINK, ATTR_URL_PUSH, ATTR_URL_UNKNOWN, CHOICES_QUERY, COMMAND_EDIT, COMMAND_NEW, COMMAND_RELEASE, COMMAND_SORT, HUB_COLUMN_AS_OF, HUB_COLUMN_ERROR, HUB_COLUMN_LOAD_STATE, HUB_COLUMN_PENDING, HUB_COLUMN_SERVER_STATE, HUB_ERROR_NETWORK, HUB_ERROR_TIMEOUT, HUB_LOAD_FAILED, HUB_LOAD_LOADED, HUB_LOAD_LOADING, HUB_QUERY, HUB_SERVER_FAILING, HUB_SERVER_REACHABLE, HUB_SERVER_UNREACHABLE, HUB_TAG_NAME, KIND_ROW, LB_DONE_EVENT_NAME, LB_ENDPOINT, LB_EVENT_NAME, LB_REFUSAL_STATUS, LB_REQUEST_TIMEOUT_MS, LB_COMMAND_PREFIX, LB_RESERVED_PREFIX, LIVE_ROW, REQUEST_ROW_DELETE, REQUEST_ROW_INSERT, REQUEST_ROW_UPDATE, URL_COLUMN_PAGE_LABEL, URL_COLUMN_PAGE_UNKNOWN, URL_COLUMN_PATH, URL_QUERY, } from "../core/lb-constants.js";
|
|
3
3
|
import { isRowRequest, nextLocation, pageNameFor, } from "../core/lb-types.js";
|
|
4
4
|
import { applyResponse, applyRow, blankRow, focusIn, gatherInto, select, forget, isControl, isFormAssociated, keptAnswer, resort, within, } from "./lb-apply.js";
|
|
5
5
|
import { attachPlaced, detachRemoved, placed, requestDone, upgradeAbsent, } from "./lb-lifecycle.js";
|
|
@@ -143,6 +143,18 @@ class RefusedError extends Error {
|
|
|
143
143
|
this.name = "RefusedError";
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* A round trip that failed, and why, in the words `lb-error` uses: the
|
|
148
|
+
* network, the deadline, or the HTTP status the server answered with.
|
|
149
|
+
*/
|
|
150
|
+
class RoundTripError extends Error {
|
|
151
|
+
reason;
|
|
152
|
+
constructor(reason, message) {
|
|
153
|
+
super(message);
|
|
154
|
+
this.reason = reason;
|
|
155
|
+
this.name = "RoundTripError";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
146
158
|
const shown = [];
|
|
147
159
|
function validates(el) {
|
|
148
160
|
const v = el;
|
|
@@ -390,19 +402,28 @@ async function fetchItems(page, request) {
|
|
|
390
402
|
const controller = new AbortController();
|
|
391
403
|
const timeout = setTimeout(() => controller.abort(), LB_REQUEST_TIMEOUT_MS);
|
|
392
404
|
try {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
405
|
+
let res;
|
|
406
|
+
try {
|
|
407
|
+
// An empty body is how the hub asks for the page's queries.
|
|
408
|
+
res = await fetch(url, {
|
|
409
|
+
method: "POST",
|
|
410
|
+
headers: { "content-type": "application/json" },
|
|
411
|
+
body: JSON.stringify(request ?? {}),
|
|
412
|
+
signal: controller.signal,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
const reason = controller.signal.aborted
|
|
417
|
+
? HUB_ERROR_TIMEOUT
|
|
418
|
+
: HUB_ERROR_NETWORK;
|
|
419
|
+
throw new RoundTripError(reason, `lb-hub: ${reason === HUB_ERROR_TIMEOUT ? "no answer in time" : String(err)} for ${url}`);
|
|
420
|
+
}
|
|
400
421
|
if (res.status === LB_REFUSAL_STATUS) {
|
|
401
422
|
const body = (await res.json().catch(() => ({})));
|
|
402
423
|
throw new RefusedError(body.refusal ?? {});
|
|
403
424
|
}
|
|
404
425
|
if (!res.ok) {
|
|
405
|
-
throw new
|
|
426
|
+
throw new RoundTripError(String(res.status), `lb-hub: server responded ${res.status} for ${url}`);
|
|
406
427
|
}
|
|
407
428
|
return (await res.json());
|
|
408
429
|
}
|
|
@@ -485,6 +506,16 @@ function point(form, row, key, live) {
|
|
|
485
506
|
* Let a pointed form go: empty it and drop its key. A form inside a
|
|
486
507
|
* `<dialog>` closes it, and the dialog's `close` finds nothing left to do.
|
|
487
508
|
*/
|
|
509
|
+
/**
|
|
510
|
+
* Whether a form's controls show a row on record: one whose key is not
|
|
511
|
+
* empty, as a form over a `row` query does once the answer to its insert
|
|
512
|
+
* lands the new row on it.
|
|
513
|
+
*/
|
|
514
|
+
function onRecord(form) {
|
|
515
|
+
const control = controlsOf(form)[0];
|
|
516
|
+
const query = control && queryOf(control);
|
|
517
|
+
return !!query && !!keyFrom(control, query);
|
|
518
|
+
}
|
|
488
519
|
function release(form) {
|
|
489
520
|
if (!pointed.has(form))
|
|
490
521
|
return;
|
|
@@ -544,6 +575,12 @@ class LbHub extends HTMLElement {
|
|
|
544
575
|
* lands, so each custom element in it is filled before it is attached.
|
|
545
576
|
*/
|
|
546
577
|
unplaced = [];
|
|
578
|
+
/** The hub's own state, as the query `lb-hub` serves it. */
|
|
579
|
+
trips = 0;
|
|
580
|
+
loadState = HUB_LOAD_LOADING;
|
|
581
|
+
serverState = "";
|
|
582
|
+
error = "";
|
|
583
|
+
asOf = "";
|
|
547
584
|
connectedCallback() {
|
|
548
585
|
this.main = this.querySelector("main");
|
|
549
586
|
// Completed in the capture phase, on the way down, so every ancestor
|
|
@@ -750,6 +787,11 @@ class LbHub extends HTMLElement {
|
|
|
750
787
|
withdraw(refused);
|
|
751
788
|
}
|
|
752
789
|
const at = here();
|
|
790
|
+
// A form pointed at a live row finishes an edit begun in that row, so
|
|
791
|
+
// its request is sent from inside the row's list. The row is the one it
|
|
792
|
+
// was pointed at when sent.
|
|
793
|
+
const saving = savedFrom.get(request);
|
|
794
|
+
const within = (saving && pointed.get(saving)) || origin;
|
|
753
795
|
const items = await this.roundTrip(this.page, request, origin);
|
|
754
796
|
if (items instanceof RefusedError)
|
|
755
797
|
this.refused(request, items, origin);
|
|
@@ -765,7 +807,7 @@ class LbHub extends HTMLElement {
|
|
|
765
807
|
const load = items.filter((item) => item !== url);
|
|
766
808
|
landed = load;
|
|
767
809
|
if (url === undefined) {
|
|
768
|
-
applyResponse(this, load, { requested: true, origin });
|
|
810
|
+
applyResponse(this, load, { requested: true, origin: within });
|
|
769
811
|
}
|
|
770
812
|
else
|
|
771
813
|
landed = await this.follow(url, load, origin);
|
|
@@ -775,12 +817,14 @@ class LbHub extends HTMLElement {
|
|
|
775
817
|
for (const box of selectorsFor(this, request.query))
|
|
776
818
|
select(box, false);
|
|
777
819
|
}
|
|
778
|
-
// A pointed form has done what it was pointed for, and lets go.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
820
|
+
// A pointed form has done what it was pointed for, and lets go. A form
|
|
821
|
+
// an insert was gathered from starts over, unless the answer landed the
|
|
822
|
+
// new row on it.
|
|
823
|
+
const inserted = insertedFrom.get(request);
|
|
824
|
+
if (saving)
|
|
825
|
+
release(saving);
|
|
826
|
+
else if (inserted && !onRecord(inserted))
|
|
827
|
+
inserted.reset();
|
|
784
828
|
done(origin, request, landed);
|
|
785
829
|
}
|
|
786
830
|
/**
|
|
@@ -813,26 +857,84 @@ class LbHub extends HTMLElement {
|
|
|
813
857
|
* flight, and `lb-request-error` when it fails. The error on failure.
|
|
814
858
|
*/
|
|
815
859
|
async roundTrip(page, request, origin) {
|
|
860
|
+
// A page load says how the page showing got its data, unless the URL
|
|
861
|
+
// has moved on by the time it returns, and a later load says instead.
|
|
862
|
+
const load = request === undefined;
|
|
863
|
+
const at = here();
|
|
816
864
|
pending(origin, true);
|
|
865
|
+
this.trips++;
|
|
866
|
+
if (load)
|
|
867
|
+
this.loadState = HUB_LOAD_LOADING;
|
|
868
|
+
this.landHub();
|
|
817
869
|
try {
|
|
818
|
-
|
|
870
|
+
const items = await fetchItems(page, request);
|
|
871
|
+
this.serverState = HUB_SERVER_REACHABLE;
|
|
872
|
+
this.error = "";
|
|
873
|
+
this.asOf = new Date().toISOString();
|
|
874
|
+
if (load && here() === at)
|
|
875
|
+
this.loadState = HUB_LOAD_LOADED;
|
|
876
|
+
return items;
|
|
819
877
|
}
|
|
820
878
|
catch (err) {
|
|
821
879
|
// A refusal is the handler's answer, not a failure: the request's
|
|
822
880
|
// sender shows it, and nothing is reported.
|
|
823
|
-
if (err instanceof RefusedError)
|
|
881
|
+
if (err instanceof RefusedError) {
|
|
882
|
+
this.serverState = HUB_SERVER_REACHABLE;
|
|
883
|
+
this.error = "";
|
|
824
884
|
return err;
|
|
885
|
+
}
|
|
825
886
|
console.error(`lb-hub: request failed for '${page}'`, err);
|
|
826
887
|
origin?.setAttribute(ATTR_REQUEST_ERROR, "");
|
|
888
|
+
// A server that answered with an error is there and failing; one that
|
|
889
|
+
// never answered is out of reach.
|
|
890
|
+
this.error = err instanceof RoundTripError ? err.reason : HUB_ERROR_NETWORK;
|
|
891
|
+
this.serverState =
|
|
892
|
+
err instanceof RoundTripError && /^\d+$/.test(err.reason)
|
|
893
|
+
? HUB_SERVER_FAILING
|
|
894
|
+
: HUB_SERVER_UNREACHABLE;
|
|
895
|
+
if (load && here() === at)
|
|
896
|
+
this.loadState = HUB_LOAD_FAILED;
|
|
827
897
|
return err instanceof Error ? err : new Error(String(err));
|
|
828
898
|
}
|
|
829
899
|
finally {
|
|
830
900
|
pending(origin, false);
|
|
901
|
+
this.trips--;
|
|
902
|
+
this.landHub();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* The hub's own query: one row, keyed by the path, holding how the page's
|
|
907
|
+
* data and the server stand. The same columns are stamped on the hub
|
|
908
|
+
* itself, so a stylesheet can reach the whole document without the
|
|
909
|
+
* markup naming the query. An empty one is not stamped at all.
|
|
910
|
+
*/
|
|
911
|
+
landHub() {
|
|
912
|
+
const row = {
|
|
913
|
+
[URL_COLUMN_PATH]: location.pathname,
|
|
914
|
+
[HUB_COLUMN_LOAD_STATE]: this.loadState,
|
|
915
|
+
[HUB_COLUMN_SERVER_STATE]: this.serverState,
|
|
916
|
+
[HUB_COLUMN_ERROR]: this.error,
|
|
917
|
+
[HUB_COLUMN_PENDING]: this.trips,
|
|
918
|
+
[HUB_COLUMN_AS_OF]: this.asOf,
|
|
919
|
+
};
|
|
920
|
+
for (const [name, value] of Object.entries(row)) {
|
|
921
|
+
if (name === URL_COLUMN_PATH)
|
|
922
|
+
continue;
|
|
923
|
+
if (value === "")
|
|
924
|
+
this.removeAttribute(name);
|
|
925
|
+
else
|
|
926
|
+
this.setAttribute(name, String(value));
|
|
831
927
|
}
|
|
928
|
+
if (!this.querySelector(`[${ATTR_QUERY}="${HUB_QUERY}"]`))
|
|
929
|
+
return;
|
|
930
|
+
applyResponse(this, [
|
|
931
|
+
{ query: HUB_QUERY, kind: KIND_ROW, key: URL_COLUMN_PATH, row },
|
|
932
|
+
]);
|
|
832
933
|
}
|
|
833
934
|
/**
|
|
834
935
|
* Answer a request for a query the hub serves, with no round trip. The
|
|
835
|
-
* hub serves `lb-url` and `lb-choices`, and updates them.
|
|
936
|
+
* hub serves `lb-url` and `lb-choices`, and updates them. It serves
|
|
937
|
+
* `lb-hub` too, which nothing but the hub writes.
|
|
836
938
|
*/
|
|
837
939
|
async answer(request, origin) {
|
|
838
940
|
if (request.query === CHOICES_QUERY && request.name === REQUEST_ROW_UPDATE) {
|
|
@@ -927,6 +1029,9 @@ class LbHub extends HTMLElement {
|
|
|
927
1029
|
return again instanceof Error ? [item] : [item, ...again];
|
|
928
1030
|
}
|
|
929
1031
|
const template = this.arrive();
|
|
1032
|
+
// The request's answer carried the page's queries for where it moved.
|
|
1033
|
+
this.loadState = HUB_LOAD_LOADED;
|
|
1034
|
+
this.landHub();
|
|
930
1035
|
this.place();
|
|
931
1036
|
if (template)
|
|
932
1037
|
applyResponse(this, load);
|
|
@@ -954,6 +1059,9 @@ class LbHub extends HTMLElement {
|
|
|
954
1059
|
async navigate(origin) {
|
|
955
1060
|
const template = this.arrive();
|
|
956
1061
|
if (!template) {
|
|
1062
|
+
// No page, so nothing to load: the unknown page is what shows.
|
|
1063
|
+
this.loadState = HUB_LOAD_LOADED;
|
|
1064
|
+
this.landHub();
|
|
957
1065
|
this.place();
|
|
958
1066
|
attachPlaced();
|
|
959
1067
|
return [];
|