@conduction/nextcloud-vue 0.1.0-beta.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 (93) hide show
  1. package/dist/nextcloud-vue.cjs.js +10710 -0
  2. package/dist/nextcloud-vue.cjs.js.map +1 -0
  3. package/dist/nextcloud-vue.css +803 -0
  4. package/dist/nextcloud-vue.esm.js +10665 -0
  5. package/dist/nextcloud-vue.esm.js.map +1 -0
  6. package/package.json +63 -0
  7. package/src/components/CnCardGrid/CnCardGrid.vue +152 -0
  8. package/src/components/CnCardGrid/index.js +1 -0
  9. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -0
  10. package/src/components/CnCellRenderer/index.js +1 -0
  11. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -0
  12. package/src/components/CnConfigurationCard/index.js +1 -0
  13. package/src/components/CnDataTable/CnDataTable.vue +354 -0
  14. package/src/components/CnDataTable/index.js +1 -0
  15. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +88 -0
  16. package/src/components/CnDetailViewLayout/index.js +1 -0
  17. package/src/components/CnEmptyState/CnEmptyState.vue +78 -0
  18. package/src/components/CnEmptyState/index.js +1 -0
  19. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +223 -0
  20. package/src/components/CnFacetSidebar/index.js +1 -0
  21. package/src/components/CnFilterBar/CnFilterBar.vue +152 -0
  22. package/src/components/CnFilterBar/index.js +1 -0
  23. package/src/components/CnIndexPage/CnIndexPage.vue +682 -0
  24. package/src/components/CnIndexPage/index.js +1 -0
  25. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -0
  26. package/src/components/CnKpiGrid/index.js +1 -0
  27. package/src/components/CnListViewLayout/CnListViewLayout.vue +80 -0
  28. package/src/components/CnListViewLayout/index.js +1 -0
  29. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -0
  30. package/src/components/CnMassActionBar/index.js +1 -0
  31. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -0
  32. package/src/components/CnMassCopyDialog/index.js +1 -0
  33. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -0
  34. package/src/components/CnMassDeleteDialog/index.js +1 -0
  35. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -0
  36. package/src/components/CnMassExportDialog/index.js +1 -0
  37. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -0
  38. package/src/components/CnMassImportDialog/index.js +1 -0
  39. package/src/components/CnObjectCard/CnObjectCard.vue +292 -0
  40. package/src/components/CnObjectCard/index.js +1 -0
  41. package/src/components/CnPagination/CnPagination.vue +252 -0
  42. package/src/components/CnPagination/index.js +1 -0
  43. package/src/components/CnRowActions/CnRowActions.vue +73 -0
  44. package/src/components/CnRowActions/index.js +1 -0
  45. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -0
  46. package/src/components/CnSettingsCard/index.js +1 -0
  47. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -0
  48. package/src/components/CnSettingsSection/index.js +1 -0
  49. package/src/components/CnStatsBlock/CnStatsBlock.vue +366 -0
  50. package/src/components/CnStatsBlock/index.js +1 -0
  51. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -0
  52. package/src/components/CnStatusBadge/index.js +1 -0
  53. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -0
  54. package/src/components/CnVersionInfoCard/index.js +1 -0
  55. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +77 -0
  56. package/src/components/CnViewModeToggle/index.js +1 -0
  57. package/src/components/index.js +25 -0
  58. package/src/composables/index.js +3 -0
  59. package/src/composables/useDetailView.js +132 -0
  60. package/src/composables/useListView.js +153 -0
  61. package/src/composables/useSubResource.js +142 -0
  62. package/src/css/badge.css +51 -0
  63. package/src/css/card.css +128 -0
  64. package/src/css/detail.css +68 -0
  65. package/src/css/index.css +8 -0
  66. package/src/css/layout.css +90 -0
  67. package/src/css/pagination.css +72 -0
  68. package/src/css/table.css +143 -0
  69. package/src/css/utilities.css +46 -0
  70. package/src/index.js +50 -0
  71. package/src/store/createSubResourcePlugin.js +135 -0
  72. package/src/store/index.js +3 -0
  73. package/src/store/plugins/auditTrails.js +17 -0
  74. package/src/store/plugins/files.js +186 -0
  75. package/src/store/plugins/index.js +4 -0
  76. package/src/store/plugins/lifecycle.js +180 -0
  77. package/src/store/plugins/relations.js +68 -0
  78. package/src/store/useObjectStore.js +625 -0
  79. package/src/types/auditTrail.d.ts +32 -0
  80. package/src/types/file.d.ts +23 -0
  81. package/src/types/index.d.ts +35 -0
  82. package/src/types/notification.d.ts +36 -0
  83. package/src/types/object.d.ts +40 -0
  84. package/src/types/organisation.d.ts +41 -0
  85. package/src/types/register.d.ts +25 -0
  86. package/src/types/schema.d.ts +39 -0
  87. package/src/types/shared.d.ts +79 -0
  88. package/src/types/source.d.ts +14 -0
  89. package/src/types/task.d.ts +31 -0
  90. package/src/utils/errors.js +96 -0
  91. package/src/utils/headers.js +44 -0
  92. package/src/utils/index.js +3 -0
  93. package/src/utils/schema.js +287 -0
