@conduction/nextcloud-vue 2.66.0 → 2.67.0
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/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +19 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +335 -313
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +5 -11
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +44 -1
- package/dist/esm/composables/useDependencyCheck.js.map +1 -1
- package/dist/esm/composables/useListView.js +43 -1
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useSetupStatus.js +26 -3
- package/dist/esm/composables/useSetupStatus.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +20 -2
- package/dist/esm/store/plugins/liveUpdates.js +19 -9
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +20 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +857 -652
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +20 -2
- package/l10n/en.json +4 -1
- package/l10n/nl.json +4 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
- package/src/components/CnAppNav/CnAppNav.vue +2 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +4 -4
- package/src/components/CnIndexPage/CnIndexPage.md +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +47 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +5 -11
- package/src/composables/useDependencyCheck.js +43 -0
- package/src/composables/useListView.js +43 -1
- package/src/composables/useSetupStatus.js +26 -3
- package/src/css/actions-bar.css +20 -2
- package/src/schemas/app-manifest-v2.schema.json +3 -3
- package/src/schemas/app-manifest.schema.json +1 -1
- package/src/store/plugins/liveUpdates.js +19 -9
- package/src/store/useObjectStore.js +19 -8
- package/src/utils/validateManifestV2.compiled.js +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
|
+
import { readServerAppStatuses, missingRequiredApps } from './useDependencyCheck.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
@@ -28,6 +29,14 @@ const cache = new Map();
|
|
|
28
29
|
* every non-admin user of an app with a `setup` block met a wizard they had no
|
|
29
30
|
* permission to complete instead of the app itself.
|
|
30
31
|
*
|
|
32
|
+
* A step that declares `requires: [appId, ...]` is **not applicable** while any
|
|
33
|
+
* of those apps is not installed and enabled: it is neither met nor unmet, so
|
|
34
|
+
* it never lands in `requiredUnmet` or `optionalUnmet`. `CnSetupWizard` skips
|
|
35
|
+
* such a step, and counting it as unmet kept the wizard reopening (optional
|
|
36
|
+
* step) or gating the app (required step) for a step it would never show. App
|
|
37
|
+
* status is resolved exactly as the wizard does it: the `dependency_statuses`
|
|
38
|
+
* initial state first, then `useAppStatus`.
|
|
39
|
+
*
|
|
31
40
|
* @param {string} appId Nextcloud app id (e.g. `"procest"`).
|
|
32
41
|
* @param {object} manifest The app manifest (reads `manifest.setup`).
|
|
33
42
|
* @return {{
|
|
@@ -36,6 +45,7 @@ const cache = new Map();
|
|
|
36
45
|
* requiredUnmet: import('vue').ComputedRef<Array<object>>,
|
|
37
46
|
* optionalUnmet: import('vue').ComputedRef<Array<object>>,
|
|
38
47
|
* optionalUnmetReported: import('vue').ComputedRef<Array<object>>,
|
|
48
|
+
* notApplicable: import('vue').ComputedRef<Array<object>>,
|
|
39
49
|
* completed: import('vue').ComputedRef<boolean>,
|
|
40
50
|
* enabled: boolean,
|
|
41
51
|
* loading: import('vue').Ref<boolean>,
|
|
@@ -46,6 +56,7 @@ const cache = new Map();
|
|
|
46
56
|
*
|
|
47
57
|
* @example
|
|
48
58
|
* const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)
|
|
59
|
+
* @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
|
|
49
60
|
*/
|
|
50
61
|
function useSetupStatus(appId, manifest) {
|
|
51
62
|
const setup = (manifest && manifest.setup) || {};
|
|
@@ -69,6 +80,9 @@ function useSetupStatus(appId, manifest) {
|
|
|
69
80
|
cache.set(appId, entry);
|
|
70
81
|
}
|
|
71
82
|
const { status, loading, error, forbidden } = entry;
|
|
83
|
+
// Same source the wizard's dependency gate reads. Initial state is fixed
|
|
84
|
+
// for the page lifetime, so one read per call is enough.
|
|
85
|
+
const serverAppStatuses = readServerAppStatuses(appId);
|
|
72
86
|
|
|
73
87
|
const steps = computed(() => stepDefs.map((s) => {
|
|
74
88
|
const bag = status.value.steps || {};
|
|
@@ -82,7 +96,12 @@ function useSetupStatus(appId, manifest) {
|
|
|
82
96
|
// that act on an unmet step — CnAppRoot auto-opening the non-gating
|
|
83
97
|
// wizard — must act only on steps the server actually reported, or an
|
|
84
98
|
// unknowable step parks the wizard over the app for ever.
|
|
85
|
-
|
|
99
|
+
//
|
|
100
|
+
// `applicable` is false when the step `requires` an app that is not
|
|
101
|
+
// installed and enabled. The wizard skips such a step, so it can never
|
|
102
|
+
// be done here; it is not applicable rather than unmet.
|
|
103
|
+
const missingApps = missingRequiredApps(s.requires, serverAppStatuses).map((row) => row.id);
|
|
104
|
+
return { ...s, done: st.done === true, reported, detail: st.detail, applicable: missingApps.length === 0, missingApps }
|
|
86
105
|
}));
|
|
87
106
|
// Presentational step types carry no work, so the server never reports a
|
|
88
107
|
// `done` flag for them. Counting them as "unmet" made `optionalUnmet`
|
|
@@ -90,7 +109,9 @@ function useSetupStatus(appId, manifest) {
|
|
|
90
109
|
// which kept CnAppRoot's non-gating setup wizard auto-opening over the
|
|
91
110
|
// app on every fresh browser profile no matter how complete setup was.
|
|
92
111
|
// Only actionable steps can be unmet.
|
|
93
|
-
|
|
112
|
+
// A step whose required apps are absent is skipped by the wizard, so it
|
|
113
|
+
// is neither met nor unmet (see `applicable` above).
|
|
114
|
+
const isActionable = (s) => s.type !== 'info' && s.type !== 'summary' && s.applicable !== false;
|
|
94
115
|
// `forbidden` empties BOTH lists, and that is what actually suppresses the
|
|
95
116
|
// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on
|
|
96
117
|
// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —
|
|
@@ -111,6 +132,8 @@ function useSetupStatus(appId, manifest) {
|
|
|
111
132
|
// suppressing it is what left dossiq's `seed` step (and with it the app's
|
|
112
133
|
// only demo-data affordance) permanently unreachable.
|
|
113
134
|
const optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true));
|
|
135
|
+
// Steps the wizard skips because an app in their `requires` is absent.
|
|
136
|
+
const notApplicable = computed(() => steps.value.filter((s) => s.applicable === false));
|
|
114
137
|
const completed = computed(() => {
|
|
115
138
|
if (!enabled) {
|
|
116
139
|
return true
|
|
@@ -171,7 +194,7 @@ function useSetupStatus(appId, manifest) {
|
|
|
171
194
|
refresh();
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, completed, enabled, loading, error, forbidden, refresh }
|
|
197
|
+
return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, notApplicable, completed, enabled, loading, error, forbidden, refresh }
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
export { useSetupStatus };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSetupStatus.js","sources":["../../../src/composables/useSetupStatus.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { computed, ref } from 'vue'\n\n/**\n * Per-`appId` cache of setup-status results. The Vue refs are stored so all\n * consumers of the same app share one fetch + reactive state for the page\n * lifetime (mirrors useAppStatus).\n */\nconst cache = new Map()\n\n/**\n * Composable that reports an app's first-time-setup status (ADR-042).\n *\n * Fetches `GET /apps/{appId}/api/setup/status` → `{ version, completed, steps:\n * { <id>: { done, detail } } }`, then cross-references the manifest's\n * `setup.steps[].required` flags so `CnAppRoot` can gate on required-unmet and\n * the wizard can drive itself. On error it falls back to \"nothing done\" so a\n * failed lookup never crashes the shell — the wizard simply shows the steps.\n *\n * One error is treated as an answer rather than a failure: a **401/403** means\n * the caller may not read setup state (setup endpoints are admin-only), so\n * `completed` reports `true` and the wizard stays out of the way. Without that,\n * every non-admin user of an app with a `setup` block met a wizard they had no\n * permission to complete instead of the app itself.\n *\n * @param {string} appId Nextcloud app id (e.g. `\"procest\"`).\n * @param {object} manifest The app manifest (reads `manifest.setup`).\n * @return {{\n * steps: import('vue').ComputedRef<Array<object>>,\n * status: import('vue').Ref<object>,\n * requiredUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmetReported: import('vue').ComputedRef<Array<object>>,\n * completed: import('vue').ComputedRef<boolean>,\n * enabled: boolean,\n * loading: import('vue').Ref<boolean>,\n * error: import('vue').Ref<Error|null>,\n * forbidden: import('vue').Ref<boolean>,\n * refresh: () => Promise<void>\n * }}\n *\n * @example\n * const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)\n */\nexport function useSetupStatus(appId, manifest) {\n\tconst setup = (manifest && manifest.setup) || {}\n\tconst enabled = setup.enabled !== false && Array.isArray(setup.steps) && setup.steps.length > 0\n\tconst stepDefs = Array.isArray(setup.steps) ? setup.steps : []\n\tconst requiredById = Object.create(null)\n\tfor (const s of stepDefs) {\n\t\trequiredById[s.id] = s.required === true\n\t}\n\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tentry = {\n\t\t\tstatus: ref({ version: null, completed: false, steps: {} }),\n\t\t\tloading: ref(enabled),\n\t\t\terror: ref(null),\n\t\t\t// Set when the server answers 401/403 — see `completed` below.\n\t\t\tforbidden: ref(false),\n\t\t\tfetched: false,\n\t\t}\n\t\tcache.set(appId, entry)\n\t}\n\tconst { status, loading, error, forbidden } = entry\n\n\tconst steps = computed(() => stepDefs.map((s) => {\n\t\tconst bag = status.value.steps || {}\n\t\tconst reported = Object.hasOwn(bag, s.id)\n\t\tconst st = bag[s.id] || {}\n\t\t// `reported` separates \"the server says this step is NOT done\" from \"the\n\t\t// server never mentioned this step at all\". Both used to flatten into\n\t\t// `done: false`, and that conflation is load-bearing: a manifest may\n\t\t// declare an optional step its SetupController has no status key for\n\t\t// (pipelinq's `demo-data`), which is UNKNOWN, not outstanding. Callers\n\t\t// that act on an unmet step — CnAppRoot auto-opening the non-gating\n\t\t// wizard — must act only on steps the server actually reported, or an\n\t\t// unknowable step parks the wizard over the app for ever.\n\t\treturn { ...s, done: st.done === true, reported, detail: st.detail }\n\t}))\n\t// Presentational step types carry no work, so the server never reports a\n\t// `done` flag for them. Counting them as \"unmet\" made `optionalUnmet`\n\t// permanently non-empty for any manifest with a welcome/summary step,\n\t// which kept CnAppRoot's non-gating setup wizard auto-opening over the\n\t// app on every fresh browser profile no matter how complete setup was.\n\t// Only actionable steps can be unmet.\n\tconst isActionable = (s) => s.type !== 'info' && s.type !== 'summary'\n\t// `forbidden` empties BOTH lists, and that is what actually suppresses the\n\t// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on\n\t// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —\n\t// it never reads `completed`. A caller who may not READ setup state has no\n\t// unmet setup work *of their own*, so reporting steps as unmet to them\n\t// described work they could neither see nor do.\n\tconst requiredUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && requiredById[s.id] && !s.done)))\n\tconst optionalUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && !requiredById[s.id] && !s.done)))\n\t// The subset of `optionalUnmet` the server positively reported as not done.\n\t// This — not `optionalUnmet` — is what may auto-open the non-gating wizard:\n\t// a step with no status key is unknown, and prompting on unknown is what\n\t// parked the wizard over pipelinq on every fresh browser profile. A step\n\t// the server DID report as `{done: false}` is genuinely outstanding, and\n\t// suppressing it is what left dossiq's `seed` step (and with it the app's\n\t// only demo-data affordance) permanently unreachable.\n\tconst optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true))\n\tconst completed = computed(() => {\n\t\tif (!enabled) {\n\t\t\treturn true\n\t\t}\n\t\t// A 401/403 means the caller may not READ setup state — first-time setup\n\t\t// is admin-only. That is not \"setup is unfinished\"; it is \"not this\n\t\t// user's concern\". Reporting incomplete here put every non-admin in\n\t\t// front of a setup wizard they cannot complete, INSTEAD of the app:\n\t\t// `/api/setup/status` answers 200 {completed:true} to an admin and 403\n\t\t// to everyone else, and the generic error path below then left status at\n\t\t// its \"nothing done\" default. Measured on openbuild, where it made the\n\t\t// app unusable for every non-admin as soon as they could see it at all.\n\t\tif (forbidden.value === true) {\n\t\t\treturn true\n\t\t}\n\t\t// Authoritative server flag, but never report complete while a required\n\t\t// step is still unmet (defends against a stale completion flag).\n\t\treturn status.value.completed === true && requiredUnmet.value.length === 0\n\t})\n\n\t/**\n\t * (Re)fetch the setup status from the server.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh() {\n\t\tif (!enabled) {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t\treturn\n\t\t}\n\t\tloading.value = true\n\t\terror.value = null\n\t\ttry {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst { data } = await axios.get(generateUrl(`/apps/${appId}/api/setup/status`))\n\t\t\tstatus.value = (data && typeof data === 'object') ? data : { steps: {} }\n\t\t\tforbidden.value = false\n\t\t} catch (err) {\n\t\t\terror.value = err\n\t\t\t// 401/403 is a DIFFERENT outcome from a failed lookup: the server\n\t\t\t// answered, and its answer was \"you may not see this\". Setup is\n\t\t\t// admin-only, so for everyone else it is settled, not unknown.\n\t\t\t// Anything else (network error, 500) stays unknown and falls back to\n\t\t\t// \"nothing done\" so the wizard is still reachable for an admin.\n\t\t\tconst httpStatus = err && err.response && err.response.status\n\t\t\tforbidden.value = (httpStatus === 401 || httpStatus === 403)\n\t\t} finally {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t}\n\t}\n\n\tif (!entry.fetched) {\n\t\trefresh()\n\t}\n\n\treturn { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, completed, enabled, loading, error, forbidden, refresh }\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetSetupStatusCacheForTests() {\n\tcache.clear()\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSetupStatus.js","sources":["../../../src/composables/useSetupStatus.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { computed, ref } from 'vue'\nimport { missingRequiredApps, readServerAppStatuses } from './useDependencyCheck.js'\n\n/**\n * Per-`appId` cache of setup-status results. The Vue refs are stored so all\n * consumers of the same app share one fetch + reactive state for the page\n * lifetime (mirrors useAppStatus).\n */\nconst cache = new Map()\n\n/**\n * Composable that reports an app's first-time-setup status (ADR-042).\n *\n * Fetches `GET /apps/{appId}/api/setup/status` → `{ version, completed, steps:\n * { <id>: { done, detail } } }`, then cross-references the manifest's\n * `setup.steps[].required` flags so `CnAppRoot` can gate on required-unmet and\n * the wizard can drive itself. On error it falls back to \"nothing done\" so a\n * failed lookup never crashes the shell — the wizard simply shows the steps.\n *\n * One error is treated as an answer rather than a failure: a **401/403** means\n * the caller may not read setup state (setup endpoints are admin-only), so\n * `completed` reports `true` and the wizard stays out of the way. Without that,\n * every non-admin user of an app with a `setup` block met a wizard they had no\n * permission to complete instead of the app itself.\n *\n * A step that declares `requires: [appId, ...]` is **not applicable** while any\n * of those apps is not installed and enabled: it is neither met nor unmet, so\n * it never lands in `requiredUnmet` or `optionalUnmet`. `CnSetupWizard` skips\n * such a step, and counting it as unmet kept the wizard reopening (optional\n * step) or gating the app (required step) for a step it would never show. App\n * status is resolved exactly as the wizard does it: the `dependency_statuses`\n * initial state first, then `useAppStatus`.\n *\n * @param {string} appId Nextcloud app id (e.g. `\"procest\"`).\n * @param {object} manifest The app manifest (reads `manifest.setup`).\n * @return {{\n * steps: import('vue').ComputedRef<Array<object>>,\n * status: import('vue').Ref<object>,\n * requiredUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmetReported: import('vue').ComputedRef<Array<object>>,\n * notApplicable: import('vue').ComputedRef<Array<object>>,\n * completed: import('vue').ComputedRef<boolean>,\n * enabled: boolean,\n * loading: import('vue').Ref<boolean>,\n * error: import('vue').Ref<Error|null>,\n * forbidden: import('vue').Ref<boolean>,\n * refresh: () => Promise<void>\n * }}\n *\n * @example\n * const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)\n * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n */\nexport function useSetupStatus(appId, manifest) {\n\tconst setup = (manifest && manifest.setup) || {}\n\tconst enabled = setup.enabled !== false && Array.isArray(setup.steps) && setup.steps.length > 0\n\tconst stepDefs = Array.isArray(setup.steps) ? setup.steps : []\n\tconst requiredById = Object.create(null)\n\tfor (const s of stepDefs) {\n\t\trequiredById[s.id] = s.required === true\n\t}\n\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tentry = {\n\t\t\tstatus: ref({ version: null, completed: false, steps: {} }),\n\t\t\tloading: ref(enabled),\n\t\t\terror: ref(null),\n\t\t\t// Set when the server answers 401/403 — see `completed` below.\n\t\t\tforbidden: ref(false),\n\t\t\tfetched: false,\n\t\t}\n\t\tcache.set(appId, entry)\n\t}\n\tconst { status, loading, error, forbidden } = entry\n\t// Same source the wizard's dependency gate reads. Initial state is fixed\n\t// for the page lifetime, so one read per call is enough.\n\tconst serverAppStatuses = readServerAppStatuses(appId)\n\n\tconst steps = computed(() => stepDefs.map((s) => {\n\t\tconst bag = status.value.steps || {}\n\t\tconst reported = Object.hasOwn(bag, s.id)\n\t\tconst st = bag[s.id] || {}\n\t\t// `reported` separates \"the server says this step is NOT done\" from \"the\n\t\t// server never mentioned this step at all\". Both used to flatten into\n\t\t// `done: false`, and that conflation is load-bearing: a manifest may\n\t\t// declare an optional step its SetupController has no status key for\n\t\t// (pipelinq's `demo-data`), which is UNKNOWN, not outstanding. Callers\n\t\t// that act on an unmet step — CnAppRoot auto-opening the non-gating\n\t\t// wizard — must act only on steps the server actually reported, or an\n\t\t// unknowable step parks the wizard over the app for ever.\n\t\t//\n\t\t// `applicable` is false when the step `requires` an app that is not\n\t\t// installed and enabled. The wizard skips such a step, so it can never\n\t\t// be done here; it is not applicable rather than unmet.\n\t\tconst missingApps = missingRequiredApps(s.requires, serverAppStatuses).map((row) => row.id)\n\t\treturn { ...s, done: st.done === true, reported, detail: st.detail, applicable: missingApps.length === 0, missingApps }\n\t}))\n\t// Presentational step types carry no work, so the server never reports a\n\t// `done` flag for them. Counting them as \"unmet\" made `optionalUnmet`\n\t// permanently non-empty for any manifest with a welcome/summary step,\n\t// which kept CnAppRoot's non-gating setup wizard auto-opening over the\n\t// app on every fresh browser profile no matter how complete setup was.\n\t// Only actionable steps can be unmet.\n\t// A step whose required apps are absent is skipped by the wizard, so it\n\t// is neither met nor unmet (see `applicable` above).\n\tconst isActionable = (s) => s.type !== 'info' && s.type !== 'summary' && s.applicable !== false\n\t// `forbidden` empties BOTH lists, and that is what actually suppresses the\n\t// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on\n\t// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —\n\t// it never reads `completed`. A caller who may not READ setup state has no\n\t// unmet setup work *of their own*, so reporting steps as unmet to them\n\t// described work they could neither see nor do.\n\tconst requiredUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && requiredById[s.id] && !s.done)))\n\tconst optionalUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && !requiredById[s.id] && !s.done)))\n\t// The subset of `optionalUnmet` the server positively reported as not done.\n\t// This — not `optionalUnmet` — is what may auto-open the non-gating wizard:\n\t// a step with no status key is unknown, and prompting on unknown is what\n\t// parked the wizard over pipelinq on every fresh browser profile. A step\n\t// the server DID report as `{done: false}` is genuinely outstanding, and\n\t// suppressing it is what left dossiq's `seed` step (and with it the app's\n\t// only demo-data affordance) permanently unreachable.\n\tconst optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true))\n\t// Steps the wizard skips because an app in their `requires` is absent.\n\tconst notApplicable = computed(() => steps.value.filter((s) => s.applicable === false))\n\tconst completed = computed(() => {\n\t\tif (!enabled) {\n\t\t\treturn true\n\t\t}\n\t\t// A 401/403 means the caller may not READ setup state — first-time setup\n\t\t// is admin-only. That is not \"setup is unfinished\"; it is \"not this\n\t\t// user's concern\". Reporting incomplete here put every non-admin in\n\t\t// front of a setup wizard they cannot complete, INSTEAD of the app:\n\t\t// `/api/setup/status` answers 200 {completed:true} to an admin and 403\n\t\t// to everyone else, and the generic error path below then left status at\n\t\t// its \"nothing done\" default. Measured on openbuild, where it made the\n\t\t// app unusable for every non-admin as soon as they could see it at all.\n\t\tif (forbidden.value === true) {\n\t\t\treturn true\n\t\t}\n\t\t// Authoritative server flag, but never report complete while a required\n\t\t// step is still unmet (defends against a stale completion flag).\n\t\treturn status.value.completed === true && requiredUnmet.value.length === 0\n\t})\n\n\t/**\n\t * (Re)fetch the setup status from the server.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh() {\n\t\tif (!enabled) {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t\treturn\n\t\t}\n\t\tloading.value = true\n\t\terror.value = null\n\t\ttry {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst { data } = await axios.get(generateUrl(`/apps/${appId}/api/setup/status`))\n\t\t\tstatus.value = (data && typeof data === 'object') ? data : { steps: {} }\n\t\t\tforbidden.value = false\n\t\t} catch (err) {\n\t\t\terror.value = err\n\t\t\t// 401/403 is a DIFFERENT outcome from a failed lookup: the server\n\t\t\t// answered, and its answer was \"you may not see this\". Setup is\n\t\t\t// admin-only, so for everyone else it is settled, not unknown.\n\t\t\t// Anything else (network error, 500) stays unknown and falls back to\n\t\t\t// \"nothing done\" so the wizard is still reachable for an admin.\n\t\t\tconst httpStatus = err && err.response && err.response.status\n\t\t\tforbidden.value = (httpStatus === 401 || httpStatus === 403)\n\t\t} finally {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t}\n\t}\n\n\tif (!entry.fetched) {\n\t\trefresh()\n\t}\n\n\treturn { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, notApplicable, completed, enabled, loading, error, forbidden, refresh }\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetSetupStatusCacheForTests() {\n\tcache.clear()\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/l10n/en.json.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var translations = {
|
|
2
|
+
Advanced: "Advanced",
|
|
2
3
|
"The item could not be created.": "The item could not be created.",
|
|
3
4
|
"Start over": "Start over",
|
|
4
5
|
"Continue where you left off": "Continue where you left off",
|
|
@@ -1084,7 +1085,9 @@ var translations = {
|
|
|
1084
1085
|
"Show the date of today above the heading": "Show the date of today above the heading",
|
|
1085
1086
|
"Plain header without a coloured background": "Plain header without a coloured background",
|
|
1086
1087
|
"Loading…": "Loading…",
|
|
1087
|
-
"A register and schema are required": "A register and schema are required"
|
|
1088
|
+
"A register and schema are required": "A register and schema are required",
|
|
1089
|
+
"Change the search or try again.": "Change the search or try again.",
|
|
1090
|
+
"Try again later.": "Try again later."
|
|
1088
1091
|
};
|
|
1089
1092
|
var plurals = {
|
|
1090
1093
|
"%n year": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/l10n/nl.json.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var translations = {
|
|
2
|
+
Advanced: "Geavanceerd",
|
|
2
3
|
"The item could not be created.": "Het item kon niet worden aangemaakt.",
|
|
3
4
|
"Start over": "Opnieuw beginnen",
|
|
4
5
|
"Continue where you left off": "Ga verder waar je was",
|
|
@@ -1136,7 +1137,9 @@ var translations = {
|
|
|
1136
1137
|
"Show the date of today above the heading": "Toon de datum van vandaag boven de kop",
|
|
1137
1138
|
"Plain header without a coloured background": "Eenvoudige kop zonder gekleurde achtergrond",
|
|
1138
1139
|
"Loading…": "Laden…",
|
|
1139
|
-
"A register and schema are required": "Een register en schema zijn verplicht"
|
|
1140
|
+
"A register and schema are required": "Een register en schema zijn verplicht",
|
|
1141
|
+
"Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
|
|
1142
|
+
"Try again later.": "Probeer het later opnieuw."
|
|
1140
1143
|
};
|
|
1141
1144
|
var plurals = {
|
|
1142
1145
|
"%n year": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1940,6 +1940,23 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
1940
1940
|
color: var(--color-text-maxcontrast);
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
|
+
/* Beside the inline search (`showCountWithSearch`): the search field shrinks
|
|
1944
|
+
first and the counter stays on one line. */
|
|
1945
|
+
.cn-actions-bar__count--beside-search {
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
white-space: nowrap;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/* Taken out of the flex row so it adds no gap, but kept in the accessibility
|
|
1951
|
+
tree so the live region exists before a total appears. */
|
|
1952
|
+
.cn-actions-bar__count--empty {
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
width: 1px;
|
|
1955
|
+
height: 1px;
|
|
1956
|
+
overflow: hidden;
|
|
1957
|
+
clip-path: inset(50%);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1943
1960
|
/* Inline search field */
|
|
1944
1961
|
.cn-actions-bar__search {
|
|
1945
1962
|
position: relative;
|
|
@@ -2199,8 +2216,9 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
2199
2216
|
display: contents;
|
|
2200
2217
|
}
|
|
2201
2218
|
|
|
2202
|
-
/* #after-search filter buttons
|
|
2203
|
-
search field (DOM order — search first via its
|
|
2219
|
+
/* #after-search filter buttons, the beside-search counter and the view
|
|
2220
|
+
toggle ride beside the search field (DOM order — search first via its
|
|
2221
|
+
own order). */
|
|
2204
2222
|
.cn-actions-bar__info > * {
|
|
2205
2223
|
order: 1;
|
|
2206
2224
|
}
|
|
@@ -423,19 +423,29 @@ function liveUpdatesPlugin(opts = {}) {
|
|
|
423
423
|
const collectionInFlight = new Map();
|
|
424
424
|
|
|
425
425
|
const originalFetchCollection = store.fetchCollection.bind(store);
|
|
426
|
-
store.fetchCollection = async function dedupedFetchCollection(type, params = {}) {
|
|
426
|
+
store.fetchCollection = async function dedupedFetchCollection(type, params = {}, options = {}) {
|
|
427
427
|
if (!store.__liveDedupActive) {
|
|
428
|
-
return originalFetchCollection(type, params)
|
|
428
|
+
return originalFetchCollection(type, params, options)
|
|
429
429
|
}
|
|
430
430
|
const key = collectionDedupKey(type, params);
|
|
431
|
-
|
|
432
|
-
|
|
431
|
+
let entry = collectionInFlight.get(key);
|
|
432
|
+
if (!entry) {
|
|
433
|
+
// The shared request reports into its own sink, copied to
|
|
434
|
+
// each caller's `options.outcome` below, so a caller that
|
|
435
|
+
// joined it learns that request's result too.
|
|
436
|
+
const outcome = {};
|
|
437
|
+
const promise = originalFetchCollection(type, params, { ...options, outcome }).finally(() => {
|
|
438
|
+
collectionInFlight.delete(key);
|
|
439
|
+
});
|
|
440
|
+
entry = { promise, outcome };
|
|
441
|
+
collectionInFlight.set(key, entry);
|
|
433
442
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
443
|
+
const results = await entry.promise;
|
|
444
|
+
const sink = options && options.outcome;
|
|
445
|
+
if (sink && typeof sink === 'object' && 'error' in entry.outcome) {
|
|
446
|
+
sink.error = entry.outcome.error;
|
|
447
|
+
}
|
|
448
|
+
return results
|
|
439
449
|
};
|
|
440
450
|
},
|
|
441
451
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveUpdates.js","sources":["../../../../src/store/plugins/liveUpdates.js"],"sourcesContent":["/**\n * liveUpdatesPlugin — Pinia plugin factory for OpenRegister real-time updates.\n *\n * Adds live-update subscription support to any store created via createObjectStore().\n * Uses @nextcloud/notify_push via the transport singleton, with automatic polling\n * fallback when push is unavailable.\n *\n * Contributed state:\n * - `liveStatus` — current transport status string (`'offline'|'connecting'|'live'|'reconnecting'|'polling'`)\n * - `liveSubscriptions` — number of active subscriptions\n * - `liveLastEventAt` — Date of last received event, or null\n *\n * Contributed getters:\n * - `getLiveStatus`, `getLiveSubscriptions`, `getLiveLastEventAt`\n *\n * Contributed actions:\n * - `subscribe(type, id?, opts?)` — subscribe to live updates; returns a handle\n * - `unsubscribe(handle)` — tear down a subscription\n *\n * Usage note (Options API):\n * `tryOnScopeDispose` auto-registers cleanup when called from a Vue setup()\n * or Options API mounted() with an active effect scope. In Options API mounted()\n * without Vue 2.7 scope tracking, manual `unsubscribe(handle)` in beforeDestroy\n * is required. Document this at the call site.\n *\n * In-flight dedup:\n * Concurrent calls to fetchObject(type, id) or fetchCollection(type, params)\n * for the same key are coalesced into one HTTP request. The dedup maps live as\n * plain (non-reactive) Maps on the store instance to avoid Vue 2 overhead.\n * Dedup activates on the FIRST `subscribe()` call — before that, fetch actions\n * pass straight through to the base implementations. This keeps the plugin\n * fully inert (zero behaviour change) on stores that never subscribe, which\n * matters now that createObjectStore installs this plugin by default\n * (live-updates-default-on).\n *\n * Laziness guarantee:\n * Installing the plugin causes NO transport activity. The transport singleton\n * (and therefore the notify_push `listen()` probe / websocket connection /\n * polling timers) is only created inside the `subscribe()` action.\n *\n * Event coalescing:\n * Live events are HINTS (\"something changed\"), not payloads — each one\n * triggers a refetch. A burst of events (mass import, bulk save, another\n * user's rapid edits) would fire a refetch per event, so the dispatch is\n * coalesced per subscription: the FIRST event refetches immediately, and\n * any further events inside the `refetchDebounce` window (default 750 ms)\n * collapse into ONE trailing refetch when the window closes. Pending\n * trailing refetches are cancelled by `unsubscribe()`.\n *\n * @param {object} [opts] Plugin options\n * @param {number} [opts.pollIntervalCollection=30000] Collection poll interval (ms)\n * @param {number} [opts.pollIntervalObject=60000] Object poll interval (ms)\n * @param {number} [opts.refetchDebounce=750] Event-burst coalescing window (ms); 0 disables\n * @return {object} Plugin definition for use with createObjectStore\n *\n * @example\n * import { createObjectStore, liveUpdatesPlugin } from '@conduction/nextcloud-vue'\n *\n * const useStore = createObjectStore('myStore', {\n * plugins: [liveUpdatesPlugin({ pollIntervalCollection: 15000 })],\n * })\n *\n * // In a Vue component:\n * const store = useStore()\n * const handle = await store.subscribe('melding') // collection\n * const handle2 = await store.subscribe('melding', id) // object\n * // In beforeDestroy (Options API without scope):\n * store.unsubscribe(handle)\n */\n\n// `tryOnScopeDispose` comes from @vueuse/core, a DECLARED (non-optional) peer\n// dependency, so it is imported statically.\n//\n// It used to be pulled in through a CommonJS call inside a try/catch \"to avoid\n// a hard failure if @vueuse/core is absent\". That could not work: this module\n// ships to `dist/esm/**` where that function is not defined, and @vueuse/core's\n// own `exports` map resolves \".\" to a single ESM file with no CommonJS\n// condition. The catch therefore fired every time, `_tryOnScopeDispose` was\n// permanently null, and auto-cleanup on scope dispose silently never happened —\n// subscriptions leaked with no warning anywhere.\nimport { tryOnScopeDispose as _tryOnScopeDispose } from '@vueuse/core'\nimport { buildCollectionKey, buildObjectKey } from '../liveUpdates/eventKeys.js'\nimport { getLiveUpdates } from '../liveUpdates/transport.js'\n\n/**\n * Compute a stable cache key for dedup of fetchCollection calls.\n *\n * @param {string} type Object type slug\n * @param {object} params Query params\n * @return {string}\n */\nfunction collectionDedupKey(type, params) {\n\treturn `${type}:${JSON.stringify(params)}`\n}\n\n/**\n * Compute the cache key for dedup of fetchObject calls.\n *\n * @param {string} type Object type slug\n * @param {string} id Object ID\n * @return {string}\n */\nfunction objectDedupKey(type, id) {\n\treturn `${type}:${id}`\n}\n\n/**\n * Wrap a refetch dispatcher in a leading+trailing burst coalescer.\n *\n * Events are hints, so the exact invocation count doesn't matter — only\n * that the data ends up fresh. The first `run()` dispatches immediately\n * (snappy single-event case); further `run()`s inside the `waitMs` window\n * collapse into one trailing dispatch when the window closes (which opens\n * a new window, so a sustained burst dispatches at most once per window).\n * `cancel()` clears any pending trailing dispatch — called on\n * unsubscribe so a torn-down subscription never fires a late refetch.\n *\n * @param {() => void} fn The refetch dispatcher.\n * @param {number} waitMs Coalescing window in ms; `<= 0` disables (every run dispatches).\n * @return {{run: () => void, cancel: () => void}} The coalesced dispatcher.\n */\nfunction createHintCoalescer(fn, waitMs) {\n\tlet timer = null\n\tlet pending = false\n\n\tfunction run() {\n\t\tif (!(waitMs > 0)) {\n\t\t\tfn()\n\t\t\treturn\n\t\t}\n\t\tif (timer) {\n\t\t\tpending = true\n\t\t\treturn\n\t\t}\n\t\tfn()\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = null\n\t\t\tif (pending) {\n\t\t\t\tpending = false\n\t\t\t\trun()\n\t\t\t}\n\t\t}, waitMs)\n\t}\n\n\tfunction cancel() {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\t\ttimer = null\n\t\tpending = false\n\t}\n\n\treturn { run, cancel }\n}\n\nexport function liveUpdatesPlugin(opts = {}) {\n\tconst pluginPollCollection = opts.pollIntervalCollection || 30000\n\tconst pluginPollObject = opts.pollIntervalObject || 60000\n\tconst pluginRefetchDebounce = opts.refetchDebounce === undefined ? 750 : opts.refetchDebounce\n\n\treturn {\n\t\tname: 'liveUpdates',\n\n\t\tstate: () => ({\n\t\t\t/** @type {'offline'|'connecting'|'live'|'reconnecting'|'polling'} */\n\t\t\tliveStatus: 'offline',\n\t\t\t/** @type {number} */\n\t\t\tliveSubscriptions: 0,\n\t\t\t/** @type {Date|null} */\n\t\t\tliveLastEventAt: null,\n\t\t}),\n\n\t\tgetters: {\n\t\t\t/**\n\t\t\t * Get the current transport status.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {string}\n\t\t\t */\n\t\t\tgetLiveStatus: (state) => state.liveStatus,\n\n\t\t\t/**\n\t\t\t * Get the number of active subscriptions.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {number}\n\t\t\t */\n\t\t\tgetLiveSubscriptions: (state) => state.liveSubscriptions,\n\n\t\t\t/**\n\t\t\t * Get the timestamp of the last received live event.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {Date|null}\n\t\t\t */\n\t\t\tgetLiveLastEventAt: (state) => state.liveLastEventAt,\n\t\t},\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Subscribe to live updates for an object type.\n\t\t\t *\n\t\t\t * When `id` is provided: subscribes to `or-object-{id}` (per-object events).\n\t\t\t * When `id` is omitted: subscribes to `or-collection-{registerSlug}-{schemaSlug}`\n\t\t\t * derived from objectTypeRegistry. Slugs are lazy-fetched if not already cached.\n\t\t\t *\n\t\t\t * **Options API note**: In `mounted()` without an active Vue scope,\n\t\t\t * `tryOnScopeDispose` cannot auto-register cleanup. Call `unsubscribe(handle)`\n\t\t\t * manually in `beforeDestroy`.\n\t\t\t *\n\t\t\t * @param {string} type Registered object type slug\n\t\t\t * @param {string} [id] Object UUID (omit for collection subscription)\n\t\t\t * @param {object} [subscribeOpts] Options\n\t\t\t * @param {number} [subscribeOpts.interval] Override poll interval (ms)\n\t\t\t * @return {Promise<object>} Opaque handle for unsubscribe()\n\t\t\t * @throws {Error} If type is not registered, or if lazy slug fetch fails (collection form)\n\t\t\t */\n\t\t\tasync subscribe(type, id, subscribeOpts = {}) {\n\t\t\t\tconst config = this.objectTypeRegistry[type]\n\t\t\t\tif (!config) {\n\t\t\t\t\tthrow new Error(`\"${type}\" is not registered. Call registerObjectType('${type}', ...) first.`)\n\t\t\t\t}\n\n\t\t\t\t// First subscribe() activates the in-flight dedup wrappers set\n\t\t\t\t// up in the plugin's setup() hook. Before this point the store\n\t\t\t\t// behaves exactly as if the plugin were not installed.\n\t\t\t\tthis.__liveDedupActive = true\n\n\t\t\t\tconst liveUpdates = getLiveUpdates({\n\t\t\t\t\tpollIntervalCollection: pluginPollCollection,\n\t\t\t\t\tpollIntervalObject: pluginPollObject,\n\t\t\t\t})\n\n\t\t\t\t// Register transport status observer (idempotent: only once per store instance)\n\t\t\t\tif (!this.__liveStatusObserverRegistered) {\n\t\t\t\t\tthis.__liveStatusObserverRegistered = true\n\t\t\t\t\tliveUpdates.onStatusChange((newStatus) => {\n\t\t\t\t\t\tthis.liveStatus = { ...this.liveStatus, valueOf: undefined, toString: undefined }\n\t\t\t\t\t\t// Vue 2 spread pattern for primitive reactive update\n\t\t\t\t\t\tthis.liveStatus = newStatus\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tlet eventKey\n\t\t\t\tlet isObject = false\n\n\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t// Object subscription — no slug lookup needed\n\t\t\t\t\tisObject = true\n\t\t\t\t\teventKey = buildObjectKey(id)\n\t\t\t\t} else {\n\t\t\t\t\t// Collection subscription — need registerSlug + schemaSlug\n\t\t\t\t\tlet { registerSlug, schemaSlug } = config\n\n\t\t\t\t\tif (!registerSlug || !schemaSlug) {\n\t\t\t\t\t\t// Lazy fetch slugs via existing store actions\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst [register, schema] = await Promise.all([\n\t\t\t\t\t\t\t\tthis.fetchRegister(type),\n\t\t\t\t\t\t\t\tthis.fetchSchema(type),\n\t\t\t\t\t\t\t])\n\n\t\t\t\t\t\t\tif (!register || !register.slug) {\n\t\t\t\t\t\t\t\tthrow new Error(`Failed to resolve register slug for \"${type}\"`)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!schema || !schema.slug) {\n\t\t\t\t\t\t\t\tthrow new Error(`Failed to resolve schema slug for \"${type}\"`)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tregisterSlug = register.slug\n\t\t\t\t\t\t\tschemaSlug = schema.slug\n\n\t\t\t\t\t\t\t// Cache slugs in registry (Vue 2 spread pattern)\n\t\t\t\t\t\t\tthis.objectTypeRegistry = {\n\t\t\t\t\t\t\t\t...this.objectTypeRegistry,\n\t\t\t\t\t\t\t\t[type]: { ...config, registerSlug, schemaSlug },\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tthrow new Error(`liveUpdatesPlugin: cannot subscribe to \"${type}\" collection — ${err.message}`, { cause: err })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\teventKey = buildCollectionKey(registerSlug, schemaSlug)\n\t\t\t\t}\n\n\t\t\t\t// Set status to 'connecting' on first subscribe (before transport sets it)\n\t\t\t\tif (this.liveStatus === 'offline') {\n\t\t\t\t\tthis.liveStatus = 'connecting'\n\t\t\t\t}\n\n\t\t\t\tconst store = this\n\n\t\t\t\t// Events are hints — coalesce refetch bursts per subscription\n\t\t\t\t// (see \"Event coalescing\" in the module docblock). A per-call\n\t\t\t\t// `subscribeOpts.debounce` overrides the plugin default; 0 disables.\n\t\t\t\tconst debounceMs = subscribeOpts.debounce === undefined ? pluginRefetchDebounce : subscribeOpts.debounce\n\t\t\t\tconst dispatch = isObject\n\t\t\t\t\t? () => {\n\t\t\t\t\t\t// Dispatch fetchObject with dedup\n\t\t\t\t\t\t\tstore.fetchObject(type, id)\n\t\t\t\t\t\t}\n\t\t\t\t\t: () => {\n\t\t\t\t\t\t// Dispatch fetchCollection with last stashed params + dedup\n\t\t\t\t\t\t\tconst lastParams = store.__lastCollectionParams?.get(type) || {}\n\t\t\t\t\t\t\tstore.fetchCollection(type, lastParams)\n\t\t\t\t\t\t}\n\t\t\t\tconst coalesced = createHintCoalescer(dispatch, debounceMs)\n\n\t\t\t\tconst callback = () => {\n\t\t\t\t\tstore.liveLastEventAt = new Date()\n\t\t\t\t\tcoalesced.run()\n\t\t\t\t}\n\n\t\t\t\tconst transportOpts = {\n\t\t\t\t\tisObject,\n\t\t\t\t\tinterval: subscribeOpts.interval || (isObject ? pluginPollObject : pluginPollCollection),\n\t\t\t\t}\n\n\t\t\t\tconst transportHandle = liveUpdates.subscribe(eventKey, callback, transportOpts)\n\n\t\t\t\t// Update reactive subscriptions count (Vue 2 spread)\n\t\t\t\tthis.liveSubscriptions = this.liveSubscriptions + 1\n\n\t\t\t\t// Update liveStatus from transport now that we've subscribed\n\t\t\t\tconst transportStatus = liveUpdates.getStatus()\n\t\t\t\tif (transportStatus !== 'offline') {\n\t\t\t\t\tthis.liveStatus = transportStatus\n\t\t\t\t}\n\n\t\t\t\tconst handle = {\n\t\t\t\t\t_livePlugin: true,\n\t\t\t\t\ttype,\n\t\t\t\t\tid,\n\t\t\t\t\teventKey,\n\t\t\t\t\ttransportHandle,\n\t\t\t\t\t// Cancels a pending coalesced (trailing) refetch — called by\n\t\t\t\t\t// unsubscribe() so a torn-down subscription never fires late.\n\t\t\t\t\t_cancelPendingRefetch: coalesced.cancel,\n\t\t\t\t}\n\n\t\t\t\t// Auto-cleanup via tryOnScopeDispose (Vue 2.7 composition API scopes)\n\t\t\t\tif (_tryOnScopeDispose) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t_tryOnScopeDispose(() => {\n\t\t\t\t\t\t\tthis.unsubscribe(handle)\n\t\t\t\t\t\t})\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Not in a scope — manual unsubscribe required in beforeDestroy\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn handle\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unsubscribe from live updates. Idempotent — releasing the same\n\t\t\t * handle twice (e.g. the plugin's own scope-dispose cleanup racing\n\t\t\t * a composable's explicit detach) is a no-op the second time, so\n\t\t\t * `liveSubscriptions` never double-decrements.\n\t\t\t *\n\t\t\t * @param {object} handle Handle returned by subscribe()\n\t\t\t */\n\t\t\tunsubscribe(handle) {\n\t\t\t\tif (!handle || !handle._livePlugin || handle._released) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thandle._released = true\n\n\t\t\t\t// Cancel any pending coalesced refetch before tearing down the\n\t\t\t\t// transport subscription — a late trailing dispatch after\n\t\t\t\t// unsubscribe would refetch for a scope nobody renders anymore.\n\t\t\t\tif (typeof handle._cancelPendingRefetch === 'function') {\n\t\t\t\t\thandle._cancelPendingRefetch()\n\t\t\t\t}\n\n\t\t\t\tconst liveUpdates = getLiveUpdates()\n\t\t\t\tliveUpdates.unsubscribe(handle.transportHandle)\n\n\t\t\t\tthis.liveSubscriptions = Math.max(0, this.liveSubscriptions - 1)\n\t\t\t},\n\t\t},\n\n\t\t/**\n\t\t * Plugin setup hook — called once per store instance.\n\t\t * Sets up:\n\t\t * 1. `$onAction` observer for fetchCollection → stash last params\n\t\t * 2. In-flight dedup Maps for fetchObject and fetchCollection\n\t\t *\n\t\t * @param {object} store The Pinia store instance\n\t\t */\n\t\tsetup(store) {\n\t\t\t// Dedup activation flag — flipped to true by the first subscribe()\n\t\t\t// call. Until then the fetch wrappers below pass straight through,\n\t\t\t// so a default-installed plugin causes zero behaviour change on\n\t\t\t// stores that never subscribe (live-updates-default-on).\n\t\t\tstore.__liveDedupActive = false\n\n\t\t\t// -- Last-params stash for collection re-fetch on events --\n\t\t\t// Plain (non-reactive) Map: type → last params object\n\t\t\tstore.__lastCollectionParams = new Map()\n\n\t\t\tstore.$onAction(({ name, args, after }) => {\n\t\t\t\tif (name === 'fetchCollection') {\n\t\t\t\t\tconst [type, params = {}] = args\n\t\t\t\t\tafter(() => {\n\t\t\t\t\t\tstore.__lastCollectionParams.set(type, params)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// -- In-flight dedup for fetchObject --\n\t\t\t// Plain Map: dedupKey → Promise\n\t\t\tconst objectInFlight = new Map()\n\n\t\t\tconst originalFetchObject = store.fetchObject.bind(store)\n\t\t\tstore.fetchObject = async function dedupedFetchObject(type, id) {\n\t\t\t\tif (!store.__liveDedupActive) {\n\t\t\t\t\treturn originalFetchObject(type, id)\n\t\t\t\t}\n\t\t\t\tconst key = objectDedupKey(type, id)\n\t\t\t\tif (objectInFlight.has(key)) {\n\t\t\t\t\treturn objectInFlight.get(key)\n\t\t\t\t}\n\t\t\t\tconst promise = originalFetchObject(type, id).finally(() => {\n\t\t\t\t\tobjectInFlight.delete(key)\n\t\t\t\t})\n\t\t\t\tobjectInFlight.set(key, promise)\n\t\t\t\treturn promise\n\t\t\t}\n\n\t\t\t// -- In-flight dedup for fetchCollection --\n\t\t\tconst collectionInFlight = new Map()\n\n\t\t\tconst originalFetchCollection = store.fetchCollection.bind(store)\n\t\t\tstore.fetchCollection = async function dedupedFetchCollection(type, params = {}) {\n\t\t\t\tif (!store.__liveDedupActive) {\n\t\t\t\t\treturn originalFetchCollection(type, params)\n\t\t\t\t}\n\t\t\t\tconst key = collectionDedupKey(type, params)\n\t\t\t\tif (collectionInFlight.has(key)) {\n\t\t\t\t\treturn collectionInFlight.get(key)\n\t\t\t\t}\n\t\t\t\tconst promise = originalFetchCollection(type, params).finally(() => {\n\t\t\t\t\tcollectionInFlight.delete(key)\n\t\t\t\t})\n\t\t\t\tcollectionInFlight.set(key, promise)\n\t\t\t\treturn promise\n\t\t\t}\n\t\t},\n\t}\n}\n"],"names":["_tryOnScopeDispose"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE;AAC1C,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,cAAc,CAAC,IAAI,EAAE,EAAE,EAAE;AAClC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE;AACzC,CAAC,IAAI,KAAK,GAAG,KAAI;AACjB,CAAC,IAAI,OAAO,GAAG,MAAK;AACpB;AACA,CAAC,SAAS,GAAG,GAAG;AAChB,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE;AACrB,GAAG,EAAE,GAAE;AACP,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,KAAK,EAAE;AACb,GAAG,OAAO,GAAG,KAAI;AACjB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,EAAE,GAAE;AACN,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM;AAC3B,GAAG,KAAK,GAAG,KAAI;AACf,GAAG,IAAI,OAAO,EAAE;AAChB,IAAI,OAAO,GAAG,MAAK;AACnB,IAAI,GAAG,GAAE;AACT,IAAI;AACJ,GAAG,EAAE,MAAM,EAAC;AACZ,EAAE;AACF;AACA,CAAC,SAAS,MAAM,GAAG;AACnB,EAAE,IAAI,KAAK,EAAE;AACb,GAAG,YAAY,CAAC,KAAK,EAAC;AACtB,GAAG;AACH,EAAE,KAAK,GAAG,KAAI;AACd,EAAE,OAAO,GAAG,MAAK;AACjB,EAAE;AACF;AACA,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE;AACvB,CAAC;AACD;AACO,SAAS,iBAAiB,CAAC,IAAI,GAAG,EAAE,EAAE;AAC7C,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,sBAAsB,IAAI,MAAK;AAClE,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,IAAI,MAAK;AAC1D,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,eAAe,KAAK,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAe;AAC9F;AACA,CAAC,OAAO;AACR,EAAE,IAAI,EAAE,aAAa;AACrB;AACA,EAAE,KAAK,EAAE,OAAO;AAChB;AACA,GAAG,UAAU,EAAE,SAAS;AACxB;AACA,GAAG,iBAAiB,EAAE,CAAC;AACvB;AACA,GAAG,eAAe,EAAE,IAAI;AACxB,GAAG,CAAC;AACJ;AACA,EAAE,OAAO,EAAE;AACX;AACA;AACA;AACA;AACA;AACA;AACA,GAAG,aAAa,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG,oBAAoB,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,iBAAiB;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG,kBAAkB,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,eAAe;AACvD,GAAG;AACH;AACA,EAAE,OAAO,EAAE;AACX;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,aAAa,GAAG,EAAE,EAAE;AACjD,IAAI,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAC;AAChD,IAAI,IAAI,CAAC,MAAM,EAAE;AACjB,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,8CAA8C,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;AACnG,KAAK;AACL;AACA;AACA;AACA;AACA,IAAI,IAAI,CAAC,iBAAiB,GAAG,KAAI;AACjC;AACA,IAAI,MAAM,WAAW,GAAG,cAAc,CAAC;AACvC,KAAK,sBAAsB,EAAE,oBAAoB;AACjD,KAAK,kBAAkB,EAAE,gBAAgB;AACzC,KAAK,EAAC;AACN;AACA;AACA,IAAI,IAAI,CAAC,IAAI,CAAC,8BAA8B,EAAE;AAC9C,KAAK,IAAI,CAAC,8BAA8B,GAAG,KAAI;AAC/C,KAAK,WAAW,CAAC,cAAc,CAAC,CAAC,SAAS,KAAK;AAC/C,MAAM,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAE;AACvF;AACA,MAAM,IAAI,CAAC,UAAU,GAAG,UAAS;AACjC,MAAM,EAAC;AACP,KAAK;AACL;AACA,IAAI,IAAI,SAAQ;AAChB,IAAI,IAAI,QAAQ,GAAG,MAAK;AACxB;AACA,IAAI,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,EAAE;AACzC;AACA,KAAK,QAAQ,GAAG,KAAI;AACpB,KAAK,QAAQ,GAAG,cAAc,CAAC,EAAE,EAAC;AAClC,KAAK,MAAM;AACX;AACA,KAAK,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,OAAM;AAC9C;AACA,KAAK,IAAI,CAAC,YAAY,IAAI,CAAC,UAAU,EAAE;AACvC;AACA,MAAM,IAAI;AACV,OAAO,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AACpD,QAAQ,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;AAChC,QAAQ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;AAC9B,QAAQ,EAAC;AACT;AACA,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AACxC,QAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,qCAAqC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACxE,QAAQ;AACR,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;AACpC,QAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,mCAAmC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACtE,QAAQ;AACR;AACA,OAAO,YAAY,GAAG,QAAQ,CAAC,KAAI;AACnC,OAAO,UAAU,GAAG,MAAM,CAAC,KAAI;AAC/B;AACA;AACA,OAAO,IAAI,CAAC,kBAAkB,GAAG;AACjC,QAAQ,GAAG,IAAI,CAAC,kBAAkB;AAClC,QAAQ,CAAC,IAAI,GAAG,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE;AACvD,SAAQ;AACR,OAAO,CAAC,OAAO,GAAG,EAAE;AACpB,OAAO,MAAM,IAAI,KAAK,CAAC,CAAC,wCAAwC,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACtH,OAAO;AACP,MAAM;AACN;AACA,KAAK,QAAQ,GAAG,kBAAkB,CAAC,YAAY,EAAE,UAAU,EAAC;AAC5D,KAAK;AACL;AACA;AACA,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE;AACvC,KAAK,IAAI,CAAC,UAAU,GAAG,aAAY;AACnC,KAAK;AACL;AACA,IAAI,MAAM,KAAK,GAAG,KAAI;AACtB;AACA;AACA;AACA;AACA,IAAI,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,qBAAqB,GAAG,aAAa,CAAC,SAAQ;AAC5G,IAAI,MAAM,QAAQ,GAAG,QAAQ;AAC7B,OAAO,MAAM;AACb;AACA,OAAO,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,EAAC;AAClC,OAAO;AACP,OAAO,MAAM;AACb;AACA,OAAO,MAAM,UAAU,GAAG,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,GAAE;AACvE,OAAO,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,UAAU,EAAC;AAC9C,QAAO;AACP,IAAI,MAAM,SAAS,GAAG,mBAAmB,CAAC,QAAQ,EAAE,UAAU,EAAC;AAC/D;AACA,IAAI,MAAM,QAAQ,GAAG,MAAM;AAC3B,KAAK,KAAK,CAAC,eAAe,GAAG,IAAI,IAAI,GAAE;AACvC,KAAK,SAAS,CAAC,GAAG,GAAE;AACpB,MAAK;AACL;AACA,IAAI,MAAM,aAAa,GAAG;AAC1B,KAAK,QAAQ;AACb,KAAK,QAAQ,EAAE,aAAa,CAAC,QAAQ,KAAK,QAAQ,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAC7F,MAAK;AACL;AACA,IAAI,MAAM,eAAe,GAAG,WAAW,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAC;AACpF;AACA;AACA,IAAI,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,GAAG,EAAC;AACvD;AACA;AACA,IAAI,MAAM,eAAe,GAAG,WAAW,CAAC,SAAS,GAAE;AACnD,IAAI,IAAI,eAAe,KAAK,SAAS,EAAE;AACvC,KAAK,IAAI,CAAC,UAAU,GAAG,gBAAe;AACtC,KAAK;AACL;AACA,IAAI,MAAM,MAAM,GAAG;AACnB,KAAK,WAAW,EAAE,IAAI;AACtB,KAAK,IAAI;AACT,KAAK,EAAE;AACP,KAAK,QAAQ;AACb,KAAK,eAAe;AACpB;AACA;AACA,KAAK,qBAAqB,EAAE,SAAS,CAAC,MAAM;AAC5C,MAAK;AACL;AACA;AACA,IAAI,IAAIA,iBAAkB,EAAE;AAC5B,KAAK,IAAI;AACT,MAAMA,iBAAkB,CAAC,MAAM;AAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAC;AAC/B,OAAO,EAAC;AACR,MAAM,CAAC,MAAM;AACb;AACA,MAAM;AACN,KAAK;AACL;AACA,IAAI,OAAO,MAAM;AACjB,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG,WAAW,CAAC,MAAM,EAAE;AACvB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,SAAS,EAAE;AAC5D,KAAK,MAAM;AACX,KAAK;AACL,IAAI,MAAM,CAAC,SAAS,GAAG,KAAI;AAC3B;AACA;AACA;AACA;AACA,IAAI,IAAI,OAAO,MAAM,CAAC,qBAAqB,KAAK,UAAU,EAAE;AAC5D,KAAK,MAAM,CAAC,qBAAqB,GAAE;AACnC,KAAK;AACL;AACA,IAAI,MAAM,WAAW,GAAG,cAAc,GAAE;AACxC,IAAI,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,eAAe,EAAC;AACnD;AACA,IAAI,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,CAAC,EAAC;AACpE,IAAI;AACJ,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,KAAK,CAAC,KAAK,EAAE;AACf;AACA;AACA;AACA;AACA,GAAG,KAAK,CAAC,iBAAiB,GAAG,MAAK;AAClC;AACA;AACA;AACA,GAAG,KAAK,CAAC,sBAAsB,GAAG,IAAI,GAAG,GAAE;AAC3C;AACA,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;AAC9C,IAAI,IAAI,IAAI,KAAK,iBAAiB,EAAE;AACpC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,KAAI;AACrC,KAAK,KAAK,CAAC,MAAM;AACjB,MAAM,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAC;AACpD,MAAM,EAAC;AACP,KAAK;AACL,IAAI,EAAC;AACL;AACA;AACA;AACA,GAAG,MAAM,cAAc,GAAG,IAAI,GAAG,GAAE;AACnC;AACA,GAAG,MAAM,mBAAmB,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,EAAC;AAC5D,GAAG,KAAK,CAAC,WAAW,GAAG,eAAe,kBAAkB,CAAC,IAAI,EAAE,EAAE,EAAE;AACnE,IAAI,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE;AAClC,KAAK,OAAO,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC;AACzC,KAAK;AACL,IAAI,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,EAAE,EAAC;AACxC,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACjC,KAAK,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;AACnC,KAAK;AACL,IAAI,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM;AAChE,KAAK,cAAc,CAAC,MAAM,CAAC,GAAG,EAAC;AAC/B,KAAK,EAAC;AACN,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAC;AACpC,IAAI,OAAO,OAAO;AAClB,KAAI;AACJ;AACA;AACA,GAAG,MAAM,kBAAkB,GAAG,IAAI,GAAG,GAAE;AACvC;AACA,GAAG,MAAM,uBAAuB,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,EAAC;AACpE,GAAG,KAAK,CAAC,eAAe,GAAG,eAAe,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,EAAE;AACpF,IAAI,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE;AAClC,KAAK,OAAO,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC;AACjD,KAAK;AACL,IAAI,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAC;AAChD,IAAI,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACrC,KAAK,OAAO,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC;AACvC,KAAK;AACL,IAAI,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM;AACxE,KAAK,kBAAkB,CAAC,MAAM,CAAC,GAAG,EAAC;AACnC,KAAK,EAAC;AACN,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAC;AACxC,IAAI,OAAO,OAAO;AAClB,KAAI;AACJ,GAAG;AACH,EAAE;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"liveUpdates.js","sources":["../../../../src/store/plugins/liveUpdates.js"],"sourcesContent":["/**\n * liveUpdatesPlugin — Pinia plugin factory for OpenRegister real-time updates.\n *\n * Adds live-update subscription support to any store created via createObjectStore().\n * Uses @nextcloud/notify_push via the transport singleton, with automatic polling\n * fallback when push is unavailable.\n *\n * Contributed state:\n * - `liveStatus` — current transport status string (`'offline'|'connecting'|'live'|'reconnecting'|'polling'`)\n * - `liveSubscriptions` — number of active subscriptions\n * - `liveLastEventAt` — Date of last received event, or null\n *\n * Contributed getters:\n * - `getLiveStatus`, `getLiveSubscriptions`, `getLiveLastEventAt`\n *\n * Contributed actions:\n * - `subscribe(type, id?, opts?)` — subscribe to live updates; returns a handle\n * - `unsubscribe(handle)` — tear down a subscription\n *\n * Usage note (Options API):\n * `tryOnScopeDispose` auto-registers cleanup when called from a Vue setup()\n * or Options API mounted() with an active effect scope. In Options API mounted()\n * without Vue 2.7 scope tracking, manual `unsubscribe(handle)` in beforeDestroy\n * is required. Document this at the call site.\n *\n * In-flight dedup:\n * Concurrent calls to fetchObject(type, id) or fetchCollection(type, params)\n * for the same key are coalesced into one HTTP request. The dedup maps live as\n * plain (non-reactive) Maps on the store instance to avoid Vue 2 overhead.\n * Dedup activates on the FIRST `subscribe()` call — before that, fetch actions\n * pass straight through to the base implementations. This keeps the plugin\n * fully inert (zero behaviour change) on stores that never subscribe, which\n * matters now that createObjectStore installs this plugin by default\n * (live-updates-default-on).\n *\n * Laziness guarantee:\n * Installing the plugin causes NO transport activity. The transport singleton\n * (and therefore the notify_push `listen()` probe / websocket connection /\n * polling timers) is only created inside the `subscribe()` action.\n *\n * Event coalescing:\n * Live events are HINTS (\"something changed\"), not payloads — each one\n * triggers a refetch. A burst of events (mass import, bulk save, another\n * user's rapid edits) would fire a refetch per event, so the dispatch is\n * coalesced per subscription: the FIRST event refetches immediately, and\n * any further events inside the `refetchDebounce` window (default 750 ms)\n * collapse into ONE trailing refetch when the window closes. Pending\n * trailing refetches are cancelled by `unsubscribe()`.\n *\n * @param {object} [opts] Plugin options\n * @param {number} [opts.pollIntervalCollection=30000] Collection poll interval (ms)\n * @param {number} [opts.pollIntervalObject=60000] Object poll interval (ms)\n * @param {number} [opts.refetchDebounce=750] Event-burst coalescing window (ms); 0 disables\n * @return {object} Plugin definition for use with createObjectStore\n *\n * @example\n * import { createObjectStore, liveUpdatesPlugin } from '@conduction/nextcloud-vue'\n *\n * const useStore = createObjectStore('myStore', {\n * plugins: [liveUpdatesPlugin({ pollIntervalCollection: 15000 })],\n * })\n *\n * // In a Vue component:\n * const store = useStore()\n * const handle = await store.subscribe('melding') // collection\n * const handle2 = await store.subscribe('melding', id) // object\n * // In beforeDestroy (Options API without scope):\n * store.unsubscribe(handle)\n */\n\n// `tryOnScopeDispose` comes from @vueuse/core, a DECLARED (non-optional) peer\n// dependency, so it is imported statically.\n//\n// It used to be pulled in through a CommonJS call inside a try/catch \"to avoid\n// a hard failure if @vueuse/core is absent\". That could not work: this module\n// ships to `dist/esm/**` where that function is not defined, and @vueuse/core's\n// own `exports` map resolves \".\" to a single ESM file with no CommonJS\n// condition. The catch therefore fired every time, `_tryOnScopeDispose` was\n// permanently null, and auto-cleanup on scope dispose silently never happened —\n// subscriptions leaked with no warning anywhere.\nimport { tryOnScopeDispose as _tryOnScopeDispose } from '@vueuse/core'\nimport { buildCollectionKey, buildObjectKey } from '../liveUpdates/eventKeys.js'\nimport { getLiveUpdates } from '../liveUpdates/transport.js'\n\n/**\n * Compute a stable cache key for dedup of fetchCollection calls.\n *\n * @param {string} type Object type slug\n * @param {object} params Query params\n * @return {string}\n */\nfunction collectionDedupKey(type, params) {\n\treturn `${type}:${JSON.stringify(params)}`\n}\n\n/**\n * Compute the cache key for dedup of fetchObject calls.\n *\n * @param {string} type Object type slug\n * @param {string} id Object ID\n * @return {string}\n */\nfunction objectDedupKey(type, id) {\n\treturn `${type}:${id}`\n}\n\n/**\n * Wrap a refetch dispatcher in a leading+trailing burst coalescer.\n *\n * Events are hints, so the exact invocation count doesn't matter — only\n * that the data ends up fresh. The first `run()` dispatches immediately\n * (snappy single-event case); further `run()`s inside the `waitMs` window\n * collapse into one trailing dispatch when the window closes (which opens\n * a new window, so a sustained burst dispatches at most once per window).\n * `cancel()` clears any pending trailing dispatch — called on\n * unsubscribe so a torn-down subscription never fires a late refetch.\n *\n * @param {() => void} fn The refetch dispatcher.\n * @param {number} waitMs Coalescing window in ms; `<= 0` disables (every run dispatches).\n * @return {{run: () => void, cancel: () => void}} The coalesced dispatcher.\n */\nfunction createHintCoalescer(fn, waitMs) {\n\tlet timer = null\n\tlet pending = false\n\n\tfunction run() {\n\t\tif (!(waitMs > 0)) {\n\t\t\tfn()\n\t\t\treturn\n\t\t}\n\t\tif (timer) {\n\t\t\tpending = true\n\t\t\treturn\n\t\t}\n\t\tfn()\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = null\n\t\t\tif (pending) {\n\t\t\t\tpending = false\n\t\t\t\trun()\n\t\t\t}\n\t\t}, waitMs)\n\t}\n\n\tfunction cancel() {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\t\ttimer = null\n\t\tpending = false\n\t}\n\n\treturn { run, cancel }\n}\n\nexport function liveUpdatesPlugin(opts = {}) {\n\tconst pluginPollCollection = opts.pollIntervalCollection || 30000\n\tconst pluginPollObject = opts.pollIntervalObject || 60000\n\tconst pluginRefetchDebounce = opts.refetchDebounce === undefined ? 750 : opts.refetchDebounce\n\n\treturn {\n\t\tname: 'liveUpdates',\n\n\t\tstate: () => ({\n\t\t\t/** @type {'offline'|'connecting'|'live'|'reconnecting'|'polling'} */\n\t\t\tliveStatus: 'offline',\n\t\t\t/** @type {number} */\n\t\t\tliveSubscriptions: 0,\n\t\t\t/** @type {Date|null} */\n\t\t\tliveLastEventAt: null,\n\t\t}),\n\n\t\tgetters: {\n\t\t\t/**\n\t\t\t * Get the current transport status.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {string}\n\t\t\t */\n\t\t\tgetLiveStatus: (state) => state.liveStatus,\n\n\t\t\t/**\n\t\t\t * Get the number of active subscriptions.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {number}\n\t\t\t */\n\t\t\tgetLiveSubscriptions: (state) => state.liveSubscriptions,\n\n\t\t\t/**\n\t\t\t * Get the timestamp of the last received live event.\n\t\t\t *\n\t\t\t * @param {object} state Pinia state\n\t\t\t * @return {Date|null}\n\t\t\t */\n\t\t\tgetLiveLastEventAt: (state) => state.liveLastEventAt,\n\t\t},\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Subscribe to live updates for an object type.\n\t\t\t *\n\t\t\t * When `id` is provided: subscribes to `or-object-{id}` (per-object events).\n\t\t\t * When `id` is omitted: subscribes to `or-collection-{registerSlug}-{schemaSlug}`\n\t\t\t * derived from objectTypeRegistry. Slugs are lazy-fetched if not already cached.\n\t\t\t *\n\t\t\t * **Options API note**: In `mounted()` without an active Vue scope,\n\t\t\t * `tryOnScopeDispose` cannot auto-register cleanup. Call `unsubscribe(handle)`\n\t\t\t * manually in `beforeDestroy`.\n\t\t\t *\n\t\t\t * @param {string} type Registered object type slug\n\t\t\t * @param {string} [id] Object UUID (omit for collection subscription)\n\t\t\t * @param {object} [subscribeOpts] Options\n\t\t\t * @param {number} [subscribeOpts.interval] Override poll interval (ms)\n\t\t\t * @return {Promise<object>} Opaque handle for unsubscribe()\n\t\t\t * @throws {Error} If type is not registered, or if lazy slug fetch fails (collection form)\n\t\t\t */\n\t\t\tasync subscribe(type, id, subscribeOpts = {}) {\n\t\t\t\tconst config = this.objectTypeRegistry[type]\n\t\t\t\tif (!config) {\n\t\t\t\t\tthrow new Error(`\"${type}\" is not registered. Call registerObjectType('${type}', ...) first.`)\n\t\t\t\t}\n\n\t\t\t\t// First subscribe() activates the in-flight dedup wrappers set\n\t\t\t\t// up in the plugin's setup() hook. Before this point the store\n\t\t\t\t// behaves exactly as if the plugin were not installed.\n\t\t\t\tthis.__liveDedupActive = true\n\n\t\t\t\tconst liveUpdates = getLiveUpdates({\n\t\t\t\t\tpollIntervalCollection: pluginPollCollection,\n\t\t\t\t\tpollIntervalObject: pluginPollObject,\n\t\t\t\t})\n\n\t\t\t\t// Register transport status observer (idempotent: only once per store instance)\n\t\t\t\tif (!this.__liveStatusObserverRegistered) {\n\t\t\t\t\tthis.__liveStatusObserverRegistered = true\n\t\t\t\t\tliveUpdates.onStatusChange((newStatus) => {\n\t\t\t\t\t\tthis.liveStatus = { ...this.liveStatus, valueOf: undefined, toString: undefined }\n\t\t\t\t\t\t// Vue 2 spread pattern for primitive reactive update\n\t\t\t\t\t\tthis.liveStatus = newStatus\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tlet eventKey\n\t\t\t\tlet isObject = false\n\n\t\t\t\tif (id !== undefined && id !== null) {\n\t\t\t\t\t// Object subscription — no slug lookup needed\n\t\t\t\t\tisObject = true\n\t\t\t\t\teventKey = buildObjectKey(id)\n\t\t\t\t} else {\n\t\t\t\t\t// Collection subscription — need registerSlug + schemaSlug\n\t\t\t\t\tlet { registerSlug, schemaSlug } = config\n\n\t\t\t\t\tif (!registerSlug || !schemaSlug) {\n\t\t\t\t\t\t// Lazy fetch slugs via existing store actions\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst [register, schema] = await Promise.all([\n\t\t\t\t\t\t\t\tthis.fetchRegister(type),\n\t\t\t\t\t\t\t\tthis.fetchSchema(type),\n\t\t\t\t\t\t\t])\n\n\t\t\t\t\t\t\tif (!register || !register.slug) {\n\t\t\t\t\t\t\t\tthrow new Error(`Failed to resolve register slug for \"${type}\"`)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!schema || !schema.slug) {\n\t\t\t\t\t\t\t\tthrow new Error(`Failed to resolve schema slug for \"${type}\"`)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tregisterSlug = register.slug\n\t\t\t\t\t\t\tschemaSlug = schema.slug\n\n\t\t\t\t\t\t\t// Cache slugs in registry (Vue 2 spread pattern)\n\t\t\t\t\t\t\tthis.objectTypeRegistry = {\n\t\t\t\t\t\t\t\t...this.objectTypeRegistry,\n\t\t\t\t\t\t\t\t[type]: { ...config, registerSlug, schemaSlug },\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tthrow new Error(`liveUpdatesPlugin: cannot subscribe to \"${type}\" collection — ${err.message}`, { cause: err })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\teventKey = buildCollectionKey(registerSlug, schemaSlug)\n\t\t\t\t}\n\n\t\t\t\t// Set status to 'connecting' on first subscribe (before transport sets it)\n\t\t\t\tif (this.liveStatus === 'offline') {\n\t\t\t\t\tthis.liveStatus = 'connecting'\n\t\t\t\t}\n\n\t\t\t\tconst store = this\n\n\t\t\t\t// Events are hints — coalesce refetch bursts per subscription\n\t\t\t\t// (see \"Event coalescing\" in the module docblock). A per-call\n\t\t\t\t// `subscribeOpts.debounce` overrides the plugin default; 0 disables.\n\t\t\t\tconst debounceMs = subscribeOpts.debounce === undefined ? pluginRefetchDebounce : subscribeOpts.debounce\n\t\t\t\tconst dispatch = isObject\n\t\t\t\t\t? () => {\n\t\t\t\t\t\t// Dispatch fetchObject with dedup\n\t\t\t\t\t\t\tstore.fetchObject(type, id)\n\t\t\t\t\t\t}\n\t\t\t\t\t: () => {\n\t\t\t\t\t\t// Dispatch fetchCollection with last stashed params + dedup\n\t\t\t\t\t\t\tconst lastParams = store.__lastCollectionParams?.get(type) || {}\n\t\t\t\t\t\t\tstore.fetchCollection(type, lastParams)\n\t\t\t\t\t\t}\n\t\t\t\tconst coalesced = createHintCoalescer(dispatch, debounceMs)\n\n\t\t\t\tconst callback = () => {\n\t\t\t\t\tstore.liveLastEventAt = new Date()\n\t\t\t\t\tcoalesced.run()\n\t\t\t\t}\n\n\t\t\t\tconst transportOpts = {\n\t\t\t\t\tisObject,\n\t\t\t\t\tinterval: subscribeOpts.interval || (isObject ? pluginPollObject : pluginPollCollection),\n\t\t\t\t}\n\n\t\t\t\tconst transportHandle = liveUpdates.subscribe(eventKey, callback, transportOpts)\n\n\t\t\t\t// Update reactive subscriptions count (Vue 2 spread)\n\t\t\t\tthis.liveSubscriptions = this.liveSubscriptions + 1\n\n\t\t\t\t// Update liveStatus from transport now that we've subscribed\n\t\t\t\tconst transportStatus = liveUpdates.getStatus()\n\t\t\t\tif (transportStatus !== 'offline') {\n\t\t\t\t\tthis.liveStatus = transportStatus\n\t\t\t\t}\n\n\t\t\t\tconst handle = {\n\t\t\t\t\t_livePlugin: true,\n\t\t\t\t\ttype,\n\t\t\t\t\tid,\n\t\t\t\t\teventKey,\n\t\t\t\t\ttransportHandle,\n\t\t\t\t\t// Cancels a pending coalesced (trailing) refetch — called by\n\t\t\t\t\t// unsubscribe() so a torn-down subscription never fires late.\n\t\t\t\t\t_cancelPendingRefetch: coalesced.cancel,\n\t\t\t\t}\n\n\t\t\t\t// Auto-cleanup via tryOnScopeDispose (Vue 2.7 composition API scopes)\n\t\t\t\tif (_tryOnScopeDispose) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t_tryOnScopeDispose(() => {\n\t\t\t\t\t\t\tthis.unsubscribe(handle)\n\t\t\t\t\t\t})\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Not in a scope — manual unsubscribe required in beforeDestroy\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn handle\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unsubscribe from live updates. Idempotent — releasing the same\n\t\t\t * handle twice (e.g. the plugin's own scope-dispose cleanup racing\n\t\t\t * a composable's explicit detach) is a no-op the second time, so\n\t\t\t * `liveSubscriptions` never double-decrements.\n\t\t\t *\n\t\t\t * @param {object} handle Handle returned by subscribe()\n\t\t\t */\n\t\t\tunsubscribe(handle) {\n\t\t\t\tif (!handle || !handle._livePlugin || handle._released) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thandle._released = true\n\n\t\t\t\t// Cancel any pending coalesced refetch before tearing down the\n\t\t\t\t// transport subscription — a late trailing dispatch after\n\t\t\t\t// unsubscribe would refetch for a scope nobody renders anymore.\n\t\t\t\tif (typeof handle._cancelPendingRefetch === 'function') {\n\t\t\t\t\thandle._cancelPendingRefetch()\n\t\t\t\t}\n\n\t\t\t\tconst liveUpdates = getLiveUpdates()\n\t\t\t\tliveUpdates.unsubscribe(handle.transportHandle)\n\n\t\t\t\tthis.liveSubscriptions = Math.max(0, this.liveSubscriptions - 1)\n\t\t\t},\n\t\t},\n\n\t\t/**\n\t\t * Plugin setup hook — called once per store instance.\n\t\t * Sets up:\n\t\t * 1. `$onAction` observer for fetchCollection → stash last params\n\t\t * 2. In-flight dedup Maps for fetchObject and fetchCollection\n\t\t *\n\t\t * @param {object} store The Pinia store instance\n\t\t */\n\t\tsetup(store) {\n\t\t\t// Dedup activation flag — flipped to true by the first subscribe()\n\t\t\t// call. Until then the fetch wrappers below pass straight through,\n\t\t\t// so a default-installed plugin causes zero behaviour change on\n\t\t\t// stores that never subscribe (live-updates-default-on).\n\t\t\tstore.__liveDedupActive = false\n\n\t\t\t// -- Last-params stash for collection re-fetch on events --\n\t\t\t// Plain (non-reactive) Map: type → last params object\n\t\t\tstore.__lastCollectionParams = new Map()\n\n\t\t\tstore.$onAction(({ name, args, after }) => {\n\t\t\t\tif (name === 'fetchCollection') {\n\t\t\t\t\tconst [type, params = {}] = args\n\t\t\t\t\tafter(() => {\n\t\t\t\t\t\tstore.__lastCollectionParams.set(type, params)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// -- In-flight dedup for fetchObject --\n\t\t\t// Plain Map: dedupKey → Promise\n\t\t\tconst objectInFlight = new Map()\n\n\t\t\tconst originalFetchObject = store.fetchObject.bind(store)\n\t\t\tstore.fetchObject = async function dedupedFetchObject(type, id) {\n\t\t\t\tif (!store.__liveDedupActive) {\n\t\t\t\t\treturn originalFetchObject(type, id)\n\t\t\t\t}\n\t\t\t\tconst key = objectDedupKey(type, id)\n\t\t\t\tif (objectInFlight.has(key)) {\n\t\t\t\t\treturn objectInFlight.get(key)\n\t\t\t\t}\n\t\t\t\tconst promise = originalFetchObject(type, id).finally(() => {\n\t\t\t\t\tobjectInFlight.delete(key)\n\t\t\t\t})\n\t\t\t\tobjectInFlight.set(key, promise)\n\t\t\t\treturn promise\n\t\t\t}\n\n\t\t\t// -- In-flight dedup for fetchCollection --\n\t\t\tconst collectionInFlight = new Map()\n\n\t\t\tconst originalFetchCollection = store.fetchCollection.bind(store)\n\t\t\tstore.fetchCollection = async function dedupedFetchCollection(type, params = {}, options = {}) {\n\t\t\t\tif (!store.__liveDedupActive) {\n\t\t\t\t\treturn originalFetchCollection(type, params, options)\n\t\t\t\t}\n\t\t\t\tconst key = collectionDedupKey(type, params)\n\t\t\t\tlet entry = collectionInFlight.get(key)\n\t\t\t\tif (!entry) {\n\t\t\t\t\t// The shared request reports into its own sink, copied to\n\t\t\t\t\t// each caller's `options.outcome` below, so a caller that\n\t\t\t\t\t// joined it learns that request's result too.\n\t\t\t\t\tconst outcome = {}\n\t\t\t\t\tconst promise = originalFetchCollection(type, params, { ...options, outcome }).finally(() => {\n\t\t\t\t\t\tcollectionInFlight.delete(key)\n\t\t\t\t\t})\n\t\t\t\t\tentry = { promise, outcome }\n\t\t\t\t\tcollectionInFlight.set(key, entry)\n\t\t\t\t}\n\t\t\t\tconst results = await entry.promise\n\t\t\t\tconst sink = options && options.outcome\n\t\t\t\tif (sink && typeof sink === 'object' && 'error' in entry.outcome) {\n\t\t\t\t\tsink.error = entry.outcome.error\n\t\t\t\t}\n\t\t\t\treturn results\n\t\t\t}\n\t\t},\n\t}\n}\n"],"names":["_tryOnScopeDispose"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE;AAC1C,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,cAAc,CAAC,IAAI,EAAE,EAAE,EAAE;AAClC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE;AACzC,CAAC,IAAI,KAAK,GAAG,KAAI;AACjB,CAAC,IAAI,OAAO,GAAG,MAAK;AACpB;AACA,CAAC,SAAS,GAAG,GAAG;AAChB,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE;AACrB,GAAG,EAAE,GAAE;AACP,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI,KAAK,EAAE;AACb,GAAG,OAAO,GAAG,KAAI;AACjB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,EAAE,GAAE;AACN,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM;AAC3B,GAAG,KAAK,GAAG,KAAI;AACf,GAAG,IAAI,OAAO,EAAE;AAChB,IAAI,OAAO,GAAG,MAAK;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"}
|
|
@@ -656,9 +656,12 @@ const baseActions = {
|
|
|
656
656
|
*
|
|
657
657
|
* @param {string} type The registered type slug
|
|
658
658
|
* @param {object} [params] Query parameters (_limit, _page, _search, _order, filters)
|
|
659
|
+
* @param {object} [options] Call options
|
|
660
|
+
* @param {{error?: object|null}} [options.outcome] Opt-in sink for THIS call's own result: `error` is set to the error it records on `errors[type]`, or `null` on success. Unlike `errors[type]`, which every call for the type shares, a later call cannot overwrite it.
|
|
659
661
|
* @return {Promise<Array>} The fetched collection (also stored in state)
|
|
660
662
|
*/
|
|
661
|
-
async fetchCollection(type, params = {}) {
|
|
663
|
+
async fetchCollection(type, params = {}, options = {}) {
|
|
664
|
+
const outcome = (options && typeof options.outcome === 'object' && options.outcome) || null;
|
|
662
665
|
this.loading = { ...this.loading, [type]: true };
|
|
663
666
|
this.errors = { ...this.errors, [type]: null };
|
|
664
667
|
|
|
@@ -685,9 +688,13 @@ const baseActions = {
|
|
|
685
688
|
);
|
|
686
689
|
|
|
687
690
|
if (!response.ok) {
|
|
688
|
-
|
|
691
|
+
const failure = await parseResponseError(response, type);
|
|
692
|
+
this.errors = { ...this.errors, [type]: failure };
|
|
693
|
+
if (outcome) {
|
|
694
|
+
outcome.error = failure;
|
|
695
|
+
}
|
|
689
696
|
// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from
|
|
690
|
-
console.error(`Error fetching ${type} collection:`,
|
|
697
|
+
console.error(`Error fetching ${type} collection:`, failure);
|
|
691
698
|
return []
|
|
692
699
|
}
|
|
693
700
|
|
|
@@ -711,14 +718,18 @@ const baseActions = {
|
|
|
711
718
|
this.facets = { ...this.facets, [type]: transformed };
|
|
712
719
|
}
|
|
713
720
|
|
|
721
|
+
if (outcome) {
|
|
722
|
+
outcome.error = null;
|
|
723
|
+
}
|
|
714
724
|
return results
|
|
715
725
|
} catch (error) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
726
|
+
const failure = error.name === 'TypeError'
|
|
727
|
+
? networkError(error)
|
|
728
|
+
: genericError(error);
|
|
729
|
+
this.errors = { ...this.errors, [type]: failure };
|
|
730
|
+
if (outcome) {
|
|
731
|
+
outcome.error = failure;
|
|
732
|
+
}
|
|
722
733
|
// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from
|
|
723
734
|
console.error(`Error fetching ${type} collection:`, error);
|
|
724
735
|
return []
|