@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
@@ -7,8 +7,7 @@
7
7
  */
8
8
  import assert from "node:assert/strict";
9
9
  import { describe, it } from "node:test";
10
- import { createHub, patch, rows, } from "../server/lb-server";
11
- import { isProjection } from "../core/lb-types";
10
+ import { createHub, list, patch, row, } from "../server/lb-server";
12
11
  import { captured } from "./helpers/console";
13
12
  /**
14
13
  * A context to compare by identity.
@@ -21,25 +20,25 @@ import { captured } from "./helpers/console";
21
20
  function context() {
22
21
  return {};
23
22
  }
24
- /** A query that answers a fixed tuple and records that it ran. */
23
+ /** A query that answers a fixed row and records that it ran. */
25
24
  function stub(log, name, answer) {
26
- return (ctx) => {
25
+ return row((ctx) => {
27
26
  log.push(name);
28
27
  seen.set(name, ctx);
29
28
  return answer;
30
- };
29
+ });
31
30
  }
32
31
  const seen = new Map();
33
32
  function pages(entries) {
34
33
  const map = {};
35
34
  for (const [name, entry] of Object.entries(entries)) {
36
- map[name] = { queries: entry.queries, hooks: entry.hooks ?? {} };
35
+ map[name] = { queries: entry.queries, requests: entry.requests ?? {} };
37
36
  }
38
37
  return map;
39
38
  }
