@metaobjectsdev/runtime-web 1.0.0-rc.3 → 1.0.0-rc.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"grid-from-metadata.d.ts","sourceRoot":"","sources":["../src/grid-from-metadata.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,0BAA0B,CAAC;AAchF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ/C,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAoBD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CA8BvE"}
1
+ {"version":3,"file":"grid-from-metadata.d.ts","sourceRoot":"","sources":["../src/grid-from-metadata.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,0BAA0B,CAAC;AAgBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM/C,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAwDD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAmCvE"}
@@ -3,26 +3,56 @@
3
3
  // constant import from it made every browser bundle fail ("Browser polyfill for module
4
4
  // node:url doesn't have a matching export named fileURLToPath"). The type import above is
5
5
  // fine on the root: `import type` is erased at build time and drags in no runtime dep.
6
- import { LAYOUT_SUBTYPE_DATA_GRID, LAYOUT_DATA_GRID_ATTR_COLUMNS, LAYOUT_DATA_GRID_ATTR_PAGE_SIZE, LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD, LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER, LAYOUT_DATA_GRID_ATTR_FILTERABLE, } from "@metaobjectsdev/metadata/constants";
6
+ import { LAYOUT_SUBTYPE_DATA_GRID, LAYOUT_DATA_GRID_ATTR_COLUMNS, LAYOUT_DATA_GRID_ATTR_PAGE_SIZE, LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD, LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER, LAYOUT_DATA_GRID_ATTR_FILTERABLE, FIELD_ATTR_SORTABLE_DEFAULT_ORDER, DOC_ATTR_TITLE, } from "@metaobjectsdev/metadata/constants";
7
7
  const DEFAULT_PAGE_SIZE = 25;
8
8
  const DEFAULT_GRID_NAME = "default";
9
- // The view's display-label attr. Mirrors the literal used by the columns-file
10
- // codegen; there is no exported constant for it in @metaobjectsdev/metadata.
11
- const VIEW_ATTR_LABEL = "label";
12
9
  /** camelCase / PascalCase → "Title Case" (matches the codegen `humanize`). */
13
10
  function humanize(s) {
14
11
  return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
15
12
  }
16
13
  function firstView(field) {
17
- return field.ownViews()[0];
14
+ // views(), not ownViews(): ADR-0039 — a field that `extends` an abstract parent
15
+ // inherits the parent's view, and an own-only read drops it, so the column would
16
+ // silently lose both its header and its renderer hint.
17
+ return field.views()[0];
18
18
  }
19
19
  function header(field) {
20
- const label = firstView(field)?.ownAttr(VIEW_ATTR_LABEL);
20
+ // DOC_ATTR_TITLE, not the literal "label". `@label` is registered by NO provider in
21
+ // any port — `@title` is the documentation commonAttr chartered as the display label
22
+ // — so this read was `undefined` for every field that ever set one, and every grid
23
+ // header silently fell back to humanize(field.name). Same shape as `attr("isArray")`:
24
+ // an unregistered name answers `undefined`, which is indistinguishable from "unset".
25
+ //
26
+ // attr(), not ownAttr(), for the same ADR-0039 reason as firstView above.
27
+ const label = firstView(field)?.attr(DOC_ATTR_TITLE);
21
28
  return typeof label === "string" ? label : humanize(field.name);
22
29
  }
23
30
  function viewKind(field) {
24
31
  return firstView(field)?.subType ?? field.subType;
25
32
  }
33
+ /**
34
+ * A grid's initial sort DIRECTION: the layout's `@defaultSortOrder` when it declares one,
35
+ * otherwise the named field's `@sortableDefaultOrder`, otherwise ascending.
36
+ *
37
+ * This is the runtime twin of `resolveGridDefaultSort()` in `codegen-ts`'s
38
+ * `filter-shared.ts`, and it must stay identical to it. It is duplicated rather than
39
+ * imported because that module lives in a SERVER package: `runtime-web` is the pure
40
+ * browser core and may depend only on `@metaobjectsdev/metadata` (see #287 — a single
41
+ * value import from the wrong entry point made every browser bundle fail).
42
+ *
43
+ * Reading only `@defaultSortOrder` — which this did — makes the runtime-built grid sort
44
+ * the opposite way from the generated grid AND from the endpoint it queries, for the one
45
+ * case the attribute exists to cover: a layout that names a field and lets the field say
46
+ * which way it runs.
47
+ *
48
+ * ADR-0039: `attr()` resolves, so a `@sortableDefaultOrder` inherited via `extends` counts.
49
+ */
50
+ function gridDefaultSortOrder(declaredOrder, field) {
51
+ if (declaredOrder === "asc" || declaredOrder === "desc")
52
+ return declaredOrder;
53
+ const fromField = field?.attr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER);
54
+ return fromField === "desc" ? "desc" : "asc";
55
+ }
26
56
  /**
27
57
  * Build a grid (config + columns) from a MetaObject at runtime.
28
58
  *
@@ -35,7 +65,12 @@ export function buildGrid(meta, gridName) {
35
65
  const layouts = meta.layouts().filter((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
36
66
  const layout = gridName ? layouts.find((l) => l.name === gridName) : layouts[0];
37
67
  const fieldsByName = new Map(meta.fields().map((f) => [f.name, f]));
38
- const columnsAttr = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
68
+ // attr(), not ownAttr(), on every layout read below — same ADR-0039 reason as
69
+ // firstView/header above, which this file already applies to its FIELD reads: a
70
+ // `layout.dataGrid` that inherits its attrs via `extends` keeps them on the parent,
71
+ // and an own-only read silently drops every one of them (no columns, default page
72
+ // size, no initial sort) on a grid that plainly declares them.
73
+ const columnsAttr = layout?.attr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
39
74
  const names = Array.isArray(columnsAttr)
40
75
  ? columnsAttr.filter((x) => typeof x === "string")
41
76
  : meta.fields().map((f) => f.name);
@@ -43,15 +78,15 @@ export function buildGrid(meta, gridName) {
43
78
  .map((n) => fieldsByName.get(n))
44
79
  .filter((f) => f !== undefined)
45
80
  .map((f) => ({ field: f.name, header: header(f), viewKind: viewKind(f) }));
46
- const pageSizeAttr = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
47
- const sortField = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
48
- const sortOrder = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
81
+ const pageSizeAttr = layout?.attr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
82
+ const sortField = layout?.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
83
+ const sortOrder = layout?.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
49
84
  const config = {
50
85
  name: layout?.name ?? gridName ?? DEFAULT_GRID_NAME,
51
86
  pageSize: typeof pageSizeAttr === "number" ? pageSizeAttr : DEFAULT_PAGE_SIZE,
52
- filterable: layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true,
87
+ filterable: layout?.attr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true,
53
88
  ...(typeof sortField === "string"
54
- ? { defaultSort: { field: sortField, order: sortOrder === "desc" ? "desc" : "asc" } }
89
+ ? { defaultSort: { field: sortField, order: gridDefaultSortOrder(sortOrder, fieldsByName.get(sortField)) } }
55
90
  : {}),
56
91
  };
57
92
  return { config, columns };
@@ -1 +1 @@
1
- {"version":3,"file":"grid-from-metadata.js","sourceRoot":"","sources":["../src/grid-from-metadata.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grid-from-metadata.js","sourceRoot":"","sources":["../src/grid-from-metadata.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Join a client base URL to a generated entity-relative path.
3
3
  *
4
4
  * ONE implementation, shared by both client tiers on purpose. `@metaobjectsdev/tanstack`
5
- * wraps `useEntityFetcher()` with it and `@metaobjectsdev/angular` wraps the
5
+ * wraps `useEntityPathFetcher()` with it and `@metaobjectsdev/angular` wraps the
6
6
  * `EntityFetcherToken`; implemented separately they would eventually disagree about a
7
7
  * trailing slash, and the disagreement would surface as a 404 in one framework only.
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * Join a client base URL to a generated entity-relative path.
3
3
  *
4
4
  * ONE implementation, shared by both client tiers on purpose. `@metaobjectsdev/tanstack`
5
- * wraps `useEntityFetcher()` with it and `@metaobjectsdev/angular` wraps the
5
+ * wraps `useEntityPathFetcher()` with it and `@metaobjectsdev/angular` wraps the
6
6
  * `EntityFetcherToken`; implemented separately they would eventually disagree about a
7
7
  * trailing slash, and the disagreement would surface as a 404 in one framework only.
8
8
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/runtime-web",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.5",
4
4
  "description": "Pure framework-agnostic browser core for metaobjects: currency, filter URL serialization, fetcher contract types.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@metaobjectsdev/metadata": "1.0.0-rc.3",
48
+ "@metaobjectsdev/metadata": "1.0.0-rc.5",
49
49
  "qs": "^6.13.0"
50
50
  },
51
51
  "devDependencies": {
@@ -22,14 +22,14 @@ import {
22
22
  LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD,
23
23
  LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER,
24
24
  LAYOUT_DATA_GRID_ATTR_FILTERABLE,
25
+ FIELD_ATTR_SORTABLE_DEFAULT_ORDER,
26
+ DOC_ATTR_TITLE,
25
27
  } from "@metaobjectsdev/metadata/constants";
26
28
  import type { GridConfig } from "./fetcher.js";
27
29
 
28
30
  const DEFAULT_PAGE_SIZE = 25;
29
31
  const DEFAULT_GRID_NAME = "default";
30
- // The view's display-label attr. Mirrors the literal used by the columns-file
31
- // codegen; there is no exported constant for it in @metaobjectsdev/metadata.
32
- const VIEW_ATTR_LABEL = "label";
32
+
33
33
 
34
34
  /** A neutral, framework-agnostic column descriptor derived from metadata. */