@@ -0,0 +1,46 @@
1
+ /* ========================================
2
+ @conduction/nextcloud-vue — Utilities
3
+ ======================================== */
4
+
5
+ /* Text Utilities */
6
+ .cn-text-ellipsis {
7
+ overflow: hidden;
8
+ text-overflow: ellipsis;
9
+ white-space: nowrap;
10
+ }
11
+
12
+ .cn-text-description {
13
+ font-size: 0.9em;
14
+ color: var(--color-text-maxcontrast);
15
+ }
16
+
17
+ .cn-text-success { color: var(--color-success); }
18
+ .cn-text-error { color: var(--color-error); }
19
+ .cn-text-warning { color: var(--color-warning); }
20
+ .cn-text-muted { color: var(--color-text-maxcontrast); }
21
+
22
+ /* Vue Transition Animations */
23
+ .cn-slide-fade-enter-active {
24
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
25
+ }
26
+
27
+ .cn-slide-fade-leave-active {
28
+ transition: all 0.3s cubic-bezier(0.4, 0, 1, 1);
29
+ }
30
+
31
+ .cn-slide-fade-enter,
32
+ .cn-slide-fade-enter-from {
33
+ transform: translateY(-10px);
34
+ opacity: 0;
35
+ }
36
+
37
+ .cn-slide-fade-leave-to {
38
+ transform: translateY(-5px);
39
+ opacity: 0;
40
+ }
41
+
42
+ /* Backwards compatibility aliases */
43
+ .textEllipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
44
+ .textDescription { font-size: 0.9em; color: var(--color-text-maxcontrast); }
45
+ .successMessage { color: var(--color-success); }
46
+ .errorMessage { color: var(--color-error); }
package/src/index.js ADDED
@@ -0,0 +1,50 @@
1
+ // Components
2
+ export {
3
+ CnDataTable,
4
+ CnFilterBar,
5
+ CnListViewLayout,
6
+ CnDetailViewLayout,
7
+ CnStatusBadge,
8
+ CnEmptyState,
9
+ CnPagination,
10
+ CnSettingsCard,
11
+ CnSettingsSection,
12
+ CnStatsBlock,
13
+ CnConfigurationCard,
14
+ CnVersionInfoCard,
15
+ CnCellRenderer,
16
+ CnObjectCard,
17
+ CnCardGrid,
18
+ CnFacetSidebar,
19
+ CnViewModeToggle,
20
+ CnRowActions,
21
+ CnIndexPage,
22
+ CnMassActionBar,
23
+ CnMassDeleteDialog,
24
+ CnMassCopyDialog,
25
+ CnKpiGrid,
26
+ CnMassExportDialog,
27
+ CnMassImportDialog,
28
+ } from './components/index.js'
29
+
30
+ // Store
31
+ export { useObjectStore, createObjectStore } from './store/index.js'
32
+ export { createSubResourcePlugin, emptyPaginated } from './store/index.js'
33
+
34
+ // Store plugins
35
+ export {
36
+ auditTrailsPlugin,
37
+ relationsPlugin,
38
+ filesPlugin,
39
+ lifecyclePlugin,
40
+ } from './store/plugins/index.js'
41
+
42
+ // Composables
43
+ export { useListView, useDetailView, useSubResource } from './composables/index.js'
44
+
45
+ // Utilities
46
+ export { buildHeaders, buildQueryString, parseResponseError, networkError, genericError } from './utils/index.js'
47
+ export { columnsFromSchema, formatValue, filtersFromSchema } from './utils/index.js'
48
+
49
+ // CSS (consumers should import separately)
50
+ // import '@conduction/nextcloud-vue/src/css/index.css'
@@ -0,0 +1,135 @@
1
+ import { buildHeaders, buildQueryString } from '../utils/headers.js'
2
+ import { parseResponseError, networkError } from '../utils/errors.js'
3
+
4
+ /**
5
+ * Standard empty paginated response shape used by all sub-resource plugins.
6
+ *
7
+ * @param {number} [limit=20] Default page size
8
+ * @return {object} Empty paginated state
9
+ */
10
+ export function emptyPaginated(limit = 20) {
11
+ return { results: [], total: 0, page: 1, pages: 0, limit, offset: 0 }
12
+ }
13
+
14
+ /**
15
+ * Capitalize the first letter of a string.
16
+ *
17
+ * @param {string} str Input string
18
+ * @return {string} Capitalized string
19
+ */
20
+ function capitalize(str) {
21
+ return str.charAt(0).toUpperCase() + str.slice(1)
22
+ }
23
+
24
+ /**
25
+ * Create a sub-resource plugin for the object store.
26
+ *
27
+ * Generates state, getters, and actions for a standard OpenRegister sub-resource
28
+ * endpoint that returns paginated results. The generated plugin follows the
29
+ * naming convention: fetch{Name}, clear{Name}, get{Name}, is{Name}Loading, etc.
30
+ *
31
+ * @param {string} name Camel-case name for the sub-resource (e.g. 'auditTrails')
32
+ * @param {string} endpoint URL path segment appended to the object URL (e.g. 'audit-trails')
33
+ * @param {object} [options={}] Plugin options
34
+ * @param {number} [options.limit=20] Default page size
35
+ * @return {Function} Plugin factory that returns the plugin definition
36
+ *
37
+ * @example
38
+ * // Simple read-only sub-resource
39
+ * export const auditTrailsPlugin = createSubResourcePlugin('auditTrails', 'audit-trails')
40
+ *
41
+ * @example
42
+ * // With custom limit
43
+ * export const contractsPlugin = createSubResourcePlugin('contracts', 'contracts', { limit: 50 })
44
+ *
45
+ * @example
46
+ * // Usage in store creation
47
+ * const useStore = createObjectStore('object', {
48
+ * plugins: [auditTrailsPlugin()],
49
+ * })
50
+ * const store = useStore()
51
+ * await store.fetchAuditTrails('case', caseId)
52
+ * console.log(store.auditTrails.results)
53
+ */
54
+ export function createSubResourcePlugin(name, endpoint, options = {}) {
55
+ const cap = capitalize(name)
56
+ const limit = options.limit || 20
57
+
58
+ return () => ({
59
+ name,
60
+
61
+ state: () => ({
62
+ [name]: emptyPaginated(limit),
63
+ [`${name}Loading`]: false,
64
+ [`${name}Error`]: null,
65
+ }),
66
+
67
+ getters: {
68
+ [`get${cap}`]: (state) => state[name],
69
+ [`is${cap}Loading`]: (state) => state[`${name}Loading`],
70
+ [`get${cap}Error`]: (state) => state[`${name}Error`],
71
+ },
72
+
73
+ actions: {
74
+ /**
75
+ * Fetch the sub-resource collection for an object.
76
+ *
77
+ * @param {string} type The registered object type slug
78
+ * @param {string} objectId The parent object ID
79
+ * @param {object} [params={}] Query parameters (_search, _limit, _page)
80
+ * @return {Promise<Array>} The fetched results
81
+ */
82
+ async [`fetch${cap}`](type, objectId, params = {}) {
83
+ this[`${name}Loading`] = true
84
+ this[`${name}Error`] = null
85
+
86
+ try {
87
+ const url = this._buildUrl(type, objectId)
88
+ + '/' + endpoint
89
+ + buildQueryString(params)
90
+
91
+ const response = await fetch(url, {
92
+ method: 'GET',
93
+ headers: buildHeaders(),
94
+ })
95
+
96
+ if (!response.ok) {
97
+ this[`${name}Error`] = await parseResponseError(response, name)
98
+ console.error(`Error fetching ${name} for ${type}/${objectId}:`, this[`${name}Error`])
99
+ return []
100
+ }
101
+
102
+ const data = await response.json()
103
+
104
+ this[name] = {
105
+ results: data.results || data,
106
+ total: data.total || (data.results || data).length,
107
+ page: data.page || 1,
108
+ pages: data.pages || 0,
109
+ limit: params._limit || limit,
110
+ offset: data.offset || 0,
111
+ }
112
+
113
+ return this[name].results
114
+ } catch (error) {
115
+ this[`${name}Error`] = error.name === 'TypeError'
116
+ ? networkError(error)
117
+ : { status: null, message: error.message, details: null, isValidation: false, fields: null, toString() { return this.message } }
118
+ console.error(`Error fetching ${name} for ${type}/${objectId}:`, error)
119
+ return []
120
+ } finally {
121
+ this[`${name}Loading`] = false
122
+ }
123
+ },
124
+
125
+ /**
126
+ * Clear all sub-resource state back to empty defaults.
127
+ */
128
+ [`clear${cap}`]() {
129
+ this[name] = emptyPaginated(limit)
130
+ this[`${name}Loading`] = false
131
+ this[`${name}Error`] = null
132
+ },
133
+ },
134
+ })
135
+ }
@@ -0,0 +1,3 @@
1
+ export { useObjectStore, createObjectStore } from './useObjectStore.js'
2
+ export { createSubResourcePlugin, emptyPaginated } from './createSubResourcePlugin.js'
3
+ export { auditTrailsPlugin, relationsPlugin, filesPlugin, lifecyclePlugin } from './plugins/index.js'
@@ -0,0 +1,17 @@
1
+ import { createSubResourcePlugin } from '../createSubResourcePlugin.js'
2
+
3
+ /**
4
+ * Audit trails plugin for the object store.
5
+ *
6
+ * Adds state, getters, and actions for fetching audit trail records
7
+ * for an object. Read-only — no create/update/delete.
8
+ *
9
+ * State: auditTrails, auditTrailsLoading, auditTrailsError
10
+ * Actions: fetchAuditTrails(type, objectId, params), clearAuditTrails()
11
+ * Getters: getAuditTrails, isAuditTrailsLoading, getAuditTrailsError
12
+ *
13
+ * @param {object} [options={}] Plugin options
14
+ * @param {number} [options.limit=20] Default page size
15
+ * @return {Function} Plugin factory
16
+ */
17
+ export const auditTrailsPlugin = createSubResourcePlugin('auditTrails', 'audit-trails')
@@ -0,0 +1,186 @@
1
+ import { createSubResourcePlugin } from '../createSubResourcePlugin.js'
2
+ import { buildHeaders } from '../../utils/headers.js'
3
+ import { parseResponseError, networkError } from '../../utils/errors.js'
4
+
5
+ /**
6
+ * Files plugin for the object store.
7
+ *
8
+ * Extends the generic sub-resource with file-specific actions:
9
+ * upload (multipart), publish, unpublish, and delete.
10
+ *
11
+ * State: files, filesLoading, filesError
12
+ * Actions: fetchFiles, uploadFiles, publishFile, unpublishFile, deleteFile, clearFiles
13
+ * Getters: getFiles, isFilesLoading, getFilesError
14
+ *
15
+ * @param {object} [options={}] Plugin options
16
+ * @param {number} [options.limit=20] Default page size
17
+ * @return {Function} Plugin factory
18
+ *
19
+ * @example
20
+ * const useStore = createObjectStore('object', {
21
+ * plugins: [filesPlugin()],
22
+ * })
23
+ * const store = useStore()
24
+ * await store.fetchFiles('case', caseId)
25
+ * await store.uploadFiles('case', caseId, formData)
26
+ * await store.publishFile('case', caseId, fileId)
27
+ */
28
+ export function filesPlugin(options = {}) {
29
+ const base = createSubResourcePlugin('files', 'files', options)()
30
+
31
+ return {
32
+ ...base,
33
+
34
+ actions: {
35
+ ...base.actions,
36
+
37
+ /**
38
+ * Upload files to an object via multipart form data.
39
+ *
40
+ * @param {string} type The registered object type slug
41
+ * @param {string} objectId The parent object ID
42
+ * @param {FormData} formData FormData with files[] and optional tags/share fields
43
+ * @return {Promise<object|null>} Upload response or null on error
44
+ */
45
+ async uploadFiles(type, objectId, formData) {
46
+ this.filesLoading = true
47
+ this.filesError = null
48
+
49
+ try {
50
+ const url = this._buildUrl(type, objectId) + '/filesMultipart'
51
+
52
+ const response = await fetch(url, {
53
+ method: 'POST',
54
+ headers: buildHeaders(null),
55
+ body: formData,
56
+ })
57
+
58
+ if (!response.ok) {
59
+ this.filesError = await parseResponseError(response, 'files')
60
+ console.error(`Error uploading files for ${type}/${objectId}:`, this.filesError)
61
+ return null
62
+ }
63
+
64
+ const data = await response.json()
65
+
66
+ await this.fetchFiles(type, objectId)
67
+
68
+ return data
69
+ } catch (error) {
70
+ this.filesError = error.name === 'TypeError'
71
+ ? networkError(error)
72
+ : { status: null, message: error.message, details: null, isValidation: false, fields: null, toString() { return this.message } }
73
+ console.error(`Error uploading files for ${type}/${objectId}:`, error)
74
+ return null
75
+ } finally {
76
+ this.filesLoading = false
77
+ }
78
+ },
79
+
80
+ /**
81
+ * Publish a file (make it publicly accessible).
82
+ *
83
+ * @param {string} type The registered object type slug
84
+ * @param {string} objectId The parent object ID
85
+ * @param {string} fileId The file ID to publish
86
+ * @return {Promise<boolean>} True if published successfully
87
+ */
88
+ async publishFile(type, objectId, fileId) {
89
+ this.filesLoading = true
90
+ this.filesError = null
91
+
92
+ try {
93
+ const url = this._buildUrl(type, objectId) + `/files/${fileId}/publish`
94
+
95
+ const response = await fetch(url, {
96
+ method: 'POST',
97
+ headers: buildHeaders(),
98
+ })
99
+
100
+ if (!response.ok) {
101
+ this.filesError = await parseResponseError(response, 'files')
102
+ return false
103
+ }
104
+
105
+ await this.fetchFiles(type, objectId)
106
+ return true
107
+ } catch (error) {
108
+ this.filesError = networkError(error)
109
+ return false
110
+ } finally {
111
+ this.filesLoading = false
112
+ }
113
+ },
114
+
115
+ /**
116
+ * Unpublish a file (revoke public access).
117
+ *
118
+ * @param {string} type The registered object type slug
119
+ * @param {string} objectId The parent object ID
120
+ * @param {string} fileId The file ID to unpublish
121
+ * @return {Promise<boolean>} True if unpublished successfully
122
+ */
123
+ async unpublishFile(type, objectId, fileId) {
124
+ this.filesLoading = true
125
+ this.filesError = null
126
+
127
+ try {
128
+ const url = this._buildUrl(type, objectId) + `/files/${fileId}/depublish`
129
+
130
+ const response = await fetch(url, {
131
+ method: 'POST',
132
+ headers: buildHeaders(),
133
+ })
134
+
135
+ if (!response.ok) {
136
+ this.filesError = await parseResponseError(response, 'files')
137
+ return false
138
+ }
139
+
140
+ await this.fetchFiles(type, objectId)
141
+ return true
142
+ } catch (error) {
143
+ this.filesError = networkError(error)
144
+ return false
145
+ } finally {
146
+ this.filesLoading = false
147
+ }
148
+ },
149
+
150
+ /**
151
+ * Delete a file from an object.
152
+ *
153
+ * @param {string} type The registered object type slug
154
+ * @param {string} objectId The parent object ID
155
+ * @param {string} fileId The file ID to delete
156
+ * @return {Promise<boolean>} True if deleted successfully
157
+ */
158
+ async deleteFile(type, objectId, fileId) {
159
+ this.filesLoading = true
160
+ this.filesError = null
161
+
162
+ try {
163
+ const url = this._buildUrl(type, objectId) + `/files/${fileId}`
164
+
165
+ const response = await fetch(url, {
166
+ method: 'DELETE',
167
+ headers: buildHeaders(),
168
+ })
169
+
170
+ if (!response.ok) {
171
+ this.filesError = await parseResponseError(response, 'files')
172
+ return false
173
+ }
174
+
175
+ await this.fetchFiles(type, objectId)
176
+ return true
177
+ } catch (error) {
178
+ this.filesError = networkError(error)
179
+ return false
180
+ } finally {
181
+ this.filesLoading = false
182
+ }
183
+ },
184
+ },
185
+ }
186
+ }
@@ -0,0 +1,4 @@
1
+ export { auditTrailsPlugin } from './auditTrails.js'
2
+ export { relationsPlugin } from './relations.js'
3
+ export { filesPlugin } from './files.js'
4
+ export { lifecyclePlugin } from './lifecycle.js'
@@ -0,0 +1,180 @@
1
+ import { buildHeaders } from '../../utils/headers.js'
2
+ import { parseResponseError, networkError } from '../../utils/errors.js'
3
+
4
+ /**
5
+ * Lifecycle plugin for the object store.
6
+ *
7
+ * Adds object lifecycle actions: lock, unlock, publish, depublish, revert, merge.
8
+ * These operate on the object itself (not sub-resources) but share the same
9
+ * URL pattern: POST /{register}/{schema}/{objectId}/{action}
10
+ *
11
+ * State: lifecycleLoading, lifecycleError
12
+ * Actions: lockObject, unlockObject, publishObject, depublishObject, revertObject, mergeObjects
13
+ *
14
+ * @return {Function} Plugin factory
15
+ *
16
+ * @example
17
+ * const useStore = createObjectStore('object', {
18
+ * plugins: [lifecyclePlugin()],
19
+ * })
20
+ * const store = useStore()
21
+ * await store.lockObject('case', caseId, { process: 'review', duration: 3600 })
22
+ * await store.publishObject('case', caseId, { date: '2025-01-01' })
23
+ */
24
+ export function lifecyclePlugin() {
25
+ return {
26
+ name: 'Lifecycle',
27
+
28
+ state: () => ({
29
+ lifecycleLoading: false,
30
+ lifecycleError: null,
31
+ }),
32
+
33
+ getters: {
34
+ isLifecycleLoading: (state) => state.lifecycleLoading,
35
+ getLifecycleError: (state) => state.lifecycleError,
36
+ },
37
+
38
+ actions: {
39
+ /**
40
+ * Perform a lifecycle action on an object.
41
+ *
42
+ * @param {string} type The registered object type slug
43
+ * @param {string} objectId The object ID
44
+ * @param {string} action The lifecycle action endpoint (e.g. 'lock', 'publish')
45
+ * @param {object} [body=null] Optional request body
46
+ * @return {Promise<object|null>} Response data or null on error
47
+ */
48
+ async _lifecycleAction(type, objectId, action, body = null) {
49
+ this.lifecycleLoading = true
50
+ this.lifecycleError = null
51
+
52
+ try {
53
+ const url = this._buildUrl(type, objectId) + '/' + action
54
+ const options = {
55
+ method: 'POST',
56
+ headers: buildHeaders(),
57
+ }
58
+ if (body) {
59
+ options.body = JSON.stringify(body)
60
+ }
61
+
62
+ const response = await fetch(url, options)
63
+
64
+ if (!response.ok) {
65
+ this.lifecycleError = await parseResponseError(response, action)
66
+ console.error(`Error performing ${action} on ${type}/${objectId}:`, this.lifecycleError)
67
+ return null
68
+ }
69
+
70
+ const data = await response.json()
71
+
72
+ if (this.objects[type] && data.id) {
73
+ this.objects[type][data.id] = data
74
+ }
75
+
76
+ return data
77
+ } catch (error) {
78
+ this.lifecycleError = error.name === 'TypeError'
79
+ ? networkError(error)
80
+ : { status: null, message: error.message, details: null, isValidation: false, fields: null, toString() { return this.message } }
81
+ console.error(`Error performing ${action} on ${type}/${objectId}:`, error)
82
+ return null
83
+ } finally {
84
+ this.lifecycleLoading = false
85
+ }
86
+ },
87
+
88
+ /**
89
+ * Lock an object to prevent concurrent edits.
90
+ *
91
+ * @param {string} type The registered object type slug
92
+ * @param {string} objectId The object ID
93
+ * @param {object} options Lock options
94
+ * @param {string} [options.process] Lock reason/process name
95
+ * @param {number} [options.duration] Lock duration in seconds
96
+ * @return {Promise<object|null>} Updated object or null on error
97
+ */
98
+ async lockObject(type, objectId, options = {}) {
99
+ return this._lifecycleAction(type, objectId, 'lock', options)
100
+ },
101
+
102
+ /**
103
+ * Unlock an object.
104
+ *
105
+ * @param {string} type The registered object type slug
106
+ * @param {string} objectId The object ID
107
+ * @return {Promise<object|null>} Updated object or null on error
108
+ */
109
+ async unlockObject(type, objectId) {
110
+ return this._lifecycleAction(type, objectId, 'unlock')
111
+ },
112
+
113
+ /**
114
+ * Publish an object (make it publicly accessible).
115
+ *
116
+ * @param {string} type The registered object type slug
117
+ * @param {string} objectId The object ID
118
+ * @param {object} [options={}] Publish options
119
+ * @param {string} [options.date] Publish date (ISO 8601)
120
+ * @return {Promise<object|null>} Updated object or null on error
121
+ */
122
+ async publishObject(type, objectId, options = {}) {
123
+ return this._lifecycleAction(type, objectId, 'publish', options)
124
+ },
125
+
126
+ /**
127
+ * Depublish an object (revoke public access).
128
+ *
129
+ * @param {string} type The registered object type slug
130
+ * @param {string} objectId The object ID
131
+ * @param {object} [options={}] Depublish options
132
+ * @param {string} [options.date] Depublish date (ISO 8601)
133
+ * @return {Promise<object|null>} Updated object or null on error
134
+ */
135
+ async depublishObject(type, objectId, options = {}) {
136
+ return this._lifecycleAction(type, objectId, 'depublish', options)
137
+ },
138
+
139
+ /**
140
+ * Revert an object to a previous version.
141
+ *
142
+ * @param {string} type The registered object type slug
143
+ * @param {string} objectId The object ID
144
+ * @param {object} options Revert options
145
+ * @param {string} [options.datetime] Target datetime
146
+ * @param {string} [options.auditTrailId] Audit trail entry ID to revert to
147
+ * @param {number} [options.version] Target version number
148
+ * @param {boolean} [options.overwriteVersion] Overwrite current version
149
+ * @return {Promise<object|null>} Updated object or null on error
150
+ */
151
+ async revertObject(type, objectId, options = {}) {
152
+ return this._lifecycleAction(type, objectId, 'revert', options)
153
+ },
154
+
155
+ /**
156
+ * Merge two objects together.
157
+ *
158
+ * @param {string} type The registered object type slug
159
+ * @param {string} sourceId The source object ID (will be merged into target)
160
+ * @param {object} options Merge options
161
+ * @param {string} options.target Target object ID
162
+ * @param {string} [options.fileAction] How to handle files ('move'|'copy'|'skip')
163
+ * @param {string} [options.relationAction] How to handle relations
164
+ * @param {string} [options.referenceAction] How to handle references
165
+ * @return {Promise<object|null>} Merge result or null on error
166
+ */
167
+ async mergeObjects(type, sourceId, options = {}) {
168
+ return this._lifecycleAction(type, sourceId, 'merge', options)
169
+ },
170
+
171
+ /**
172
+ * Clear lifecycle state.
173
+ */
174
+ clearLifecycle() {
175
+ this.lifecycleLoading = false
176
+ this.lifecycleError = null
177
+ },
178
+ },
179
+ }
180
+ }
@@ -0,0 +1,68 @@
1
+ import { createSubResourcePlugin } from '../createSubResourcePlugin.js'
2
+
3
+ /**
4
+ * Relations plugin for the object store.
5
+ *
6
+ * Adds three sub-resources for object relations:
7
+ * - contracts: contractual relations between objects
8
+ * - uses: outgoing references (this object uses other objects)
9
+ * - used: incoming references (other objects use this object)
10
+ *
11
+ * Each sub-resource gets its own state, loading, error, fetch, and clear.
12
+ */
13
+
14
+ const contractsBase = createSubResourcePlugin('contracts', 'contracts')
15
+ const usesBase = createSubResourcePlugin('uses', 'uses')
16
+ const usedBase = createSubResourcePlugin('used', 'used')
17
+
18
+ /**
19
+ * Combined relations plugin that registers contracts, uses, and used sub-resources.
20
+ *
21
+ * @param {object} [options={}] Plugin options
22
+ * @return {Function} Plugin factory
23
+ *
24
+ * @example
25
+ * const useStore = createObjectStore('object', {
26
+ * plugins: [relationsPlugin()],
27
+ * })
28
+ * const store = useStore()
29
+ * await store.fetchContracts('case', caseId)
30
+ * await store.fetchUses('case', caseId)
31
+ * await store.fetchUsed('case', caseId)
32
+ */
33
+ export function relationsPlugin(options = {}) {
34
+ const contracts = contractsBase(options)
35
+ const uses = usesBase(options)
36
+ const used = usedBase(options)
37
+
38
+ return {
39
+ name: 'Relations',
40
+
41
+ state: () => ({
42
+ ...contracts.state(),
43
+ ...uses.state(),
44
+ ...used.state(),
45
+ }),
46
+
47
+ getters: {
48
+ ...contracts.getters,
49
+ ...uses.getters,
50
+ ...used.getters,
51
+ },
52
+
53
+ actions: {
54
+ ...contracts.actions,
55
+ ...uses.actions,
56
+ ...used.actions,
57
+
58
+ /**
59
+ * Clear all relation sub-resources.
60
+ */
61
+ clearRelations() {
62
+ this.clearContracts()
63
+ this.clearUses()
64
+ this.clearUsed()
65
+ },
66
+ },
67
+ }
68
+ }