40
- // --- The page GET --------------------------------------------------------
41
- describe("a page GET", () => {
42
- it("runs beforeGet before any query, then the whole query set", async () => {
39
+ // --- Entering a page ----------------------------------------------------
40
+ describe("entering a page", () => {
41
+ it("runs onPageEnter before any query, then the whole query set", async () => {
43
42
  const log = [];
44
43
  const hub = createHub(pages({
45
44
  report: {
@@ -47,16 +46,16 @@ describe("a page GET", () => {
47
46
  head: stub(log, "head", { title: "Q1" }),
48
47
  body: stub(log, "body", { line: "one" }),
49
48
  },
50
- hooks: {
51
- beforeGet: () => void log.push("beforeGet"),
49
+ requests: {
50
+ onPageEnter: () => void log.push("onPageEnter"),
52
51
  },
53
52
  },
54
53
  }));
55
54
  const data = await hub.dataForPage("report", context());
56
- assert.deepEqual(log, ["beforeGet", "head", "body"]);
55
+ assert.deepEqual(log, ["onPageEnter", "head", "body"]);
57
56
  assert.deepEqual(data, { head: { title: "Q1" }, body: { line: "one" } });
58
57
  });
59
- it("runs the query set when the page declares no hooks", async () => {
58
+ it("runs the query set when the page declares no requests", async () => {
60
59
  const log = [];
61
60
  const hub = createHub(pages({ report: { queries: { head: stub(log, "head", { a: "1" }) } } }));
62
61
  assert.deepEqual(await hub.dataForPage("report", context()), {
@@ -89,7 +88,7 @@ describe("a page GET", () => {
89
88
  });
90
89
  it("awaits a query that returns a promise", async () => {
91
90
  const hub = createHub(pages({
92
- report: { queries: { head: async () => ({ a: "1" }) } },
91
+ report: { queries: { head: row(async () => ({ a: "1" })) } },
93
92
  }));
94
93
  assert.deepEqual(await hub.dataForPage("report", context()), {
95
94
  head: { a: "1" },
@@ -97,6 +96,28 @@ describe("a page GET", () => {
97
96
  });
98
97
  });
99
98
  // --- Actions -------------------------------------------------------------
99
+ describe("a reserved name", () => {
100
+ // Names beginning with lb- are Loadbare's: the hub's own query, the CRUD
101
+ // operations. See LB_RESERVED_PREFIX in core/lb-constants.ts.
102
+ it("is refused as a query, at startup", () => {
103
+ assert.throws(() => createHub({
104
+ home: {
105
+ queries: { "lb-navigation": row(async () => ({})) },
106
+ requests: {},
107
+ },
108
+ }), /page 'home' declares 'lb-navigation', but names beginning with 'lb-' are reserved/);
109
+ });
110
+ it("is refused as an action, at startup", () => {
111
+ assert.throws(() => createHub({
112
+ home: {
113
+ queries: {},
114
+ requests: {
115
+ actions: { "lb-delete": { run: () => { }, refresh: [] } },
116
+ },
117
+ },
118
+ }), /declares 'lb-delete', but names beginning with 'lb-' are reserved/);
119
+ });
120
+ });
100
121
  describe("an action", () => {
101
122
  it("is refused unless the page declared it", async () => {
102
123
  // The rule that keeps this from being an RPC endpoint: the wire cannot
@@ -105,7 +126,7 @@ describe("an action", () => {
105
126
  const hub = createHub(pages({
106
127
  counter: {
107
128
  queries: { count: stub(log, "count", { n: "1" }) },
108
- hooks: {
129
+ requests: {
109
130
  actions: {
110
131
  bump: { run: () => void log.push("bump"), refresh: ["count"] },
111
132
  },
@@ -131,7 +152,7 @@ describe("an action", () => {
131
152
  count: stub(log, "count", { n: "1" }),
132
153
  unrelated: stub(log, "unrelated", { x: "9" }),
133
154
  },
134
- hooks: {
155
+ requests: {
135
156
  actions: {
136
157
  bump: { run: () => void log.push("bump"), refresh: ["count"] },
137
158
  },
@@ -147,16 +168,16 @@ describe("an action", () => {
147
168
  const hub = createHub(pages({
148
169
  roster: {
149
170
  queries: {},
150
- hooks: {
171
+ requests: {
151
172
  actions: {
152
173
  pick: { run: (_ctx, w) => void (where = w), refresh: [] },
153
174
  },
154
175
  },
155
176
  },
156
177
  }));
157
- await hub.runAction("roster", "pick", { query: "members", key: "7", cell: "role", value: "lead" }, context());
178
+ await hub.runAction("roster", "pick", { list: "members", key: "7", cell: "role", value: "lead" }, context());
158
179
  assert.deepEqual(where, {
159
- query: "members",
180
+ list: "members",
160
181
  key: "7",
161
182
  cell: "role",
162
183
  value: "lead",
@@ -169,7 +190,7 @@ describe("an action", () => {
169
190
  const hub = createHub(pages({
170
191
  counter: {
171
192
  queries: { count: stub(log, "count", {}) },
172
- hooks: {
193
+ requests: {
173
194
  actions: {
174
195
  bump: {
175
196
  run: (ctx) => void (actionCtx = ctx),
@@ -190,8 +211,8 @@ describe("an action", () => {
190
211
  const store = { n: 1 };
191
212
  const hub = createHub(pages({
192
213
  counter: {
193
- queries: { count: () => ({ n: String(store.n) }) },
194
- hooks: {
214
+ queries: { count: row(() => ({ n: String(store.n) })) },
215
+ requests: {
195
216
  actions: {
196
217
  bump: { run: () => void (store.n += 1), refresh: ["count"] },
197
218
  },
@@ -208,8 +229,8 @@ describe("an action", () => {
208
229
  // what it changed and may say only that. The narrower answer wins.
209
230
  const hub = createHub(pages({
210
231
  roster: {
211
- queries: { members: () => rows([{ id: "1" }, { id: "2" }]) },
212
- hooks: {
232
+ queries: { members: list(() => [{ id: "1" }, { id: "2" }]) },
233
+ requests: {
213
234
  actions: {
214
235
  rename: {
215
236
  run: () => ({ members: patch({ rows: [{ id: "2" }] }) }),
@@ -220,13 +241,15 @@ describe("an action", () => {
220
241
  },
221
242
  }));
222
243
  const data = await hub.runAction("roster", "rename", {}, context());
223
- assert.deepEqual(data, { members: { op: "patch", rows: [{ id: "2" }] } });
244
+ assert.deepEqual(data, { members: { rows: [{ id: "2" }] } });
224
245
  });
225
246
  it("answers with the refresh set alone when it states nothing", async () => {
226
247
  const hub = createHub(pages({
227
248
  counter: {
228
- queries: { count: () => ({ n: "1" }) },
229
- hooks: { actions: { bump: { run: () => { }, refresh: ["count"] } } },
249
+ queries: { count: row(() => ({ n: "1" })) },
250
+ requests: {
251
+ actions: { bump: { run: () => { }, refresh: ["count"] } },
252
+ },
230
253
  },
231
254
  }));
232
255
  assert.deepEqual(await hub.runAction("counter", "bump", {}, context()), {
@@ -236,8 +259,8 @@ describe("an action", () => {
236
259
  it("skips a refresh name the page does not declare, and runs its siblings", async () => {
237
260
  const hub = createHub(pages({
238
261
  counter: {
239
- queries: { count: () => ({ n: "1" }) },
240
- hooks: {
262
+ queries: { count: row(() => ({ n: "1" })) },
263
+ requests: {
241
264
  actions: {
242
265
  bump: { run: () => { }, refresh: ["ghost", "count"] },
243
266
  },
@@ -253,7 +276,9 @@ describe("an action", () => {
253
276
  run: async () => ({ count: { n: "5" } }),
254
277
  refresh: [],
255
278
  };
256
- const hub = createHub(pages({ counter: { queries: {}, hooks: { actions: { bump: action } } } }));
279
+ const hub = createHub(pages({
280
+ counter: { queries: {}, requests: { actions: { bump: action } } },
281
+ }));
257
282
  assert.deepEqual(await hub.runAction("counter", "bump", {}, context()), {
258
283
  count: { n: "5" },
259
284
  });
@@ -262,10 +287,10 @@ describe("an action", () => {
262
287
  // --- Cell change -----------------------------------------------------------
263
288
  describe("a cell-change", () => {
264
289
  it("is refused unless the query declared it", async () => {
265
- const hub = createHub(pages({ roster: { queries: { members: () => rows([]) } } }));
266
- const { result, warn } = await captured(() => hub.runCellChange("roster", { query: "members", key: "1", cell: "name", value: "Ada" }, context()));
290
+ const hub = createHub(pages({ roster: { queries: { members: list(() => []) } } }));
291
+ const { result, warn } = await captured(() => hub.runCellChange("roster", { list: "members", key: "1", cell: "name", value: "Ada" }, context()));
267
292
  assert.deepEqual(result, {});
268
- assert.match(warn.join("\n"), /page 'roster' declares no cell-change for query 'members'/);
293
+ assert.match(warn.join("\n"), /page 'roster' declares no cellChange for 'members'/);
269
294
  });
270
295
  it("hands the operation its key, cell and value, then runs its refresh set", async () => {
271
296
  let where;
@@ -273,7 +298,7 @@ describe("a cell-change", () => {
273
298
  const hub = createHub(pages({
274
299
  roster: {
275
300
  queries: { members: stub(log, "members", { n: "1" }) },
276
- hooks: {
301
+ requests: {
277
302
  crud: {
278
303
  members: {
279
304
  cellChange: {
@@ -285,7 +310,7 @@ describe("a cell-change", () => {
285
310
  },
286
311
  },
287
312
  }));
288
- const data = await hub.runCellChange("roster", { query: "members", key: "7", cell: "role", value: "lead" }, context());
313
+ const data = await hub.runCellChange("roster", { list: "members", key: "7", cell: "role", value: "lead" }, context());
289
314
  assert.deepEqual(where, { key: "7", cell: "role", value: "lead" });
290
315
  assert.deepEqual(log, ["members"]);
291
316
  assert.deepEqual(data, { members: { n: "1" } });
@@ -293,8 +318,8 @@ describe("a cell-change", () => {
293
318
  it("lays what it stated over what the refresh produced", async () => {
294
319
  const hub = createHub(pages({
295
320
  roster: {
296
- queries: { members: () => rows([{ id: "1" }, { id: "2" }]) },
297
- hooks: {
321
+ queries: { members: list(() => [{ id: "1" }, { id: "2" }]) },
322
+ requests: {
298
323
  crud: {
299
324
  members: {
300
325
  cellChange: {
@@ -308,29 +333,29 @@ describe("a cell-change", () => {
308
333
  },
309
334
  },
310
335
  }));
311
- const data = await hub.runCellChange("roster", { query: "members", key: "2", cell: "name", value: "Grace" }, context());
336
+ const data = await hub.runCellChange("roster", { list: "members", key: "2", cell: "name", value: "Grace" }, context());
312
337
  assert.deepEqual(data, {
313
- members: { op: "patch", rows: [{ id: "2", name: "Grace" }] },
338
+ members: { rows: [{ id: "2", name: "Grace" }] },
314
339
  });
315
340
  });
316
341
  });
317
- // --- Tuple delete ------------------------------------------------------
318
- describe("a tuple-delete", () => {
342
+ // --- Row delete ------------------------------------------------------
343
+ describe("a row-delete", () => {
319
344
  it("is refused unless the query declared it", async () => {
320
- const hub = createHub(pages({ roster: { queries: { members: () => rows([]) } } }));
321
- const { result, warn } = await captured(() => hub.runTupleDelete("roster", { query: "members", key: "1" }, context()));
345
+ const hub = createHub(pages({ roster: { queries: { members: list(() => []) } } }));
346
+ const { result, warn } = await captured(() => hub.runRowDelete("roster", { list: "members", key: "1" }, context()));
322
347
  assert.deepEqual(result, {});
323
- assert.match(warn.join("\n"), /page 'roster' declares no tuple-delete for query 'members'/);
348
+ assert.match(warn.join("\n"), /page 'roster' declares no rowDelete for 'members'/);
324
349
  });
325
350
  it("hands the operation its key, then runs its refresh set", async () => {
326
351
  let where;
327
352
  const hub = createHub(pages({
328
353
  roster: {
329
- queries: { members: () => rows([]) },
330
- hooks: {
354
+ queries: { members: list(() => []) },
355
+ requests: {
331
356
  crud: {
332
357
  members: {
333
- tupleDelete: {
358
+ rowDelete: {
334
359
  run: (_ctx, w) => void (where = w),
335
360
  refresh: [],
336
361
  },
@@ -339,17 +364,17 @@ describe("a tuple-delete", () => {
339
364
  },
340
365
  },
341
366
  }));
342
- await hub.runTupleDelete("roster", { query: "members", key: "7" }, context());
367
+ await hub.runRowDelete("roster", { list: "members", key: "7" }, context());
343
368
  assert.deepEqual(where, { key: "7" });
344
369
  });
345
370
  it("answers with the drop it stated", async () => {
346
371
  const hub = createHub(pages({
347
372
  roster: {
348
- queries: { members: () => rows([{ id: "1" }, { id: "2" }]) },
349
- hooks: {
373
+ queries: { members: list(() => [{ id: "1" }, { id: "2" }]) },
374
+ requests: {
350
375
  crud: {
351
376
  members: {
352
- tupleDelete: {
377
+ rowDelete: {
353
378
  run: (_ctx, w) => ({
354
379
  members: patch({ drop: [w.key] }),
355
380
  }),
@@ -360,27 +385,27 @@ describe("a tuple-delete", () => {
360
385
  },
361
386
  },
362
387
  }));
363
- const data = await hub.runTupleDelete("roster", { query: "members", key: "1" }, context());
364
- assert.deepEqual(data, { members: { op: "patch", drop: ["1"] } });
388
+ const data = await hub.runRowDelete("roster", { list: "members", key: "1" }, context());
389
+ assert.deepEqual(data, { members: { drop: ["1"] } });
365
390
  });
366
391
  });
367
- // --- Tuple insert --------------------------------------------------------
368
- describe("a tuple-insert", () => {
392
+ // --- Row insert --------------------------------------------------------
393
+ describe("a row-insert", () => {
369
394
  it("is refused unless the query declared it", async () => {
370
- const hub = createHub(pages({ roster: { queries: { members: () => rows([]) } } }));
371
- const { result, warn } = await captured(() => hub.runTupleInsert("roster", { query: "members", values: { name: "Ada" } }, context()));
395
+ const hub = createHub(pages({ roster: { queries: { members: list(() => []) } } }));
396
+ const { result, warn } = await captured(() => hub.runRowInsert("roster", { list: "members", values: { name: "Ada" } }, context()));
372
397
  assert.deepEqual(result, {});
373
- assert.match(warn.join("\n"), /page 'roster' declares no tuple-insert for query 'members'/);
398
+ assert.match(warn.join("\n"), /page 'roster' declares no rowInsert for 'members'/);
374
399
  });
375
400
  it("hands the operation the gathered values, then runs its refresh set", async () => {
376
401
  let where;
377
402
  const hub = createHub(pages({
378
403
  roster: {
379
- queries: { members: () => rows([]) },
380
- hooks: {
404
+ queries: { members: list(() => []) },
405
+ requests: {
381
406
  crud: {
382
407
  members: {
383
- tupleInsert: {
408
+ rowInsert: {
384
409
  run: (_ctx, w) => void (where = w),
385
410
  refresh: [],
386
411
  },
@@ -389,17 +414,17 @@ describe("a tuple-insert", () => {
389
414
  },
390
415
  },
391
416
  }));
392
- await hub.runTupleInsert("roster", { query: "members", values: { name: "Ada", role: "Analyst" } }, context());
417
+ await hub.runRowInsert("roster", { list: "members", values: { name: "Ada", role: "Analyst" } }, context());
393
418
  assert.deepEqual(where, { values: { name: "Ada", role: "Analyst" } });
394
419
  });
395
420
  it("answers with the row it stated", async () => {
396
421
  const hub = createHub(pages({
397
422
  roster: {
398
- queries: { members: () => rows([{ id: "1" }]) },
399
- hooks: {
423
+ queries: { members: list(() => [{ id: "1" }]) },
424
+ requests: {
400
425
  crud: {
401
426
  members: {
402
- tupleInsert: {
427
+ rowInsert: {
403
428
  run: (_ctx, w) => ({
404
429
  members: patch({ rows: [{ id: "2", ...w.values }] }),
405
430
  }),
@@ -410,29 +435,29 @@ describe("a tuple-insert", () => {
410
435
  },
411
436
  },
412
437
  }));
413
- const data = await hub.runTupleInsert("roster", { query: "members", values: { name: "Ada" } }, context());
438
+ const data = await hub.runRowInsert("roster", { list: "members", values: { name: "Ada" } }, context());
414
439
  assert.deepEqual(data, {
415
- members: { op: "patch", rows: [{ id: "2", name: "Ada" }] },
440
+ members: { rows: [{ id: "2", name: "Ada" }] },
416
441
  });
417
442
  });
418
443
  });
419
- // --- Tuple update ----------------------------------------------------------
420
- describe("a tuple-update", () => {
444
+ // --- Row update ----------------------------------------------------------
445
+ describe("a row-update", () => {
421
446
  it("is refused unless the query declared it", async () => {
422
- const hub = createHub(pages({ roster: { queries: { members: () => rows([]) } } }));
423
- const { result, warn } = await captured(() => hub.runTupleUpdate("roster", { query: "members", key: "1", values: { name: "Ada" } }, context()));
447
+ const hub = createHub(pages({ roster: { queries: { members: list(() => []) } } }));
448
+ const { result, warn } = await captured(() => hub.runRowUpdate("roster", { list: "members", key: "1", values: { name: "Ada" } }, context()));
424
449
  assert.deepEqual(result, {});
425
- assert.match(warn.join("\n"), /page 'roster' declares no tuple-update for query 'members'/);
450
+ assert.match(warn.join("\n"), /page 'roster' declares no rowUpdate for 'members'/);
426
451
  });
427
452
  it("hands the operation the key and the gathered values, then runs its refresh set", async () => {
428
453
  let where;
429
454
  const hub = createHub(pages({
430
455
  roster: {
431
- queries: { members: () => rows([]) },
432
- hooks: {
456
+ queries: { members: list(() => []) },
457
+ requests: {
433
458
  crud: {
434
459
  members: {
435
- tupleUpdate: {
460
+ rowUpdate: {
436
461
  run: (_ctx, w) => void (where = w),
437
462
  refresh: [],
438
463
  },
@@ -441,7 +466,7 @@ describe("a tuple-update", () => {
441
466
  },
442
467
  },
443
468
  }));
444
- await hub.runTupleUpdate("roster", { query: "members", key: "7", values: { name: "Ada", role: "Analyst" } }, context());
469
+ await hub.runRowUpdate("roster", { list: "members", key: "7", values: { name: "Ada", role: "Analyst" } }, context());
445
470
  assert.deepEqual(where, {
446
471
  key: "7",
447
472
  values: { name: "Ada", role: "Analyst" },
@@ -450,11 +475,11 @@ describe("a tuple-update", () => {
450
475
  it("answers with the row it stated", async () => {
451
476
  const hub = createHub(pages({
452
477
  roster: {
453
- queries: { members: () => rows([{ id: "1", name: "old" }]) },
454
- hooks: {
478
+ queries: { members: list(() => [{ id: "1", name: "old" }]) },
479
+ requests: {
455
480
  crud: {
456
481
  members: {
457
- tupleUpdate: {
482
+ rowUpdate: {
458
483
  run: (_ctx, w) => ({
459
484
  members: patch({ rows: [{ id: w.key, ...w.values }] }),
460
485
  }),
@@ -465,31 +490,57 @@ describe("a tuple-update", () => {
465
490
  },
466
491
  },
467
492
  }));
468
- const data = await hub.runTupleUpdate("roster", { query: "members", key: "1", values: { name: "Ada" } }, context());
493
+ const data = await hub.runRowUpdate("roster", { list: "members", key: "1", values: { name: "Ada" } }, context());
469
494
  assert.deepEqual(data, {
470
- members: { op: "patch", rows: [{ id: "1", name: "Ada" }] },
495
+ members: { rows: [{ id: "1", name: "Ada" }] },
471
496
  });
472
497
  });
473
498
  });
474
499
  // --- The wire vocabulary -------------------------------------------------
475
- describe("the projection helpers", () => {
476
- it("name the whole set", () => {
477
- assert.deepEqual(rows([{ id: "1" }]), { op: "rows", rows: [{ id: "1" }] });
478
- });
479
- it("name only what changed, and carry nothing they were not given", () => {
480
- assert.deepEqual(patch({ drop: ["1"] }), { op: "patch", drop: ["1"] });
481
- assert.deepEqual(patch({}), { op: "patch" });
482
- });
483
- it("are what isProjection recognises", () => {
484
- assert.equal(isProjection(rows([])), true);
485
- assert.equal(isProjection(patch({})), true);
486
- assert.equal(isProjection({ id: "1", name: "Ada" }), false);
487
- });
488
- it("share one word with a tuple's cells, which reserves it", () => {
489
- // Stated in core/lb-types.ts: `op` is a reserved cell name, because a
490
- // tuple carrying one whose value is 'rows' or 'patch' is read as a
491
- // projection. The hazard is real and this is where it is written down.
492
- assert.equal(isProjection({ op: "rows" }), true);
493
- assert.equal(isProjection({ op: "deleted" }), false);
500
+ describe("cardinality is declared, not inspected", () => {
501
+ it("marks a list query and a row query apart", () => {
502
+ assert.equal(list(() => []).kind, "list");
503
+ assert.equal(row(() => ({})).kind, "row");
504
+ });
505
+ it("carries a whole set as the array itself, with no wrapper", async () => {
506
+ const set = [{ id: "1" }, { id: "2" }];
507
+ const hub = createHub(pages({ roster: { queries: { members: list(() => set) } } }));
508
+ assert.deepEqual(await hub.dataForPage("roster", context()), {
509
+ members: set,
510
+ });
511
+ });
512
+ it("carries only what changed as an object, and adds nothing", () => {
513
+ assert.deepEqual(patch({ drop: ["1"] }), { drop: ["1"] });
514
+ assert.deepEqual(patch({}), {});
515
+ });
516
+ it("tells a whole set from a patch by Array.isArray, so no column is reserved", () => {
517
+ // `op` used to be a reserved column name, because the hub read it to
518
+ // decide which shape had arrived. Nothing inspects a value now.
519
+ assert.equal(Array.isArray(patch({ rows: [] })), false);
520
+ assert.equal(Array.isArray([{ op: "rows" }]), true);
521
+ });
522
+ it("refuses a list query that answers with one row", async () => {
523
+ const hub = createHub(pages({
524
+ roster: {
525
+ queries: {
526
+ members: list(() => ({})),
527
+ },
528
+ },
529
+ }));
530
+ const { result, warn } = await captured(() => hub.dataForPage("roster", context()));
531
+ assert.deepEqual(result, {});
532
+ assert.match(warn.join("\n"), /query 'members' is declared list but answered with one row/);
533
+ });
534
+ it("refuses a row query that answers with rows", async () => {
535
+ const hub = createHub(pages({
536
+ roster: {
537
+ queries: {
538
+ chosen: row(() => []),
539
+ },
540
+ },
541
+ }));
542
+ const { result, warn } = await captured(() => hub.dataForPage("roster", context()));
543
+ assert.deepEqual(result, {});
544
+ assert.match(warn.join("\n"), /query 'chosen' is declared row but answered with rows/);
494
545
  });
495
546
  });
@@ -91,7 +91,7 @@ describe("scanOrigin", () => {
91
91
  const dir = await tree({
92
92
  "chrome.html": "<lb-hub><main></main></lb-hub>",
93
93
  "about.page.html": "<h1>About</h1>",
94
- "about.hooks.ts": "export const hooks = {};",
94
+ "about.requests.ts": "export const requests = {};",
95
95
  "imports.ts": "export default {};",
96
96
  });
97
97
  try {
@@ -118,6 +118,24 @@ describe("scanOrigin", () => {
118
118
  await rm(dir, { recursive: true, force: true });
119
119
  }
120
120
  });
121
+ it("throws when a definition filename carries capitals", async () => {
122
+ const dir = await tree({ "App-Box.html": "<div></div>" });
123
+ try {
124
+ await assert.rejects(() => scanOrigin({ name: "src", dirs: [dir] }), /carries capitals.*must be named 'app-box\.html'/);
125
+ }
126
+ finally {
127
+ await rm(dir, { recursive: true, force: true });
128
+ }
129
+ });
130
+ it("throws when a widget script filename carries capitals", async () => {
131
+ const dir = await tree({ "App-Box.browser.ts": "export {};" });
132
+ try {
133
+ await assert.rejects(() => scanOrigin({ name: "src", dirs: [dir] }), /must be named 'app-box\.browser\.ts'/);
134
+ }
135
+ finally {
136
+ await rm(dir, { recursive: true, force: true });
137
+ }
138
+ });
121
139
  it("throws when one origin has two definitions for the same tag", async () => {
122
140
  const dir = await tree({
123
141
  "a/app-box.html": "<div></div>",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Discovering `*.hooks.ts`/`*.queries.ts` pairs (build/locations.ts) and
2
+ * Discovering `*.requests.ts`/`*.queries.ts` pairs (build/locations.ts) and
3
3
  * generating the hub registry from them (build/pages.ts).
4
4
  */
5
5
  export {};