@loadbare/app 0.5.5 → 0.6.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.
Files changed (81) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +80 -7
  5. package/dist/build/cli.d.ts +2 -2
  6. package/dist/build/cli.js +2 -2
  7. package/dist/build/expand.d.ts.map +1 -1
  8. package/dist/build/expand.js +30 -34
  9. package/dist/build/locations.d.ts +3 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +15 -3
  12. package/dist/build/origins.d.ts +0 -13
  13. package/dist/build/origins.d.ts.map +1 -1
  14. package/dist/build/origins.js +32 -8
  15. package/dist/build/pages.d.ts +3 -3
  16. package/dist/build/pages.d.ts.map +1 -1
  17. package/dist/build/pages.js +9 -7
  18. package/dist/core/lb-constants.d.ts +17 -12
  19. package/dist/core/lb-constants.d.ts.map +1 -1
  20. package/dist/core/lb-constants.js +106 -43
  21. package/dist/core/lb-types.d.ts +103 -62
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +11 -3
  24. package/dist/hub/lb-apply.d.ts +18 -4
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +184 -34
  27. package/dist/hub/lb-hub.browser.d.ts +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  29. package/dist/hub/lb-hub.browser.js +165 -83
  30. package/dist/server/lb-express.d.ts +7 -4
  31. package/dist/server/lb-express.d.ts.map +1 -1
  32. package/dist/server/lb-express.js +45 -33
  33. package/dist/server/lb-server.d.ts +67 -45
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +55 -18
  36. package/dist/tests/assemble.test.js +154 -2
  37. package/dist/tests/expand.test.js +30 -3
  38. package/dist/tests/helpers/hub.d.ts +73 -0
  39. package/dist/tests/helpers/hub.d.ts.map +1 -0
  40. package/dist/tests/helpers/hub.js +151 -0
  41. package/dist/tests/lb-apply.test.js +86 -62
  42. package/dist/tests/lb-express.test.d.ts +1 -1
  43. package/dist/tests/lb-express.test.js +43 -38
  44. package/dist/tests/lb-hub.test.d.ts +14 -0
  45. package/dist/tests/lb-hub.test.d.ts.map +1 -0
  46. package/dist/tests/lb-hub.test.js +319 -0
  47. package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
  48. package/dist/tests/lb-list.test.d.ts.map +1 -0
  49. package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
  50. package/dist/tests/lb-server.test.js +151 -100
  51. package/dist/tests/origins.test.js +19 -1
  52. package/dist/tests/pages.test.d.ts +1 -1
  53. package/dist/tests/pages.test.js +64 -14
  54. package/docs/TECHREF-1.0.md +1000 -0
  55. package/docs/reference/builder.md +4 -4
  56. package/docs/reference/chrome.md +56 -5
  57. package/docs/reference/custom-elements.md +59 -36
  58. package/docs/reference/data-binding.md +142 -84
  59. package/docs/reference/overview.md +1 -1
  60. package/docs/reference/page-files.md +64 -49
  61. package/docs/reference/server.md +7 -6
  62. package/docs/reference/widgets.md +43 -32
  63. package/docs/roadmap.md +68 -22
  64. package/docs/testing.md +47 -17
  65. package/docs/theory.md +2 -2
  66. package/docs/tutorials/010-pages-and-navigation.md +14 -8
  67. package/docs/tutorials/040-displaying-data.md +9 -9
  68. package/docs/tutorials/050-actions.md +5 -5
  69. package/docs/tutorials/060-custom-element-code.md +1 -1
  70. package/docs/tutorials/065-conditional-rendering.md +4 -4
  71. package/docs/tutorials/070-displaying-a-list.md +24 -47
  72. package/docs/tutorials/072-inserting-into-a-list.md +17 -17
  73. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  74. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  75. package/docs/tutorials/080-widget-requests.md +27 -23
  76. package/docs/tutorials/090-using-widget-libraries.md +23 -1
  77. package/package.json +1 -2
  78. package/dist/hub/lb-rows.d.ts +0 -18
  79. package/dist/hub/lb-rows.d.ts.map +0 -1
  80. package/dist/hub/lb-rows.js +0 -106
  81. package/dist/tests/lb-rows.test.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,aAAa,CAAC;AACrC,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,SAAS,YAAY,CAAC;AACnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAMrC,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,SAAS,YAAY,CAAC;AAOnC,eAAO,MAAM,cAAc,cAAc,CAAC;AAK1C,eAAO,MAAM,aAAa,eAAe,CAAC;AAM1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAKvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAOvC,eAAO,MAAM,WAAW,cAAc,CAAC;AAMvC,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAQ5C,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAQnD,eAAO,MAAM,YAAY,WAAW,CAAC;AAQrC,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAO9C,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAG1C,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAIzC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
