|
| 1 | +# Design — SoftwareCatalog store migration |
| 2 | + |
| 3 | +## Architectural decision: keep the four vanilla stores |
| 4 | + |
| 5 | +The project-memory rule reads "Do not use custom stores; use Options |
| 6 | +API with createObjectStore." Read literally that would suggest every |
| 7 | +Pinia store in the app moves to `createObjectStore`. That reading |
| 8 | +is wrong, and the lib's API confirms it: `createObjectStore` is a |
| 9 | +factory **for OpenRegister CRUD** — its base accepts `register` / |
| 10 | +`schema` per type, exposes `fetchObject` / `fetchCollection` / |
| 11 | +`patchObject` / `lockObject` / `publishObject` and so on. State that |
| 12 | +isn't a CRUD wrapper around an OpenRegister entity does NOT fit the |
| 13 | +factory. |
| 14 | + |
| 15 | +softwarecatalog has five Pinia stores and we map each to its correct |
| 16 | +shape: |
| 17 | + |
| 18 | +| Store | Shape | What it holds | Decision | |
| 19 | +| ------------- | ---------------------------------- | ---------------------------------------------------------------- | ---------------- | |
| 20 | +| object | `createObjectStore` + 4 plugins | All OpenRegister CRUD across voorzieningen schemas | Migrated | |
| 21 | +| navigation | vanilla `defineStore` | `selected` menu item, modal, dialog, transferData | Stay vanilla | |
| 22 | +| settings | vanilla `defineStore` | settings load/save, ArchiMate import/export polling, configs | Stay vanilla | |
| 23 | +| catalog | vanilla `defineStore` | placeholder (currentCatalog, loading, error) | Stay vanilla | |
| 24 | +| organisatie | vanilla `defineStore` | contactpersoon endpoints, user-mgmt (password, groups, enable) | Stay vanilla | |
| 25 | + |
| 26 | +The four vanilla stores hit **softwarecatalog-specific backend |
| 27 | +endpoints** under `/index.php/apps/softwarecatalog/api/...`, NOT |
| 28 | +OpenRegister. Forcing them through `createObjectStore` would mean: |
| 29 | + |
| 30 | +- inventing fake "type" registrations for non-entity APIs |
| 31 | + (ArchiMate import status, email config, user-group config), |
| 32 | +- tunnelling state mutations through an unfit |
| 33 | + `register / schema / objectId` URL builder, and |
| 34 | +- exposing a CRUD surface (`fetchObject`, `patchObject`, etc.) |
| 35 | + that has no semantics for these endpoints. |
| 36 | + |
| 37 | +That's the same anti-pattern the rule was written to avoid, just |
| 38 | +inverted. |
| 39 | + |
| 40 | +## What the object store now exposes |
| 41 | + |
| 42 | +The migration centred on `src/store/modules/object.js`: |
| 43 | + |
| 44 | +```js |
| 45 | +import { createObjectStore, filesPlugin, auditTrailsPlugin, relationsPlugin } from '@conduction/nextcloud-vue' |
| 46 | +import { softwarecatalogPlugin } from '../plugins/softwarecatalogPlugin.js' |
| 47 | + |
| 48 | +export const useObjectStore = createObjectStore('object', { |
| 49 | + plugins: [ |
| 50 | + filesPlugin(), |
| 51 | + auditTrailsPlugin(), |
| 52 | + relationsPlugin(), |
| 53 | + softwarecatalogPlugin(), |
| 54 | + ], |
| 55 | +}) |
| 56 | +``` |
| 57 | + |
| 58 | +The `'object'` ID matches the legacy Pinia store ID, so all 41 |
| 59 | +existing importers of `objectStore` from `store/store.js` keep |
| 60 | +working without source edits. The plugin contributes everything |
| 61 | +the legacy store had that the lib base does not: |
| 62 | + |
| 63 | +- `settings`, `objectItem`, `activeObjects`, `relatedData`, |
| 64 | + `selectedObjects`, `success`, `objectErrors`, `metadata`, |
| 65 | + `properties`, `columnFilters` state slots |
| 66 | +- 16 lib-getter shims (`objectTypes`, `availableRegisters`, |
| 67 | + `availableSchemas`, `getActiveObject`, `getRelatedData`, |
| 68 | + `getAuditTrails`, `getCollection` array-or-results normaliser, …) |
| 69 | +- 27 actions split across: |
| 70 | + 1. settings management (`fetchSettings`, |
| 71 | + `initializeVoorzieningenObjectTypes`, `getSchemaConfig`) |
| 72 | + 2. active-object management (`setActiveObject`, `clearActiveObject`, |
| 73 | + `setObjectItem`, `downloadObject`, `fetchRelatedData`) |
| 74 | + 3. CRUD shims that accept BOTH the legacy |
| 75 | + `(objectItem, {register,schema})` AND the new |
| 76 | + `(type, data)` signatures (`saveObject`, `deleteObject`, |
| 77 | + `patchObject`, `copyObject`) |
| 78 | + 4. lifecycle ops (`publishObject`, `depublishObject`, `lockObject`, |
| 79 | + `unlockObject`, `validateObject`) |
| 80 | + 5. mass ops (`_runMassOperation`, `massPublishObjects`, |
| 81 | + `massDepublishObjects`, `massDeleteObjects`, `massLockObjects`, |
| 82 | + `massUnlockObjects`, `massValidateObjects`) |
| 83 | + 6. selection mgmt (`setSelectedObjects`, `toggleSelectAllObjects`) |
| 84 | + 7. error mgmt (`setObjectError`, `clearObjectError`, |
| 85 | + `clearAllObjectErrors`, `getObjectError`) |
| 86 | + 8. column mgmt (`updateColumnFilter`, `initializeProperties`, |
| 87 | + `initializeColumnFilters`) |
| 88 | + 9. merge & migration (`mergeObjects`, `getMappings`, |
| 89 | + `refreshObjectList`) |
| 90 | + 10. state mgmt (`setState`, `clearSoftwarecatalog`) |
| 91 | + |
| 92 | +## Plugin fates |
| 93 | + |
| 94 | +This change creates no new plugins. For the record, the existing |
| 95 | +plugin's responsibilities map to lib equivalents as follows: |
| 96 | + |
| 97 | +| softwarecatalogPlugin section | Lib alternative | Decision | |
| 98 | +| ----------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 99 | +| settings management | none | KEEP local — softwarecatalog has its own `/api/settings` shape (voorzieningen + amef configs, version info, etc.) that the lib doesn't model. | |
| 100 | +| active-object management | partly `selectionPlugin` | KEEP local — `activeObjects` is keyed-per-type with related-data fan-out (`logs`/`uses`/`used`/`files`), which is richer than `selectionPlugin`'s single-active-object focus. | |
| 101 | +| CRUD shims (dual signature) | base store | KEEP local — necessary for legacy `(objectItem, {register,schema})` callers in 41 view files. Could be deprecated incrementally; out-of-scope here. | |
| 102 | +| lifecycle ops | `lifecyclePlugin` | KEEP local — softwarecatalog's `lockObject` accepts `(process, duration)` extras the base `lifecyclePlugin` doesn't carry. Refactoring is its own change. | |
| 103 | +| mass ops | `selectionPlugin` | KEEP local — `_runMassOperation` adds per-object error tracking (`setObjectError`) the lib doesn't yet model. Refactoring is its own change. | |
| 104 | +| selection mgmt | `selectionPlugin` | KEEP local — couples to the local `objectErrors` flow. | |
| 105 | +| error mgmt | none | KEEP local — per-object error keyed map (used by mass-ops UX). | |
| 106 | +| column mgmt | none | KEEP local — `metadata` + `properties` + `columnFilters` are softwarecatalog-specific UI state. | |
| 107 | +| merge & migration | none | KEEP local — `mergeObjects` is a softwarecatalog escalation flow over the OR merge endpoint. | |
| 108 | + |
| 109 | +## Lib gaps flagged |
| 110 | + |
| 111 | +### Gap 1 — `@resolve:` sentinel not implemented |
| 112 | + |
| 113 | +`src/manifest.json` ships 12 `@resolve:voorzieningen_register` |
| 114 | +sentinels (PR #218). The lib openspec change |
| 115 | +`nextcloud-vue/openspec/changes/manifest-resolve-sentinel/` defines |
| 116 | +the loader semantics: |
| 117 | + |
| 118 | +> "The loader walks an object tree and replaces every `@resolve:{key}` |
| 119 | +> string with the result of `getAppConfigValue(appId, key)`. … walks |
| 120 | +> only `pages[].config` subtrees by default." |
| 121 | +
|
| 122 | +Tasks are unchecked. Phase 1 (`src/utils/resolveManifestSentinels.js`) |
| 123 | +is **not** implemented. The frontend still receives literal |
| 124 | +`@resolve:voorzieningen_register` strings as the `register` field |
| 125 | +on 12 manifest pages. Until the loader lands, every consumer (incl. |
| 126 | +softwarecatalog) must either: |
| 127 | + |
| 128 | +- pre-resolve the manifest server-side before serving it, OR |
| 129 | +- substitute at runtime in the consumer (forbidden — that's exactly |
| 130 | + the divergence the lib change is supposed to prevent). |
| 131 | + |
| 132 | +This change DOES NOT fix the gap. It flags it for the lib roadmap. |
| 133 | + |
| 134 | +### Gap 2 — `liveUpdatesPlugin` not wired |
| 135 | + |
| 136 | +The motivation for the project-memory rule (decidesk #162) is that |
| 137 | +`liveUpdatesPlugin` requires `fetchObject` / `fetchCollection` on the |
| 138 | +store. softwarecatalog's `useObjectStore` now satisfies that contract |
| 139 | +via `createObjectStore`, but the plugin is NOT in the plugin list. |
| 140 | +Adding it requires: |
| 141 | + |
| 142 | +- backend SSE/WS endpoint exposing object-changed events, |
| 143 | +- a plugin-options block declaring the exclusion key namespace, |
| 144 | +- end-to-end test of cache-invalidation on remote edits. |
| 145 | + |
| 146 | +That work belongs in its own change; the migration only unblocks it. |
| 147 | + |
| 148 | +## Validation checklist |
| 149 | + |
| 150 | +- `npx eslint src` — zero new errors. Pre-existing warnings |
| 151 | + (`jsdoc/no-defaults` × 4 in `softwarecatalogPlugin.js`) cleared |
| 152 | + in this change. |
| 153 | +- `node tests/validate-manifest.js` — PRE-EXISTING failure |
| 154 | + unrelated to store migration (`ajv-formats` constructor |
| 155 | + TypeError on `addFormats` — a tooling bug introduced separately). |
| 156 | + Documented; not in-scope. |
| 157 | +- `npx webpack --mode production` — succeeds, three |
| 158 | + entrypoint-size warnings (pre-existing). |
| 159 | +- All 41 importers of `objectStore` from `store/store.js` |
| 160 | + continue to compile and resolve their named exports. |
0 commit comments