@antelopejs/dms 0.10.0 → 0.11.1

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 (97) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/config.d.ts +0 -2
  3. package/dist/config.js +0 -4
  4. package/dist/config.js.map +1 -1
  5. package/dist/implementations/dms/frontend-modules.js +17 -6
  6. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  7. package/dist/implementations/dms/manifest.d.ts +6 -0
  8. package/dist/implementations/dms/manifest.js +10 -7
  9. package/dist/implementations/dms/manifest.js.map +1 -1
  10. package/dist/implementations/dms/page.js +45 -16
  11. package/dist/implementations/dms/page.js.map +1 -1
  12. package/dist/index.js +4 -7
  13. package/dist/index.js.map +1 -1
  14. package/dist/store/listing.js +1 -2
  15. package/dist/store/listing.js.map +1 -1
  16. package/dist/store/operations.js +1 -2
  17. package/dist/store/operations.js.map +1 -1
  18. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  19. package/dist/utils/ensure-default-tenant.js +13 -1
  20. package/dist/utils/ensure-default-tenant.js.map +1 -1
  21. package/dist/utils/export-jobs.js +2 -1
  22. package/dist/utils/export-jobs.js.map +1 -1
  23. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +1 -2
  24. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +3 -0
  25. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  26. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  27. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +5 -37
  28. package/frontend-vue/layers/dms-core/app/utils/menu.ts +66 -70
  29. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +2 -0
  30. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +2 -0
  31. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  32. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  33. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  34. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  35. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  36. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  37. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  38. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +2 -7
  39. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +3 -11
  40. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +2 -106
  41. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +0 -8
  42. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +0 -10
  43. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  44. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +18 -3
  45. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +24 -4
  46. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +10 -2
  47. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  48. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -7
  49. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +2 -7
  50. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +7 -0
  51. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +9 -9
  52. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +9 -9
  53. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  54. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  55. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  56. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +6 -4
  57. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +6 -4
  58. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  59. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +11 -1
  60. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  61. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +2 -5
  62. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +26 -5
  63. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +22 -1
  64. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  65. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  66. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +17 -4
  67. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +30 -6
  68. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +13 -3
  69. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  70. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  71. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  72. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  73. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  74. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  75. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +1 -0
  76. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  77. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +27 -6
  78. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +14 -7
  79. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  80. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +3 -9
  81. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +16 -1
  82. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +12 -0
  83. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +47 -7
  84. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +19 -9
  85. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  86. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -1
  87. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -1
  88. package/frontend-vue/package.json +6 -2
  89. package/package.json +8 -11
  90. package/skills/dms-module/SKILL.md +1 -1
  91. package/skills/dms-pages/REFERENCE.md +7 -3
  92. package/dist/hooks/tenant-instances.d.ts +0 -1
  93. package/dist/hooks/tenant-instances.js +0 -13
  94. package/dist/hooks/tenant-instances.js.map +0 -1
  95. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +0 -29
  96. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +0 -50
  97. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +0 -71
@@ -11,7 +11,7 @@
11
11
  "license": "Apache-2.0",
12
12
  "packageManager": "pnpm@10.6.5",
13
13
  "engines": {
14
- "@antelopejs/dms-frontend": ">=0.8.0 <0.9.0"
14
+ "@antelopejs/dms-frontend": ">=0.9.0 <0.10.0"
15
15
  },
16
16
  "scripts": {
17
17
  "typecheck": "ajs dms verify-source --layer \"$PWD\" --local-package @antelopejs/dms=.. --local-package @antelopejs/interface-dms=../../interface-dms",
@@ -37,7 +37,11 @@
37
37
  "@iconify-json/ph": "^1.2.2",
38
38
  "@internationalized/date": "^3.10.0",
39
39
  "@lezer/common": "^1.5.3",
40
+ "@lezer/css": "^1.3.8",
40
41
  "@lezer/highlight": "^1.2.5",
42
+ "@lezer/html": "^1.3.13",
43
+ "@lezer/javascript": "^1.5.6",
44
+ "@lezer/json": "^1.0.3",
41
45
  "@tailwindcss/typography": "^0.5.19",
42
46
  "@tanstack/vue-table": "^8.21.3",
43
47
  "@tiptap/core": "^3.30.2",
@@ -75,7 +79,7 @@
75
79
  },
