@antelopejs/dms 0.7.0 → 0.7.2

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 (110) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/hooks/tenant-instances.d.ts +1 -0
  3. package/dist/hooks/tenant-instances.js +13 -0
  4. package/dist/hooks/tenant-instances.js.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/test/unit/interfaces/dms/page-topics.test.js +67 -0
  8. package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
  9. package/dist/test/unit/interfaces/dms-base/block-vocabulary.test.js +1 -0
  10. package/dist/test/unit/interfaces/dms-base/block-vocabulary.test.js.map +1 -1
  11. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.d.ts +1 -0
  12. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.js +46 -0
  13. package/dist/test/unit/interfaces/dms-base/code-block-catalog.test.js.map +1 -0
  14. package/dist/test/unit/interfaces/dms-base/column-display.test.js +11 -0
  15. package/dist/test/unit/interfaces/dms-base/column-display.test.js.map +1 -1
  16. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +31 -0
  17. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
  18. package/dist/test/unit/utils/tenant-instances.test.d.ts +1 -0
  19. package/dist/test/unit/utils/tenant-instances.test.js +53 -0
  20. package/dist/test/unit/utils/tenant-instances.test.js.map +1 -0
  21. package/dist/utils/index.d.ts +1 -0
  22. package/dist/utils/index.js +1 -0
  23. package/dist/utils/index.js.map +1 -1
  24. package/dist/utils/tenant-instances.d.ts +2 -0
  25. package/dist/utils/tenant-instances.js +38 -0
  26. package/dist/utils/tenant-instances.js.map +1 -0
  27. package/dist/validation/auth.schema.d.ts +2 -2
  28. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +4 -0
  29. package/frontend-vue/layers/dms-ui/app/build/components/activity-feed/activityFeedDays.ts +35 -23
  30. package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockActions.vue +34 -6
  31. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +26 -8
  32. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +3 -1
  33. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +1 -1
  34. package/frontend-vue/layers/dms-ui/app/build/components/table/MonoCell.vue +3 -2
  35. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +24 -13
  36. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +1 -1
  37. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useBlockItems.ts +12 -5
  38. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useRefreshTriggers.ts +40 -0
  39. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +15 -4
  40. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +85 -22
  41. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/clearTableFilters.ts +10 -2
  42. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/usePersistedPagination.ts +79 -0
  43. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +94 -0
  44. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +41 -5
  45. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +3 -0
  46. package/frontend-vue/layers/dms-ui/app/build/utils/codeEditor.ts +2 -13
  47. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +59 -0
  48. package/frontend-vue/layers/dms-ui/app/build/utils/codeLanguages.ts +36 -0
  49. package/frontend-vue/layers/dms-ui/app/build/utils/codePalette.ts +32 -0
  50. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +55 -0
  51. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +77 -20
  52. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +15 -4
  53. package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +111 -25
  54. package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +95 -0
  55. package/frontend-vue/layers/dms-ui/app/components/blocks/EmptyStateBlock.vue +18 -0
  56. package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +8 -0
  57. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +21 -9
  58. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +8 -0
  59. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +97 -0
  60. package/frontend-vue/layers/dms-ui/app/components/empty-state/EmptyState.vue +13 -4
  61. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +14 -3
  62. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +2 -8
  63. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +19 -0
  64. package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +54 -48
  65. package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +24 -9
  66. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +4 -1
  67. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +66 -74
  68. package/frontend-vue/layers/dms-ui/app/components/top-list/internal/TopListRow.vue +4 -3
  69. package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +5 -2
  70. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +2 -29
  71. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +2 -1
  72. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +2 -0
  73. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +4 -0
  74. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +2 -2
  75. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +1 -0
  76. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +1 -0
  77. package/frontend-vue/package.json +2 -2
  78. package/frontend-vue/pnpm-lock.yaml +5 -5
  79. package/frontend-vue/tests/activity-feed-days.test.ts +26 -12
  80. package/frontend-vue/tests/banner-block-fetch.test.ts +162 -0
  81. package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +100 -0
  82. package/frontend-vue/tests/block-items-period-realtime.test.ts +147 -0
  83. package/frontend-vue/tests/block-route-params.test.ts +48 -0
  84. package/frontend-vue/tests/code-block.test.ts +155 -0
  85. package/frontend-vue/tests/code-highlight.test.ts +72 -0
  86. package/frontend-vue/tests/composed-text-blocks.test.ts +5 -0
  87. package/frontend-vue/tests/composed-text-more-blocks.test.ts +391 -0
  88. package/frontend-vue/tests/drawer-target-direction.test.ts +205 -0
  89. package/frontend-vue/tests/empty-state-late-slots.test.ts +53 -0
  90. package/frontend-vue/tests/flow-canvas-delete-badge.test.ts +88 -0
  91. package/frontend-vue/tests/form-redirect-placeholders.test.ts +11 -0
  92. package/frontend-vue/tests/key-value-list-copy.test.ts +114 -0
  93. package/frontend-vue/tests/master-detail-layout.test.ts +53 -0
  94. package/frontend-vue/tests/mono-cell-truncate.test.ts +50 -0
  95. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +1 -0
  96. package/frontend-vue/tests/page-fill-height.test.ts +20 -3
  97. package/frontend-vue/tests/route-param-occurrences.test.ts +9 -0
  98. package/frontend-vue/tests/table-actions-search-label.test.ts +147 -0
  99. package/frontend-vue/tests/table-clear-filters.test.ts +22 -0
  100. package/frontend-vue/tests/table-empty-body-viewport.test.ts +25 -0
  101. package/frontend-vue/tests/table-metric-cells.test.ts +22 -0
  102. package/frontend-vue/tests/table-pills-cell.test.ts +126 -0
  103. package/frontend-vue/tests/table-source-navigation.test.ts +123 -0
  104. package/frontend-vue/tests/table-view-hidden-filters-page.test.ts +137 -0
  105. package/frontend-vue/tests/table-view-refresh.test.ts +84 -0
  106. package/frontend-vue/tests/table-view-sources.test.ts +67 -0
  107. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  108. package/package.json +2 -2
  109. package/skills/dms/SKILL.md +14 -3
  110. package/skills/dms-pages/REFERENCE.md +17 -1
