@sfxcode/nuxt-ui-mongocamp 1.1.1 → 1.1.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.
package/README.md CHANGED
@@ -292,14 +292,14 @@ pnpm run docs:preview
292
292
  [MIT](./LICENSE)
293
293
 
294
294
  <!-- Badges -->
295
- [npm-version-src]: https://img.shields.io/npm/v/nuxt-ui-mongocamp/latest.svg?style=flat&colorA=020420&colorB=00DC82
296
- [npm-version-href]: https://npmjs.com/package/nuxt-ui-mongocamp
295
+ [npm-version-src]: https://img.shields.io/npm/v/@sfxcode/nuxt-ui-mongocamp/latest.svg?style=flat&colorA=020420&colorB=00DC82
296
+ [npm-version-href]: https://npmjs.com/package/@sfxcode/nuxt-ui-mongocamp
297
297
 
298
- [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-ui-mongocamp.svg?style=flat&colorA=020420&colorB=00DC82
299
- [npm-downloads-href]: https://npm.chart.dev/nuxt-ui-mongocamp
298
+ [npm-downloads-src]: https://img.shields.io/npm/dm/@sfxcode/nuxt-ui-mongocamp.svg?style=flat&colorA=020420&colorB=00DC82
299
+ [npm-downloads-href]: https://npm.chart.dev/@sfxcode/nuxt-ui-mongocamp
300
300
 
301
- [license-src]: https://img.shields.io/npm/l/nuxt-ui-mongocamp.svg?style=flat&colorA=020420&colorB=00DC82
302
- [license-href]: https://npmjs.com/package/nuxt-ui-mongocamp
301
+ [license-src]: https://img.shields.io/npm/l/@sfxcode/nuxt-ui-mongocamp.svg?style=flat&colorA=020420&colorB=00DC82
302
+ [license-href]: https://npmjs.com/package/@sfxcode/nuxt-ui-mongocamp
303
303
 
304
304
  [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
305
305
  [nuxt-href]: https://nuxt.com
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "1.1.1",
7
+ "version": "1.1.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -3,8 +3,7 @@ import { h, ref, resolveComponent, watch } from "vue";
3
3
  import { useMongocampApi } from "#imports";
4
4
  import useMongocampCollection from "../composables/useMongocampCollection";
5
5
  import { useMongocampBucket } from "../composables/useMongocampBucket";
6
- import { useMongocampSchema } from "../composables/useMongocampSchema";
7
- import { columnsToFormKitSchema, documentToFormData, formDataToDocument } from "../composables/useMongocampDynamicForm";
6
+ import { unwrapExtendedJson, wrapExtendedJson } from "../composables/useMongocampExtendedJson";
8
7
  import useMongocampDocument from "../composables/useMongocampDocument";
9
8
  import { useMongocampQuery } from "../composables/useMongocampQuery";
10
9
  const props = defineProps({
@@ -13,7 +12,6 @@ const props = defineProps({
13
12
  const { documentApi } = useMongocampApi();
14
13
  const { pagination, total } = useMongocampCollection();
15
14
  const { isBucketCollection, fileIdForRow, downloadingFileIds, uploading, downloadFile, uploadFile } = useMongocampBucket();
16
- const { schemaFromSamples } = useMongocampSchema();
17
15
  const { ensureMetaData } = useMongocampDocument();
18
16
  const { like, or } = useMongocampQuery();
19
17
  const fileInputRef = ref(null);
@@ -41,10 +39,14 @@ const editJson = ref("{}");
41
39
  const editError = ref("");
42
40
  const docToDelete = ref(void 0);
43
41
  const deleteError = ref("");
44
- const editColumns = ref([]);
45
- const editSchema = ref([]);
42
+ const editSamples = ref([]);
46
43
  const editFormData = ref({});
47
44
  const originalRow = ref(void 0);
45
+ function omitId(row) {
46
+ const rest = { ...row };
47
+ delete rest._id;
48
+ return rest;
49
+ }
48
50
  function getDocId(row) {
49
51
  const id = row._id;
50
52
  if (typeof id === "string") return id;
@@ -57,9 +59,8 @@ function openInsert() {
57
59
  editDocId.value = void 0;
58
60
  editError.value = "";
59
61
  originalRow.value = void 0;
60
- editColumns.value = schemaFromSamples(documents.value);
61
- if (editColumns.value.length > 0) {
62
- editSchema.value = columnsToFormKitSchema(editColumns.value);
62
+ editSamples.value = documents.value.map((row) => omitId(unwrapExtendedJson(row)));
63
+ if (editSamples.value.length > 0) {
63
64
  editFormData.value = {};
64
65
  } else {
65
66
  editJson.value = "{}";
@@ -71,10 +72,9 @@ function openEdit(row) {
71
72
  editDocId.value = getDocId(row);
72
73
  editError.value = "";
73
74
  originalRow.value = row;
74
- editColumns.value = schemaFromSamples(documents.value);
75
- if (editColumns.value.length > 0) {
76
- editSchema.value = columnsToFormKitSchema(editColumns.value);
77
- editFormData.value = documentToFormData(row, editColumns.value);
75
+ editSamples.value = documents.value.map((r) => omitId(unwrapExtendedJson(r)));
76
+ if (editSamples.value.length > 0) {
77
+ editFormData.value = omitId(unwrapExtendedJson(row));
78
78
  } else {
79
79
  editJson.value = JSON.stringify(row, null, 2);
80
80
  }
@@ -88,8 +88,8 @@ function confirmDelete(row) {
88
88
  async function handleSave() {
89
89
  editError.value = "";
90
90
  let payload;
91
- if (editColumns.value.length > 0) {
92
- payload = formDataToDocument(editFormData.value, editColumns.value, editMode.value === "update" ? originalRow.value : void 0);
91
+ if (editSamples.value.length > 0) {
92
+ payload = wrapExtendedJson(editFormData.value, editMode.value === "update" ? originalRow.value : void 0);
93
93
  } else {
94
94
  try {
95
95
  payload = JSON.parse(editJson.value);
@@ -439,10 +439,10 @@ watch(() => props.collectionName, init, { immediate: true });
439
439
  :ui="{ footer: 'justify-end' }"
440
440
  >
441
441
  <template #body>
442
- <FUDataEdit
443
- v-if="editColumns.length > 0"
444
- :data="editFormData"
445
- :schema="editSchema"
442
+ <FUAutoForm
443
+ v-if="editSamples.length > 0"
444
+ v-model="editFormData"
445
+ :data="editSamples"
446
446
  :submit-label="editMode === 'insert' ? 'Insert' : 'Save'"
447
447
  :submit-icon="editMode === 'insert' ? 'i-lucide-plus' : 'i-lucide-save'"
448
448
  @data-saved="handleSave"
@@ -461,7 +461,7 @@ watch(() => props.collectionName, init, { immediate: true });
461
461
  </p>
462
462
  </template>
463
463
  <template
464
- v-if="editColumns.length === 0"
464
+ v-if="editSamples.length === 0"
465
465
  #footer
466
466
  >
467
467
  <UButton
@@ -0,0 +1,2 @@
1
+ export declare function unwrapExtendedJson(value: unknown): unknown;
2
+ export declare function wrapExtendedJson(formData: Record<string, unknown>, originalDoc?: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,62 @@
1
+ function isPlainObject(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ const NUMBER_WRAPPER_KEYS = ["$numberInt", "$numberLong", "$numberDouble", "$numberDecimal"];
5
+ const ISO_DATE_TIME_REGEX = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
6
+ function isIsoDateTimeString(value) {
7
+ return ISO_DATE_TIME_REGEX.test(value) && !Number.isNaN(Date.parse(value));
8
+ }
9
+ function unwrapDateTime(raw) {
10
+ const inner = raw.$date;
11
+ if (typeof inner === "string" || typeof inner === "number") return new Date(inner).toISOString();
12
+ if (isPlainObject(inner) && typeof inner.$numberLong === "string") return new Date(Number(inner.$numberLong)).toISOString();
13
+ return raw;
14
+ }
15
+ function unwrapNumber(raw) {
16
+ for (const key of NUMBER_WRAPPER_KEYS) {
17
+ const wrapped = raw[key];
18
+ if (typeof wrapped === "string" || typeof wrapped === "number") {
19
+ const parsed = Number(wrapped);
20
+ if (!Number.isNaN(parsed)) return parsed;
21
+ }
22
+ }
23
+ return void 0;
24
+ }
25
+ export function unwrapExtendedJson(value) {
26
+ if (Array.isArray(value)) return value.map(unwrapExtendedJson);
27
+ if (!isPlainObject(value)) return value;
28
+ if (typeof value.$oid === "string") return value.$oid;
29
+ if ("$date" in value) return unwrapDateTime(value);
30
+ const number = unwrapNumber(value);
31
+ if (number !== void 0) return number;
32
+ const result = {};
33
+ for (const [key, v] of Object.entries(value)) result[key] = unwrapExtendedJson(v);
34
+ return result;
35
+ }
36
+ function rewrapValue(value, originalValue) {
37
+ if (typeof value === "string" && isIsoDateTimeString(value)) {
38
+ return { $date: value };
39
+ }
40
+ if (typeof value === "string" && isPlainObject(originalValue) && typeof originalValue.$oid === "string") {
41
+ return { $oid: value };
42
+ }
43
+ if (Array.isArray(value)) {
44
+ const originalArray = Array.isArray(originalValue) ? originalValue : [];
45
+ return value.map((item, index) => rewrapValue(item, originalArray[index]));
46
+ }
47
+ if (isPlainObject(value)) {
48
+ const originalObject = isPlainObject(originalValue) ? originalValue : void 0;
49
+ const nested = {};
50
+ for (const [key, v] of Object.entries(value)) nested[key] = rewrapValue(v, originalObject?.[key]);
51
+ return nested;
52
+ }
53
+ return value;
54
+ }
55
+ export function wrapExtendedJson(formData, originalDoc) {
56
+ const result = originalDoc ? { ...originalDoc } : {};
57
+ for (const [key, value] of Object.entries(formData)) {
58
+ result[key] = rewrapValue(value, originalDoc?.[key]);
59
+ }
60
+ delete result._id;
61
+ return result;
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfxcode/nuxt-ui-mongocamp",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "A Nuxt module that wraps @sfxcode/nuxt-mongocamp-server with ready-made UI components, composables, and a runtime plugin",
5
5
  "repository": "sfxcode/nuxt-ui-mongocamp",
6
6
  "homepage": "https://sfxcode.github.io/nuxt-ui-mongocamp/",
@@ -58,8 +58,8 @@
58
58
  "@nuxt/kit": "^4.4.8",
59
59
  "@nuxt/ui": "4.9.0",
60
60
  "@sfxcode/nuxt-mongocamp-server": "^1.4.4",
61
- "@sfxcode/nuxt-ui-formkit": "^1.2.1",
62
- "unocss-nuxt-ui": "1.2.1"
61
+ "@sfxcode/nuxt-ui-formkit": "^1.2.6",
62
+ "unocss-nuxt-ui": "^1.2.2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@nuxt/devtools": "4.0.0-alpha.7",
@@ -69,13 +69,13 @@
69
69
  "@nuxt/test-utils": "^4.0.3",
70
70
  "@types/node": "latest",
71
71
  "changelogen": "^0.6.2",
72
- "eslint": "^10.6.0",
72
+ "eslint": "^10.7.0",
73
73
  "nuxt": "^4.4.8",
74
74
  "tailwindcss": "^4.3.2",
75
75
  "typescript": "~6.0.3",
76
76
  "vitepress": "2.0.0-alpha.18",
77
77
  "vitest": "^4.1.10",
78
- "vue-tsc": "^3.3.6"
78
+ "vue-tsc": "^3.3.7"
79
79
  },
80
80
  "packageManager": "pnpm@11.10.0+sha512.0b7f8b98060031904c017e3a41eb187a16d40eeb829b95c4f8cb03681761fc4ab53dd219115b9b447f4dce1a05a214764461e7d3703392a9f32f9511ce8c86c8"
81
81
  }
@@ -1,14 +0,0 @@
1
- import type { ColumnDefinition } from './useMongocampSchema.js';
2
- export interface FormKitSchemaNode {
3
- $formkit?: string;
4
- $el?: string;
5
- name?: string;
6
- label?: string;
7
- validation?: string;
8
- attrs?: Record<string, unknown>;
9
- children?: FormKitSchemaNode[];
10
- [key: string]: unknown;
11
- }
12
- export declare function columnsToFormKitSchema(columns: ColumnDefinition[]): FormKitSchemaNode[];
13
- export declare function documentToFormData(doc: Record<string, unknown>, columns: ColumnDefinition[]): Record<string, unknown>;
14
- export declare function formDataToDocument(formData: Record<string, unknown>, columns: ColumnDefinition[], originalDoc?: Record<string, unknown>): Record<string, unknown>;
@@ -1,231 +0,0 @@
1
- import { parseAbsoluteToLocal } from "@internationalized/date";
2
- const SCALAR_FORMKIT_TYPES = {
3
- "string": "nuxtUIInput",
4
- "number": "nuxtUIInputNumber",
5
- "boolean": "nuxtUISwitch",
6
- "date-time": "nuxtUIInputDate"
7
- };
8
- function scalarNode(column, formkitType) {
9
- const node = {
10
- $formkit: formkitType,
11
- name: column.columnKey,
12
- label: column.columnName
13
- };
14
- if (column.required) {
15
- node.validation = "required";
16
- }
17
- return node;
18
- }
19
- function groupNode(column) {
20
- return {
21
- $formkit: "group",
22
- name: column.columnKey,
23
- label: column.columnName,
24
- children: columnsToFormKitSchema(column.children ?? [])
25
- };
26
- }
27
- function wrapGroup(node) {
28
- return {
29
- $el: "div",
30
- attrs: { class: "pt-4 flex flex-col gap-3" },
31
- children: [node]
32
- };
33
- }
34
- function scalarArrayNode(column) {
35
- const node = {
36
- $formkit: "nuxtUIInputTags",
37
- name: column.columnKey,
38
- label: column.columnName
39
- };
40
- if (column.required) {
41
- node.validation = "required";
42
- }
43
- return node;
44
- }
45
- function defaultScalarValue(formkitType) {
46
- if (formkitType === "nuxtUIInputNumber") return 0;
47
- if (formkitType === "nuxtUISwitch") return false;
48
- return "";
49
- }
50
- function defaultValueForColumn(column) {
51
- if (column.columnType === "object") return defaultObjectValue(column.children ?? []);
52
- if (column.columnType === "array") return [];
53
- const formkitType = SCALAR_FORMKIT_TYPES[column.columnType];
54
- return formkitType ? defaultScalarValue(formkitType) : "";
55
- }
56
- function defaultObjectValue(children) {
57
- const value = {};
58
- children.forEach((child) => {
59
- value[child.columnKey] = defaultValueForColumn(child);
60
- });
61
- return value;
62
- }
63
- const REPEATER_BUTTON_PROPS = {
64
- alwaysDisplayInsertButton: true,
65
- displayAddButton: true,
66
- displayCloneButton: true,
67
- displayDeleteButton: true
68
- };
69
- const REPEATER_LAYOUT_PROPS = {
70
- listClass: "flex flex-col gap-3",
71
- listItemClass: "flex flex-wrap items-end gap-3 p-3 border border-gray-100 dark:border-gray-800 rounded-lg shadow-sm",
72
- buttonGroupClass: "flex items-center gap-1 ml-auto",
73
- displayDragHandle: true,
74
- draggable: true,
75
- hideMoveButtons: true,
76
- dragHandleClass: "cursor-grab active:cursor-grabbing text-dimmed"
77
- };
78
- function repeaterArrayNode(column, itemFormkitType) {
79
- const node = {
80
- $formkit: "nuxtUIRepeater",
81
- name: column.columnKey,
82
- label: column.columnName,
83
- newItem: { value: defaultScalarValue(itemFormkitType) },
84
- ...REPEATER_BUTTON_PROPS,
85
- ...REPEATER_LAYOUT_PROPS,
86
- children: [
87
- { $formkit: itemFormkitType, name: "value", label: "Value" }
88
- ]
89
- };
90
- if (column.required) {
91
- node.validation = "required";
92
- }
93
- return node;
94
- }
95
- function objectArrayNode(column) {
96
- const node = {
97
- $formkit: "nuxtUIRepeater",
98
- name: column.columnKey,
99
- label: column.columnName,
100
- newItem: defaultObjectValue(column.children ?? []),
101
- ...REPEATER_BUTTON_PROPS,
102
- ...REPEATER_LAYOUT_PROPS,
103
- children: columnsToFormKitSchema(column.children ?? [])
104
- };
105
- if (column.required) {
106
- node.validation = "required";
107
- }
108
- return node;
109
- }
110
- function arrayNode(column) {
111
- if (column.arrayItemType === "object") {
112
- return objectArrayNode(column);
113
- }
114
- if (column.arrayItemType === "string" || column.arrayItemType === "unknown") {
115
- return scalarArrayNode(column);
116
- }
117
- const itemFormkitType = column.arrayItemType ? SCALAR_FORMKIT_TYPES[column.arrayItemType] : void 0;
118
- if (itemFormkitType) {
119
- return repeaterArrayNode(column, itemFormkitType);
120
- }
121
- return void 0;
122
- }
123
- export function columnsToFormKitSchema(columns) {
124
- const schema = [];
125
- columns.forEach((column) => {
126
- if (column.columnType === "object") {
127
- schema.push(wrapGroup(groupNode(column)));
128
- return;
129
- }
130
- if (column.columnType === "array") {
131
- const node = arrayNode(column);
132
- if (node) schema.push(node);
133
- return;
134
- }
135
- const formkitType = SCALAR_FORMKIT_TYPES[column.columnType];
136
- if (formkitType) {
137
- schema.push(scalarNode(column, formkitType));
138
- }
139
- });
140
- return schema;
141
- }
142
- function isPlainObject(value) {
143
- return typeof value === "object" && value !== null && !Array.isArray(value);
144
- }
145
- function unwrapOid(raw) {
146
- if (isPlainObject(raw) && typeof raw.$oid === "string") return raw.$oid;
147
- return raw;
148
- }
149
- const NUMBER_WRAPPER_KEYS = ["$numberInt", "$numberLong", "$numberDouble", "$numberDecimal"];
150
- function unwrapNumber(raw) {
151
- if (!isPlainObject(raw)) return raw;
152
- for (const key of NUMBER_WRAPPER_KEYS) {
153
- const wrapped = raw[key];
154
- if (typeof wrapped === "string" || typeof wrapped === "number") {
155
- const parsed = Number(wrapped);
156
- if (!Number.isNaN(parsed)) return parsed;
157
- }
158
- }
159
- return raw;
160
- }
161
- function unwrapDateTime(raw) {
162
- if (!isPlainObject(raw) || !("$date" in raw)) return raw;
163
- const inner = raw.$date;
164
- if (typeof inner === "string" || typeof inner === "number") {
165
- return new Date(inner).toISOString();
166
- }
167
- if (isPlainObject(inner) && typeof inner.$numberLong === "string") {
168
- return new Date(Number(inner.$numberLong)).toISOString();
169
- }
170
- return raw;
171
- }
172
- function toZonedDateTime(value) {
173
- if (typeof value !== "string") return value;
174
- try {
175
- return parseAbsoluteToLocal(value);
176
- } catch {
177
- return value;
178
- }
179
- }
180
- function isZonedDateTimeLike(value) {
181
- return typeof value === "object" && value !== null && typeof value.toAbsoluteString === "function";
182
- }
183
- export function documentToFormData(doc, columns) {
184
- const formData = {};
185
- columns.forEach((column) => {
186
- if (!(column.columnKey in doc)) return;
187
- const raw = doc[column.columnKey];
188
- if (column.columnType === "object") {
189
- if (isPlainObject(raw)) {
190
- formData[column.columnKey] = documentToFormData(raw, column.children ?? []);
191
- }
192
- return;
193
- }
194
- if (column.columnType === "array") {
195
- if (!Array.isArray(raw)) return;
196
- formData[column.columnKey] = column.arrayItemType === "object" ? raw.map((item) => isPlainObject(item) ? documentToFormData(item, column.children ?? []) : item) : raw;
197
- return;
198
- }
199
- if (column.columnType === "date-time") {
200
- formData[column.columnKey] = toZonedDateTime(unwrapDateTime(raw));
201
- } else if (column.columnType === "string") {
202
- formData[column.columnKey] = unwrapOid(raw);
203
- } else if (column.columnType === "number") {
204
- formData[column.columnKey] = unwrapNumber(raw);
205
- } else {
206
- formData[column.columnKey] = raw;
207
- }
208
- });
209
- return formData;
210
- }
211
- function wasOriginallyOid(originalValue) {
212
- return isPlainObject(originalValue) && typeof originalValue.$oid === "string";
213
- }
214
- export function formDataToDocument(formData, columns, originalDoc) {
215
- const result = originalDoc ? { ...originalDoc } : {};
216
- columns.forEach((column) => {
217
- if (!(column.columnKey in formData)) return;
218
- const value = formData[column.columnKey];
219
- if (column.columnType === "date-time" && isZonedDateTimeLike(value)) {
220
- result[column.columnKey] = { $date: value.toAbsoluteString() };
221
- } else if (column.columnType === "date-time" && typeof value === "string") {
222
- result[column.columnKey] = { $date: value };
223
- } else if (column.columnType === "string" && wasOriginallyOid(originalDoc?.[column.columnKey])) {
224
- result[column.columnKey] = { $oid: value };
225
- } else {
226
- result[column.columnKey] = value;
227
- }
228
- });
229
- delete result._id;
230
- return result;
231
- }