76
80
  "devDependencies": {
77
81
  "@antelopejs/core": ">=1.16.0 <2",
78
- "@antelopejs/dms-frontend": ">=0.8.0 <0.9.0",
82
+ "@antelopejs/dms-frontend": ">=0.9.0 <0.10.0",
79
83
  "@eslint/js": "^8.57.0",
80
84
  "@inertiajs/vue3": "^3.7.0",
81
85
  "@nuxt/ui": "4.10.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "The AntelopeJS dashboard management system: admin dashboards declared in TypeScript, with auth and tenancy, data management, file uploads, notifications and realtime.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -58,10 +58,10 @@
58
58
  "@antelopejs/interface-api": ">=0.0.15 <1.0.0",
59
59
  "@antelopejs/interface-api-util": ">=0.1.3 <1.0.0",
60
60
  "@antelopejs/interface-core": ">=0.2.4 <1.0.0",
61
- "@antelopejs/interface-data-api": ">=0.3.0 <1.0.0",
62
- "@antelopejs/interface-database": ">=0.1.10 <1.0.0",
63
- "@antelopejs/interface-database-decorators": ">=0.1.9 <1.0.0",
64
- "@antelopejs/interface-dms": ">=0.8.0 <0.9.0",
61
+ "@antelopejs/interface-data-api": ">=0.4.0 <1.0.0",
62
+ "@antelopejs/interface-database": ">=0.1.11 <1.0.0",
63
+ "@antelopejs/interface-database-decorators": ">=0.1.10 <1.0.0",
64
+ "@antelopejs/interface-dms": ">=0.9.1 <0.10.0",
65
65
  "@antelopejs/interface-email": ">=0.0.6 <1.0.0",
66
66
  "@antelopejs/interface-file-storage": ">=0.1.4 <1.0.0",
67
67
  "archiver": "^7.0.1",
@@ -77,8 +77,8 @@
77
77
  "zod": "~3.24.2"
78
78
  },
79
79
  "devDependencies": {
80
- "@antelopejs/core": ">=1.16.0 <2",
81
- "@antelopejs/tooling-configs": ">=0.0.9 <1.0.0",
80
+ "@antelopejs/core": ">=1.17.0 <2",
81
+ "@antelopejs/tooling-configs": "0.2.0",
82
82
  "@types/archiver": "^6.0.4",
83
83
  "@types/chai": "^4.3.20",
84
84
  "@types/jsonwebtoken": "^9.0.10",
@@ -120,7 +120,7 @@
120
120
  "@antelopejs/interface-redis": ">=0.0.9 <1.0.0"
121
121
  },
122
122
  "peerDependencies": {
123
- "@antelopejs/core": ">=1.16.0 <2"
123
+ "@antelopejs/core": ">=1.17.0 <2"
124
124
  },
