@loadbare/app 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/lb-constants.d.ts +1 -2
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +13 -12
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +4 -10
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +47 -24
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +2 -7
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +8 -15
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +0 -3
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +21 -13
- package/docs/analysis-closed-set.md +16 -9
- package/docs/comparison.md +7 -6
- package/docs/reference/custom-elements.md +11 -6
- package/docs/reference/data-binding.md +36 -12
- package/docs/reference/page-files.md +13 -9
- package/docs/reference/widgets.md +2 -2
- package/docs/roadmap.md +1 -1
- package/docs/testing.md +5 -1
- package/docs/theory.md +1 -1
- package/docs/tutorials/080-widget-requests.md +9 -28
- package/package.json +1 -1
|
@@ -94,17 +94,13 @@ export interface CrudOp<W> {
|
|
|
94
94
|
run: (ctx: HubContext, where: W) => void | HubData | Promise<void | HubData>;
|
|
95
95
|
refresh: string[];
|
|
96
96
|
}
|
|
97
|
-
export type CellChangeOp = CrudOp<{
|
|
98
|
-
key: string;
|
|
99
|
-
cell: string;
|
|
100
|
-
value: string;
|
|
101
|
-
}>;
|
|
102
97
|
export type RowDeleteOp = CrudOp<{
|
|
103
98
|
key: string;
|
|
104
99
|
}>;
|
|
105
100
|
export type RowInsertOp = CrudOp<{
|
|
106
101
|
values: Record<string, string>;
|
|
107
102
|
}>;
|
|
103
|
+
/** `values` holds only the columns being set; see `Hub.runRowUpdate`. */
|
|
108
104
|
export type RowUpdateOp = CrudOp<{
|
|
109
105
|
key: string;
|
|
110
106
|
values: Record<string, string>;
|
|
@@ -117,11 +113,10 @@ export type RowUpdateOp = CrudOp<{
|
|
|
117
113
|
*
|
|
118
114
|
* Every key here is the reserved `lb-action` value with the prefix stripped
|
|
119
115
|
* and the rest camel-cased, so the attribute, the wire field and this key
|
|
120
|
-
* are one vocabulary. All
|
|
116
|
+
* are one vocabulary. All three are list operations: each needs a key, and a
|
|
121
117
|
* key exists only on a live row inside a list.
|
|
122
118
|
*/
|
|
123
119
|
export interface Crud {
|
|
124
|
-
cellChange?: CellChangeOp;
|
|
125
120
|
rowDelete?: RowDeleteOp;
|
|
126
121
|
rowInsert?: RowInsertOp;
|
|
127
122
|
rowUpdate?: RowUpdateOp;
|
|
@@ -165,13 +160,6 @@ export interface Hub {
|
|
|
165
160
|
* set declared with it.
|
|
166
161
|
*/
|
|
167
162
|
runAction(page: string, name: string, where: Where, ctx: HubContext): Promise<HubData>;
|
|
168
|
-
/** Edit one cell: run the list's declared `cellChange`, then its refresh set. */
|
|
169
|
-
runCellChange(page: string, where: {
|
|
170
|
-
list: string;
|
|
171
|
-
key: string;
|
|
172
|
-
cell: string;
|
|
173
|
-
value: string;
|
|
174
|
-
}, ctx: HubContext): Promise<HubData>;
|
|
175
163
|
/** Drop one row: run the list's declared `rowDelete`, then its refresh set. */
|
|
176
164
|
runRowDelete(page: string, where: {
|
|
177
165
|
list: string;
|
|
@@ -182,7 +170,12 @@ export interface Hub {
|
|
|
182
170
|
list: string;
|
|
183
171
|
values: Record<string, string>;
|
|
184
172
|
}, ctx: HubContext): Promise<HubData>;
|
|
185
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* Edit a row: run the list's declared `rowUpdate`, then its refresh set.
|
|
175
|
+
* `values` holds only the columns being set — one from a widget that
|
|
176
|
+
* commits a cell, several from a form — and a column absent from it is left
|
|
177
|
+
* as it is, as an SQL UPDATE leaves it.
|
|
178
|
+
*/
|
|
186
179
|
runRowUpdate(page: string, where: {
|
|
187
180
|
list: string;
|
|
188
181
|
key: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAG1E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,UAAU;CAAG;AAE9B;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACnE;AAED,yCAAyC;AACzC,wBAAgB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,KAAK,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,KAAK,CAEtE;AAED,iDAAiD;AACjD,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,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;;;;;;;GAOG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,CACH,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,KAAK,KACT,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
|
package/dist/server/lb-server.js
CHANGED
|
@@ -100,9 +100,6 @@ export function createHub(pages) {
|
|
|
100
100
|
runAction(page, name, where, ctx) {
|
|
101
101
|
return settle(page, pages[page]?.requests.actions?.[name], where, ctx, `loadbare: page '${page}' declares no action '${name}'`);
|
|
102
102
|
},
|
|
103
|
-
runCellChange(page, where, ctx) {
|
|
104
|
-
return settle(page, pages[page]?.requests.crud?.[where.list]?.cellChange, { key: where.key, cell: where.cell, value: where.value }, ctx, `loadbare: page '${page}' declares no cellChange for '${where.list}'`);
|
|
105
|
-
},
|
|
106
103
|
runRowDelete(page, where, ctx) {
|
|
107
104
|
return settle(page, pages[page]?.requests.crud?.[where.list]?.rowDelete, { key: where.key }, ctx, `loadbare: page '${page}' declares no rowDelete for '${where.list}'`);
|
|
108
105
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.js","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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","sourcesContent":["// The server-side contract and the engine that runs it.\n//\n// This is the counterpart to core/lb-constants.ts: that file is the\n// vocabulary the browser writes, this one is the vocabulary the application\n// writes. The engine below is the whole of Hub on the server. It ships no\n// HTTP server, no router, and no data layer.\n\nimport type { Patch, Row, HubData, HubResult } from \"../core/lb-types.js\";\nimport { LB_RESERVED_PREFIX } from \"../core/lb-constants.js\";\n\n/**\n * Whatever the application hands the engine for the duration of one request.\n *\n * Hub declares it empty and never reads it. An application fills it in by\n * declaration merging, once, anywhere in its own source:\n *\n * declare module \"@loadbare/app/server\" {\n * interface HubContext {\n * db: Db;\n * }\n * }\n *\n * That is why no type on this page takes a type parameter. The context is a\n * request-scoped handle — an authenticated database connection is the\n * expected case — so it is passed per call rather than held by the engine.\n */\nexport interface HubContext {}\n\n/**\n * A declared query: the request context in, one result out.\n *\n * Cardinality is a property of the name rather than of any one answer, so it\n * is declared here and never inferred from what comes back. One name answers\n * with one shape, always. A page that wants the roster once as a single row\n * and once as a set declares two queries.\n *\n * Write one with `row()` or `list()` below; nothing else builds one.\n */\nexport interface Query {\n readonly kind: \"row\" | \"list\";\n readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;\n}\n\n/** A query that answers with one row. */\nexport function row(run: (ctx: HubContext) => Row | Promise<Row>): Query {\n return { kind: \"row\", run };\n}\n\n/**\n * A query that answers with the entire set, and therefore also the order. A\n * list reconciles to exactly this: a row whose key is not here is gone.\n *\n * There is no wrapper around the array, because the declaration already said\n * this name answers with rows.\n */\nexport function list(run: (ctx: HubContext) => Row[] | Promise<Row[]>): Query {\n return { kind: \"list\", run };\n}\n\n/**\n * Only what changed, returned from a `crud` run rather than from a query.\n * Rows named here arrive or are updated, keys in `drop` are gone, and\n * everything unnamed is left alone — its contents, and its place in whatever\n * order the widget is keeping.\n *\n * `Array.isArray` is what tells a patch from a whole set, so a patch needs no\n * marker of its own and no column name is reserved to carry one.\n */\nexport function patch(change: { rows?: Row[]; drop?: string[] }): Patch {\n return { ...change };\n}\n\n/** The shape of a `<name>.queries.ts` module. */\nexport type Queries = Record<string, Query>;\n\n/**\n * Where the interaction happened, in the binding vocabulary, plus the one\n * value a control may carry.\n *\n * The browser fills these from attributes it already has. It never names a\n * function — only a name the page declared — which is what keeps this from\n * being an RPC endpoint. A value may ride along because a `<select>` has one\n * and there is nowhere else to put it; it is a string from a control, not an\n * argument list.\n */\nexport interface Where {\n list?: string;\n row?: string;\n key?: string;\n cell?: string;\n value?: string;\n}\n\n/**\n * What an action does, and which queries must re-run once it has.\n *\n * `run` may also return results of its own, which are laid over the refreshed\n * ones. That is how a patch reaches the browser: a query answers for its\n * whole set and cannot know why it was re-run, but the action knows exactly\n * what it changed and can say only that.\n */\nexport interface Action {\n run: (\n ctx: HubContext,\n where: Where,\n ) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\n/**\n * One CRUD operation on a declared query. Same shape as `Action` — run, then\n * refresh — but `where` carries only what that operation is typed to carry\n * on the wire, rather than the general `Where`.\n */\nexport interface CrudOp<W> {\n run: (ctx: HubContext, where: W) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\nexport type CellChangeOp = CrudOp<{ key: string; cell: string; value: string }>;\nexport type RowDeleteOp = CrudOp<{ key: string }>;\nexport type RowInsertOp = CrudOp<{ values: Record<string, string> }>;\nexport type RowUpdateOp = CrudOp<{\n key: string;\n values: Record<string, string>;\n}>;\n\n/**\n * The CRUD operations declared for one list, keyed by its name in\n * `Requests.crud`. A name with no entry here permits none of them — the wire\n * cannot reach anything the page has not published, exactly as for a named\n * action.\n *\n * Every key here is the reserved `lb-action` value with the prefix stripped\n * and the rest camel-cased, so the attribute, the wire field and this key\n * are one vocabulary. All four are list operations: each needs a key, and a\n * key exists only on a live row inside a list.\n */\nexport interface Crud {\n cellChange?: CellChangeOp;\n rowDelete?: RowDeleteOp;\n rowInsert?: RowInsertOp;\n rowUpdate?: RowUpdateOp;\n}\n\n/**\n * The shape of a `<name>.requests.ts` module.\n *\n * `onPageEnter` runs once when the page is entered, before any query. It\n * declares no refresh set: entering the page runs the whole query set\n * afterward, so whatever the hook changed is already in the response.\n *\n * `actions` names what this page may be asked to do. A name the page did not\n * declare is refused, so the wire cannot reach anything the page has not\n * published.\n *\n * `crud` is the same rule for the typed CRUD operations, keyed by the list\n * they operate on rather than by a declared name — there is nothing to name,\n * since the row's own binding says what it is.\n */\nexport interface Requests {\n onPageEnter?: (ctx: HubContext) => void | Promise<void>;\n actions?: Record<string, Action>;\n crud?: Record<string, Crud>;\n}\n\n/** A page is three files sharing a basename; two of them are these. */\nexport interface Page {\n queries: Queries;\n requests: Requests;\n}\n\n/**\n * The page registry. The builder populates this automatically from every\n * `.requests.ts`/`.queries.ts` pair it discovers — see docs/reference/builder.md,\n * \"Generating the server-side page registry\".\n */\nexport type Pages = Record<string, Page>;\n\nexport interface Hub {\n /** Entering a page: run its onPageEnter hook, then all of its queries. */\n dataForPage(page: string, ctx: HubContext): Promise<HubData>;\n\n /**\n * An action: run what the page declared under that name, then the refresh\n * set declared with it.\n */\n runAction(\n page: string,\n name: string,\n where: Where,\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Edit one cell: run the list's declared `cellChange`, then its refresh set. */\n runCellChange(\n page: string,\n where: { list: string; key: string; cell: string; value: string },\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Drop one row: run the list's declared `rowDelete`, then its refresh set. */\n runRowDelete(\n page: string,\n where: { list: string; key: string },\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Add one row: run the list's declared `rowInsert`, then its refresh set. */\n runRowInsert(\n page: string,\n where: { list: string; values: Record<string, string> },\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Edit several cells at once: run the list's declared `rowUpdate`, then its refresh set. */\n runRowUpdate(\n page: string,\n where: { list: string; key: string; values: Record<string, string> },\n ctx: HubContext,\n ): Promise<HubData>;\n}\n\n/**\n * Build the engine over a set of pages. The pages are fixed at startup; the\n * context is not, and arrives with each call.\n */\nexport function createHub(pages: Pages): Hub {\n // Names beginning with the reserved prefix are Loadbare's — the hub's\n // own query, the CRUD operations — so an application cannot declare one.\n // Refused at startup, because a name is a fact about the page and not\n // about any request.\n for (const [page, entry] of Object.entries(pages)) {\n const declared = [\n ...Object.keys(entry.queries),\n ...Object.keys(entry.requests.actions ?? {}),\n ];\n for (const name of declared) {\n if (name.startsWith(LB_RESERVED_PREFIX)) {\n throw new Error(\n `loadbare: page '${page}' declares '${name}', but names ` +\n `beginning with '${LB_RESERVED_PREFIX}' are reserved`,\n );\n }\n }\n }\n\n async function run(\n page: string,\n names: string[],\n ctx: HubContext,\n ): Promise<HubData> {\n const data: HubData = {};\n for (const name of names) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const result = await query.run(ctx);\n // Cardinality is declared, so an answer that disagrees is a mistake in\n // the query rather than a case to handle. Refused here, because a page\n // is better off missing one name than showing the wrong shape for it.\n if (Array.isArray(result) !== (query.kind === \"list\")) {\n console.warn(\n `loadbare: query '${name}' is declared ${query.kind} but answered ` +\n `with ${Array.isArray(result) ? \"rows\" : \"one row\"}`,\n );\n continue;\n }\n data[name] = result;\n }\n return data;\n }\n\n /**\n * Shared by every operation kind: run what was declared, then its refresh\n * set against the same context, laying what the operation itself stated\n * over the refreshed queries — the narrower answer wins because it is the\n * one that knows what actually changed.\n */\n async function settle<W>(\n page: string,\n declared: CrudOp<W> | Action | undefined,\n where: W,\n ctx: HubContext,\n notFound: string,\n ): Promise<HubData> {\n if (!declared) {\n console.warn(notFound);\n return {};\n }\n const stated = await declared.run(ctx, where as never);\n return { ...(await run(page, declared.refresh, ctx)), ...(stated ?? {}) };\n }\n\n return {\n async dataForPage(page, ctx) {\n const entry = pages[page];\n if (!entry) {\n console.warn(`loadbare: no page '${page}'`);\n return {};\n }\n await entry.requests.onPageEnter?.(ctx);\n return run(page, Object.keys(entry.queries), ctx);\n },\n\n runAction(page, name, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.actions?.[name],\n where,\n ctx,\n `loadbare: page '${page}' declares no action '${name}'`,\n );\n },\n\n runCellChange(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.cellChange,\n { key: where.key, cell: where.cell, value: where.value },\n ctx,\n `loadbare: page '${page}' declares no cellChange for '${where.list}'`,\n );\n },\n\n runRowDelete(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowDelete,\n { key: where.key },\n ctx,\n `loadbare: page '${page}' declares no rowDelete for '${where.list}'`,\n );\n },\n\n runRowInsert(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowInsert,\n { values: where.values },\n ctx,\n `loadbare: page '${page}' declares no rowInsert for '${where.list}'`,\n );\n },\n\n runRowUpdate(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowUpdate,\n { key: where.key, values: where.values },\n ctx,\n `loadbare: page '${page}' declares no rowUpdate for '${where.list}'`,\n );\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lb-server.js","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,EAAE;AACF,oEAAoE;AACpE,4EAA4E;AAC5E,0EAA0E;AAC1E,6CAA6C;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAmC7D,yCAAyC;AACzC,MAAM,UAAU,GAAG,CAAC,GAA4C;IAC9D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAAC,GAAgD;IACnE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,KAAK,CAAC,MAAyC;IAC7D,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;AACvB,CAAC;AAsJD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAY;IACpC,sEAAsE;IACtE,yEAAyE;IACzE,sEAAsE;IACtE,qBAAqB;IACrB,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG;YACf,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAC7B,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;SAC7C,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,eAAe,IAAI,eAAe;oBACvD,mBAAmB,kBAAkB,gBAAgB,CACxD,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,GAAG,CAChB,IAAY,EACZ,KAAe,EACf,GAAe;QAEf,MAAM,IAAI,GAAY,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,mBAAmB,IAAI,wBAAwB,IAAI,GAAG,CAAC,CAAC;gBACrE,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,uEAAuE;YACvE,uEAAuE;YACvE,sEAAsE;YACtE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;gBACtD,OAAO,CAAC,IAAI,CACV,oBAAoB,IAAI,iBAAiB,KAAK,CAAC,IAAI,gBAAgB;oBACjE,QAAQ,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,CACvD,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QACtB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,KAAK,UAAU,MAAM,CACnB,IAAY,EACZ,QAAwC,EACxC,KAAQ,EACR,GAAe,EACf,QAAgB;QAEhB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAc,CAAC,CAAC;QACvD,OAAO,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;IAED,OAAO;QACL,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG;YACzB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,sBAAsB,IAAI,GAAG,CAAC,CAAC;gBAC5C,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;YACxC,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QAED,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG;YAC9B,OAAO,MAAM,CACX,IAAI,EACJ,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EACrC,KAAK,EACL,GAAG,EACH,mBAAmB,IAAI,yBAAyB,IAAI,GAAG,CACxD,CAAC;QACJ,CAAC;QAED,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG;YAC3B,OAAO,MAAM,CACX,IAAI,EACJ,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EACnD,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAClB,GAAG,EACH,mBAAmB,IAAI,gCAAgC,KAAK,CAAC,IAAI,GAAG,CACrE,CAAC;QACJ,CAAC;QAED,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG;YAC3B,OAAO,MAAM,CACX,IAAI,EACJ,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EACnD,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EACxB,GAAG,EACH,mBAAmB,IAAI,gCAAgC,KAAK,CAAC,IAAI,GAAG,CACrE,CAAC;QACJ,CAAC;QAED,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG;YAC3B,OAAO,MAAM,CACX,IAAI,EACJ,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EACnD,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EACxC,GAAG,EACH,mBAAmB,IAAI,gCAAgC,KAAK,CAAC,IAAI,GAAG,CACrE,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// The server-side contract and the engine that runs it.\n//\n// This is the counterpart to core/lb-constants.ts: that file is the\n// vocabulary the browser writes, this one is the vocabulary the application\n// writes. The engine below is the whole of Hub on the server. It ships no\n// HTTP server, no router, and no data layer.\n\nimport type { Patch, Row, HubData, HubResult } from \"../core/lb-types.js\";\nimport { LB_RESERVED_PREFIX } from \"../core/lb-constants.js\";\n\n/**\n * Whatever the application hands the engine for the duration of one request.\n *\n * Hub declares it empty and never reads it. An application fills it in by\n * declaration merging, once, anywhere in its own source:\n *\n * declare module \"@loadbare/app/server\" {\n * interface HubContext {\n * db: Db;\n * }\n * }\n *\n * That is why no type on this page takes a type parameter. The context is a\n * request-scoped handle — an authenticated database connection is the\n * expected case — so it is passed per call rather than held by the engine.\n */\nexport interface HubContext {}\n\n/**\n * A declared query: the request context in, one result out.\n *\n * Cardinality is a property of the name rather than of any one answer, so it\n * is declared here and never inferred from what comes back. One name answers\n * with one shape, always. A page that wants the roster once as a single row\n * and once as a set declares two queries.\n *\n * Write one with `row()` or `list()` below; nothing else builds one.\n */\nexport interface Query {\n readonly kind: \"row\" | \"list\";\n readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;\n}\n\n/** A query that answers with one row. */\nexport function row(run: (ctx: HubContext) => Row | Promise<Row>): Query {\n return { kind: \"row\", run };\n}\n\n/**\n * A query that answers with the entire set, and therefore also the order. A\n * list reconciles to exactly this: a row whose key is not here is gone.\n *\n * There is no wrapper around the array, because the declaration already said\n * this name answers with rows.\n */\nexport function list(run: (ctx: HubContext) => Row[] | Promise<Row[]>): Query {\n return { kind: \"list\", run };\n}\n\n/**\n * Only what changed, returned from a `crud` run rather than from a query.\n * Rows named here arrive or are updated, keys in `drop` are gone, and\n * everything unnamed is left alone — its contents, and its place in whatever\n * order the widget is keeping.\n *\n * `Array.isArray` is what tells a patch from a whole set, so a patch needs no\n * marker of its own and no column name is reserved to carry one.\n */\nexport function patch(change: { rows?: Row[]; drop?: string[] }): Patch {\n return { ...change };\n}\n\n/** The shape of a `<name>.queries.ts` module. */\nexport type Queries = Record<string, Query>;\n\n/**\n * Where the interaction happened, in the binding vocabulary, plus the one\n * value a control may carry.\n *\n * The browser fills these from attributes it already has. It never names a\n * function — only a name the page declared — which is what keeps this from\n * being an RPC endpoint. A value may ride along because a `<select>` has one\n * and there is nowhere else to put it; it is a string from a control, not an\n * argument list.\n */\nexport interface Where {\n list?: string;\n row?: string;\n key?: string;\n cell?: string;\n value?: string;\n}\n\n/**\n * What an action does, and which queries must re-run once it has.\n *\n * `run` may also return results of its own, which are laid over the refreshed\n * ones. That is how a patch reaches the browser: a query answers for its\n * whole set and cannot know why it was re-run, but the action knows exactly\n * what it changed and can say only that.\n */\nexport interface Action {\n run: (\n ctx: HubContext,\n where: Where,\n ) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\n/**\n * One CRUD operation on a declared query. Same shape as `Action` — run, then\n * refresh — but `where` carries only what that operation is typed to carry\n * on the wire, rather than the general `Where`.\n */\nexport interface CrudOp<W> {\n run: (ctx: HubContext, where: W) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\nexport type RowDeleteOp = CrudOp<{ key: string }>;\nexport type RowInsertOp = CrudOp<{ values: Record<string, string> }>;\n/** `values` holds only the columns being set; see `Hub.runRowUpdate`. */\nexport type RowUpdateOp = CrudOp<{\n key: string;\n values: Record<string, string>;\n}>;\n\n/**\n * The CRUD operations declared for one list, keyed by its name in\n * `Requests.crud`. A name with no entry here permits none of them — the wire\n * cannot reach anything the page has not published, exactly as for a named\n * action.\n *\n * Every key here is the reserved `lb-action` value with the prefix stripped\n * and the rest camel-cased, so the attribute, the wire field and this key\n * are one vocabulary. All three are list operations: each needs a key, and a\n * key exists only on a live row inside a list.\n */\nexport interface Crud {\n rowDelete?: RowDeleteOp;\n rowInsert?: RowInsertOp;\n rowUpdate?: RowUpdateOp;\n}\n\n/**\n * The shape of a `<name>.requests.ts` module.\n *\n * `onPageEnter` runs once when the page is entered, before any query. It\n * declares no refresh set: entering the page runs the whole query set\n * afterward, so whatever the hook changed is already in the response.\n *\n * `actions` names what this page may be asked to do. A name the page did not\n * declare is refused, so the wire cannot reach anything the page has not\n * published.\n *\n * `crud` is the same rule for the typed CRUD operations, keyed by the list\n * they operate on rather than by a declared name — there is nothing to name,\n * since the row's own binding says what it is.\n */\nexport interface Requests {\n onPageEnter?: (ctx: HubContext) => void | Promise<void>;\n actions?: Record<string, Action>;\n crud?: Record<string, Crud>;\n}\n\n/** A page is three files sharing a basename; two of them are these. */\nexport interface Page {\n queries: Queries;\n requests: Requests;\n}\n\n/**\n * The page registry. The builder populates this automatically from every\n * `.requests.ts`/`.queries.ts` pair it discovers — see docs/reference/builder.md,\n * \"Generating the server-side page registry\".\n */\nexport type Pages = Record<string, Page>;\n\nexport interface Hub {\n /** Entering a page: run its onPageEnter hook, then all of its queries. */\n dataForPage(page: string, ctx: HubContext): Promise<HubData>;\n\n /**\n * An action: run what the page declared under that name, then the refresh\n * set declared with it.\n */\n runAction(\n page: string,\n name: string,\n where: Where,\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Drop one row: run the list's declared `rowDelete`, then its refresh set. */\n runRowDelete(\n page: string,\n where: { list: string; key: string },\n ctx: HubContext,\n ): Promise<HubData>;\n\n /** Add one row: run the list's declared `rowInsert`, then its refresh set. */\n runRowInsert(\n page: string,\n where: { list: string; values: Record<string, string> },\n ctx: HubContext,\n ): Promise<HubData>;\n\n /**\n * Edit a row: run the list's declared `rowUpdate`, then its refresh set.\n * `values` holds only the columns being set — one from a widget that\n * commits a cell, several from a form — and a column absent from it is left\n * as it is, as an SQL UPDATE leaves it.\n */\n runRowUpdate(\n page: string,\n where: { list: string; key: string; values: Record<string, string> },\n ctx: HubContext,\n ): Promise<HubData>;\n}\n\n/**\n * Build the engine over a set of pages. The pages are fixed at startup; the\n * context is not, and arrives with each call.\n */\nexport function createHub(pages: Pages): Hub {\n // Names beginning with the reserved prefix are Loadbare's — the hub's\n // own query, the CRUD operations — so an application cannot declare one.\n // Refused at startup, because a name is a fact about the page and not\n // about any request.\n for (const [page, entry] of Object.entries(pages)) {\n const declared = [\n ...Object.keys(entry.queries),\n ...Object.keys(entry.requests.actions ?? {}),\n ];\n for (const name of declared) {\n if (name.startsWith(LB_RESERVED_PREFIX)) {\n throw new Error(\n `loadbare: page '${page}' declares '${name}', but names ` +\n `beginning with '${LB_RESERVED_PREFIX}' are reserved`,\n );\n }\n }\n }\n\n async function run(\n page: string,\n names: string[],\n ctx: HubContext,\n ): Promise<HubData> {\n const data: HubData = {};\n for (const name of names) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const result = await query.run(ctx);\n // Cardinality is declared, so an answer that disagrees is a mistake in\n // the query rather than a case to handle. Refused here, because a page\n // is better off missing one name than showing the wrong shape for it.\n if (Array.isArray(result) !== (query.kind === \"list\")) {\n console.warn(\n `loadbare: query '${name}' is declared ${query.kind} but answered ` +\n `with ${Array.isArray(result) ? \"rows\" : \"one row\"}`,\n );\n continue;\n }\n data[name] = result;\n }\n return data;\n }\n\n /**\n * Shared by every operation kind: run what was declared, then its refresh\n * set against the same context, laying what the operation itself stated\n * over the refreshed queries — the narrower answer wins because it is the\n * one that knows what actually changed.\n */\n async function settle<W>(\n page: string,\n declared: CrudOp<W> | Action | undefined,\n where: W,\n ctx: HubContext,\n notFound: string,\n ): Promise<HubData> {\n if (!declared) {\n console.warn(notFound);\n return {};\n }\n const stated = await declared.run(ctx, where as never);\n return { ...(await run(page, declared.refresh, ctx)), ...(stated ?? {}) };\n }\n\n return {\n async dataForPage(page, ctx) {\n const entry = pages[page];\n if (!entry) {\n console.warn(`loadbare: no page '${page}'`);\n return {};\n }\n await entry.requests.onPageEnter?.(ctx);\n return run(page, Object.keys(entry.queries), ctx);\n },\n\n runAction(page, name, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.actions?.[name],\n where,\n ctx,\n `loadbare: page '${page}' declares no action '${name}'`,\n );\n },\n\n runRowDelete(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowDelete,\n { key: where.key },\n ctx,\n `loadbare: page '${page}' declares no rowDelete for '${where.list}'`,\n );\n },\n\n runRowInsert(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowInsert,\n { values: where.values },\n ctx,\n `loadbare: page '${page}' declares no rowInsert for '${where.list}'`,\n );\n },\n\n runRowUpdate(page, where, ctx) {\n return settle(\n page,\n pages[page]?.requests.crud?.[where.list]?.rowUpdate,\n { key: where.key, values: where.values },\n ctx,\n `loadbare: page '${page}' declares no rowUpdate for '${where.list}'`,\n );\n },\n };\n}\n"]}
|
package/docs/TECHREF-1.0.md
CHANGED
|
@@ -529,8 +529,7 @@ name, the same test that decides how a value lands — see
|
|
|
529
529
|
| -------------- | ------------------------------------------------- |
|
|
530
530
|
| lb-row-insert | a `<form>`, or a button in a `<tr>`, in a list |
|
|
531
531
|
| lb-row-delete | anything inside a live row |
|
|
532
|
-
| lb-row-update | a `<form>`,
|
|
533
|
-
| lb-cell-change | a widget wrapping one control |
|
|
532
|
+
| lb-row-update | a `<form>`, a button in a live row, or a widget cell |
|
|
534
533
|
| anything else | must be a named routine in the page's server code |
|
|
535
534
|
|
|
536
535
|
The wire format is not visible to the user, but uses the same lb-*
|
|
@@ -545,7 +544,6 @@ carries.
|
|
|
545
544
|
| `action` | Filled from scope | Required |
|
|
546
545
|
| ---------------- | ------------------------------ | ---------------------- |
|
|
547
546
|
| a declared name | `list` or `row`, `key`, `cell` | nothing |
|
|
548
|
-
| `lb-cell-change` | `list`, `key`, `cell` | all three, and `value` |
|
|
549
547
|
| `lb-row-insert` | `list` | `list`, and `values` |
|
|
550
548
|
| `lb-row-delete` | `list`, `key` | both |
|
|
551
549
|
| `lb-row-update` | `list`, `key` | both, and `values` |
|
|
@@ -556,7 +554,13 @@ way a control belongs to the form around it. `list` or `row` comes from the
|
|
|
556
554
|
nearest ancestor scope, `key` from the nearest live row inside that scope,
|
|
557
555
|
and `cell` from the dispatching element's own `lb-cell`.
|
|
558
556
|
A request missing a required field is not sent. The hub never fills
|
|
559
|
-
`value`. An `lb-row-insert` or `lb-row-update`
|
|
557
|
+
`value`. An `lb-row-insert` or `lb-row-update` from an element carrying
|
|
558
|
+
`lb-cell` is a record of one cell, the way a control has a value and a form
|
|
559
|
+
has values: `values` holds that cell alone, taken from the `value` the widget
|
|
560
|
+
sent or else read from the control it is or wraps, and `value` is not sent
|
|
561
|
+
beside it. A widget that sends a `value` from an element carrying no
|
|
562
|
+
`lb-cell` is refused, since nothing names the column. Any other
|
|
563
|
+
`lb-row-insert` or `lb-row-update` gathers the row it belongs
|
|
560
564
|
to: `values` holds every `lb-cell` with a control to read in the nearest
|
|
561
565
|
`<form>`, `<tr>` or live row around the dispatching element, itself
|
|
562
566
|
included, inside its scope. The cells are found the way a row lands, so a
|
|
@@ -569,10 +573,11 @@ own form; a live row counts because it is the row the key names. The scope
|
|
|
569
573
|
itself is never the row, since its cells belong to other rows. A request
|
|
570
574
|
from an element in no form or row is not sent, and the hub reports that its
|
|
571
575
|
cells belong in a `<form>`. A request that already carries `values` keeps
|
|
572
|
-
them, and one whose row has no cell to read is not sent. A click
|
|
573
|
-
native element that carries either operation and
|
|
574
|
-
holds cells
|
|
575
|
-
would send the row: put the action on a form or on a
|
|
576
|
+
them, and one whose row has no cell to read is not sent. A click on a
|
|
577
|
+
native element that carries either operation and is a cell or
|
|
578
|
+
holds cells is not sent, since clicking into one of its controls
|
|
579
|
+
would send the row: put the action on a form, on a button, or on a widget
|
|
580
|
+
that decides when its cell has changed.
|
|
576
581
|
|
|
577
582
|
A widget dispatches the action and, where it wraps a control, that control's
|
|
578
583
|
value.
|
|
@@ -581,7 +586,7 @@ value.
|
|
|
581
586
|
{ action: "lb-row-insert", list: "rosterList", values: {...} }
|
|
582
587
|
{ action: "lb-row-delete", list: "rosterList", key: "42" }
|
|
583
588
|
{ action: "lb-row-update", list: "rosterList", key: "42", values: {...} }
|
|
584
|
-
{ action: "lb-
|
|
589
|
+
{ action: "lb-row-update", list: "rosterList", key: "42", values: { name: "Ann" } }
|
|
585
590
|
{ action: "selectTab", row: "prefs", cell: "active_tab", value: "two" }
|
|
586
591
|
```
|
|
587
592
|
|
|
@@ -912,7 +917,7 @@ It has three optional keys.
|
|
|
912
917
|
| Key | Keyed by | Answers |
|
|
913
918
|
| ------------- | --------------------- | -------------------------------------------------- |
|
|
914
919
|
| `actions` | The `lb-action` value | Anything the page chooses to declare |
|
|
915
|
-
| `crud` | A query name | The
|
|
920
|
+
| `crud` | A query name | The three reserved `lb-action` values |
|
|
916
921
|
| `onPageEnter` | Nothing | Runs once on entering the page, before its queries |
|
|
917
922
|
|
|
918
923
|
|
|
@@ -963,16 +968,19 @@ export const requests: Requests = {
|
|
|
963
968
|
|
|
964
969
|
Each key under a `crud` entry is a reserved `lb-action` value with its prefix
|
|
965
970
|
stripped and the rest camel-cased, so the attribute, the wire and this key
|
|
966
|
-
are one vocabulary. All
|
|
971
|
+
are one vocabulary. All three operate on a list, because each needs a key and
|
|
967
972
|
a key exists only on a live row.
|
|
968
973
|
|
|
969
974
|
| `lb-action` | Key under `crud` | `where` carries |
|
|
970
975
|
| ---------------- | ---------------- | ---------------------- |
|
|
971
|
-
| `lb-cell-change` | `cellChange` | `key`, `cell`, `value` |
|
|
972
976
|
| `lb-row-delete` | `rowDelete` | `key` |
|
|
973
977
|
| `lb-row-insert` | `rowInsert` | `values` |
|
|
974
978
|
| `lb-row-update` | `rowUpdate` | `key`, `values` |
|
|
975
979
|
|
|
980
|
+
A `rowUpdate` sets the columns `values` names and leaves the rest as they
|
|
981
|
+
are, as an SQL `UPDATE` does. A form sends the cells it holds and a widget
|
|
982
|
+
cell sends itself, so one handler answers both.
|
|
983
|
+
|
|
976
984
|
## Widgets
|
|
977
985
|
|
|
978
986
|
---- UNEDITED ----
|
|
@@ -1124,7 +1132,7 @@ application writes the ones this table names and invents none of its own,
|
|
|
1124
1132
|
because a name Loadbare has not defined today it may define tomorrow.
|
|
1125
1133
|
|
|
1126
1134
|
The prefix reaches past HTML. A query name cannot begin with `lb-` either,
|
|
1127
|
-
which is what keeps the
|
|
1135
|
+
which is what keeps the three operations apart from an application's own
|
|
1128
1136
|
actions on the wire — see [Page Queries](#page-queries).
|
|
1129
1137
|
|
|
1130
1138
|
Each attribute is defined in one section, and this table says which.
|
|
@@ -71,17 +71,23 @@ closed.
|
|
|
71
71
|
| Operation | Takes from position | Carries | Answers |
|
|
72
72
|
| ---------------- | ------------------- | -------------------------------------------- | ------------ |
|
|
73
73
|
| `lb-row-insert` | list | values, gathered from the record | patch rows |
|
|
74
|
-
| `lb-row-update` | list, key | values, gathered or
|
|
74
|
+
| `lb-row-update` | list, key | values, gathered, or one cell from a widget | patch rows |
|
|
75
75
|
| `lb-row-delete` | list, key | nothing | patch drop |
|
|
76
76
|
| a declared name | list or row, key, cell | value | any names |
|
|
77
77
|
|
|
78
|
-
###
|
|
78
|
+
### Done: `lb-cell-change` folded into `lb-row-update`
|
|
79
79
|
|
|
80
|
-
A cell change is an update whose `values` holds one entry
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
A cell change is an update whose `values` holds one entry. SQL has one
|
|
81
|
+
UPDATE whether it sets one column or many. On the server, `cellChange` and
|
|
82
|
+
`rowUpdate` merged into `rowUpdate(key, values)`, where a column absent from
|
|
83
|
+
`values` is left as it is.
|
|
84
|
+
|
|
85
|
+
The fold needed one rule beyond this proposal. Without it, a widget sending
|
|
86
|
+
`lb-row-update` gathers its whole record, and every other editable cell in
|
|
87
|
+
the row goes with it. The rule: an element carrying `lb-cell` is a record
|
|
88
|
+
of one cell, the way a control has a value and a form has values. Its
|
|
89
|
+
`values` holds that cell alone, from the `value` the widget sent or else its
|
|
90
|
+
control, so a widget never writes its own column name into the request.
|
|
85
91
|
|
|
86
92
|
### Reordering is not an operation
|
|
87
93
|
|
|
@@ -177,8 +183,9 @@ overwrites that in 0.7.3, so the rule introduces nothing new.
|
|
|
177
183
|
|
|
178
184
|
1. The hub holds each name's current value and fills scopes that appear
|
|
179
185
|
late.
|
|
180
|
-
2. `lb-cell-change` folds into `lb-row-update`, with
|
|
181
|
-
`
|
|
186
|
+
2. `lb-cell-change` folds into `lb-row-update`, with an element carrying
|
|
187
|
+
`lb-cell` sending itself alone, and the server's `cellChange` and
|
|
188
|
+
`rowUpdate` merge. Done.
|
|
182
189
|
3. The reference states the patch row rule: upsert by key, unnamed columns
|
|
183
190
|
unchanged.
|
|
184
191
|
|
package/docs/comparison.md
CHANGED
|
@@ -411,25 +411,26 @@ Data types are an open blocker in TECHREF-1.0.
|
|
|
411
411
|
### Loadbare/app
|
|
412
412
|
|
|
413
413
|
`lb-action` names what an interaction asks for. A value beginning with `lb-`
|
|
414
|
-
is one of
|
|
414
|
+
is one of three reserved operations; any other value is an action the page
|
|
415
415
|
declares.
|
|
416
416
|
|
|
417
417
|
| `lb-action` | Written on | Carries | Server key under `crud` |
|
|
418
418
|
|------------------|---------------------------------------------|---------------------------------|-------------------------|
|
|
419
419
|
| `lb-row-insert` | A `<form>`, or a button in a `<tr>`, in a list | `list`, `values` | `rowInsert` |
|
|
420
|
-
| `lb-row-update` | A `<form
|
|
420
|
+
| `lb-row-update` | A `<form>`, a button in a live row, or a widget cell | `list`, `key`, `values` | `rowUpdate` |
|
|
421
421
|
| `lb-row-delete` | Anything inside a live row | `list`, `key` | `rowDelete` |
|
|
422
|
-
| `lb-cell-change` | A widget wrapping one control | `list`, `key`, `cell`, `value` | `cellChange` |
|
|
423
422
|
| Any other name | Any element | Scope in effect, and a widget's `value` | Under `actions` |
|
|
424
423
|
|
|
425
424
|
- Position comes from the document. `list` or `row` comes from the nearest
|
|
426
425
|
ancestor scope, `key` from the nearest live row, `cell` from the element's
|
|
427
426
|
own `lb-cell`. The author writes the action and not the position.
|
|
428
427
|
- Values are gathered from the nearest `<form>`, `<tr>` or live row around
|
|
429
|
-
the element, the way a button submits its form owner.
|
|
428
|
+
the element, the way a button submits its form owner. A widget carrying
|
|
429
|
+
`lb-cell` sends its own cell alone, so one `rowUpdate` answers a form and a
|
|
430
|
+
per-field edit.
|
|
430
431
|
- A native element sends on click, a form on submit. A widget sends its own.
|
|
431
432
|
- A declared action carries no argument list.
|
|
432
|
-
- A single-row scope is read-only for the
|
|
433
|
+
- A single-row scope is read-only for the three operations, because each needs
|
|
433
434
|
a key and a key exists only on a live row in a list.
|
|
434
435
|
|
|
435
436
|
On the server, each entry has `run` and `refresh`. `run` does the work and
|
|
@@ -728,7 +729,7 @@ TECHREF-1.0 lists every name Loadbare/app owns in one cross-reference.
|
|
|
728
729
|
| Owned | Count | Names |
|
|
729
730
|
|-------------------------------------|-------|---------------------------------------------------------------|
|
|
730
731
|
| `lb-*` HTML attributes | 16 | `lb-list`, `lb-row`, `lb-cell`, `lb-show`, `lb-key`, `lb-key-value`, `lb-value`, `lb-action`, `lb-nav-link`, `lb-pending`, `lb-error`, `lb-row-count`, `lb-unknown-page`, `lb-slot`, `lb-template`, `lb-page` |
|
|
731
|
-
| Reserved `lb-action` values |
|
|
732
|
+
| Reserved `lb-action` values | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
|
|
732
733
|
| `lb*` methods on custom elements | 2 | `lbPlaceRow`, `lbRowsLanded` |
|
|
733
734
|
| Attribute namespace for expansion | 1 | `exp-*` |
|
|
734
735
|
| Reserved tags | 1 | `<lb-hub>` |
|
|
@@ -229,8 +229,8 @@ as a string literal.
|
|
|
229
229
|
| `ATTR_KEY` | `lb-key` |
|
|
230
230
|
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
231
231
|
| `ATTR_ACTION` | `lb-action`|
|
|
232
|
-
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE
|
|
233
|
-
| `LB_ACTIONS` | all
|
|
232
|
+
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE` | the reserved `lb-action` values |
|
|
233
|
+
| `LB_ACTIONS` | all three of them, in one array |
|
|
234
234
|
| `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
|
|
235
235
|
| `ATTR_ROW_COUNT` | `lb-row-count`|
|
|
236
236
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
@@ -287,7 +287,7 @@ control's value as its `detail`:
|
|
|
287
287
|
import { LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
288
288
|
import type { HubRequest } from "@loadbare/app/types";
|
|
289
289
|
|
|
290
|
-
const detail: HubRequest = { action: "lb-
|
|
290
|
+
const detail: HubRequest = { action: "lb-row-update", value: input.value };
|
|
291
291
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
292
292
|
```
|
|
293
293
|
|
|
@@ -296,9 +296,14 @@ event, and does not send a request missing a field its operation requires.
|
|
|
296
296
|
See [TECHREF-1.0](../TECHREF-1.0.md#requests) for what each operation is
|
|
297
297
|
filled with.
|
|
298
298
|
|
|
299
|
-
A widget
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
A widget that carries `lb-cell` and sends `lb-row-insert` or `lb-row-update`
|
|
300
|
+
sends that one cell. The hub builds `values` from the cell's name and the
|
|
301
|
+
`value` the widget sent, or reads the control the widget wraps when it sent
|
|
302
|
+
none.
|
|
303
|
+
|
|
304
|
+
A widget that carries no `lb-cell` doesn't read its own controls. It
|
|
305
|
+
dispatches the bare action from the row, or from anything inside it, and the
|
|
306
|
+
hub gathers `values` from the row the event came from:
|
|
302
307
|
the nearest `<form>`, `<tr>` or live row around the dispatching element,
|
|
303
308
|
itself included, inside its scope. Every readable `lb-cell` in that row is
|
|
304
309
|
gathered, wherever in the row it sits, except the cells of a scope nested in
|
|
@@ -152,9 +152,9 @@ the prefix stripped and the rest camel-cased.
|
|
|
152
152
|
| `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
|
|
153
153
|
| `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
|
|
154
154
|
| `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
|
|
155
|
-
| `lb-action="lb-
|
|
155
|
+
| `lb-action="lb-row-update"` on a widget cell | `rowUpdate` | `list`, `key`, `values` of one cell |
|
|
156
156
|
|
|
157
|
-
All
|
|
157
|
+
All three operations are list operations. Each needs a key, and a key exists
|
|
158
158
|
only on a live row the hub stamped inside a list, so a single-row scope is
|
|
159
159
|
read-only and a declared action is the only thing it can send. An application
|
|
160
160
|
that wants a writable single row declares a list that answers with one row.
|
|
@@ -166,15 +166,11 @@ with one an application already uses. That reservation is also the whole of
|
|
|
166
166
|
the wire discriminant: a value beginning with `lb-` is an operation, and
|
|
167
167
|
anything else is a name the page declared.
|
|
168
168
|
|
|
169
|
-
The hub sends
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
cell
|
|
173
|
-
|
|
174
|
-
`<lb-input>` does when it carries `lb-action="lb-cell-change"`, and stays
|
|
175
|
-
quiet otherwise — an input inside an `lb-row-insert` or `lb-row-update` form is read
|
|
176
|
-
again by the form on submit, so a widget that sent on its own would write
|
|
177
|
-
the same edit twice.
|
|
169
|
+
The hub sends a request from a native element on the element's own event: a
|
|
170
|
+
form on submit, anything else on click. An insert or update clicked from a
|
|
171
|
+
button reads the form or row the button is in (see [Forms](#forms)). To
|
|
172
|
+
commit one cell as it changes, put `lb-row-update` on a widget cell instead
|
|
173
|
+
(see [Committing one cell](#committing-one-cell)).
|
|
178
174
|
|
|
179
175
|
Declare every action on the server, and permit every operation on its list. A
|
|
180
176
|
name the page has not declared, and an operation a list does not permit, are
|
|
@@ -184,7 +180,7 @@ refused; see
|
|
|
184
180
|
### Actions
|
|
185
181
|
|
|
186
182
|
Write `lb-action` on a button to ask the server to do something that is not
|
|
187
|
-
one of the
|
|
183
|
+
one of the three CRUD operations:
|
|
188
184
|
|
|
189
185
|
```html
|
|
190
186
|
<button lb-action="mailRoster">Mail the roster</button>
|
|
@@ -280,6 +276,34 @@ refuses it. A widget may dispatch either from any element, and the hub
|
|
|
280
276
|
gathers the same way; see
|
|
281
277
|
[Sending a request](./custom-elements.md#sending-a-request).
|
|
282
278
|
|
|
279
|
+
### Committing one cell
|
|
280
|
+
|
|
281
|
+
Put `lb-row-update` on a widget that carries `lb-cell` to save that one cell
|
|
282
|
+
whenever it changes. The shipped `<lb-input>` sends it on `change`:
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<template lb-key="id">
|
|
286
|
+
<tr>
|
|
287
|
+
<td><lb-input lb-cell="name" lb-action="lb-row-update"></lb-input></td>
|
|
288
|
+
<td><lb-input lb-cell="note" lb-action="lb-row-update"></lb-input></td>
|
|
289
|
+
</tr>
|
|
290
|
+
</template>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
An element carrying `lb-cell` is a record of one cell, the way a control has
|
|
294
|
+
a value and a form has values. Its update carries `values` holding that cell
|
|
295
|
+
alone, so an edit in one input never sends the other. It reaches the same
|
|
296
|
+
`rowUpdate` a form does. SQL has one UPDATE whether it sets one column or
|
|
297
|
+
many, and the application writes one handler for both.
|
|
298
|
+
|
|
299
|
+
The widget decides when the cell has changed. A native `<input>` carrying
|
|
300
|
+
`lb-cell` and `lb-row-update` has no such moment, since a click into it
|
|
301
|
+
would send it, so the hub refuses it and says so.
|
|
302
|
+
|
|
303
|
+
Leave `lb-action` off a widget inside an `lb-row-insert` or `lb-row-update`
|
|
304
|
+
form. The form reads every `lb-cell` in it on submit, so a widget that also
|
|
305
|
+
sent its own would write the same edit twice.
|
|
306
|
+
|
|
283
307
|
## Conditional rendering
|
|
284
308
|
|
|
285
309
|
Loadbare ships static HTML and hydrates elements that are already in the
|
|
@@ -86,7 +86,7 @@ Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
|
|
|
86
86
|
|---------------|------------------------------------------------------|
|
|
87
87
|
| `onPageEnter` | Before the page's queries, on entering the page |
|
|
88
88
|
| `actions` | What the page may be asked to do, by name |
|
|
89
|
-
| `crud` | The
|
|
89
|
+
| `crud` | The three operations a list permits on its rows |
|
|
90
90
|
|
|
91
91
|
### onPageEnter
|
|
92
92
|
|
|
@@ -127,17 +127,21 @@ carries `list` or `row`, whichever attribute scoped the element, plus `key`,
|
|
|
127
127
|
### crud
|
|
128
128
|
|
|
129
129
|
Declare CRUD operations under `crud`, keyed by the list they operate on. All
|
|
130
|
-
|
|
130
|
+
three are list operations: each needs a key, and a key exists only on a live
|
|
131
131
|
row inside a list, so a single-row scope is read-only and a declared action is
|
|
132
132
|
the only thing it can send. Each operation takes the binding its trigger
|
|
133
133
|
supplies:
|
|
134
134
|
|
|
135
|
-
| Operation
|
|
136
|
-
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
|
|
135
|
+
| Operation | The page writes | `run` receives |
|
|
136
|
+
|-------------|------------------------------------------------------------------------|-----------------|
|
|
137
|
+
| `rowDelete` | `lb-action="lb-row-delete"` | `key` |
|
|
138
|
+
| `rowInsert` | `<form lb-action="lb-row-insert">` | `values` |
|
|
139
|
+
| `rowUpdate` | `<form lb-action="lb-row-update">` or `<lb-input lb-action="lb-row-update">` | `key`, `values` |
|
|
140
|
+
|
|
141
|
+
Write `rowUpdate` to set the columns `values` names and leave every other
|
|
142
|
+
column as it is. A form sends the cells it holds, and a widget cell sends
|
|
143
|
+
itself alone. Check the names in `values` against the columns the list lets
|
|
144
|
+
the page edit.
|
|
141
145
|
|
|
142
146
|
The operation names are reserved: a name beginning with `lb-` cannot be
|
|
143
147
|
declared under `actions` or as a query, and `createHub` refuses a page that
|
|
@@ -179,7 +183,7 @@ would. What `run` returns is laid over the refreshed queries:
|
|
|
179
183
|
| `patch({ drop: [...] })` | These keys are gone; the rest stand |
|
|
180
184
|
|
|
181
185
|
Return a patch for a change the operation knows the extent of — one row added,
|
|
182
|
-
one row dropped, one
|
|
186
|
+
one row dropped, one row edited — and leave `refresh` empty. Re-run the query
|
|
183
187
|
instead when membership or order changed in a way the operation cannot name:
|
|
184
188
|
|
|
185
189
|
```ts
|
|
@@ -27,7 +27,7 @@ for where a listed package sits in the cascade.
|
|
|
27
27
|
|
|
28
28
|
Wraps an `<input>`. `lb-value` sets the input's `.value`. The widget sends
|
|
29
29
|
nothing on its own: `lb-action` names what the input's `change` sends. The
|
|
30
|
-
reserved `lb-
|
|
30
|
+
reserved `lb-row-update` saves the input's own cell; any other name sends that
|
|
31
31
|
action. Either carries the input's value, and the hub adds the scope the
|
|
32
32
|
input sits in.
|
|
33
33
|
|
|
@@ -42,7 +42,7 @@ of them, so an input that also sent its own would write the same edit twice.
|
|
|
42
42
|
|
|
43
43
|
| Attribute | Asks for |
|
|
44
44
|
| ---------- | ----- |
|
|
45
|
-
| `lb-action` | what to send on `change`; `lb-
|
|
45
|
+
| `lb-action` | what to send on `change`; `lb-row-update` to save the cell's own edit |
|
|
46
46
|
|
|
47
47
|
## `lb-select`
|
|
48
48
|
|
package/docs/roadmap.md
CHANGED
|
@@ -140,7 +140,7 @@ machinery would then also let `lb-list` and `lb-row` names be checked against
|
|
|
140
140
|
the declared queries.
|
|
141
141
|
|
|
142
142
|
One piece is separable and needs none of the above: an `lb-action` beginning
|
|
143
|
-
with `lb-` that names none of the
|
|
143
|
+
with `lb-` that names none of the three operations is a typo the builder can
|
|
144
144
|
refuse from markup alone. `LB_ACTIONS` in `core/lb-constants.ts` exists for
|
|
145
145
|
this, and the builder does not yet import it.
|
|
146
146
|
|
package/docs/testing.md
CHANGED
|
@@ -188,10 +188,14 @@ What the hub is tested for here:
|
|
|
188
188
|
its scope, whatever shares the button's cell: one ghost row among its
|
|
189
189
|
neighbours, a whole live row, a form nested in a row, a button's form
|
|
190
190
|
owner; and refuses a `<div>` holding cells, pointing to `<form>`
|
|
191
|
+
- an insert or update from an element carrying `lb-cell` sends that cell
|
|
192
|
+
alone, from the value a widget sent or else its control; refuses a value
|
|
193
|
+
with no `lb-cell` to name its column; and refuses a click on a native
|
|
194
|
+
element that is a cell
|
|
191
195
|
- gathering skips the cells of a nested scope, so a picker in a row sends
|
|
192
196
|
its own cell and nothing about its options
|
|
193
197
|
- a request arriving with no action, or with a reserved name that is not one
|
|
194
|
-
of the
|
|
198
|
+
of the three, is refused before it reaches the wire
|
|
195
199
|
- `lb-pending` lands on the element that dispatched, `lb-error` replaces it
|
|
196
200
|
on failure, and the next request clears it
|
|
197
201
|
- `aria-busy` comes and goes with `lb-pending`; a native button or form
|
package/docs/theory.md
CHANGED
|
@@ -460,7 +460,7 @@ that "knows what to do", assembling a request driven purely from
|
|
|
460
460
|
attributes.
|
|
461
461
|
|
|
462
462
|
This requirement is satisfied with a single new attribute, `lb-action`.
|
|
463
|
-
|
|
463
|
+
Three values are reserved, and any other value is interpreted as the
|
|
464
464
|
name of a routine on the server.
|
|
465
465
|
|
|
466
466
|
A request is an action and a position. The author writes the action,
|
|
@@ -37,7 +37,7 @@ class NoteInput extends HTMLElement {
|
|
|
37
37
|
this.dispatchEvent(
|
|
38
38
|
new CustomEvent(LB_EVENT_NAME, {
|
|
39
39
|
bubbles: true,
|
|
40
|
-
detail: { action: "lb-
|
|
40
|
+
detail: { action: "lb-row-update", value: input.value },
|
|
41
41
|
}),
|
|
42
42
|
);
|
|
43
43
|
});
|
|
@@ -49,9 +49,9 @@ customElements.define("note-input", NoteInput);
|
|
|
49
49
|
|
|
50
50
|
A widget with both a `.html` and a `.browser.ts` file shares one tag name; the
|
|
51
51
|
build finds each half independently. On change, it dispatches a
|
|
52
|
-
`lb-
|
|
53
|
-
of the row it is in and
|
|
54
|
-
`lb-
|
|
52
|
+
`lb-row-update` carrying the input's value. The hub adds the list and the key
|
|
53
|
+
of the row it is in, and turns the value into a `values` map holding only
|
|
54
|
+
the widget's own `lb-cell`.
|
|
55
55
|
|
|
56
56
|
## Using it
|
|
57
57
|
|
|
@@ -72,30 +72,11 @@ every change, so there's nothing left to batch.
|
|
|
72
72
|
|
|
73
73
|
## Answering it server-side
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// ...onPageEnter and actions unchanged...
|
|
81
|
-
crud: {
|
|
82
|
-
notes: {
|
|
83
|
-
// ...rowInsert, rowDelete, rowUpdate unchanged...
|
|
84
|
-
cellChange: {
|
|
85
|
-
run: async (ctx, { key, value }) => {
|
|
86
|
-
const note = await ctx.db.updateNote(key, value);
|
|
87
|
-
return { notes: patch({ rows: [note] }) };
|
|
88
|
-
},
|
|
89
|
-
refresh: [],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
`cellChange` reuses `updateNote` from [Updating a List Item](./076-updating-a-list-item.md)
|
|
97
|
-
— the database doesn't care whether the edit came from a form's values
|
|
98
|
-
map or one widget's value.
|
|
75
|
+
There is nothing to add. The request is the same `lb-row-update` the form
|
|
76
|
+
sent in [Updating a List Item](./076-updating-a-list-item.md), with `values`
|
|
77
|
+
holding only `text`, so the `rowUpdate` written there answers it unchanged.
|
|
78
|
+
A `rowUpdate` sets the columns `values` names and leaves the rest alone, as
|
|
79
|
+
an SQL `UPDATE` does, so one handler serves a form and a widget alike.
|
|
99
80
|
|
|
100
81
|
## Run it
|
|
101
82
|
|