@loadbare/app 0.5.6 → 0.7.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 (143) 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 +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
@@ -15,7 +15,7 @@ to reset the visit count.
15
15
  <h1>About</h1>
16
16
  <p class="about-note">This is the about page.</p>
17
17
 
18
- <div lb-query="visits">
18
+ <div lb-row="visits">
19
19
  <p>This page has been visited <span lb-cell="count"></span> times.</p>
20
20
  <button lb-action="resetVisits">Reset count</button>
21
21
  </div>
@@ -23,12 +23,12 @@ to reset the visit count.
23
23
 
24
24
  ## Implement the action on the server
25
25
 
26
- Actions go into the page's hooks file:
26
+ Actions go into the page's requests file:
27
27
 
28
28
  ```ts
29
- // src/pages/about.hooks.ts
30
- export const hooks = {
31
- beforeGet: (ctx) => ctx.db.recordVisit(),
29
+ // src/pages/about.requests.ts
30
+ export const requests = {
31
+ onPageEnter: (ctx) => ctx.db.recordVisit(),
32
32
  actions: {
33
33
  resetVisits: {
34
34
  run: (ctx) => ctx.db.resetVisits(),
@@ -16,7 +16,7 @@ then implement.
16
16
 
17
17
  ```html
18
18
  <!-- src/pages/about.page.html -->
19
- <div lb-query="visits">
19
+ <div lb-row="visits">
20
20
  <p>This page has been <visit-count lb-cell="count"></visit-count>.</p>
21
21
  <button lb-action="resetVisits">Reset count</button>
22
22
  </div>
@@ -19,7 +19,7 @@ two of them carry the standard `hidden` attribute.
19
19
  ```html
20
20
  <!-- src/pages/about.page.html -->
21
21
  <h2>Sign up</h2>
22
- <lb-wizard lb-query="signup" lb-cell="step">
22
+ <lb-wizard lb-row="signup" lb-cell="step">
23
23
  <section data-step="name">
24
24
  <h3>1. Name</h3>
25
25
  <p>This step is not hidden, because it's the one the page ships showing.</p>
@@ -96,9 +96,9 @@ export const queries = {
96
96
  ```
97
97
 
98
98
  ```ts
99
- // src/pages/about.hooks.ts
100
- export const hooks = {
101
- // ...beforeGet, resetVisits, and crud unchanged...
99
+ // src/pages/about.requests.ts
100
+ export const requests = {
101
+ // ...onPageEnter, resetVisits, and crud unchanged...
102
102
  actions: {
103
103
  resetVisits: { run: (ctx) => ctx.db.resetVisits(), refresh: ["visits"] },
104
104
  wizardBack: { run: (ctx) => ctx.db.moveSignup(-1), refresh: ["signup"] },
@@ -1,22 +1,24 @@
1
1
  # Displaying a List
2
2
 
3
- Every query so far has answered with one tuple. Now we add a query that
4
- answers with many rows, and a widget to show them: `<lb-list>`.
3
+ Every query so far has answered with one row. Now we add a query that
4
+ answers with many rows, and markup to show them. No widget is involved: the
5
+ hub reconciles a list itself.
5
6
 
6
7
  ## Writing the query
7
8
 
8
9
  ```ts
9
10
  // src/pages/about.queries.ts
10
- import { rows } from "@loadbare/app/server";
11
+ import { list, row } from "@loadbare/app/server";
11
12
 
12
13
  export const queries = {
13
- visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
14
- notes: async (ctx) => rows(await ctx.db.notes()),
14
+ visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
15
+ notes: list((ctx) => ctx.db.notes()),
15
16
  };
