@antelopejs/dms 0.9.5 → 0.9.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.9.6
4
+
5
+ [compare changes](https://github.com/AntelopeJS/dms/compare/v0.9.5...v0.9.6)
6
+
7
+ ### 🚀 Enhancements
8
+
9
+ - **tenant-lifecycle:** Pause producer admission and evaluate every tenant access gate ([#366](https://github.com/AntelopeJS/dms/pull/366))
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - **frontend:** Read the onboarding status and permissions through the data layer ([#357](https://github.com/AntelopeJS/dms/pull/357))
14
+ - **deps:** Require the tenant admission barrier and access evaluation contract ([#367](https://github.com/AntelopeJS/dms/pull/367))
15
+
16
+ ### 🏡 Chore
17
+
18
+ - **release:** @antelopejs/interface-dms v0.7.4 ([9577327b](https://github.com/AntelopeJS/dms/commit/9577327b))
19
+
20
+ ### ❤️ Contributors
21
+
22
+ - Antony Rizzitelli <rizzitelli.antony@pm.me>
23
+ - Alessandro Aloisio ([@alessaloisio](http://github.com/alessaloisio))
24
+
3
25
  ## v0.9.5
4
26
 
5
27
  [compare changes](https://github.com/AntelopeJS/dms/compare/v0.9.3...v0.9.5)
@@ -1,4 +1,4 @@
1
- import type { TenantAccessGateInfo, TenantAccessResult } from "@antelopejs/interface-dms/tenant-access";
1
+ import type { TenantAccessDenial, TenantAccessGateInfo, TenantAccessResult } from "@antelopejs/interface-dms/tenant-access";
2
2
  export declare namespace internal {
3
3
  const RegisterTenantAccessGate: {
4
4
  register: (info: TenantAccessGateInfo) => void;
@@ -6,3 +6,4 @@ export declare namespace internal {
6
6
  };
7
7
  }
8
8
  export declare function CheckTenantAccess(userId: string | undefined, tenantId: string): Promise<TenantAccessResult>;
9
+ export declare function EvaluateTenantAccess(userId: string | undefined, tenantId: string): Promise<TenantAccessDenial[]>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.internal = void 0;
4
4
  exports.CheckTenantAccess = CheckTenantAccess;
5
+ exports.EvaluateTenantAccess = EvaluateTenantAccess;
5
6
  const gates = [];
6
7
  var internal;
7
8
  (function (internal) {
@@ -31,4 +32,14 @@ async function CheckTenantAccess(userId, tenantId) {
31
32
  }
32
33
  return { allowed: true };
33
34
  }
35
+ async function EvaluateTenantAccess(userId, tenantId) {
36
+ const denials = [];
37
+ const registeredGates = gates.slice();
38
+ for (const { id, gate } of registeredGates) {
39
+ const result = await gate(userId, tenantId);
40
+ if (!result.allowed)
41
+ denials.push({ gateId: id, code: result.code });
42
+ }
43
+ return denials;
44
+ }
34
45
  //# sourceMappingURL=tenant-access.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../../../src/implementations/dms/tenant-access.ts"],"names":[],"mappings":";;;AAyBA,8CASC;AA7BD,MAAM,KAAK,GAA2B,EAAE,CAAC;AAEzC,IAAiB,QAAQ,CAgBxB;AAhBD,WAAiB,QAAQ;IACV,iCAAwB,GAAG;QACtC,QAAQ,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC7C,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,UAAU,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,IAAI,KAAK,IAAI,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC,EAhBgB,QAAQ,wBAAR,QAAQ,QAgBxB;AAEM,KAAK,UAAU,iBAAiB,CACrC,MAA0B,EAC1B,QAAgB;IAEhB,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../../../src/implementations/dms/tenant-access.ts"],"names":[],"mappings":";;;AA0BA,8CASC;AAED,oDAYC;AA3CD,MAAM,KAAK,GAA2B,EAAE,CAAC;AAEzC,IAAiB,QAAQ,CAgBxB;AAhBD,WAAiB,QAAQ;IACV,iCAAwB,GAAG;QACtC,QAAQ,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC7C,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,UAAU,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,IAAI,KAAK,IAAI,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC,EAhBgB,QAAQ,wBAAR,QAAQ,QAgBxB;AAEM,KAAK,UAAU,iBAAiB,CACrC,MAA0B,EAC1B,QAAgB;IAEhB,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,QAAgB;IAEhB,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IACtC,KAAK,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -11,6 +11,7 @@ const db_1 = require("@antelopejs/interface-dms/db");
11
11
  const edit_1 = require("@antelopejs/interface-dms/invite-extensions/internal/edit");
12
12
  const request_tenant_1 = require("@antelopejs/interface-dms/request-tenant");
13
13
  const base_1 = require("@antelopejs/interface-dms/base");
14
+ const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
14
15
  const HTTP_NOT_FOUND = 404;
15
16
  const HTTP_CONFLICT = 409;
16
17
  async function loadPendingInvite(tenantId, inviteId) {
@@ -32,6 +33,9 @@ exports.inviteGetRoute = {
32
33
  },
33
34
  };
34
35
  async function editPendingInvite(tenantId, inviteId, body, writeRow) {
36
+ return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, () => editAdmittedPendingInvite(tenantId, inviteId, body, writeRow));
37
+ }
38
+ async function editAdmittedPendingInvite(tenantId, inviteId, body, writeRow) {
35
39
  const edits = (0, edit_1.CollectInviteExtensionEdits)(body);
36
40
  const invite = await loadPendingInvite(tenantId, inviteId);
37
41
  await writeRow();
@@ -49,8 +53,14 @@ async function editPendingInvite(tenantId, inviteId, body, writeRow) {
49
53
  const editInviteWithExtensions = {
50
54
  ...interface_data_api_1.DefaultRoutes.Edit,
51
55
  func: function (ctx, params, body) {
52
- return editPendingInvite((0, request_tenant_1.getRequestTenantId)(ctx), String(params.id), components_1.Validation.ParseBody(body), () => interface_data_api_1.DefaultRoutes.Edit.func.call(this, ctx, params, body));
56
+ return editAdmittedPendingInvite((0, request_tenant_1.getRequestTenantId)(ctx), String(params.id), components_1.Validation.ParseBody(body), () => interface_data_api_1.DefaultRoutes.Edit.func.call(this, ctx, params, body));
57
+ },
58
+ };
59
+ const guardedInviteEditRoute = base_1.TableViewRoutes.EditWith(editInviteWithExtensions);
60
+ exports.inviteEditRoute = {
61
+ ...guardedInviteEditRoute,
62
+ func(...args) {
63
+ return (0, tenant_lifecycle_1.runTenantLifecycleOperation)((0, request_tenant_1.getRequestTenantId)(args[0]), () => guardedInviteEditRoute.func.apply(this, args));
53
64
  },
54
65
  };
55
- exports.inviteEditRoute = base_1.TableViewRoutes.EditWith(editInviteWithExtensions);
56
66
  //# sourceMappingURL=invite-extension-routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"invite-extension-routes.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/invite-extension-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invite-extension-routes.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/invite-extension-routes.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,8 @@ const interface_database_decorators_1 = require("@antelopejs/interface-database-
12
12
  const db_1 = require("@antelopejs/interface-dms/db");
13
13
  const tenant_ownership_1 = require("@antelopejs/interface-dms/tenant-ownership");
14
14
  const db_2 = require("@antelopejs/interface-dms/auth/db");
15
+ const hooks_1 = require("@antelopejs/interface-dms/hooks");
16
+ const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
15
17
  const HTTP_NOT_FOUND = 404;
16
18
  const HTTP_CONFLICT = 409;
17
19
  const MEMBER_NOT_FOUND = "$page.settings.members.error.user_not_found";
@@ -28,6 +30,12 @@ async function assertNotLastTenantOwner(tenantId, current) {
28
30
  (0, interface_api_util_1.assert)(remaining > 0, HTTP_CONFLICT, "$page.settings.members.error.last_owner");
29
31
  }
30
32
  async function prepareOwnerChange(tenantId, current, requested) {
33
+ await (0, hooks_1.ExecuteHooks)(hooks_1.Hook.MEMBER_BEING_CHANGED, {
34
+ tenantId,
35
+ userId: current.userId,
36
+ isTenantOwner: requested ?? current.isTenantOwner === true,
37
+ previousIsTenantOwner: current.isTenantOwner === true,
38
+ });
31
39
  const change = detectOwnerChange(current, requested);
32
40
  if (change === null)
33
41
  return;
@@ -41,12 +49,14 @@ async function requireMember(tenantId, memberId) {
41
49
  return member;
42
50
  }
43
51
  async function setMemberOwnership(tenantId, memberId, isTenantOwner) {
44
- const member = await requireMember(tenantId, memberId);
45
- const change = detectOwnerChange({ isTenantOwner: member.isTenantOwner === true }, isTenantOwner);
46
- await prepareOwnerChange(tenantId, member, isTenantOwner);
47
- member.isTenantOwner = isTenantOwner;
48
- await (0, interface_database_decorators_1.GetModel)(db_1.TenantMemberModel, tenantId).update(member);
49
- return { member, change };
52
+ return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, async () => {
53
+ const member = await requireMember(tenantId, memberId);
54
+ const change = detectOwnerChange({ isTenantOwner: member.isTenantOwner === true }, isTenantOwner);
55
+ await prepareOwnerChange(tenantId, member, isTenantOwner);
56
+ member.isTenantOwner = isTenantOwner;
57
+ await (0, interface_database_decorators_1.GetModel)(db_1.TenantMemberModel, tenantId).update(member);
58
+ return { member, change };
59
+ });
50
60
  }
51
61
  async function roleNames(tenantId, roleIds) {
52
62
  if (roleIds.length === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"member-management.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/member-management.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"member-management.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/member-management.ts"],"names":[],"mappings":"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"}
@@ -1,98 +1,102 @@
1
+ import { computed, effectScope } from "vue";
2
+
1
3
  const PERMISSIONS_REFRESH_MS = 300_000;
2
4
  const PERMISSIONS_ENDPOINT = "/dms/permissions";
3
5
  const WILDCARD_PERMISSION = "*";
4
6
 
5
- interface PermissionsState {
6
- permissions: Ref<Set<string>>;
7
- isLoaded: Ref<boolean>;
8
- lastFetchTime: Ref<number>;
9
- fetchGeneration: Ref<number>;
10
- }
11
-
12
- // The set a server render loads (the `auth` middleware preloads it before the
13
- // page renders) is the one the page renders with: the browser starts from it
14
- // rather than asking again, and its age keeps counting from the server's load.
15
- // The generation only guards the runs in flight of one side.
16
- const TRANSFERRED = { transfer: true };
7
+ /**
8
+ * The async data key of the permission set, in the account and workspace's
9
+ * scope like every key: a switch of either drops the set. The set a server
10
+ * render loads (the `auth` middleware's, preloaded as the app starts) is the
11
+ * one the page renders with, and the page hands it to the browser, which
12
+ * starts from it rather than asking again.
13
+ */
14
+ const PERMISSIONS_KEY = "dms-permissions";
17
15
 
18
- function usePermissionsState(): PermissionsState {
19
- return {
20
- permissions: useDmsState<Set<string>>(
21
- "dms-user-permissions",
22
- () => new Set(),
23
- TRANSFERRED,
24
- ),
25
- isLoaded: useDmsState<boolean>(
26
- "dms-permissions-loaded",
27
- () => false,
28
- TRANSFERRED,
29
- ),
30
- lastFetchTime: useDmsState<number>(
31
- "dms-permissions-lastFetch",
32
- () => 0,
33
- TRANSFERRED,
34
- ),
35
- fetchGeneration: useDmsState<number>("dms-permissions-gen", () => 0),
36
- };
16
+ /** The permission set, and when it was read: its age counts from there. */
17
+ interface LoadedPermissions {
18
+ ids: string[];
19
+ loadedAt: number;
37
20
  }
38
21
 
39
- function createFetchPermissions(state: PermissionsState) {
40
- const runFetch = async () => {
41
- const generation = state.fetchGeneration.value;
42
- const { $authFetch } = useAuthFetch();
43
- const result = await $authFetch<string[]>(PERMISSIONS_ENDPOINT);
44
- if (state.fetchGeneration.value !== generation) return;
45
- state.permissions.value = new Set(result);
46
- state.isLoaded.value = true;
47
- state.lastFetchTime.value = Date.now();
48
- };
49
-
50
- // A refetch asked while one is in flight gets its own pass instead of the
51
- // older response, so an invalidation is never swallowed. Scoped to the DMS
52
- // app, so concurrent server-rendered requests never share a pass.
53
- const deduper = useTrailingDeduper("permissions");
54
- const fetchPermissions = (): Promise<void> => deduper.run(runFetch);
55
- // A caller that only needs a set has nothing to invalidate: it joins the
56
- // load in flight instead of queueing a second one.
57
- const loadPermissions = async (): Promise<void> => {
58
- if (state.isLoaded.value) return;
59
- await deduper.join(runFetch);
60
- // The run it joined was started before a `clear()` (another account):
61
- // its answer was dropped, and this caller still needs one.
62
- if (!state.isLoaded.value) await deduper.join(runFetch);
63
- };
64
- return { fetchPermissions, loadPermissions };
22
+ /**
23
+ * The set's async data. Only the calls below start a read: a component that
24
+ * shows the set reads it through `useDmsData`, which never reads it again on
25
+ * a mount. Out of the page's refreshes: it has triggers of its own.
26
+ */
27
+ function permissionsData(immediate = true) {
28
+ return useApiData<LoadedPermissions>(
29
+ PERMISSIONS_KEY,
30
+ async ({ $authFetch, signal }) => ({
31
+ ids: await $authFetch<string[]>(PERMISSIONS_ENDPOINT, { signal }),
32
+ loadedAt: Date.now(),
33
+ }),
34
+ { immediate, refreshOnPageRefresh: false },
35
+ );
65
36
  }
66
37
 
67
38
  export function usePermissions() {
68
- const state = usePermissionsState();
69
- const { fetchPermissions, loadPermissions } = createFetchPermissions(state);
39
+ const loaded = useDmsData<LoadedPermissions>(PERMISSIONS_KEY).data;
40
+ const permissions = computed(() => new Set(loaded.value?.ids ?? []));
41
+ const isLoaded = computed(() => loaded.value != null);
70
42
 
71
- async function refreshIfStale() {
72
- const elapsed = Date.now() - state.lastFetchTime.value;
73
- if (elapsed < PERMISSIONS_REFRESH_MS) {
74
- return;
43
+ // A caller that only needs a set joins the load in flight instead of
44
+ // starting another: one request per page however many ask.
45
+ async function loadPermissions(): Promise<void> {
46
+ // The load it joined may be cleared meanwhile (another account): its
47
+ // answer is dropped, and this caller still needs one.
48
+ for (let attempt = 0; attempt < 2 && !loaded.value; attempt++) {
49
+ const result = await permissionsData();
50
+ if (!result.data.value && result.error.value) throw result.error.value;
75
51
  }
52
+ }
53
+
54
+ // A refresh (the set changed on the backend) asked while a read is in
55
+ // flight replaces it rather than taking its older answer, so an
56
+ // invalidation is never swallowed. A failed one keeps the last set, and
57
+ // rejects so the caller knows it is stale.
58
+ async function fetchPermissions(): Promise<void> {
59
+ if (loaded.value) await refreshLoadedSet();
60
+ else await refreshDmsData(PERMISSIONS_KEY);
61
+ await loadPermissions();
62
+ }
63
+
64
+ // `refreshDmsData` does not say whether the read failed: the set's own
65
+ // async data does. Its binding lives in a scope stopped once the read
66
+ // settled, so refreshes do not pile bindings up; the load that brought
67
+ // the set keeps the key bound, so stopping it never aborts nor evicts.
68
+ async function refreshLoadedSet(): Promise<void> {
69
+ const scope = effectScope(true);
70
+ const data = scope.run(() => permissionsData(false))!;
71
+ try {
72
+ await data.refresh();
73
+ } finally {
74
+ scope.stop();
75
+ }
76
+ // A later refresh that replaced this one reports its own failure.
77
+ if (data.status.value === "error") throw data.error.value;
78
+ }
79
+
80
+ async function refreshIfStale() {
81
+ const set = loaded.value;
82
+ if (set && Date.now() - set.loadedAt < PERMISSIONS_REFRESH_MS) return;
76
83
  await fetchPermissions();
77
84
  }
78
85
 
79
86
  function hasPermission(permissionId: string): boolean {
80
- if (state.permissions.value.has(WILDCARD_PERMISSION)) {
81
- return true;
82
- }
83
- return state.permissions.value.has(permissionId);
87
+ const set = permissions.value;
88
+ return set.has(WILDCARD_PERMISSION) || set.has(permissionId);
84
89
  }
85
90
 
91
+ // Empties the set and drops the answer of a read in flight, so a logout or
92
+ // an account switch is never overwritten by the previous account's set.
86
93
  function clear() {
87
- state.fetchGeneration.value++;
88
- state.permissions.value = new Set();
89
- state.isLoaded.value = false;
90
- state.lastFetchTime.value = 0;
94
+ permissionsData(false).clear();
91
95
  }
92
96
 
93
97
  return {
94
- permissions: state.permissions,
95
- isLoaded: state.isLoaded,
98
+ permissions,
99
+ isLoaded,
96
100
  fetchPermissions,
97
101
  loadPermissions,
98
102
  refreshIfStale,
@@ -7,37 +7,41 @@ interface OnboardingInfoResponse {
7
7
  }
8
8
 
9
9
  /**
10
- * Transferred: the global middleware reads it while a server renders the
11
- * page, and the browser's run of the middleware then finds it instead of
12
- * asking again. It holds nothing private.
10
+ * The async data key of the onboarding status. The global middleware reads
11
+ * it while a server renders a page, and the page hands it to the browser,
12
+ * whose run of the middleware then finds it instead of asking again. It
13
+ * holds nothing private.
13
14
  */
14
- function useOnboardingCache() {
15
- return useDmsState<OnboardingInfo | null>("dms-onboarding-info", () => null, {
16
- transfer: true,
17
- });
18
- }
15
+ const ONBOARDING_KEY = "dms-onboarding";
19
16
 
20
17
  export const useOnboarding = async (): Promise<OnboardingInfo> => {
21
- const cache = useOnboardingCache();
22
-
23
- const known = cache.value;
18
+ const known = useDmsData<OnboardingInfo>(ONBOARDING_KEY).data.value;
24
19
  if (known) return known;
25
20
 
26
- const { $authFetch } = useAuthFetch();
27
-
28
21
  // One request per app however many callers ask before it answers: the
29
- // preload started with the app and the `onboarding` middleware share it.
30
- await useTrailingDeduper("onboarding").join(async () => {
31
- const response = await $authFetch<OnboardingInfoResponse>(
32
- "/api/onboarding/informations",
33
- { method: "GET" },
34
- );
35
- cache.value = { hasOnboarded: response.has_onboarded };
36
- });
37
- return cache.value as OnboardingInfo;
22
+ // preload started with the app and the `onboarding` middleware share the
23
+ // key. Its own triggers only: the status changes once, when the first
24
+ // administrator registers (`setOnboardingComplete`).
25
+ const status = await useApiData<OnboardingInfo>(
26
+ ONBOARDING_KEY,
27
+ async ({ $authFetch, signal }) => {
28
+ const response = await $authFetch<OnboardingInfoResponse>(
29
+ "/api/onboarding/informations",
30
+ { method: "GET", signal },
31
+ );
32
+ return { hasOnboarded: response.has_onboarded };
33
+ },
34
+ { refreshOnPageRefresh: false },
35
+ );
36
+ // A failed read leaves the status unknown: the middleware lets the
37
+ // navigation through, and the next one asks again.
38
+ if (!status.data.value) throw status.error.value;
39
+ return status.data.value;
38
40
  };
39
41
 
40
42
  /** Release the cached onboarding guard after the backend creates the admin. */
41
43
  export const setOnboardingComplete = () => {
42
- useOnboardingCache().value = { hasOnboarded: true };
44
+ useDmsData<OnboardingInfo>(ONBOARDING_KEY).data.value = {
45
+ hasOnboarded: true,
46
+ };
43
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "The AntelopeJS dashboard management system: admin dashboards declared in TypeScript, with auth and tenancy, data management, file uploads, notifications and realtime.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -60,7 +60,7 @@
60
60
  "@antelopejs/interface-data-api": ">=0.2.2 <1.0.0",
61
61
  "@antelopejs/interface-database": ">=0.1.10 <1.0.0",
62
62
  "@antelopejs/interface-database-decorators": ">=0.1.9 <1.0.0",
63
- "@antelopejs/interface-dms": ">=0.7.3 <0.8.0",
63
+ "@antelopejs/interface-dms": ">=0.7.4 <0.8.0",
64
64
  "@antelopejs/interface-email": ">=0.0.6 <1.0.0",
65
65
  "@antelopejs/interface-file-storage": ">=0.1.4 <1.0.0",
66
66
  "archiver": "^7.0.1",