@@ -0,0 +1,84 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * A table view reads its rows again when they moved without a write through
4
+ * its own routes: on each event of its `realtimeTopic`, and each time the page
5
+ * asks its blocks to refresh (refreshPageBlocks). The re-read goes through the
6
+ * same refresh as the toolbar's button, which keeps the rows on screen.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { createApp, defineComponent, h } from "vue";
11
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
12
+ import type { RealtimeTopicHandler } from "../layers/dms-core/app/composables/realtime/useUserRealtime";
13
+ import { refreshPageBlocks } from "../layers/dms-ui/app/utils/blockRefresh";
14
+
15
+ const topicHandlers = new Map<string, RealtimeTopicHandler>();
16
+
17
+ vi.mock("../layers/dms-core/app/composables/realtime/useRealtimeTopic", () => ({
18
+ useRealtimeTopic: (topic: string, handler: RealtimeTopicHandler) => {
19
+ topicHandlers.set(topic, handler);
20
+ },
21
+ }));
22
+
23
+ const { useRefreshTriggers } = await import(
24
+ "../layers/dms-ui/app/build/composables/blocks/useRefreshTriggers"
25
+ );
26
+
27
+ const tableViewSource = readFileSync(
28
+ resolve(
29
+ __dirname,
30
+ "../layers/dms-ui/app/components/table-view/TableView.vue",
31
+ ),
32
+ "utf8",
33
+ );
34
+
35
+ const UPLOADS_TOPIC = "media:uploads";
36
+ const mounted: Array<{ unmount: () => void }> = [];
37
+
38
+ function mountBlock(topic: string | string[] | undefined) {
39
+ const onRefresh = vi.fn();
40
+ const Block = defineComponent({
41
+ setup() {
42
+ useRefreshTriggers(topic, onRefresh);
43
+ return () => h("div");
44
+ },
45
+ });
46
+ const app = createApp(Block);
47
+ app.mount(document.createElement("div"));
48
+ mounted.push(app);
49
+ return { onRefresh, unmount: () => app.unmount() };
50
+ }
51
+
52
+ beforeEach(() => topicHandlers.clear());
53
+
54
+ afterEach(() => {
55
+ mounted.splice(0).forEach((app) => app.unmount());
56
+ });
57
+
58
+ it("refreshes on each page refresh while mounted", () => {
59
+ const block = mountBlock(undefined);
60
+ refreshPageBlocks();
61
+ expect(block.onRefresh).toHaveBeenCalledTimes(1);
62
+
63
+ block.unmount();
64
+ mounted.length = 0;
65
+ refreshPageBlocks();
66
+ expect(block.onRefresh).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it("refreshes on each event of any of its topics, not on a snapshot", () => {
70
+ const block = mountBlock([UPLOADS_TOPIC, "media:jobs"]);
71
+ expect([...topicHandlers.keys()]).toEqual([UPLOADS_TOPIC, "media:jobs"]);
72
+
73
+ topicHandlers.get(UPLOADS_TOPIC)?.({ type: "batch.done" } as never);
74
+ topicHandlers.get("media:jobs")?.({ type: "job.done" } as never);
75
+ topicHandlers.get(UPLOADS_TOPIC)?.({ entries: [] } as never);
76
+ expect(block.onRefresh).toHaveBeenCalledTimes(2);
77
+ });
78
+
79
+ it("wires the table view's realtimeTopic and page refresh to its refresh", () => {
80
+ expect(tableViewSource).toMatch(/realtimeTopic\?: string \| string\[\];/);
81
+ expect(tableViewSource).toContain(
82
+ "useRefreshTriggers(props.realtimeTopic, () => void refreshAll());",
83
+ );
84
+ });
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
+ import { reactive } from "vue";
2
3
  import {
3
4
  processSourceRows,
4
5
  sourceRouteQuery,
@@ -25,6 +26,11 @@ const COLUMNS = [
25
26
 
26
27
  type Api = Parameters<typeof useTableRows>[0]["api"];
27
28
 
29
+ interface PageRoute {
30
+ params: Record<string, string>;
31
+ query: Record<string, unknown>;
32
+ }
33
+
28
34
  describe("source tables", () => {
29
35
  it("sends the route only the part of the query it handles", () => {
30
36
  const query = {
@@ -112,4 +118,65 @@ describe("source tables", () => {
112
118
  body: { queries: [{ id: "all", query: {} }] },
113
119
  });
114
120
  });
121
+
122
+ describe("with a templated fetchUrl", () => {
123
+ const FETCH_URL = "/api/ws/{{params.id}}/requests?route={{query.route}}";
124
+
125
+ function sourceRows(route: PageRoute) {
126
+ const api = vi.fn(async (_url: string) => ({ results: ROWS, total: 3 }));
127
+ const rows = useTableRows<LogRow>({
128
+ api: api as unknown as Api,
129
+ location: FETCH_URL,
130
+ source: { fetchUrl: FETCH_URL, capabilities: {} },
131
+ columns: COLUMNS,
132
+ route: () => ({ routeParams: route.params, routeQuery: route.query }),
133
+ });
134
+ return { api, rows };
135
+ }
136
+
137
+ it("requests the page's URL, its values encoded", async () => {
138
+ const { api, rows } = sourceRows({
139
+ params: { id: "ws-1" },
140
+ query: { route: "GET /api/a?b=c" },
141
+ });
142
+ await rows.list({});
143
+ expect(api).toHaveBeenCalledWith(
144
+ "/api/ws/ws-1/requests?route=GET%20%2Fapi%2Fa%3Fb%3Dc",
145
+ { query: {} },
146
+ );
147
+ });
148
+
149
+ it("requests nothing, and lists nothing, while a token has no value", async () => {
150
+ const { api, rows } = sourceRows({ params: { id: "ws-1" }, query: {} });
151
+ expect(rows.sourceUrl.value).toBe(undefined);
152
+ expect(await rows.list({ offset: 10, limit: 10 })).toEqual({
153
+ results: [],
154
+ total: 0,
155
+ offset: 10,
156
+ limit: 10,
157
+ });
158
+ expect(
159
+ await rows.countBatch([{ id: "orders", query: { search: "orders" } }]),
160
+ ).toEqual({ orders: 0 });
161
+ expect(api).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it("follows the page URL, tab counters included", async () => {
165
+ const route = reactive<PageRoute>({
166
+ params: { id: "ws-1" },
167
+ query: { route: "GET /a" },
168
+ });
169
+ const { api, rows } = sourceRows(route);
170
+ await rows.list({});
171
+ route.query = { route: "POST /b" };
172
+ expect(rows.sourceUrl.value).toBe(
173
+ "/api/ws/ws-1/requests?route=POST%20%2Fb",
174
+ );
175
+ await rows.countBatch([{ id: "all", query: {} }]);
176
+ expect(api.mock.calls.map(([url]) => url)).toEqual([
177
+ "/api/ws/ws-1/requests?route=GET%20%2Fa",
178
+ "/api/ws/ws-1/requests?route=POST%20%2Fb",
179
+ ]);
180
+ });
181
+ });
115
182
  });
@@ -2,7 +2,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import { describe, expect, it } from "vitest";
4
4
 
5
- const EXPECTED_VUE_FILES = 271;
5
+ const EXPECTED_VUE_FILES = 273;
6
6
 
7
7
  function walk(directory: string): string[] {
8
8
  return readdirSync(directory).flatMap((name) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/AntelopeJS/dms.git"
@@ -37,7 +37,7 @@
37
37
  "@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
38
38
  "@antelopejs/interface-database": ">=0.1.8 <1.0.0",
39
39
  "@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
40
- "@antelopejs/interface-dms": ">=0.4.2 <0.5.0",
40
+ "@antelopejs/interface-dms": ">=0.5.1 <0.6.0",
41
41
  "@antelopejs/interface-email": ">=0.0.5 <1.0.0",
42
42
  "@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
43
43
  "archiver": "^7.0.1",
@@ -32,16 +32,27 @@ For the framework underneath (the core, `ajs` CLI, interfaces as versioned contr
32
32
  `Grid`, `KpiCard`, … — fluent builders producing a **serializable description**, not live UI: the
33
33
  frontend renders it. **Components fetch their own data** via URLs (`fetchUrl`, `submitUrl`, …) —
34
34
  routes often declared `@Get`/`@Post` on the same page class; the backend stays the source of truth.
35
- A URL names the record of its page with `{{params.id}}` / `{{query.X}}` (filled from the page URL;
35
+ A URL names the record of its page with `{{params.id}}` / `{{query.X}}` (filled from the page URL, percent-encoded;
36
36
  nothing is fetched while one is unresolved), and `.watch(FormEvents.SUBMIT_SUCCESS,
37
- BlockFunctions.REFRESH_PAGE)` makes the page's data blocks re-fetch after a form saves.
37
+ BlockFunctions.REFRESH_PAGE)` makes the page's data blocks (table views included) re-fetch after a form saves;
38
+ `periodScope` binds a card, chart, list block or `Meter` to a `PeriodSelector`, and
39
+ `realtimeTopic` re-fetches a chart, list block, `Meter`, `Tab` badges or `TableView` on each `PublishMessage`.
40
+ A status the backend computes (health, outage) is a `Banner({ fetchUrl })`: the route answers
41
+ a `BannerContent` (its buttons may run `api` / modal / quick-action targets with `confirm`)
42
+ or `null` for no banner — the block then draws nothing.
38
43
  A text naming values (an amount, a date, a count) is sent as a `ComposedText` —
39
44
  `{ key, params: { amount: { type: "money", value: 92200, currency: "EUR" } } }` — that the
40
- browser writes in the reader's language: StatGroup / KeyValueList / Banner texts and layout
45
+ browser writes in the reader's language: StatGroup / KeyValueList / Banner / NavCardGrid /
46
+ ActivityFeed / Meter / TopListCard item texts (and ActivityFeed `params`, typed `count` for
47
+ plurals) and layout
41
48
  banners (`RegisterLayoutBanner({ resolve })` → `{ text, actions }`) take it; never format text server-side.
42
49
  A table cell's secondary line is a row field holding one (or `{ text, tone }`), named by
43
50
  `StatusPillDisplay({ subField })`, `IdentityDisplay({ subtitleField })` or
44
51
  `TwoLineDisplay({ subField })`; `IdentityDisplay({ emptyLabel, emptyIcon })` draws an empty row.
52
+ `StatusPillDisplay({ toneField })` reads the pill's tone from a row field (an HTTP status class),
53
+ and a `PillsDisplay` item may be `{ label, tone?, variant: "soft" | "outline" }`, styled per row.
54
+ A `KeyValueList` row takes `copy: true` (a copy button for its shown value) or `copyValue`
55
+ (the text to copy instead, e.g. a full URL behind a short label).
45
56
  - **It's interfaces all the way down.** Everything you consume from the DMS is an AntelopeJS
46
57
  interface imported as an `@antelopejs/interface-dms/...` subpath (see the imports below);
47
58
  a distributable module exposes its own from a companion `@antelopejs/interface-<name>` package listed in `antelopeJs.implements`.
@@ -9,12 +9,28 @@ Main builders: `Form`; `TableView(controller, options)`; the charts (`ChartLine`
9
9
  `ChartArea`, `ChartBar`, `ChartColumn`, `ChartDonut`, `ChartPie`, `ChartScatter`,
10
10
  `ChartMixed`, `ChartRadar`, `ChartRadialBar`, `ChartHeatmap`, `ChartCandlestick`, usually
11
11
  inside `ChartCard`); layout `Grid` + `GridRow`, `HStack` / `VStack` / `Spacer`, `Tab`,
12
- `Placeholder`; dashboard widgets `KpiCard`, `TopListCard`, `PeriodSelector`; and
12
+ `Placeholder`; dashboard widgets `KpiCard`, `TopListCard`, `PeriodSelector`; display blocks
13
+ `StatGroup`, `KeyValueList`, `NavCardGrid`, `EmptyState` (its `code` shows a snippet to
14
+ start with), `Banner`, `Card`, and `CodeBlock` (read-only highlighted code with a copy
15
+ button: `code` + `language`, or `fetchUrl` answering `{ code, language? }` — see
16
+ `docs/04.components/11.display-blocks.md`); and
13
17
  `CustomComponent` (deep import from `@antelopejs/interface-dms/base/custom` — not in the barrel). Nest with
14
18
  `.child("key", builder)`; `Grid` children must be `GridRow`s. Builders also carry a
15
19
  behavioral DSL — permissioned `.action()`s and `.watch()` reactivity — see
16
20
  `docs/02.building/05.actions-and-reactivity.md`.
17
21
 
22
+ A `TableView` scoped by the page URL takes `queryParamFilters: { status: { field: "status" } }`
23
+ (hidden filters read from `?status=`; `routeParamFilters` for slug params like `:id`). No
24
+ glue is needed around it: an active query filter shows the `filtered` empty state, and a
25
+ link changing its value sends the table back to page 1. A drawer or modal row action already
26
+ gets `navigation` (J / K, `index` / `total` in the order shown) —
27
+ `docs/04.components/06.tables/8.row-drawers.md`.
28
+
29
+ A button or row action (`customButtons`, `rowActions.custom`, a page's `headerActions`)
30
+ points at an `ActionTarget`. A `drawer` target opens from the bottom; give it
31
+ `direction: "right"` for a side sheet (an inspector, a detail panel) instead of hosting your
32
+ own drawer in a `CustomComponent`.
33
+
18
34
  ## DataTypes
19
35
 
20
36
  A field's `type` is a **DataType instance**: `new DefaultDataTypes.StringType({ … })`. The