@antelopejs/dms-database 0.0.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.
- package/LICENSE +190 -0
- package/README.md +53 -0
- package/dist/automation/index.d.ts +2 -0
- package/dist/automation/index.js +13 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/tableWatch.d.ts +17 -0
- package/dist/automation/tableWatch.js +189 -0
- package/dist/automation/tableWatch.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +19 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/models/diagram_layouts.model.d.ts +25 -0
- package/dist/db/models/diagram_layouts.model.js +29 -0
- package/dist/db/models/diagram_layouts.model.js.map +1 -0
- package/dist/db/models/diagram_notes.model.d.ts +23 -0
- package/dist/db/models/diagram_notes.model.js +15 -0
- package/dist/db/models/diagram_notes.model.js.map +1 -0
- package/dist/db/models/index.d.ts +4 -0
- package/dist/db/models/index.js +21 -0
- package/dist/db/models/index.js.map +1 -0
- package/dist/db/models/query_history.model.d.ts +25 -0
- package/dist/db/models/query_history.model.js +33 -0
- package/dist/db/models/query_history.model.js.map +1 -0
- package/dist/db/models/saved_queries.model.d.ts +24 -0
- package/dist/db/models/saved_queries.model.js +21 -0
- package/dist/db/models/saved_queries.model.js.map +1 -0
- package/dist/db/tables/diagram_layouts.table.d.ts +7 -0
- package/dist/db/tables/diagram_layouts.table.js +35 -0
- package/dist/db/tables/diagram_layouts.table.js.map +1 -0
- package/dist/db/tables/diagram_notes.table.d.ts +13 -0
- package/dist/db/tables/diagram_notes.table.js +59 -0
- package/dist/db/tables/diagram_notes.table.js.map +1 -0
- package/dist/db/tables/index.d.ts +4 -0
- package/dist/db/tables/index.js +21 -0
- package/dist/db/tables/index.js.map +1 -0
- package/dist/db/tables/query_history.table.d.ts +11 -0
- package/dist/db/tables/query_history.table.js +54 -0
- package/dist/db/tables/query_history.table.js.map +1 -0
- package/dist/db/tables/saved_queries.table.d.ts +12 -0
- package/dist/db/tables/saved_queries.table.js +58 -0
- package/dist/db/tables/saved_queries.table.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/data.d.ts +9 -0
- package/dist/pages/data.js +29 -0
- package/dist/pages/data.js.map +1 -0
- package/dist/pages/index.d.ts +4 -0
- package/dist/pages/index.js +21 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/overview.d.ts +9 -0
- package/dist/pages/overview.js +29 -0
- package/dist/pages/overview.js.map +1 -0
- package/dist/pages/query.d.ts +9 -0
- package/dist/pages/query.js +29 -0
- package/dist/pages/query.js.map +1 -0
- package/dist/pages/schemas.d.ts +9 -0
- package/dist/pages/schemas.js +29 -0
- package/dist/pages/schemas.js.map +1 -0
- package/dist/routes/data.d.ts +25 -0
- package/dist/routes/data.js +324 -0
- package/dist/routes/data.js.map +1 -0
- package/dist/routes/diagram.d.ts +47 -0
- package/dist/routes/diagram.js +200 -0
- package/dist/routes/diagram.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.js +35 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +6 -0
- package/dist/routes/index.js +23 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/query.d.ts +26 -0
- package/dist/routes/query.js +245 -0
- package/dist/routes/query.js.map +1 -0
- package/dist/routes/schemas.d.ts +8 -0
- package/dist/routes/schemas.js +35 -0
- package/dist/routes/schemas.js.map +1 -0
- package/dist/routes/tables.d.ts +23 -0
- package/dist/routes/tables.js +94 -0
- package/dist/routes/tables.js.map +1 -0
- package/dist/service/fieldDescriptor.d.ts +66 -0
- package/dist/service/fieldDescriptor.js +156 -0
- package/dist/service/fieldDescriptor.js.map +1 -0
- package/dist/service/health.d.ts +20 -0
- package/dist/service/health.js +58 -0
- package/dist/service/health.js.map +1 -0
- package/dist/service/introspect.d.ts +6 -0
- package/dist/service/introspect.js +312 -0
- package/dist/service/introspect.js.map +1 -0
- package/dist/service/schemaLabels.d.ts +7 -0
- package/dist/service/schemaLabels.js +39 -0
- package/dist/service/schemaLabels.js.map +1 -0
- package/dist/service/schemaRegistry.d.ts +2 -0
- package/dist/service/schemaRegistry.js +8 -0
- package/dist/service/schemaRegistry.js.map +1 -0
- package/dist/service/stagedSerialization.d.ts +6 -0
- package/dist/service/stagedSerialization.js +54 -0
- package/dist/service/stagedSerialization.js.map +1 -0
- package/dist/service/types.d.ts +67 -0
- package/dist/service/types.js +3 -0
- package/dist/service/types.js.map +1 -0
- package/dist/types/constants.d.ts +3 -0
- package/dist/types/constants.js +7 -0
- package/dist/types/constants.js.map +1 -0
- package/dist/types/responses.d.ts +14 -0
- package/dist/types/responses.js +3 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/query.d.ts +17 -0
- package/dist/utils/query.js +116 -0
- package/dist/utils/query.js.map +1 -0
- package/dist/utils/requestValidation.d.ts +2 -0
- package/dist/utils/requestValidation.js +14 -0
- package/dist/utils/requestValidation.js.map +1 -0
- package/frontend-vue/.editorconfig +12 -0
- package/frontend-vue/.prettierrc +11 -0
- package/frontend-vue/app/components/DataBrowserSidebar.vue +254 -0
- package/frontend-vue/app/components/DataBrowserTabBar.vue +166 -0
- package/frontend-vue/app/components/DataGrid.vue +735 -0
- package/frontend-vue/app/components/DataGridCell.vue +147 -0
- package/frontend-vue/app/components/DataGridHeaderCell.vue +175 -0
- package/frontend-vue/app/components/DiagramNoteNode.vue +100 -0
- package/frontend-vue/app/components/DiagramRelationEdge.vue +62 -0
- package/frontend-vue/app/components/DiagramStubNode.vue +37 -0
- package/frontend-vue/app/components/DiagramTableNode.vue +94 -0
- package/frontend-vue/app/components/JsonCellPanel.vue +93 -0
- package/frontend-vue/app/components/QueryBuilderPanel.vue +501 -0
- package/frontend-vue/app/components/QueryFavoritesPanel.vue +127 -0
- package/frontend-vue/app/components/QueryHistoryPanel.vue +134 -0
- package/frontend-vue/app/components/QueryResultPanel.vue +204 -0
- package/frontend-vue/app/components/SchemaDiagram.vue +1150 -0
- package/frontend-vue/app/components/SchemaTableInspector.vue +278 -0
- package/frontend-vue/app/composables/useDataBrowserGrid.ts +162 -0
- package/frontend-vue/app/composables/useDataBrowserTabs.ts +255 -0
- package/frontend-vue/app/composables/useDatabaseHealth.ts +50 -0
- package/frontend-vue/app/composables/useDatabaseSchemas.ts +92 -0
- package/frontend-vue/app/composables/useDiagramPersistence.ts +99 -0
- package/frontend-vue/app/composables/useQueryRuntime.ts +118 -0
- package/frontend-vue/app/composables/useQueryStore.ts +117 -0
- package/frontend-vue/app/composables/useTableViewRowClick.ts +22 -0
- package/frontend-vue/app/custom-pages/database/data.vue +42 -0
- package/frontend-vue/app/custom-pages/database/overview.vue +367 -0
- package/frontend-vue/app/custom-pages/database/query.vue +432 -0
- package/frontend-vue/app/custom-pages/database/schemas.vue +216 -0
- package/frontend-vue/app/utils/databaseFieldModifiers.ts +33 -0
- package/frontend-vue/app/utils/databaseFieldTypeIcons.ts +224 -0
- package/frontend-vue/app/utils/dmsTableView.ts +83 -0
- package/frontend-vue/app/utils/relativeTime.ts +15 -0
- package/frontend-vue/app/utils/stagedSerialization.ts +91 -0
- package/frontend-vue/dms.frontend.ts +38 -0
- package/frontend-vue/eslint.config.mjs +16 -0
- package/frontend-vue/i18n/locales/database-en-GB.json +239 -0
- package/frontend-vue/i18n/locales/database-fr-FR.json +239 -0
- package/frontend-vue/package.json +42 -0
- package/frontend-vue/pnpm-lock.yaml +5819 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- package/frontend-vue/tests/persistence.test.ts +70 -0
- package/frontend-vue/tests/queryRuntime.test.ts +53 -0
- package/frontend-vue/tests/registration.test.ts +33 -0
- package/frontend-vue/tsconfig.json +13 -0
- package/frontend-vue/vitest.config.ts +5 -0
- package/package.json +78 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { FieldDescriptor } from "../composables/useDatabaseSchemas";
|
|
2
|
+
|
|
3
|
+
export interface TypeIcon {
|
|
4
|
+
icon: string;
|
|
5
|
+
color: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const NAMED_TYPE_ICONS: Record<string, TypeIcon> = {
|
|
10
|
+
email: { icon: "i-ph-at", color: "text-sky-500", label: "Email" },
|
|
11
|
+
password: { icon: "i-ph-lock", color: "text-amber-500", label: "Password" },
|
|
12
|
+
secret: { icon: "i-ph-lock", color: "text-amber-500", label: "Secret" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const FALLBACK_ICON: TypeIcon = {
|
|
16
|
+
icon: "i-ph-question",
|
|
17
|
+
color: "text-muted",
|
|
18
|
+
label: "Unknown",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const STRING_ICON: TypeIcon = {
|
|
22
|
+
icon: "i-ph-text-aa",
|
|
23
|
+
color: "text-neutral-500",
|
|
24
|
+
label: "String",
|
|
25
|
+
};
|
|
26
|
+
const NUMBER_ICON: TypeIcon = {
|
|
27
|
+
icon: "i-ph-hash",
|
|
28
|
+
color: "text-blue-500",
|
|
29
|
+
label: "Number",
|
|
30
|
+
};
|
|
31
|
+
const BOOLEAN_ICON: TypeIcon = {
|
|
32
|
+
icon: "i-ph-toggle-right",
|
|
33
|
+
color: "text-emerald-500",
|
|
34
|
+
label: "Boolean",
|
|
35
|
+
};
|
|
36
|
+
const DATE_ICON: TypeIcon = {
|
|
37
|
+
icon: "i-ph-calendar-blank",
|
|
38
|
+
color: "text-violet-500",
|
|
39
|
+
label: "Date",
|
|
40
|
+
};
|
|
41
|
+
const ARRAY_ICON: TypeIcon = {
|
|
42
|
+
icon: "i-ph-brackets-square",
|
|
43
|
+
color: "text-orange-500",
|
|
44
|
+
label: "Array",
|
|
45
|
+
};
|
|
46
|
+
const OBJECT_ICON: TypeIcon = {
|
|
47
|
+
icon: "i-ph-brackets-curly",
|
|
48
|
+
color: "text-amber-600",
|
|
49
|
+
label: "Object",
|
|
50
|
+
};
|
|
51
|
+
const RECORD_ICON: TypeIcon = {
|
|
52
|
+
icon: "i-ph-brackets-curly",
|
|
53
|
+
color: "text-amber-600",
|
|
54
|
+
label: "Record",
|
|
55
|
+
};
|
|
56
|
+
const TUPLE_ICON: TypeIcon = {
|
|
57
|
+
icon: "i-ph-brackets-square",
|
|
58
|
+
color: "text-orange-500",
|
|
59
|
+
label: "Tuple",
|
|
60
|
+
};
|
|
61
|
+
const ENUM_ICON: TypeIcon = {
|
|
62
|
+
icon: "i-ph-list-checks",
|
|
63
|
+
color: "text-cyan-500",
|
|
64
|
+
label: "Enum",
|
|
65
|
+
};
|
|
66
|
+
const UNION_ICON: TypeIcon = {
|
|
67
|
+
icon: "i-ph-split",
|
|
68
|
+
color: "text-fuchsia-500",
|
|
69
|
+
label: "Union",
|
|
70
|
+
};
|
|
71
|
+
const NULL_ICON: TypeIcon = {
|
|
72
|
+
icon: "i-ph-circle-dashed",
|
|
73
|
+
color: "text-muted",
|
|
74
|
+
label: "Null",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const PRIMARY_KEY_NAMES = new Set(["id", "_id"]);
|
|
78
|
+
const PRIMARY_KEY_INDEXES = ["pk", "primary"] as const;
|
|
79
|
+
|
|
80
|
+
function literalLabel(value: string | number | boolean): string {
|
|
81
|
+
if (typeof value === "string") return `"${value}"`;
|
|
82
|
+
return String(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function describeDescriptor(d: FieldDescriptor): string {
|
|
86
|
+
switch (d.kind) {
|
|
87
|
+
case "string":
|
|
88
|
+
case "number":
|
|
89
|
+
case "boolean":
|
|
90
|
+
case "date":
|
|
91
|
+
case "any":
|
|
92
|
+
case "unknown":
|
|
93
|
+
case "null":
|
|
94
|
+
case "undefined":
|
|
95
|
+
return d.kind;
|
|
96
|
+
case "literal":
|
|
97
|
+
return literalLabel(d.value);
|
|
98
|
+
case "array":
|
|
99
|
+
return `${d.element ? describeDescriptor(d.element) : "any"}[]`;
|
|
100
|
+
case "tuple":
|
|
101
|
+
return `[${d.elements.map(describeDescriptor).join(", ")}]`;
|
|
102
|
+
case "object":
|
|
103
|
+
return d.partial ? "Partial object" : "Object";
|
|
104
|
+
case "record":
|
|
105
|
+
return `Record<${d.key ? describeDescriptor(d.key) : "string"}, ${describeDescriptor(
|
|
106
|
+
d.value,
|
|
107
|
+
)}>`;
|
|
108
|
+
case "union":
|
|
109
|
+
return d.members.map(describeDescriptor).join(" | ");
|
|
110
|
+
case "intersection":
|
|
111
|
+
return d.members.map(describeDescriptor).join(" & ");
|
|
112
|
+
case "keyof":
|
|
113
|
+
return d.keys.length > 0 ? d.keys.map((k) => `"${k}"`).join(" | ") : "keyof";
|
|
114
|
+
case "refinement":
|
|
115
|
+
return d.name;
|
|
116
|
+
case "recursive":
|
|
117
|
+
return d.name;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function descriptorIcon(d: FieldDescriptor): TypeIcon {
|
|
122
|
+
switch (d.kind) {
|
|
123
|
+
case "string":
|
|
124
|
+
return STRING_ICON;
|
|
125
|
+
case "number":
|
|
126
|
+
return NUMBER_ICON;
|
|
127
|
+
case "boolean":
|
|
128
|
+
return BOOLEAN_ICON;
|
|
129
|
+
case "date":
|
|
130
|
+
return DATE_ICON;
|
|
131
|
+
case "null":
|
|
132
|
+
case "undefined":
|
|
133
|
+
return NULL_ICON;
|
|
134
|
+
case "any":
|
|
135
|
+
case "unknown":
|
|
136
|
+
return FALLBACK_ICON;
|
|
137
|
+
case "array":
|
|
138
|
+
return d.element
|
|
139
|
+
? { ...ARRAY_ICON, label: `${describeDescriptor(d.element)}[]` }
|
|
140
|
+
: ARRAY_ICON;
|
|
141
|
+
case "tuple":
|
|
142
|
+
return { ...TUPLE_ICON, label: describeDescriptor(d) };
|
|
143
|
+
case "object":
|
|
144
|
+
return d.partial ? { ...OBJECT_ICON, label: "Partial object" } : OBJECT_ICON;
|
|
145
|
+
case "record":
|
|
146
|
+
return { ...RECORD_ICON, label: describeDescriptor(d) };
|
|
147
|
+
case "literal": {
|
|
148
|
+
const base =
|
|
149
|
+
typeof d.value === "string"
|
|
150
|
+
? STRING_ICON
|
|
151
|
+
: typeof d.value === "number"
|
|
152
|
+
? NUMBER_ICON
|
|
153
|
+
: BOOLEAN_ICON;
|
|
154
|
+
return { ...base, label: literalLabel(d.value) };
|
|
155
|
+
}
|
|
156
|
+
case "union": {
|
|
157
|
+
const allLiteral =
|
|
158
|
+
d.members.length > 0 && d.members.every((m) => m.kind === "literal");
|
|
159
|
+
if (allLiteral) {
|
|
160
|
+
return { ...ENUM_ICON, label: describeDescriptor(d) };
|
|
161
|
+
}
|
|
162
|
+
return { ...UNION_ICON, label: describeDescriptor(d) };
|
|
163
|
+
}
|
|
164
|
+
case "intersection":
|
|
165
|
+
return { ...UNION_ICON, label: describeDescriptor(d) };
|
|
166
|
+
case "keyof":
|
|
167
|
+
return { ...ENUM_ICON, label: describeDescriptor(d) };
|
|
168
|
+
case "refinement": {
|
|
169
|
+
const base = descriptorIcon(d.base);
|
|
170
|
+
return { ...base, label: d.name };
|
|
171
|
+
}
|
|
172
|
+
case "recursive":
|
|
173
|
+
return { ...FALLBACK_ICON, label: d.name };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isDescriptor(value: unknown): value is FieldDescriptor {
|
|
178
|
+
return (
|
|
179
|
+
value !== null &&
|
|
180
|
+
typeof value === "object" &&
|
|
181
|
+
typeof (value as { kind?: unknown }).kind === "string"
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const RUNTIME_TYPE_ICONS: Record<string, TypeIcon> = {
|
|
186
|
+
string: STRING_ICON,
|
|
187
|
+
number: NUMBER_ICON,
|
|
188
|
+
boolean: BOOLEAN_ICON,
|
|
189
|
+
array: ARRAY_ICON,
|
|
190
|
+
object: OBJECT_ICON,
|
|
191
|
+
null: NULL_ICON,
|
|
192
|
+
undefined: NULL_ICON,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Icon for a COARSE RUNTIME type as sampled by the browse `/columns` endpoint
|
|
196
|
+
// (string | number | boolean | object | array | null | undefined) — the data
|
|
197
|
+
// grid's headers use this so runtime-sampled and schema-declared columns share
|
|
198
|
+
// one icon set. Named-field overrides (email, password…) still apply.
|
|
199
|
+
export function runtimeTypeIcon(type: string, fieldName: string): TypeIcon {
|
|
200
|
+
const named = NAMED_TYPE_ICONS[fieldName.toLowerCase()];
|
|
201
|
+
if (named) return named;
|
|
202
|
+
return RUNTIME_TYPE_ICONS[type] ?? FALLBACK_ICON;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function inferFieldTypeIcon(
|
|
206
|
+
field: unknown,
|
|
207
|
+
fieldName: string,
|
|
208
|
+
): TypeIcon {
|
|
209
|
+
const named = NAMED_TYPE_ICONS[fieldName.toLowerCase()];
|
|
210
|
+
if (named) return named;
|
|
211
|
+
if (isDescriptor(field)) return descriptorIcon(field);
|
|
212
|
+
return FALLBACK_ICON;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function isPrimaryKey(
|
|
216
|
+
fieldName: string,
|
|
217
|
+
indexes: Record<string, { fields?: string[] }>,
|
|
218
|
+
): boolean {
|
|
219
|
+
if (PRIMARY_KEY_NAMES.has(fieldName)) return true;
|
|
220
|
+
for (const indexName of PRIMARY_KEY_INDEXES) {
|
|
221
|
+
if (indexes[indexName]?.fields?.includes(fieldName)) return true;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Helpers that build, by hand, the *serialised* column config the shared
|
|
2
|
+
// dms-ui `DmsTableView` component expects.
|
|
3
|
+
//
|
|
4
|
+
// The DMS normally generates these objects on the backend from a
|
|
5
|
+
// `DataController` (one fixed table per controller). The module's navigation
|
|
6
|
+
// lists (overview, schema inspector) render read-only tables against module
|
|
7
|
+
// endpoints without such a controller, so we reproduce the same serialised
|
|
8
|
+
// shapes the DMS emits for its own tables.
|
|
9
|
+
//
|
|
10
|
+
// Reference shapes:
|
|
11
|
+
// DataTypeConfig — dms-core/app/types/data-type.ts
|
|
12
|
+
// TableViewColumn — dms-ui/.../table-view/types/column.ts
|
|
13
|
+
// ComponentInfo — { componentName, options? }
|
|
14
|
+
|
|
15
|
+
// The component name a serialised `FormComponents.InputText()` resolves to.
|
|
16
|
+
const TEXT_INPUT_COMPONENT = "dms-input-text";
|
|
17
|
+
|
|
18
|
+
export interface DmsComponentInfo {
|
|
19
|
+
componentName: string;
|
|
20
|
+
options?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DmsDataTypeConfig {
|
|
24
|
+
id: string;
|
|
25
|
+
compareModes: string[];
|
|
26
|
+
defaultCompareMode?: string;
|
|
27
|
+
filterComponents: Record<string, DmsComponentInfo | "noInput">;
|
|
28
|
+
inputComponent: DmsComponentInfo;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DmsTableViewColumn {
|
|
32
|
+
id: string;
|
|
33
|
+
header: string;
|
|
34
|
+
accessorKey: string;
|
|
35
|
+
listable: boolean;
|
|
36
|
+
type: DmsDataTypeConfig;
|
|
37
|
+
visible?: boolean;
|
|
38
|
+
enableSorting?: boolean;
|
|
39
|
+
enableColumnFilter?: boolean;
|
|
40
|
+
readonlyBehavior?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A string/text data type — the safe default for an introspected, schemaless
|
|
44
|
+
// column. `id` is the load-bearing field: DmsTableView dereferences
|
|
45
|
+
// `column.type.id` during setup, so every column MUST carry a valid type config
|
|
46
|
+
// or the component throws immediately.
|
|
47
|
+
export function stringDataType(): DmsDataTypeConfig {
|
|
48
|
+
return {
|
|
49
|
+
id: "string",
|
|
50
|
+
// Filter modes offered when column filtering is enabled; the browse backend
|
|
51
|
+
// decodes `is` (exact) and `contains` (substring).
|
|
52
|
+
compareModes: ["is", "contains"],
|
|
53
|
+
defaultCompareMode: "contains",
|
|
54
|
+
filterComponents: {
|
|
55
|
+
default: { componentName: TEXT_INPUT_COMPONENT },
|
|
56
|
+
is: { componentName: TEXT_INPUT_COMPONENT },
|
|
57
|
+
contains: { componentName: TEXT_INPUT_COMPONENT },
|
|
58
|
+
},
|
|
59
|
+
inputComponent: { componentName: TEXT_INPUT_COMPONENT },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ReadonlyColumnSpec {
|
|
64
|
+
key: string;
|
|
65
|
+
header: string;
|
|
66
|
+
enableSorting?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Build read-only, sortable columns with explicit (i18n) headers — used by the
|
|
70
|
+
// table-navigation lists, which are display-only (no form components).
|
|
71
|
+
export function buildReadonlyColumns(
|
|
72
|
+
specs: ReadonlyColumnSpec[],
|
|
73
|
+
): DmsTableViewColumn[] {
|
|
74
|
+
return specs.map((spec) => ({
|
|
75
|
+
id: spec.key,
|
|
76
|
+
accessorKey: spec.key,
|
|
77
|
+
header: spec.header,
|
|
78
|
+
listable: true,
|
|
79
|
+
type: stringDataType(),
|
|
80
|
+
enableSorting: spec.enableSorting ?? true,
|
|
81
|
+
enableColumnFilter: false,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const relativeFormatter = new Intl.RelativeTimeFormat(undefined, {
|
|
2
|
+
numeric: "auto",
|
|
3
|
+
});
|
|
4
|
+
|
|
5
|
+
// Human "3 minutes ago" style label for an ISO timestamp, relative to now.
|
|
6
|
+
// Shared by the query history and favorites panels.
|
|
7
|
+
export function formatRelativeTime(iso: string): string {
|
|
8
|
+
const diffMs = new Date(iso).getTime() - Date.now();
|
|
9
|
+
const minutes = Math.round(diffMs / 60_000);
|
|
10
|
+
if (Math.abs(minutes) < 60) return relativeFormatter.format(minutes, "minute");
|
|
11
|
+
const hours = Math.round(minutes / 60);
|
|
12
|
+
if (Math.abs(hours) < 24) return relativeFormatter.format(hours, "hour");
|
|
13
|
+
const days = Math.round(hours / 24);
|
|
14
|
+
return relativeFormatter.format(days, "day");
|
|
15
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Datum,
|
|
3
|
+
Query,
|
|
4
|
+
Schema,
|
|
5
|
+
SchemaInstance,
|
|
6
|
+
Selection,
|
|
7
|
+
SingleSelection,
|
|
8
|
+
Stream,
|
|
9
|
+
Table,
|
|
10
|
+
ValueProxy,
|
|
11
|
+
} from '@antelopejs/interface-database/staged-query'
|
|
12
|
+
|
|
13
|
+
export interface QueryStage {
|
|
14
|
+
stage: string
|
|
15
|
+
options?: unknown
|
|
16
|
+
args: unknown[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type StagedCtor = new (...args: never[]) => unknown
|
|
20
|
+
|
|
21
|
+
const STAGED_CLASSES: Record<string, StagedCtor> = {
|
|
22
|
+
Schema,
|
|
23
|
+
SchemaInstance,
|
|
24
|
+
Table,
|
|
25
|
+
Selection,
|
|
26
|
+
SingleSelection,
|
|
27
|
+
Stream,
|
|
28
|
+
Datum,
|
|
29
|
+
Query,
|
|
30
|
+
ValueProxy,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function classNameOf(obj: object): string | undefined {
|
|
34
|
+
for (const [name, ctor] of Object.entries(STAGED_CLASSES)) {
|
|
35
|
+
if (obj instanceof ctor) return name
|
|
36
|
+
}
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function encodeStaged(node: unknown): unknown {
|
|
41
|
+
if (node === null || node === undefined) return node
|
|
42
|
+
if (typeof node !== 'object') return node
|
|
43
|
+
if (Array.isArray(node)) return node.map(encodeStaged)
|
|
44
|
+
|
|
45
|
+
const cls = classNameOf(node)
|
|
46
|
+
if (cls) {
|
|
47
|
+
const stages = (node as { stages?: unknown[] }).stages ?? []
|
|
48
|
+
return {
|
|
49
|
+
__cls: cls,
|
|
50
|
+
stages: stages.map(encodeStage),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const out: Record<string, unknown> = {}
|
|
55
|
+
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
56
|
+
out[key] = encodeStaged(value)
|
|
57
|
+
}
|
|
58
|
+
return out
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function encodeStage(stage: unknown): unknown {
|
|
62
|
+
if (!stage || typeof stage !== 'object') return stage
|
|
63
|
+
const { stage: name, options, args } = stage as Partial<QueryStage>
|
|
64
|
+
const encoded: QueryStage = {
|
|
65
|
+
stage: typeof name === 'string' ? name : '',
|
|
66
|
+
args: Array.isArray(args) ? args.map(encodeStaged) : [],
|
|
67
|
+
}
|
|
68
|
+
if (options !== undefined) encoded.options = encodeStaged(options)
|
|
69
|
+
return encoded
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const MUTATING_STAGES = new Set(['insert', 'update', 'replace', 'delete'])
|
|
73
|
+
|
|
74
|
+
export function containsMutations(root: unknown): boolean {
|
|
75
|
+
if (!root || typeof root !== 'object') return false
|
|
76
|
+
const stages = (root as { stages?: unknown }).stages
|
|
77
|
+
if (!Array.isArray(stages)) return false
|
|
78
|
+
for (const stage of stages) {
|
|
79
|
+
if (!stage || typeof stage !== 'object') continue
|
|
80
|
+
const s = stage as { stage?: unknown; args?: unknown }
|
|
81
|
+
if (typeof s.stage === 'string' && MUTATING_STAGES.has(s.stage)) {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(s.args)) {
|
|
85
|
+
for (const arg of s.args) {
|
|
86
|
+
if (containsMutations(arg)) return true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineAsyncComponent, type Component } from 'vue'
|
|
2
|
+
import type { DmsFrontendModule } from '#dms-inertia/frontend-module'
|
|
3
|
+
|
|
4
|
+
interface VueModule {
|
|
5
|
+
default: Component
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const components = import.meta.glob<VueModule>('./app/components/**/*.vue')
|
|
9
|
+
const pages = import.meta.glob<VueModule>('./app/custom-pages/**/*.vue')
|
|
10
|
+
|
|
11
|
+
const frontendModule: DmsFrontendModule = {
|
|
12
|
+
setup(sdk) {
|
|
13
|
+
for (const [path, loader] of Object.entries(components).sort()) {
|
|
14
|
+
const name = path
|
|
15
|
+
.split('/')
|
|
16
|
+
.at(-1)!
|
|
17
|
+
.replace(/\.vue$/, '')
|
|
18
|
+
sdk.registerComponent(
|
|
19
|
+
`DmsDatabase${name}`,
|
|
20
|
+
defineAsyncComponent(async () => (await loader()).default),
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
for (const [path, loader] of Object.entries(pages).sort()) {
|
|
24
|
+
const name = path.replace('./app/custom-pages/', '').replace(/\.vue$/, '')
|
|
25
|
+
const component = defineAsyncComponent(
|
|
26
|
+
async () => (await loader()).default,
|
|
27
|
+
)
|
|
28
|
+
const componentName = name
|
|
29
|
+
.split('/')
|
|
30
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
31
|
+
.join('')
|
|
32
|
+
sdk.registerPage(name, component, loader)
|
|
33
|
+
sdk.registerComponent(`Dms${componentName}`, component)
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default frontendModule
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import vue from 'eslint-plugin-vue'
|
|
2
|
+
import typescript from '@typescript-eslint/parser'
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{ ignores: ['node_modules/**', 'dist/**'] },
|
|
6
|
+
...vue.configs['flat/essential'],
|
|
7
|
+
{
|
|
8
|
+
files: ['**/*.ts'],
|
|
9
|
+
languageOptions: { parser: typescript },
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
files: ['**/*.vue'],
|
|
13
|
+
languageOptions: { parserOptions: { parser: typescript } },
|
|
14
|
+
rules: { 'vue/multi-word-component-names': 'off' },
|
|
15
|
+
},
|
|
16
|
+
]
|