16
17
  ```
17
18
 
18
- Loadbare assumes a query's results are always one tuple. We tell it that
19
- we are returning a set of rows by wrapping the query in `rows()`.
19
+ Every query says which shape it answers with. `row()` answers with one row
20
+ and `list()` answers with a set of them, and a name never answers with both.
21
+ A page that needs the same data both ways declares two queries.
20
22
 
21
23
  ## Extending the database
22
24
 
@@ -64,35 +66,11 @@ export function openDb() {
64
66
  `write` is now a merge, not a whole-file replace — otherwise saving
65
67
  `notes` would erase `visitCount`.
66
68
 
67
- ## Installing the widget library
68
-
69
- `<lb-list>` is the first widget we take from a library rather than write
70
- ourselves. It ships in `@loadbare/widgets`:
71
-
72
- ```
73
- npm install @loadbare/widgets
74
- ```
75
-
76
- The builder scans `src/` on its own, but it will not go looking through
77
- `node_modules` uninvited. List the package in `src/imports.ts`:
78
-
79
- ```ts
80
- // src/imports.ts
81
- export default ["@loadbare/widgets"];
82
- ```
83
-
84
- That is the whole of it. List the package, not its tags — the builder finds
85
- `<lb-list>` by filename, and bundles only the tags a page actually uses.
86
- [Using Widget Libraries](./090-using-widget-libraries.md) covers the same
87
- step for anyone else's package.
88
-
89
69
  ## A list display with a template
90
70
 
91
- The `<lb-list>` widget will display a list, but when we use that widget
92
- we need to tell it what a list item looks like. We do that by
93
- putting a `<template>` inside of it. The widget itself contains a loop
94
- to display one template per row, and invokes code in the hub `<lb-hub>` to
95
- display bound data values.
71
+ A list scope needs to be told what one row looks like, and we say so with a
72
+ `<template>` inside it. The hub clones that template once per row and fills
73
+ each clone the same way it fills any other scope.
96
74
 
97
75
  > Loadbare does not use Shadow DOM because Shadow DOM is generally obtuse,
98
76
  > interferes with CSS scoping, and makes templates difficult to supply at point
@@ -103,24 +81,23 @@ display bound data values.
103
81
  > Rather than mess with the Shadow DOM, we use Light DOM.
104
82
  > We take advantage of the fact that `<template>`
105
83
  > elements are not rendered, so we drop the `<template>` directly
106
- > into the light DOM, where it's easy to see how `<lb-list>` is
107
- > going to render our list.
84
+ > into the light DOM, where it's easy to see how a row
85
+ > is going to be rendered.
108
86
 
109
- We know already that a DOM subtree can be scoped to a query with
110
- `lb-query="X"`. Now we see the use of `lb-key="id"`, which further
111
- scopes a subtree to a specific row within the query, naming the
112
- primary key field that uniquely identifies the row.
87
+ We know already that a DOM subtree can be scoped to one row with
88
+ `lb-row="X"`. A set of rows uses `lb-list="X"` instead, and adds
89
+ `lb-key="id"`, which names the column that uniquely identifies a row. Each
90
+ row the hub clones is stamped with `lb-key-value`, holding that row's key,
91
+ which is what scopes the row's subtree to one specific row.
113
92
 
114
93
  ```html
115
94
  <!-- src/pages/about.page.html -->
116
95
  <h2>Notes</h2>
117
- <lb-list lb-query="notes">
118
- <ul>
119
- <template lb-key="id">
120
- <li lb-cell="text"></li>
121
- </template>
122
- </ul>
123
- </lb-list>
96
+ <ul lb-list="notes">
97
+ <template lb-key="id">
98
+ <li lb-cell="text"></li>
99
+ </template>
100
+ </ul>
124
101
  ```
125
102
 
126
103
  ## Run it
@@ -8,37 +8,40 @@ that allows a user to add a note.
8
8
  ```html
9
9
  <!-- src/pages/about.page.html -->
10
10
  <h2>Notes</h2>
11
- <form lb-insert lb-query="notes">
12
- <input lb-cell="text" placeholder="Write a note" />
13
- <button type="submit">Add</button>
14
- </form>
11
+ <section lb-list="notes">
12
+ <form lb-action="lb-row-insert">
13
+ <input lb-cell="text" placeholder="Write a note" />
14
+ <button type="submit">Add</button>
15
+ </form>
15
16
 
16
- <lb-list lb-query="notes">
17
17
  <ul>
18
18
  <template lb-key="id">
19
19
  <li lb-cell="text"></li>
20
20
  </template>
21
21
  </ul>
22
- </lb-list>
22
+ </section>
23
23
  ```
24
24
 
25
- `lb-insert` on a `<form>` gathers its `lb-cell`s into a values map and
26
- submits them against `lb-query`. There's no `lb-key` — there's no row
25
+ `lb-action="lb-row-insert"` on a `<form>` gathers its `lb-cell`s into a values
26
+ map on submit and sends them against the `lb-list` around it, the way an
27
+ `<input>` belongs to the `<form>` around it. The name is one of
28
+ Loadbare's reserved ones — every value beginning with `lb-` is — so it
29
+ needs no declaring, and nothing of yours may be called that. There's no `lb-key-value` — there's no row
27
30
  yet.
28
31
 
29
32
  ## Implementing CRUD server-side
30
33
 
31
- Our CRUD operations go into the page's hooks file:
34
+ Our CRUD operations go into the page's requests file:
32
35
 
33
36
  ```ts