125
125
  "peerDependenciesMeta": {
126
126
  "@antelopejs/core": {
@@ -129,11 +129,8 @@
129
129
  },
130
130
  "scripts": {
131
131
  "build": "pnpm --filter @antelopejs/interface-dms build && rimraf dist && tsc && tsc-alias",
132
- "format": "oxfmt . && pnpm --dir frontend-vue format",
133
132
  "dev": "ajs project run --inspect -w -p playground",
134
133
  "frontend:dev": "pnpm --dir playground frontend:dev",
135
- "lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt . && pnpm --dir frontend-vue lint:fix",
136
- "lint": "oxlint && oxfmt --check . && pnpm --dir frontend-vue lint && antelopejs-check-interface-ranges",
137
134
  "typecheck": "node scripts/unless-prebuilt.mjs pnpm --filter @antelopejs/interface-dms build && tsc --noEmit",
138
135
  "test": "node scripts/unless-prebuilt.mjs pnpm run build && ajs module test .",
139
136
  "test:unit": "node scripts/unless-prebuilt.mjs pnpm run build && DMS_TEST_DIR=dist/test/unit ajs module test .",
@@ -85,7 +85,7 @@ module's frontend; without it the module is backend-only.
85
85
 
86
86
  `renderer` names the framework (Vue 3), not a loader release. The `@antelopejs/dms-frontend`
87
87
  releases the frontend module runs on go in the frontend module's own `package.json`, as
88
- `"engines": { "@antelopejs/dms-frontend": ">=0.8.0 <0.9.0" }`: the loader refuses the module
88
+ `"engines": { "@antelopejs/dms-frontend": ">=0.9.0 <0.10.0" }`: the loader refuses the module
89
89
  outside that range. Cap it below the next loader minor while the loader is 0.x.
90
90
 
91
91
  An absolute link (a public file, a snippet to copy, a mail) is built on `await GetApiBaseUrl()`
@@ -206,9 +206,13 @@ row's ⋯ button — `docs/04.components/08.tables/0.index.md#pinned-rows`.
206
206
  `{ label, tone?, variant: "soft" | "outline" }`.
207
207
  - A `TableView` over a shared controller hides a column on one page with
208
208
  `columns: { hidden: ["workspace"] }` — no view just to hide it.
209
- - Conditional fields on a `TableView`'s generated forms go in `forms: { new?, edit?, view? }`, each a
210
- callback receiving the form's builder: `new: (form) => form.watch(FormEvents.FIELD_CHANGE, …)` —
211
- they run in a drawer, a modal or a form page alike; no custom page just to add a watch.
209
+ - A field shown, required or locked by another column's value goes on its `@Column`:
210
+ `visibleWhen: { field: "stage", in: ["lost"] }`, `disabledWhen: { field: "stage", in: ["won"] }`.
211
+ The generated forms and the data routes both apply them (hidden: not mandatory, value ignored,
212
+ stored value cleared on edit; disabled: sent value ignored, stored one kept) — no watch, no custom page.
213
+ - `forms: { new?, edit?, view? }` hands each generated form's builder to a callback
214
+ (`new: (form) => form.watch(FormEvents.FIELD_CHANGE, …)`) for input comfort only — a value filled
215
+ in from another, a field one table's forms leave out. No data rule there: the routes never see it.
212
216
  - A table of more than a few columns that phones will read offers the cards and opens them there:
213
217
  `displays: [{ id: "table" }, { id: "cards" }], mobileDisplay: "cards"`. Only a display the user
214
218
  saved or the open view's own `display` overrides it: a `views.defaultView` that sets no display
@@ -1 +0,0 @@
1
- export declare function registerTenantInstanceProvisioning(): void;
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerTenantInstanceProvisioning = registerTenantInstanceProvisioning;
4
- const hooks_1 = require("@antelopejs/interface-dms/hooks");
5
- const tenant_instances_1 = require("../utils/tenant-instances");
6
- async function onTenantBeingProvisioned(payload) {
7
- await (0, tenant_instances_1.registerTenantInstance)(payload.tenantId);
8
- return undefined;
9
- }
10
- function registerTenantInstanceProvisioning() {
11
- (0, hooks_1.RegisterHook)(hooks_1.Hook.TENANT_BEING_PROVISIONED, onTenantBeingProvisioned);
12
- }
13
- //# sourceMappingURL=tenant-instances.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-instances.js","sourceRoot":"","sources":["../../src/hooks/tenant-instances.ts"],"names":[],"mappings":";;AAqBA,gFAEC;AAvBD,2DAIyC;AACzC,gEAAmE;AAEnE,KAAK,UAAU,wBAAwB,CACrC,OAAsC;IAEtC,MAAM,IAAA,yCAAsB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/C,OAAO,SAAS,CAAC;AACnB,CAAC;AASD,SAAgB,kCAAkC;IAChD,IAAA,oBAAY,EAAC,YAAI,CAAC,wBAAwB,EAAE,wBAAwB,CAAC,CAAC;AACxE,CAAC"}
@@ -1,29 +0,0 @@
1
- import { getCurrentInstance } from "vue";
2
-
3
- /**
4
- * Whether the component setting up or rendering now is hydrating the
5
- * server's HTML.
6
- *
7
- * Vue binds a hydrating component's vnode to its server-rendered node before
8
- * its setup runs, where a fresh mount has no node until it is mounted; once
9
- * mounted, the component no longer hydrates. So this is true from the setup
10
- * to the first render of a component the browser takes over from the server,
11
- * and false in a client navigation, on a later re-render, on the server, and
12
- * outside a component.
13
- *
14
- * Read it where the browser must draw what the server drew: a part drawn
15
- * in the browser only, or a value only the browser knows (local storage, the
16
- * clock), waits until mounted while hydrating, and shows at once anywhere
17
- * else. Call it before the setup's first `await`, while the component is
18
- * still Vue's current instance. Data needs no check: `useApiData` hands the
19
- * server's answer to the browser on its own.
20
- *
21
- * ```ts
22
- * const isDrawn = ref(!import.meta.env.SSR && !isHydratingComponent());
23
- * onMounted(() => (isDrawn.value = true));
24
- * ```
25
- */
26
- export function isHydratingComponent(): boolean {
27
- const instance = getCurrentInstance();
28
- return !!instance && !instance.isMounted && !!instance.vnode.el;
29
- }
@@ -1,50 +0,0 @@
1
- import { computed, type ComputedRef } from "vue";
2
- import { resolveUrlVariables } from "#dms-ui/app/build/utils/urlVariables";
3
- import type { PageRecord } from "../../utils/pageRecord";
4
-
5
- /** The record a page shows, as the layout declares it (`record`). */
6
- export interface PageRecordSource {
7
- /** `{{params.X}}` and `{{query.X}}` are filled in from the page URL. */
8
- fetchUrl: string;
9
- }
10
-
11
- /**
12
- * The record the page shows, for the header buttons acting on it: loading
13
- * until it answers, missing when it does not, or when its URL names a value
14
- * the page does not carry. Undefined for a page declaring no record.
15
- *
16
- * It is read through `useApiData`, keyed on its URL like a block's
17
- * `fetchUrl`: a block of the page reading the same URL shares the one
18
- * request, and a server render hands the record to the browser with the page.
19
- */
20
- export function usePageRecord(
21
- source: () => PageRecordSource | undefined,
22
- ): ComputedRef<PageRecord | undefined> {
23
- const route = useDmsRoute();
24
- const siteLayout = useSiteLayout();
25
-
26
- // Null while the URL names a value the page does not carry.
27
- const url = computed(() => {
28
- const declared = source();
29
- if (!declared) return undefined;
30
- return (
31
- resolveUrlVariables(declared.fetchUrl, {
32
- routeParams: siteLayout.findMatchingRoute(route.path)?.params,
33
- routeQuery: route.query as Record<string, unknown>,
34
- }) ?? null
35
- );
36
- });
37
- // The options a block's read takes (`useChartFetch`): a block never holds
38
- // a navigation, and neither does the header.
39
- const { data, error } = useApiData<Record<string, unknown>>(
40
- () => url.value ?? null,
41
- { lazy: true },
42
- );
43
-
44
- return computed<PageRecord | undefined>(() => {
45
- if (url.value === undefined) return undefined;
46
- if (url.value === null || error.value) return { status: "missing" };
47
- if (data.value) return { status: "loaded", values: data.value };
48
- return { status: "loading" };
49
- });
50
- }
@@ -1,71 +0,0 @@
1
- import { get } from "@nuxt/ui/runtime/utils/index.js";
2
- import type { ActionTarget } from "#dms-ui/app/composables/table-view/types/action-target";
3
-
4
- // The record a page shows (`DefaultLayout({ record })`), as its header
5
- // buttons read it: `{{record.X}}` in a target, a button acting on it.
6
-
7
- /** The record of the page as the header knows it. */
8
- export type PageRecord =
9
- | { status: "loading" }
10
- | { status: "loaded"; values: Record<string, unknown> }
11
- /** Not found, refused, or its URL names a value the page does not carry. */
12
- | { status: "missing" };
13
-
14
- /** What the header needs of a button to bind it to the page's record. */
15
- export interface RecordBoundButton {
16
- target?: ActionTarget;
17
- requiresRecord?: boolean;
18
- disabled?: boolean;
19
- }
20
-
21
- const RECORD_TOKEN = /\{\{\s*record\.([\w.-]+)\s*\}\}/g;
22
- const NAMES_RECORD = /\{\{\s*record\./;
23
-
24
- const targetUrl = (target: ActionTarget | undefined): string | undefined => {
25
- const url = (target as { url?: unknown } | undefined)?.url;
26
- return typeof url === "string" ? url : undefined;
27
- };
28
-
29
- /** Whether a button acts on the page's record. */
30
- export function isRecordBound(button: RecordBoundButton): boolean {
31
- if (button.requiresRecord) return true;
32
- const url = targetUrl(button.target);
33
- return !!url && NAMES_RECORD.test(url);
34
- }
35
-
36
- /**
37
- * A URL with its `{{record.X}}` tokens filled in, each value percent-encoded
38
- * (an address is one query value). A token the record has no value for is
39
- * left as written.
40
- */
41
- export function fillRecordTokens(
42
- url: string,
43
- record: Record<string, unknown>,
44
- ): string {
45
- return url.replace(RECORD_TOKEN, (match, key: string) => {
46
- const value: unknown = get(record, key);
47
- return typeof value === "string" || typeof value === "number"
48
- ? encodeURIComponent(String(value))
49
- : match;
50
- });
51
- }
52
-
53
- /**
54
- * The button as the page's record leaves it: unchanged when it does not act
55
- * on the record; disabled while the record loads; left out (`null`) when the
56
- * record is missing; its target's `{{record.X}}` filled in once loaded.
57
- */
58
- export function withPageRecord<T extends RecordBoundButton>(
59
- button: T,
60
- record: PageRecord | undefined,
61
- ): T | null {
62
- if (!record || !isRecordBound(button)) return button;
63
- if (record.status === "missing") return null;
64
- if (record.status === "loading") return { ...button, disabled: true };
65
- const url = targetUrl(button.target);
66
- if (!url) return button;
67
- return {
68
- ...button,
69
- target: { ...button.target, url: fillRecordTokens(url, record.values) },
70
- } as T;
71
- }