1
+ {"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAQ7C,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAOrC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAK7C,eAAO,MAAM,aAAa,eAAe,CAAC;AAS1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAYvC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAMxC,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAKjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAOjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAMjD,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAInD,eAAO,MAAM,UAAU,gFAKb,CAAC;AAMX,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAM3C,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAKvC,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,YAAY,aAAa,CAAC;AAUvC,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAQnD,eAAO,MAAM,YAAY,WAAW,CAAC;AAQrC,eAAO,MAAM,YAAY,eAAe,CAAC;AAOzC,eAAO,MAAM,UAAU,aAAa,CAAC;AAMrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
@@ -2,49 +2,93 @@
2
2
  // literal — not a widget, not a page host, not a test. See
3
3
  // docs/reference/data-binding.md.
4
4
  // --- Binding (docs/reference/data-binding.md) ---------------------------
5
- export const ATTR_QUERY = "lb-query";
5
+ // One containment ladder: a list holds rows, a row holds cells. Both scope
6
+ // attributes take a name the server declares, and a nested one of either
7
+ // kind begins a new scope.
8
+ // Scopes DOM children to a named set of rows.
9
+ export const ATTR_LIST = "lb-list";
10
+ // Scopes DOM children to one named row.
11
+ //
12
+ // A single-row scope is read-only. Every write operation needs a key, and a
13
+ // key exists only on a live row the hub stamped inside a list, so a declared
14
+ // action is the only thing such a scope can send. An application that wants
15
+ // a writable single row declares a list that answers with one row.
16
+ export const ATTR_ROW = "lb-row";
17
+ // Named on a row template by the developer: the column that identifies a row
18
+ // of the list. A property of the list rather than of the template — a list
19
+ // without a fixed key column is meaningless — but written where the rows
20
+ // land, because that is the only place the page says anything about them.
6
21
  export const ATTR_KEY = "lb-key";
22
+ // Stamped on a live row by the row machinery: that row's value of the column
23
+ // ATTR_KEY names. Written by the hub, never by a developer, and read back by
24
+ // whatever needs to know which row it is inside — a delete button, an update
25
+ // form, a widget building its own request, or a stylesheet.
26
+ export const ATTR_KEY_VALUE = "lb-key-value";
27
+ // The column an element displays.
28
+ //
29
+ // Its value is a column name and never a cell name. A cell has no name of
30
+ // its own: it is identified by its row and its column, and the row arrives
31
+ // from scope, so the column is all that is left to say. ATTR_KEY holds a
32
+ // column name for the same reason.
7
33
  export const ATTR_CELL = "lb-cell";
8
34
  export const ATTR_VALUE = "lb-value";
9
- // Named on a row template, and read by a list widget that groups its rows
10
- // rather than by the hub. Grouping is placement, and placement is the
11
- // widget's — but the name is shared, because a grouped <select> and a table
12
- // with section headings are asking the same thing of the same data.
13
- export const ATTR_GROUP = "lb-group";
14
- // Named on a row template beside lb-group, and read by the same kind of
15
- // widget for the same reason. Sorting and grouping are one question — where
16
- // does this row go? — and both answers are local.
17
- export const ATTR_SORT = "lb-sort";
18
- // Stamped on a list widget by the shared row machinery: how many rows it is
19
- // showing. It is HTML's namespace rather than Loadbare App's because nothing
20
- // binds to it — it is derived from rows the hub already knows, exactly as
21
- // the section heading in lb-table is. It exists so that "no members yet"
22
- // is a stylesheet rule rather than a conditional in every list widget.
23
- export const ATTR_ROW_COUNT = "data-rows";
35
+ // Stamped on a list scope by the shared row machinery: how many rows it is
36
+ // showing. Written by the hub and never bound to — it is derived from rows
37
+ // the hub already knows, exactly as the section heading in lb-table is. A
38
+ // list widget watches it, and it exists so that "no members yet" is a
39
+ // stylesheet rule rather than a conditional in every list widget.
40
+ export const ATTR_ROW_COUNT = "lb-row-count";
24
41
  // --- Requests (docs/reference/data-binding.md) --------------------------
25
42
  // One event name, used by a widget reaching the server and by a widget
26
43
  // reaching an ancestor widget.
27
44
  export const LB_EVENT_NAME = "lb-request";
28
- // Names a declared action. On a native element, the hub turns a click into
29
- // the request; on a widget, the widget builds it from whatever interaction
30
- // it owns. Either way the name must appear in the page's hooks or it is
31
- // refused.
45
+ // Names what an interaction asks for.
46
+ //
47
+ // One attribute name, one wire field, one set of values: the request field
48
+ // `action` carries this attribute's value verbatim, and nothing is
49
+ // translated in between. On a native element the hub turns the element's own
50
+ // event into the request — a form submits, anything else clicks; on a
51
+ // widget, the widget builds it from whatever interaction it owns.
32
52
  export const ATTR_ACTION = "lb-action";
33
- // Marks a native element as deleting the tuple it is inside — the CRUD
34
- // counterpart to lb-action. It needs no declared name: tuple-delete takes
35
- // only the row's own lb-query/lb-key binding, which is all the server
53
+ // --- Reserved names ----------------------------------------------------
54
+ // Loadbare owns every value beginning with this prefix, in every lb-*
55
+ // attribute: a name so prefixed is the hub's own (NAV_ROW), and an
56
+ // lb-action so prefixed is an operation (below). The server refuses a page
57
+ // that declares a query or an action beginning with it, so a new reserved
58
+ // name can never collide with one an application already uses.
59
+ //
60
+ // It is also the whole of the wire discriminant. A request whose `action`
61
+ // begins with it is one of the four operations; anything else is a name the
62
+ // page declared under its requests. Nothing else tells them apart.
63
+ export const LB_RESERVED_PREFIX = "lb-";
64
+ // On a <form> in a list scope: builds a new row on submit. The hub gathers
65
+ // every lb-cell inside the form into a values map — no key, because there is
66
+ // no row yet — and the form's own lb-list says which list it is inserting
67
+ // into.
68
+ export const ACTION_ROW_INSERT = "lb-row-insert";
69
+ // Deletes the row the element is inside. Needs no declared name: it takes
70
+ // only the row's own lb-list/lb-key-value binding, which is all the server
36
71
  // needs to know which row and whether the operation is permitted on it.
37
- export const ATTR_DELETE = "lb-delete";
38
- // Marks a <form> as building a new tuple on submit. The hub gathers every
39
- // lb-cell inside it into a values map — no lb-key, because there is no row
40
- // yet — and the form's own lb-query says which query it is inserting into.
41
- export const ATTR_INSERT = "lb-insert";
42
- // Marks a <form> as committing several cells of one existing tuple at once
43
- // on submit — the batch counterpart to cell-change. The hub gathers the
44
- // same values map lb-insert does, plus the row's own lb-key: a form that is
45
- // a list's row template root, or sits inside one, has both from its
46
- // ancestors exactly as a delete button does.
47
- export const ATTR_UPDATE = "lb-update";
72
+ export const ACTION_ROW_DELETE = "lb-row-delete";
73
+ // On a <form> inside a live row: commits several cells of that row at once
74
+ // on submit — the batch counterpart to a cell change. The hub gathers the
75
+ // same values map, plus the row's own lb-key-value: a form that is a list's
76
+ // row template root, or sits inside one, has both from its ancestors exactly
77
+ // as a delete button does.
78
+ export const ACTION_ROW_UPDATE = "lb-row-update";
79
+ // On a widget wrapping one control: commits that one cell when the control
80
+ // changes. The hub never sends this itself — a native element has no widget
81
+ // to decide what a change is — so a widget that carries it builds the
82
+ // operation from its own lb-list/lb-key-value/lb-cell binding.
83
+ export const ACTION_CELL_CHANGE = "lb-cell-change";
84
+ // Every reserved lb-action, in one place, so the builder can refuse an
85
+ // unknown one and the hub can refuse to send one it does not implement.
86
+ export const LB_ACTIONS = [
87
+ ACTION_ROW_INSERT,
88
+ ACTION_ROW_DELETE,
89
+ ACTION_ROW_UPDATE,
90
+ ACTION_CELL_CHANGE,
91
+ ];
48
92
  // --- Expansion (docs/reference/custom-elements.md) ---------------------
49
93
  // Marks the element in a definition whose children receive the authored
50
94
  // inner content. An attribute rather than an element because HTML content
@@ -65,14 +109,31 @@ export const ATTR_TEMPLATE = "lb-template";
65
109
  // Only anchors carrying this are intercepted, so external links are never
66
110
  // hijacked.
67
111
  export const ATTR_NAV_LINK = "lb-nav-link";
68
- // A page host ships as <template id="page-<name>">.
69
- export const PAGE_TEMPLATE_PREFIX = "page-";
112
+ // A page host ships as <template lb-page="<name>">. An attribute rather than
113
+ // an id: an id lives in the application's own namespace, where a page named
114
+ // for something the chrome also ids would collide, and getElementById()
115
+ // answers a collision by silently picking one.
116
+ export const ATTR_PAGE = "lb-page";
117
+ // The hub's own row. Landed on every navigation, on any subtree inside the
118
+ // hub that names it, through the same applyData() a server answer goes
119
+ // through — so a chrome displays where the page is the way it displays
120
+ // anything else. One row rather than a list, so a subtree binds it with
121
+ // lb-row. A reserved name (LB_RESERVED_PREFIX): the server refuses to
122
+ // declare a query so named.
123
+ export const NAV_ROW = "lb-navigation";
124
+ // Its two columns. The label is the nav's: the text of the lb-nav-link
125
+ // anchor whose href names the current page, and empty when no anchor does.
126
+ // The URI is the path as the browser has it.
127
+ export const NAV_CELL_LABEL = "page-label";
128
+ export const NAV_CELL_URI = "page-uri";
70
129
  // Marks the chrome's own <dialog> for a page name that resolves to no
71
130
  // template — a routing miss discovered client-side, not an HTTP 404 (every
72
131
  // route gets the same 200 response; see docs/tutorials/010-pages-and-navigation.md).
73
- // Optional: an application that declares none gets today's console.error
74
- // and nothing more. The builder requires the element it's on to be a
75
- // <dialog> (build/assemble.ts), since the hub calls showModal() on it.
132
+ // The hub only opens it; what it says comes from NAV_ROW, which the dialog
133
+ // consumes like any other subtree, by naming it in lb-row. Optional: an
134
+ // application that declares none gets today's console.error and nothing
135
+ // more. The builder requires the element it's on to be a <dialog>
136
+ // (build/assemble.ts), since the hub calls showModal() on it.
76
137
  export const ATTR_UNKNOWN_PAGE = "lb-unknown-page";
77
138
  // The hub's own tag. It is exempt from the rule every other LB-* tag
78
139
  // follows: expansion does not require it to have a definition (build/expand.ts),
@@ -87,16 +148,18 @@ export const HUB_TAG_NAME = "lb-hub";
87
148
  // meaning beyond "in flight": a stylesheet dims the element, or a custom
88
149
  // button widget watches it to disable itself, but the hub itself only sets
89
150
  // and clears it.
90
- export const ATTR_PENDING = "data-lb-pending";
151
+ export const ATTR_PENDING = "lb-pending";
91
152
  // Stamped on the same element if that round trip failed — a non-2xx
92
153
  // response, a thrown application error, or a timeout, all alike. Cleared at
93
154
  // the start of that element's next request. What (if anything) an
94
155
  // application shows for it is undecided; this is only the fact that it
95
156
  // happened.
96
- export const ATTR_ERROR = "data-lb-error";
157
+ export const ATTR_ERROR = "lb-error";
97
158
  // --- Wire --------------------------------------------------------------
98
- export const LB_DATA_ENDPOINT = "/lb/data";
99
- export const LB_REQUEST_ENDPOINT = "/lb";
159
+ // One path, one method: everything the hub asks of the server is a POST to
160
+ // this path. Navigation is therefore the only GET an application serves,
161
+ // which is what leaves the page name space unrestricted.
162
+ export const LB_ENDPOINT = "/lb";
100
163
  // A client-side deadline for one round trip. fetch() has no deadline of its
101
164
  // own; past this the request is aborted and treated as a failure.
102
165
  export const LB_REQUEST_TIMEOUT_MS = 10_000;
@@ -1,88 +1,129 @@
1
- /** docs/reference/data-binding.md — the operation set is closed. */
2
- export type HubRequest = {
3
- op: "cell-change";
4
- query: string;
5
- key: string;
6
- cell: string;
7
- value: string;
8
- } | {
9
- op: "tuple-insert";
10
- query: string;
1
+ import { ACTION_CELL_CHANGE, ACTION_ROW_DELETE, ACTION_ROW_INSERT, ACTION_ROW_UPDATE } from "./lb-constants";
2
+ /**
3
+ * One row: its columns, by name.
4
+ *
5
+ * A value is whatever the server serialized. The hub knows nothing about its
6
+ * type and enforces nothing: it hands each value to the browser untouched,
7
+ * and the browser renders it as it sees fit. What a number, a date or a null
8
+ * should look like is the application's decision, made in the query.
9
+ */
10
+ export type Row = Record<string, unknown>;
11
+ /**
12
+ * Part of a list — see docs/reference/server.md, "Queries".
13
+ *
14
+ * Rows named here arrive or are updated, keys in `drop` are gone, and
15
+ * anything unnamed is left alone: its contents, and its place in whatever
16
+ * order the widget is keeping. Add and remove are one result rather than two
17
+ * because the interesting cases are both at once — a row whose sort key
18
+ * changed has to move, a swap is one out and one in — and two messages would
19
+ * paint the intermediate state.
20
+ */
21
+ export interface Patch {
22
+ rows?: Row[];
23
+ drop?: unknown[];
24
+ }
25
+ /**
26
+ * What a list answers with.
27
+ *
28
+ * An array is the entire set and therefore also the order: a row whose key
29
+ * is not in it is gone. A Patch disturbs only what it names.
30
+ *
31
+ * `Array.isArray` tells the two apart, which is why no column name has to be
32
+ * reserved to carry a discriminant. A list answering with an object is a
33
+ * patch, because the declaration already said this name answers with rows.
34
+ */
35
+ export type ListResult = Row[] | Patch;
36
+ export declare function isPatch(result: ListResult): result is Patch;
37
+ /**
38
+ * What any one name answers with. Which of the two it is comes from the
39
+ * query's own declaration, never from inspecting the value.
40
+ */
41
+ export type HubResult = Row | ListResult;
42
+ /**
43
+ * Every response on the data channel is the same shape, whether it is a cold
44
+ * start or the narrowest refresh. The hub does not know which case it is in.
45
+ *
46
+ * A name rides as an object key and is labelled by nothing, which is why the
47
+ * response direction never needed a noun for the addressable thing.
48
+ */
49
+ export type HubData = Record<string, HubResult>;
50
+ /**
51
+ * The four operations — docs/reference/data-binding.md. The set is closed.
52
+ *
53
+ * `action` carries the value of `lb-action` verbatim, so the attribute, the
54
+ * wire field and the CRUD key are one vocabulary with no translation step.
55
+ * The scope field is `list` here because all four are list operations: each
56
+ * needs a key, and a key exists only on a live row inside a list.
57
+ */
58
+ export type LbOperation = {
59
+ action: typeof ACTION_ROW_INSERT;
60
+ list: string;
11
61
  values: Record<string, string>;
12
62
  } | {
13
- op: "tuple-update";
14
- query: string;
63
+ action: typeof ACTION_ROW_DELETE;
64
+ list: string;
65
+ key: string;
66
+ } | {
67
+ action: typeof ACTION_ROW_UPDATE;
68
+ list: string;
15
69
  key: string;
16
70
  values: Record<string, string>;
17
71
  } | {
18
- op: "tuple-delete";
19
- query: string;
72
+ action: typeof ACTION_CELL_CHANGE;
73
+ list: string;
20
74
  key: string;
21
- }
75
+ cell: string;
76
+ value: string;
77
+ };
22
78
  /**
23
- * Everything that is not a CRUD operation.
79
+ * Everything that is not one of the four.
24
80
  *
25
81
  * The name is the application's and is looked up in the page's declared
26
82
  * actions. What keeps this from being an RPC endpoint is that the name must
27
- * already appear in the page's hooks: the browser cannot reach anything the
83
+ * already appear in the page's requests: the browser cannot reach anything the
28
84
  * page has not published, and there is no argument list — only where the
29
85
  * interaction happened and, where a control has one, its value.
30
86
  *
31
- * A button carries no value, so the server computes the whole of the new
87
+ * The scope field is `list` or `row`, whichever attribute scoped the element
88
+ * the interaction came from, so an action fired from a single-row scope says
89
+ * so. A button carries no value, so the server computes the whole of the new
32
90
  * state and the browser never displays a number it has not confirmed. A
33
91
  * `<select>` carries one, because the choice is the interaction.
34
92
  */
35
- | {
36
- op: "action";
37
- name: string;
38
- query?: string;
93
+ export interface DeclaredAction {
94
+ action: string;
95
+ list?: string;
96
+ row?: string;
39
97
  key?: string;
40
98
  cell?: string;
41
99
  value?: string;
42
- };
43
- /** One query's result: the cells of a single tuple. Every value is a string. */
44
- export type QueryResult = Record<string, string>;
45
- /**
46
- * A query that returns many tuples — see docs/reference/server.md, "Queries".
47
- *
48
- * Two results, because a list widget must be told the difference. `rows` is
49
- * the entire set and therefore also the order; `patch` names only what
50
- * changed and leaves everything it does not name alone. Add and remove are
51
- * one result rather than two because the interesting cases are both at once
52
- * — a row whose sort key changed has to move, a swap is one out and one in —
53
- * and two messages would paint the intermediate state.
54
- *
55
- * `op` is the same discriminant word the request union uses, so one
56
- * vocabulary runs in both directions. It is therefore a reserved cell name:
57
- * a tuple with a cell called `op` whose value is `rows` or `patch` would be
58
- * read as a projection.
59
- */
60
- export type Projection = {
61
- op: "rows";
62
- rows: QueryResult[];
63
- } | {
64
- op: "patch";
65
- rows?: QueryResult[];
66
- drop?: string[];
67
- };
68
- /** A tuple is the sugar and the common case; a projection says so. */
69
- export type HubResult = QueryResult | Projection;
70
- export declare function isProjection(result: HubResult): result is Projection;
100
+ }
101
+ export type HubRequest = LbOperation | DeclaredAction;
71
102
  /**
72
- * Every response on the data channel is the same shape, whether it is a
73
- * cold start or the narrowest refresh. The hub does not know which case
74
- * it is in.
103
+ * The reserved prefix is the whole of the discriminant. Nothing else tells
104
+ * an operation from a declared action, on the wire or anywhere else.
75
105
  */
76
- export type HubData = Record<string, HubResult>;
106
+ export declare function isOperation(request: HubRequest): request is LbOperation;
77
107
  /**
78
- * What a list widget implements. The hub delivers a projection by calling
79
- * this and stops; where a row goes is the widget's, because only the widget
80
- * knows whether it sorts, groups, or appends.
108
+ * The two hooks a list widget may implement, both optional.
109
+ *
110
+ * The hub reconciles every list scope itself, so a widget supplies placement
111
+ * and scaffolding and nothing else. A scope that implements neither is a
112
+ * plain element with a row template inside it, which is why there is no
113
+ * repeater widget.
81
114
  *
82
- * One protocol name, not a table of tag names — the hub tests for the method,
83
- * never for the element.
115
+ * They are methods on an element the hub does not own, so they carry the
116
+ * `lb` prefix that Loadbare reserves for exactly that — see
117
+ * docs/reference/custom-elements.md.
84
118
  */
85
- export interface HubRowHost {
86
- acceptRows(result: Projection): void;
119
+ export interface ListHost {
120
+ /**
121
+ * Where a row belongs, called with the row detached on its first
122
+ * appearance. Without it a row lands immediately before the template, so
123
+ * rows accumulate in the order they arrive.
124
+ */
125
+ lbPlaceRow?(el: Element, row: Row, template: HTMLTemplateElement): void;
126
+ /** Called once after a whole result has landed, for derived scaffolding. */
127
+ lbRowsLanded?(): void;
87
128
  }
88
129
  //# sourceMappingURL=lb-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAClB;IACE,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACrE;IACE,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACpD;;;;;;;;;;;;GAYG;GACD;IACE,EAAE,EAAE,QAAQ,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3D,sEAAsE;AACtE,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEjD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,IAAI,UAAU,CAGpE;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACtC"}
1
+ {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAElB,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC;AAEvC,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,IAAI,KAAK,CAE3D;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,UAAU,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IAAE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/D;IACE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IACE,MAAM,EAAE,OAAO,kBAAkB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,IAAI,WAAW,CAEvE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,UAAU,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAExE,4EAA4E;IAC5E,YAAY,CAAC,IAAI,IAAI,CAAC;CACvB"}
@@ -1,5 +1,13 @@
1
+ /// <reference lib="dom" />
1
2
  // The wire vocabulary. Shared by browser and server.
2
- export function isProjection(result) {
3
- const op = result.op;
4
- return op === "rows" || op === "patch";
3
+ import { LB_RESERVED_PREFIX, } from "./lb-constants";
4
+ export function isPatch(result) {
5
+ return !Array.isArray(result);
6
+ }
7
+ /**
8
+ * The reserved prefix is the whole of the discriminant. Nothing else tells
9
+ * an operation from a declared action, on the wire or anywhere else.
10
+ */
11
+ export function isOperation(request) {
12
+ return request.action.startsWith(LB_RESERVED_PREFIX);
5
13
  }
@@ -1,13 +1,27 @@
1
- import { type QueryResult, type HubData } from "../core/lb-types";
1
+ import { type HubData, type ListResult, type Row } from "../core/lb-types";
2
2
  /**
3
- * Fill one scope from one tuple.
3
+ * Fill one scope from one row.
4
4
  *
5
5
  * The root counts as a cell if it carries one. A `<tr>` holds its cells in
6
6
  * `<td>` children, but `<option>`'s content model is text, so an option row
7
7
  * has to be the cell it displays. Requiring a wrapper there would require an
8
8
  * element HTML does not allow.
9
9
  */
10
- export declare function applyTuple(root: Element, cells: QueryResult): void;
11
- /** Land a whole response. Every query result arrives through here. */
10
+ export declare function applyRow(root: Element, row: Row): void;
11
+ /**
12
+ * Land a list result in a list scope.
13
+ *
14
+ * An array is the whole set, so it decides membership and order: every row is
15
+ * placed in the order given, and a row whose key did not arrive is gone. A
16
+ * patch disturbs only what it names — a row it did not mention keeps its
17
+ * contents and its position.
18
+ *
19
+ * A widget that carries `lbPlaceRow` decides where a row goes, because only
20
+ * it knows whether it sorts or groups. Without it a row lands immediately
21
+ * before the template, so rows accumulate in the order they arrive and the
22
+ * template stays put as the insertion marker.
23
+ */
24
+ export declare function applyList(scope: Element, result: ListResult): void;
25
+ /** Land a whole response. Every result arrives through here. */
12
26
  export declare function applyData(root: ParentNode, data: HubData): void;
13
27
  //# sourceMappingURL=lb-apply.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,OAAO,EAEb,MAAM,kBAAkB,CAAC;AAkB1B;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAMlE;AAmBD,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAY/D"}
1
+ {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AA4BA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,GAAG,EACT,MAAM,kBAAkB,CAAC;AAiD1B;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAMtD;AAoCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAgElE;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAkC/D"}