34
- // src/pages/about.hooks.ts
37
+ // src/pages/about.requests.ts
35
38
  import { patch } from "@loadbare/app/server";
36
39
 
37
- export const hooks = {
38
- // ...beforeGet and actions unchanged...
40
+ export const requests = {
41
+ // ...onPageEnter and actions unchanged...
39
42
  crud: {
40
43
  notes: {
41
- tupleInsert: {
44
+ rowInsert: {
42
45
  run: async (ctx, { values }) => {
43
46
  const note = await ctx.db.addNote(values.text);
44
47
  return { notes: patch({ rows: [note] }) };
@@ -50,12 +53,12 @@ export const hooks = {
50
53
  };
51
54
  ```
52
55
 
53
- `tupleInsert` is a CRUD operation, declared under `crud` and keyed by
56
+ `rowInsert` is a CRUD operation, declared under `crud` and keyed by
54
57
  query name.
55
58
 
56
59
  Notice that we do not use the `refresh` mechanism, as that would return
57
60
  the entire query which would be wasteful. We instead return a `patch`
58
- with a single new row and the `<lb-list>` element just adds the row.
61
+ with a single new row and the hub just adds the row.
59
62
 
60
63
  ## Extending the database
61
64
 
@@ -6,34 +6,32 @@ Now that we can add notes, we need to be able to delete a note.
6
6
 
7
7
  ```html
8
8
  <!-- src/pages/about.page.html -->
9
- <lb-list lb-query="notes">
10
- <ul>
11
- <template lb-key="id">
12
- <li>
13
- <span lb-cell="text"></span>
14
- <button lb-delete>Delete</button>
15
- </li>
16
- </template>
17
- </ul>
18
- </lb-list>
9
+ <ul lb-list="notes">
10
+ <template lb-key="id">
11
+ <li>
12
+ <span lb-cell="text"></span>
13
+ <button lb-action="lb-row-delete">Delete</button>
14
+ </li>
15
+ </template>
16
+ </ul>
19
17
  ```
20
18
 
21
19
  The `<li>` now has two children, so the text moves onto its own `<span>`.
22
- `lb-delete` needs no form — just the `lb-query` and `lb-key` already in
23
- scope from its ancestors.
20
+ `lb-row-delete` is a reserved name, so it needs no form and no declaring — just
21
+ the `lb-list` and `lb-key-value` already in scope from its ancestors.
24
22
 
25
23
  ## Implementing delete server-side
26
24
 
27
25
  ```ts
28
- // src/pages/about.hooks.ts
26
+ // src/pages/about.requests.ts
29
27
  import { patch } from "@loadbare/app/server";
30
28
 
31
- export const hooks = {
32
- // ...beforeGet and actions unchanged...
29
+ export const requests = {
30
+ // ...onPageEnter and actions unchanged...
33
31
  crud: {
34
32
  notes: {
35
- // ...tupleInsert unchanged...
36
- tupleDelete: {
33
+ // ...rowInsert unchanged...
34
+ rowDelete: {
37
35
  run: async (ctx, { key }) => {
38
36
  await ctx.db.deleteNote(key);
39
37
  return { notes: patch({ drop: [key] }) };
@@ -6,37 +6,35 @@ Now we edit a row in place, instead of removing and re-adding it.
6
6
 
7
7
  ```html
8
8
  <!-- src/pages/about.page.html -->
9
- <lb-list lb-query="notes">
10
- <ul>
11
- <template lb-key="id">
12
- <li>
13
- <form lb-update>
14
- <input lb-cell="text" />
15
- <button type="submit">Save</button>
16
- </form>
17
- <button lb-delete>Delete</button>
18
- </li>
19
- </template>
20
- </ul>
21
- </lb-list>
9
+ <ul lb-list="notes">
10
+ <template lb-key="id">
11
+ <li>
12
+ <form lb-action="lb-row-update">
13
+ <input lb-cell="text" />
14
+ <button type="submit">Save</button>
15
+ </form>
16
+ <button lb-action="lb-row-delete">Delete</button>
17
+ </li>
18
+ </template>
19
+ </ul>
22
20
  ```
23
21
 
24
- `lb-update` gathers its `lb-cell`s the same way `lb-insert` does, but
25
- also reads `lb-key` from the row it's inside — the same ancestor
26
- `lb-delete` already reads.
22
+ `lb-row-update` gathers its `lb-cell`s the same way `lb-row-insert` does, but
23
+ also reads `lb-key-value` from the row it's inside — the same ancestor
24
+ `lb-row-delete` already reads.
27
25
 
28
26
  ## Implementing update server-side
29
27
 
30
28
  ```ts
31
- // src/pages/about.hooks.ts
29
+ // src/pages/about.requests.ts
32
30
  import { patch } from "@loadbare/app/server";
33
31
 
34
- export const hooks = {
35
- // ...beforeGet and actions unchanged...
32
+ export const requests = {
33
+ // ...onPageEnter and actions unchanged...
36
34
  crud: {
37
35
  notes: {
38
- // ...tupleInsert, tupleDelete unchanged...
39
- tupleUpdate: {
36
+ // ...rowInsert, rowDelete unchanged...
37
+ rowUpdate: {
40
38
  run: async (ctx, { key, values }) => {
41
39
  const note = await ctx.db.updateNote(key, values.text);
42
40
  return { notes: patch({ rows: [note] }) };
@@ -50,7 +48,7 @@ export const hooks = {
50
48
 
51
49
  With more than one note, `key` is what says which row this request
52
50
  means. `patch({ rows: [note] })` looks like insert's response, but since
53
- this id already exists, `<lb-list>` updates that row instead of adding
51
+ this id already exists, the hub updates that row instead of adding
54
52
  one.
55
53
 
56
54
  ## Extending the database
@@ -9,11 +9,9 @@ changes.
9
9
 
10
10
  ## Writing the widget
11
11
 
12
- Our new custom input will no longer rely on Loadbare's form handling,
13
- and so it must reimplement the data scoping logic that is in
14
- Loadbare. This reimplementation approach is neither encouraged nor
15
- discouraged. This tutorial shows only that it is possible, and exposes
16
- how Loadbare internals work.
12
+ Our new custom input will no longer rely on Loadbare's form handling. It
13
+ says what happened and what its value is. Where it sits, the list, the row
14
+ and the cell, is added by the hub.
17
15
 
18
16
  ```html
19
17
  <!-- src/note-input.html -->
@@ -22,13 +20,7 @@ how Loadbare internals work.
22
20
 
23
21
  ```ts
24
22
  // src/note-input.browser.ts
25
- import {
26
- ATTR_CELL,
27
- ATTR_KEY,
28
- ATTR_QUERY,
29
- ATTR_VALUE,
30
- LB_EVENT_NAME,
31
- } from "@loadbare/app/constants";
23
+ import { ATTR_VALUE, LB_EVENT_NAME } from "@loadbare/app/constants";
32
24
 
33
25
  class NoteInput extends HTMLElement {
34
26
  static observedAttributes = [ATTR_VALUE];
@@ -42,14 +34,10 @@ class NoteInput extends HTMLElement {
42
34
  this.addEventListener("change", () => {
43
35
  const input = this.querySelector("input");
44
36
  if (!input) return;
45
- const query = this.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
46
- const key = this.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
47
- const cell = this.getAttribute(ATTR_CELL);
48
- if (!query || !key || !cell) return;
49
37
  this.dispatchEvent(
50
38
  new CustomEvent(LB_EVENT_NAME, {
51
39
  bubbles: true,
52
- detail: { op: "cell-change", query, key, cell, value: input.value },
40
+ detail: { action: "lb-cell-change", value: input.value },
53
41
  }),
54
42
  );
55
43
  });
@@ -60,40 +48,39 @@ customElements.define("note-input", NoteInput);
60
48
  ```
61
49
 
62
50
  A widget with both a `.html` and a `.browser.ts` file shares one tag name; the
63
- build finds each half independently. On change, it reads its own
64
- `lb-query`/`lb-key`/`lb-cell` coordinates and dispatches a `cell-change`
65
- request itself, the way `lb-update` does for a whole form.
51
+ build finds each half independently. On change, it dispatches a
52
+ `lb-cell-change` carrying the input's value. The hub adds the list, the key
53
+ of the row it is in and its own `lb-cell`, the same way it does for a
54
+ `lb-row-update` form.
66
55
 
67
56
  ## Using it
68
57
 
69
58
  ```html
70
59
  <!-- src/pages/about.page.html -->
71
- <lb-list lb-query="notes">
72
- <ul>
73
- <template lb-key="id">
74
- <li>
75
- <note-input lb-cell="text"></note-input>
76
- <button lb-delete>Delete</button>
77
- </li>
78
- </template>
79
- </ul>
80
- </lb-list>
60
+ <ul lb-list="notes">
61
+ <template lb-key="id">
62
+ <li>
63
+ <note-input lb-cell="text"></note-input>
64
+ <button lb-action="lb-row-delete">Delete</button>
65
+ </li>
66
+ </template>
67
+ </ul>
81
68
  ```
82
69
 
83
- The `<form lb-update>` and Save button are gone — `note-input` commits on
70
+ The `lb-row-update` form and Save button are gone — `note-input` commits on
84
71
  every change, so there's nothing left to batch.
85
72
 
86
73
  ## Answering it server-side
87
74
 
88
75
  ```ts
89
- // src/pages/about.hooks.ts
76
+ // src/pages/about.requests.ts
90
77
  import { patch } from "@loadbare/app/server";
91
78
 
92
- export const hooks = {
93
- // ...beforeGet and actions unchanged...
79
+ export const requests = {
80
+ // ...onPageEnter and actions unchanged...
94
81
  crud: {
95
82
  notes: {
96
- // ...tupleInsert, tupleDelete, tupleUpdate unchanged...
83
+ // ...rowInsert, rowDelete, rowUpdate unchanged...
97
84
  cellChange: {
98
85
  run: async (ctx, { key, value }) => {
99
86
  const note = await ctx.db.updateNote(key, value);
@@ -46,7 +46,7 @@ regardless of where the script came from:
46
46
 
47
47
  ```html
48
48
  <!-- src/pages/index.page.html -->
49
- <div lb-query="status">
49
+ <div lb-row="status">
50
50
  <lb-select lb-cell="state">
51
51
  <option value="open">Open</option>
52
52
  <option value="closed">Closed</option>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.5.6",
4
+ "version": "0.7.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -18,7 +18,6 @@
18
18
  ".": "./dist/hub/lb-hub.browser.js",
19
19
  "./constants": "./dist/core/lb-constants.js",
20
20
  "./types": "./dist/core/lb-types.js",
21
- "./rows": "./dist/hub/lb-rows.js",
22
21
  "./server": "./dist/server/lb-server.js",
23
22
  "./express": "./dist/server/lb-express.js",
24
23
  "./build": "./dist/build/elements.js"
@@ -33,7 +32,7 @@
33
32
  "prebuild": "node --eval \"fs.rmSync('dist',{recursive:true,force:true})\" --input-type=module",
34
33
  "build:server": "tsc --project tsconfig.build.json",
35
34
  "build": "npm run build:server",
36
- "postbuild": "node --eval \"fs.chmodSync('dist/build/cli.js', 0o755)\" --input-type=module",
35
+ "postbuild": "node --eval \"fs.chmodSync('dist/build/cli.js', 0o755)\" --input-type=module && node ../../scripts/check-dist-imports.mjs",
37
36
  "test": "tsx --test \"tests/**/*.test.ts\"",
38
37
  "typecheck": "tsc --noEmit",
39
38
  "format": "prettier --write .",
@@ -1,18 +0,0 @@
1
- import type { Projection, QueryResult } from "../core/lb-types";
2
- /**
3
- * Where a row belongs, called with the row detached on its first appearance.
4
- *
5
- * The default puts it immediately before the template, so rows accumulate in
6
- * the order they arrive and the template stays put as the insertion marker.
7
- */
8
- export type Place = (row: Element, tuple: QueryResult, template: HTMLTemplateElement) => void;
9
- /**
10
- * Land a projection in a widget's subtree.
11
- *
12
- * `rows` is the whole set, so it decides membership and order: every row is
13
- * placed in the order given, and a row whose key did not arrive is gone.
14
- * `patch` disturbs only what it names — a row it did not mention keeps its
15
- * contents and its position.
16
- */
17
- export declare function applyRows(scope: Element, result: Projection, place?: Place): void;
18
- //# sourceMappingURL=lb-rows.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lb-rows.d.ts","sourceRoot":"","sources":["../../hub/lb-rows.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAClB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,mBAAmB,KAC1B,IAAI,CAAC;AAsCV;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,GAAE,KAAc,GACpB,IAAI,CAkDN"}
@@ -1,106 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * The row machinery a list widget shares — see docs/reference/custom-elements.md,
4
- * "Accepting rows".
5
- *
6
- * A widget owns where a row goes and nothing else. Cloning the template,
7
- * matching a tuple to the row already showing it, and filling that row are
8
- * the same in every list, so they are here, and a widget that sorts or groups
9
- * supplies one function rather than a second implementation of all of it.
10
- */
11
- import { ATTR_KEY, ATTR_ROW_COUNT } from "../core/lb-constants";
12
- import { applyTuple } from "./lb-apply";
13
- const before = (row, _tuple, template) => {
14
- template.parentElement.insertBefore(row, template);
15
- };
16
- /** The row template: the one the page author wrote inside this widget. */
17
- function templateIn(scope) {
18
- const template = scope.querySelector("template");
19
- if (!template) {
20
- console.error(`lb-rows: <${scope.localName}> has no <template> to clone`);
21
- return null;
22
- }
23
- if (!template.getAttribute(ATTR_KEY)) {
24
- console.error(`lb-rows: the <template> in <${scope.localName}> has no ${ATTR_KEY} ` +
25
- `naming the column that identifies a row`);
26
- return null;
27
- }
28
- return template;
29
- }
30
- /**
31
- * The rows already showing, by the key value each carries.
32
- *
33
- * `lb-key` names the key column on the template and carries the key value on
34
- * a live row. One name in two positions, and never ambiguous, because a
35
- * template is not a row.
36
- */
37
- function showing(scope, template) {
38
- const rows = new Map();
39
- for (const el of scope.querySelectorAll(`[${ATTR_KEY}]`)) {
40
- if (el !== template)
41
- rows.set(el.getAttribute(ATTR_KEY), el);
42
- }
43
- return rows;
44
- }
45
- /**
46
- * Land a projection in a widget's subtree.
47
- *
48
- * `rows` is the whole set, so it decides membership and order: every row is
49
- * placed in the order given, and a row whose key did not arrive is gone.
50
- * `patch` disturbs only what it names — a row it did not mention keeps its
51
- * contents and its position.
52
- */
53
- export function applyRows(scope, result, place = before) {
54
- const template = templateIn(scope);
55
- if (!template)
56
- return;
57
- const keyCell = template.getAttribute(ATTR_KEY);
58
- const rows = showing(scope, template);
59
- const upsert = (tuple) => {
60
- const key = tuple[keyCell];
61
- if (key === undefined) {
62
- console.error(`lb-rows: a tuple for <${scope.localName}> has no '${keyCell}' cell`);
63
- return null;
64
- }
65
- let row = rows.get(key);
66
- const fresh = row === undefined;
67
- if (!row) {
68
- row = template.content.firstElementChild.cloneNode(true);
69
- row.setAttribute(ATTR_KEY, key);
70
- rows.set(key, row);
71
- }
72
- // Fill before insertion. The attributes are already there when the row
73
- // upgrades, which is the same thing that makes hydration and refresh one
74
- // operation everywhere else.
75
- applyTuple(row, tuple);
76
- if (fresh || result.op === "rows")
77
- place(row, tuple, template);
78
- return key;
79
- };
80
- if (result.op === "rows") {
81
- const arrived = new Set();
82
- for (const tuple of result.rows) {
83
- const key = upsert(tuple);
84
- if (key !== null)
85
- arrived.add(key);
86
- }
87
- for (const [key, row] of rows)
88
- if (!arrived.has(key))
89
- row.remove();
90
- }
91
- else {
92
- for (const tuple of result.rows ?? [])
93
- upsert(tuple);
94
- for (const key of result.drop ?? [])
95
- rows.get(key)?.remove();
96
- }
97
- // How many rows are showing, counted from the DOM rather than from either
98
- // branch above, so a set and a patch report the same fact the same way.
99
- //
100
- // It is stamped here because only this function knows the count: it is the
101
- // one conditional a page cannot be sent, since the server answers with rows
102
- // and says nothing about how many survived reconciliation. A page says what
103
- // an empty list looks like in a stylesheet, and no list widget carries code
104
- // for it. See docs/reference/data-binding.md.
105
- scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope, template).size));
106
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Assembling a chrome file and its pages — see build/assemble.ts.
3
- *
4
- * The chrome and pages together are one write to a temp tree per test,
5
- * since assemble() reads from disk rather than taking sources in memory.
6
- */
7
- export {};
8
- //# sourceMappingURL=assemble.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"assemble.test.d.ts","sourceRoot":"","sources":["../../tests/assemble.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}