35
35
  export interface MetaColumn {
@@ -52,11 +52,21 @@ function humanize(s: string): string {
52
52
  }
53
53
 
54
54
  function firstView(field: MetaField): MetaView | undefined {
55
- return field.ownViews()[0];
55
+ // views(), not ownViews(): ADR-0039 — a field that `extends` an abstract parent
56
+ // inherits the parent's view, and an own-only read drops it, so the column would
57
+ // silently lose both its header and its renderer hint.
58
+ return field.views()[0];
56
59
  }
57
60
 
58
61
  function header(field: MetaField): string {
59
- const label = firstView(field)?.ownAttr(VIEW_ATTR_LABEL);
62
+ // DOC_ATTR_TITLE, not the literal "label". `@label` is registered by NO provider in
63
+ // any port — `@title` is the documentation commonAttr chartered as the display label
64
+ // — so this read was `undefined` for every field that ever set one, and every grid
65
+ // header silently fell back to humanize(field.name). Same shape as `attr("isArray")`:
66
+ // an unregistered name answers `undefined`, which is indistinguishable from "unset".
67
+ //
68
+ // attr(), not ownAttr(), for the same ADR-0039 reason as firstView above.
69
+ const label = firstView(field)?.attr(DOC_ATTR_TITLE);
60
70
  return typeof label === "string" ? label : humanize(field.name);
61
71
  }
62
72
 
@@ -64,6 +74,32 @@ function viewKind(field: MetaField): string {
64
74
  return firstView(field)?.subType ?? field.subType;
65
75
  }
66
76
 
77
+ /**
78
+ * A grid's initial sort DIRECTION: the layout's `@defaultSortOrder` when it declares one,
79
+ * otherwise the named field's `@sortableDefaultOrder`, otherwise ascending.
80
+ *
81
+ * This is the runtime twin of `resolveGridDefaultSort()` in `codegen-ts`'s
82
+ * `filter-shared.ts`, and it must stay identical to it. It is duplicated rather than
83
+ * imported because that module lives in a SERVER package: `runtime-web` is the pure
84
+ * browser core and may depend only on `@metaobjectsdev/metadata` (see #287 — a single
85
+ * value import from the wrong entry point made every browser bundle fail).
86
+ *
87
+ * Reading only `@defaultSortOrder` — which this did — makes the runtime-built grid sort
88
+ * the opposite way from the generated grid AND from the endpoint it queries, for the one
89
+ * case the attribute exists to cover: a layout that names a field and lets the field say
90
+ * which way it runs.
91
+ *
92
+ * ADR-0039: `attr()` resolves, so a `@sortableDefaultOrder` inherited via `extends` counts.
93
+ */
94
+ function gridDefaultSortOrder(
95
+ declaredOrder: unknown,
96
+ field: MetaField | undefined,
97
+ ): "asc" | "desc" {
98
+ if (declaredOrder === "asc" || declaredOrder === "desc") return declaredOrder;
99
+ const fromField = field?.attr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER);
100
+ return fromField === "desc" ? "desc" : "asc";
101
+ }
102
+
67
103
  /**
68
104
  * Build a grid (config + columns) from a MetaObject at runtime.
69
105
  *
@@ -78,7 +114,12 @@ export function buildGrid(meta: MetaObject, gridName?: string): MetaGrid {
78
114
 
79
115
  const fieldsByName = new Map(meta.fields().map((f) => [f.name, f] as const));
80
116
 
81
- const columnsAttr = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
117
+ // attr(), not ownAttr(), on every layout read below — same ADR-0039 reason as
118
+ // firstView/header above, which this file already applies to its FIELD reads: a
119
+ // `layout.dataGrid` that inherits its attrs via `extends` keeps them on the parent,
120
+ // and an own-only read silently drops every one of them (no columns, default page
121
+ // size, no initial sort) on a grid that plainly declares them.
122
+ const columnsAttr = layout?.attr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
82
123
  const names: string[] = Array.isArray(columnsAttr)
83
124
  ? columnsAttr.filter((x): x is string => typeof x === "string")
84
125
  : meta.fields().map((f) => f.name);
@@ -88,16 +129,16 @@ export function buildGrid(meta: MetaObject, gridName?: string): MetaGrid {
88
129
  .filter((f): f is MetaField => f !== undefined)
89
130
  .map((f) => ({ field: f.name, header: header(f), viewKind: viewKind(f) }));
90
131
 
91
- const pageSizeAttr = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
92
- const sortField = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
93
- const sortOrder = layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
132
+ const pageSizeAttr = layout?.attr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
133
+ const sortField = layout?.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
134
+ const sortOrder = layout?.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
94
135
 
95
136
  const config: GridConfig = {
96
137
  name: layout?.name ?? gridName ?? DEFAULT_GRID_NAME,
97
138
  pageSize: typeof pageSizeAttr === "number" ? pageSizeAttr : DEFAULT_PAGE_SIZE,
98
- filterable: layout?.ownAttr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true,
139
+ filterable: layout?.attr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true,
99
140
  ...(typeof sortField === "string"
100
- ? { defaultSort: { field: sortField, order: sortOrder === "desc" ? "desc" : "asc" } }
141
+ ? { defaultSort: { field: sortField, order: gridDefaultSortOrder(sortOrder, fieldsByName.get(sortField)) } }
101
142
  : {}),
102
143
  };
103
144
 
@@ -2,7 +2,7 @@
2
2
  * Join a client base URL to a generated entity-relative path.
3
3
  *
4
4
  * ONE implementation, shared by both client tiers on purpose. `@metaobjectsdev/tanstack`
5
- * wraps `useEntityFetcher()` with it and `@metaobjectsdev/angular` wraps the
5
+ * wraps `useEntityPathFetcher()` with it and `@metaobjectsdev/angular` wraps the
6
6
  * `EntityFetcherToken`; implemented separately they would eventually disagree about a
7
7
  * trailing slash, and the disagreement would surface as a 404 in one framework only.
8
8
  *