@flowselections/core 1.0.16 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/auth/handover.functions.d.ts +16 -0
- package/dist/auth/handover.functions.d.ts.map +1 -0
- package/dist/auth/handover.functions.js +23 -0
- package/dist/components/layout/HandoverPage.d.ts +8 -0
- package/dist/components/layout/HandoverPage.d.ts.map +1 -0
- package/dist/components/layout/HandoverPage.js +70 -0
- package/dist/components/layout/LoginPage.d.ts +2 -1
- package/dist/components/layout/LoginPage.d.ts.map +1 -1
- package/dist/components/layout/LoginPage.js +23 -4
- package/dist/components/layout/Sidebar.d.ts +1 -1
- package/dist/components/layout/Sidebar.d.ts.map +1 -1
- package/dist/components/layout/Sidebar.js +4 -1
- package/dist/extensions/ModuleGuardSlot.d.ts +10 -0
- package/dist/extensions/ModuleGuardSlot.d.ts.map +1 -0
- package/dist/extensions/ModuleGuardSlot.js +13 -0
- package/dist/extensions/ShellExtensions.d.ts +31 -0
- package/dist/extensions/ShellExtensions.d.ts.map +1 -0
- package/dist/extensions/ShellExtensions.js +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/permissions/ModuleAccessGuard.d.ts +19 -0
- package/dist/permissions/ModuleAccessGuard.d.ts.map +1 -0
- package/dist/permissions/ModuleAccessGuard.js +20 -0
- package/dist/permissions/useModulePermission.d.ts +217 -0
- package/dist/permissions/useModulePermission.d.ts.map +1 -0
- package/dist/permissions/useModulePermission.js +137 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/permissions.server.d.ts +16 -0
- package/dist/server/permissions.server.d.ts.map +1 -0
- package/dist/server/permissions.server.js +41 -0
- package/dist/server/provider-token.d.ts +17 -0
- package/dist/server/provider-token.d.ts.map +1 -1
- package/dist/server/provider-token.js +53 -6
- package/dist/types/module.d.ts +23 -0
- package/dist/types/module.d.ts.map +1 -1
- package/dist/types/module.js +24 -1
- package/package.json +6 -3
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { ModuleListTransform } from "../extensions/ShellExtensions.js";
|
|
2
|
+
export type PermissionLevel = "none" | "view" | "edit";
|
|
3
|
+
export interface ModulePermissionRow {
|
|
4
|
+
moduleId: string;
|
|
5
|
+
featureId: string | null;
|
|
6
|
+
permission: PermissionLevel;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Leest de rechten van de ingelogde gebruiker via RPC `my_module_permissions()`.
|
|
10
|
+
*
|
|
11
|
+
* Ontbreekt de RPC (add-on niet geïnstalleerd) of faalt de call, dan is het
|
|
12
|
+
* resultaat een LEGE lijst zonder zichtbare fout en zonder retry — en dat
|
|
13
|
+
* betekent bij alle consumers default-allow.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useMyModulePermissions(): {
|
|
16
|
+
permissions: ModulePermissionRow[];
|
|
17
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
18
|
+
isUnknown: boolean;
|
|
19
|
+
data: ModulePermissionRow[];
|
|
20
|
+
error: Error;
|
|
21
|
+
isError: true;
|
|
22
|
+
isPending: false;
|
|
23
|
+
isLoading: false;
|
|
24
|
+
isLoadingError: false;
|
|
25
|
+
isRefetchError: true;
|
|
26
|
+
isSuccess: false;
|
|
27
|
+
isPlaceholderData: false;
|
|
28
|
+
status: "error";
|
|
29
|
+
dataUpdatedAt: number;
|
|
30
|
+
errorUpdatedAt: number;
|
|
31
|
+
failureCount: number;
|
|
32
|
+
failureReason: Error | null;
|
|
33
|
+
errorUpdateCount: number;
|
|
34
|
+
isFetched: boolean;
|
|
35
|
+
isFetchedAfterMount: boolean;
|
|
36
|
+
isFetching: boolean;
|
|
37
|
+
isInitialLoading: boolean;
|
|
38
|
+
isPaused: boolean;
|
|
39
|
+
isRefetching: boolean;
|
|
40
|
+
isStale: boolean;
|
|
41
|
+
isEnabled: boolean;
|
|
42
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
43
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
44
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
45
|
+
} | {
|
|
46
|
+
permissions: ModulePermissionRow[];
|
|
47
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
48
|
+
isUnknown: boolean;
|
|
49
|
+
data: ModulePermissionRow[];
|
|
50
|
+
error: null;
|
|
51
|
+
isError: false;
|
|
52
|
+
isPending: false;
|
|
53
|
+
isLoading: false;
|
|
54
|
+
isLoadingError: false;
|
|
55
|
+
isRefetchError: false;
|
|
56
|
+
isSuccess: true;
|
|
57
|
+
isPlaceholderData: false;
|
|
58
|
+
status: "success";
|
|
59
|
+
dataUpdatedAt: number;
|
|
60
|
+
errorUpdatedAt: number;
|
|
61
|
+
failureCount: number;
|
|
62
|
+
failureReason: Error | null;
|
|
63
|
+
errorUpdateCount: number;
|
|
64
|
+
isFetched: boolean;
|
|
65
|
+
isFetchedAfterMount: boolean;
|
|
66
|
+
isFetching: boolean;
|
|
67
|
+
isInitialLoading: boolean;
|
|
68
|
+
isPaused: boolean;
|
|
69
|
+
isRefetching: boolean;
|
|
70
|
+
isStale: boolean;
|
|
71
|
+
isEnabled: boolean;
|
|
72
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
73
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
74
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
75
|
+
} | {
|
|
76
|
+
permissions: ModulePermissionRow[];
|
|
77
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
78
|
+
isUnknown: boolean;
|
|
79
|
+
data: undefined;
|
|
80
|
+
error: Error;
|
|
81
|
+
isError: true;
|
|
82
|
+
isPending: false;
|
|
83
|
+
isLoading: false;
|
|
84
|
+
isLoadingError: true;
|
|
85
|
+
isRefetchError: false;
|
|
86
|
+
isSuccess: false;
|
|
87
|
+
isPlaceholderData: false;
|
|
88
|
+
status: "error";
|
|
89
|
+
dataUpdatedAt: number;
|
|
90
|
+
errorUpdatedAt: number;
|
|
91
|
+
failureCount: number;
|
|
92
|
+
failureReason: Error | null;
|
|
93
|
+
errorUpdateCount: number;
|
|
94
|
+
isFetched: boolean;
|
|
95
|
+
isFetchedAfterMount: boolean;
|
|
96
|
+
isFetching: boolean;
|
|
97
|
+
isInitialLoading: boolean;
|
|
98
|
+
isPaused: boolean;
|
|
99
|
+
isRefetching: boolean;
|
|
100
|
+
isStale: boolean;
|
|
101
|
+
isEnabled: boolean;
|
|
102
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
103
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
104
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
105
|
+
} | {
|
|
106
|
+
permissions: ModulePermissionRow[];
|
|
107
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
108
|
+
isUnknown: boolean;
|
|
109
|
+
data: undefined;
|
|
110
|
+
error: null;
|
|
111
|
+
isError: false;
|
|
112
|
+
isPending: true;
|
|
113
|
+
isLoading: true;
|
|
114
|
+
isLoadingError: false;
|
|
115
|
+
isRefetchError: false;
|
|
116
|
+
isSuccess: false;
|
|
117
|
+
isPlaceholderData: false;
|
|
118
|
+
status: "pending";
|
|
119
|
+
dataUpdatedAt: number;
|
|
120
|
+
errorUpdatedAt: number;
|
|
121
|
+
failureCount: number;
|
|
122
|
+
failureReason: Error | null;
|
|
123
|
+
errorUpdateCount: number;
|
|
124
|
+
isFetched: boolean;
|
|
125
|
+
isFetchedAfterMount: boolean;
|
|
126
|
+
isFetching: boolean;
|
|
127
|
+
isInitialLoading: boolean;
|
|
128
|
+
isPaused: boolean;
|
|
129
|
+
isRefetching: boolean;
|
|
130
|
+
isStale: boolean;
|
|
131
|
+
isEnabled: boolean;
|
|
132
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
133
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
134
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
135
|
+
} | {
|
|
136
|
+
permissions: ModulePermissionRow[];
|
|
137
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
138
|
+
isUnknown: boolean;
|
|
139
|
+
data: undefined;
|
|
140
|
+
error: null;
|
|
141
|
+
isError: false;
|
|
142
|
+
isPending: true;
|
|
143
|
+
isLoadingError: false;
|
|
144
|
+
isRefetchError: false;
|
|
145
|
+
isSuccess: false;
|
|
146
|
+
isPlaceholderData: false;
|
|
147
|
+
status: "pending";
|
|
148
|
+
dataUpdatedAt: number;
|
|
149
|
+
errorUpdatedAt: number;
|
|
150
|
+
failureCount: number;
|
|
151
|
+
failureReason: Error | null;
|
|
152
|
+
errorUpdateCount: number;
|
|
153
|
+
isFetched: boolean;
|
|
154
|
+
isFetchedAfterMount: boolean;
|
|
155
|
+
isFetching: boolean;
|
|
156
|
+
isLoading: boolean;
|
|
157
|
+
isInitialLoading: boolean;
|
|
158
|
+
isPaused: boolean;
|
|
159
|
+
isRefetching: boolean;
|
|
160
|
+
isStale: boolean;
|
|
161
|
+
isEnabled: boolean;
|
|
162
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
163
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
164
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
165
|
+
} | {
|
|
166
|
+
permissions: ModulePermissionRow[];
|
|
167
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
168
|
+
isUnknown: boolean;
|
|
169
|
+
data: ModulePermissionRow[];
|
|
170
|
+
isError: false;
|
|
171
|
+
error: null;
|
|
172
|
+
isPending: false;
|
|
173
|
+
isLoading: false;
|
|
174
|
+
isLoadingError: false;
|
|
175
|
+
isRefetchError: false;
|
|
176
|
+
isSuccess: true;
|
|
177
|
+
isPlaceholderData: true;
|
|
178
|
+
status: "success";
|
|
179
|
+
dataUpdatedAt: number;
|
|
180
|
+
errorUpdatedAt: number;
|
|
181
|
+
failureCount: number;
|
|
182
|
+
failureReason: Error | null;
|
|
183
|
+
errorUpdateCount: number;
|
|
184
|
+
isFetched: boolean;
|
|
185
|
+
isFetchedAfterMount: boolean;
|
|
186
|
+
isFetching: boolean;
|
|
187
|
+
isInitialLoading: boolean;
|
|
188
|
+
isPaused: boolean;
|
|
189
|
+
isRefetching: boolean;
|
|
190
|
+
isStale: boolean;
|
|
191
|
+
isEnabled: boolean;
|
|
192
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ModulePermissionRow[], Error>>;
|
|
193
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
194
|
+
promise: Promise<ModulePermissionRow[]>;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Rechtenniveau voor één module (optioneel één feature). Tijdens laden en bij
|
|
198
|
+
* elke fout is het antwoord `edit` (fail-open).
|
|
199
|
+
*/
|
|
200
|
+
export declare function useModulePermission(moduleId: string, featureId?: string | null): {
|
|
201
|
+
level: PermissionLevel;
|
|
202
|
+
canView: boolean;
|
|
203
|
+
canEdit: boolean;
|
|
204
|
+
isLoading: boolean;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* ModuleListTransform die zijbalkitems met `none` weglaat.
|
|
208
|
+
*
|
|
209
|
+
* - Tijdens laden / bij fouten: lijst ongewijzigd.
|
|
210
|
+
* - Modules zonder rij in het rechtenschema: default-allow.
|
|
211
|
+
* - Groepen: de kinderen bepalen zichtbaarheid. Blijft er geen kind over, dan
|
|
212
|
+
* valt de groep weg. Is de parent-sleutel geweigerd maar een kind toegestaan,
|
|
213
|
+
* dan blijft de groep staan met alleen de toegestane kinderen — de parent-href
|
|
214
|
+
* van een groep is een uitklapknop, geen bestemming.
|
|
215
|
+
*/
|
|
216
|
+
export declare function useModulePermissionTransform(): ModuleListTransform;
|
|
217
|
+
//# sourceMappingURL=useModulePermission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/core/permissions/useModulePermission.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,eAAe,CAAC;CAC7B;AAuCD;;;;;;GAMG;AACH,wBAAgB,sBAAsB;;IA0BlC,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGvE;AAyBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;EAW9E;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,IAAI,mBAAmB,CAyBlE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { supabase } from "../supabase/client.js";
|
|
4
|
+
import { moduleKey, navKey } from "../types/module.js";
|
|
5
|
+
function normalizeLevel(value) {
|
|
6
|
+
const v = typeof value === "string" ? value.toLowerCase() : "";
|
|
7
|
+
if (v === "none" || v === "view" || v === "edit")
|
|
8
|
+
return v;
|
|
9
|
+
// Onbekende waarde → fail-open (zichtbaarheid is geen beveiliging).
|
|
10
|
+
return "edit";
|
|
11
|
+
}
|
|
12
|
+
function normalizeRows(data) {
|
|
13
|
+
if (!Array.isArray(data))
|
|
14
|
+
return [];
|
|
15
|
+
return data.map((raw) => {
|
|
16
|
+
const row = (raw ?? {});
|
|
17
|
+
const rawModule = (row["module_id"] ?? row["moduleId"] ?? "");
|
|
18
|
+
const rawFeature = (row["feature_id"] ?? row["featureId"] ?? null);
|
|
19
|
+
return {
|
|
20
|
+
moduleId: navKey(String(rawModule ?? "")),
|
|
21
|
+
featureId: rawFeature ? String(rawFeature) : null,
|
|
22
|
+
permission: normalizeLevel(row["permission"] ?? row["level"] ?? row["permission_level"]),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Waar of de fout betekent "de rechten-add-on staat niet op deze database". */
|
|
27
|
+
function isMissingRpc(error) {
|
|
28
|
+
const e = (error ?? {});
|
|
29
|
+
const code = e.code ?? "";
|
|
30
|
+
const message = (e.message ?? "").toLowerCase();
|
|
31
|
+
return (code === "42883" ||
|
|
32
|
+
code === "PGRST202" ||
|
|
33
|
+
code === "PGRST302" ||
|
|
34
|
+
e.status === 404 ||
|
|
35
|
+
message.includes("does not exist") ||
|
|
36
|
+
message.includes("could not find the function") ||
|
|
37
|
+
message.includes("schema cache"));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Leest de rechten van de ingelogde gebruiker via RPC `my_module_permissions()`.
|
|
41
|
+
*
|
|
42
|
+
* Ontbreekt de RPC (add-on niet geïnstalleerd) of faalt de call, dan is het
|
|
43
|
+
* resultaat een LEGE lijst zonder zichtbare fout en zonder retry — en dat
|
|
44
|
+
* betekent bij alle consumers default-allow.
|
|
45
|
+
*/
|
|
46
|
+
export function useMyModulePermissions() {
|
|
47
|
+
const query = useQuery({
|
|
48
|
+
queryKey: ["my-module-permissions"],
|
|
49
|
+
queryFn: async () => {
|
|
50
|
+
const { data, error } = await supabase.rpc("my_module_permissions");
|
|
51
|
+
if (error) {
|
|
52
|
+
if (isMissingRpc(error))
|
|
53
|
+
return [];
|
|
54
|
+
// Ook andere fouten mogen niet blokkeren: fail-open, geen retry.
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
return normalizeRows(data);
|
|
58
|
+
},
|
|
59
|
+
// GEEN retry: een ontbrekende RPC mag nooit een retry-storm veroorzaken.
|
|
60
|
+
retry: false,
|
|
61
|
+
refetchOnWindowFocus: false,
|
|
62
|
+
staleTime: 5 * 60 * 1000,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
...query,
|
|
66
|
+
permissions: query.data ?? [],
|
|
67
|
+
/** True zolang we niets weten; callers behandelen dit als allow. */
|
|
68
|
+
isUnknown: query.isLoading || query.data === undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function resolveLevel(rows, moduleId, featureId) {
|
|
72
|
+
const key = navKey(moduleId);
|
|
73
|
+
const forModule = rows.filter((r) => r.moduleId === key);
|
|
74
|
+
// Geen rij voor deze module → default-allow.
|
|
75
|
+
if (forModule.length === 0)
|
|
76
|
+
return "edit";
|
|
77
|
+
if (featureId) {
|
|
78
|
+
const feature = forModule.find((r) => r.featureId === featureId);
|
|
79
|
+
if (feature)
|
|
80
|
+
return feature.permission;
|
|
81
|
+
}
|
|
82
|
+
const moduleLevel = forModule.find((r) => !r.featureId);
|
|
83
|
+
if (moduleLevel)
|
|
84
|
+
return moduleLevel.permission;
|
|
85
|
+
// Alleen feature-rijen bekend: hoogste feature-recht bepaalt moduletoegang.
|
|
86
|
+
if (forModule.some((r) => r.permission === "edit"))
|
|
87
|
+
return "edit";
|
|
88
|
+
if (forModule.some((r) => r.permission === "view"))
|
|
89
|
+
return "view";
|
|
90
|
+
return "none";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Rechtenniveau voor één module (optioneel één feature). Tijdens laden en bij
|
|
94
|
+
* elke fout is het antwoord `edit` (fail-open).
|
|
95
|
+
*/
|
|
96
|
+
export function useModulePermission(moduleId, featureId) {
|
|
97
|
+
const { permissions, isUnknown } = useMyModulePermissions();
|
|
98
|
+
const level = isUnknown ? "edit" : resolveLevel(permissions, moduleId, featureId);
|
|
99
|
+
return {
|
|
100
|
+
level,
|
|
101
|
+
canView: level === "view" || level === "edit",
|
|
102
|
+
canEdit: level === "edit",
|
|
103
|
+
isLoading: isUnknown,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* ModuleListTransform die zijbalkitems met `none` weglaat.
|
|
108
|
+
*
|
|
109
|
+
* - Tijdens laden / bij fouten: lijst ongewijzigd.
|
|
110
|
+
* - Modules zonder rij in het rechtenschema: default-allow.
|
|
111
|
+
* - Groepen: de kinderen bepalen zichtbaarheid. Blijft er geen kind over, dan
|
|
112
|
+
* valt de groep weg. Is de parent-sleutel geweigerd maar een kind toegestaan,
|
|
113
|
+
* dan blijft de groep staan met alleen de toegestane kinderen — de parent-href
|
|
114
|
+
* van een groep is een uitklapknop, geen bestemming.
|
|
115
|
+
*/
|
|
116
|
+
export function useModulePermissionTransform() {
|
|
117
|
+
const { permissions, isUnknown } = useMyModulePermissions();
|
|
118
|
+
return useCallback((modules) => {
|
|
119
|
+
if (isUnknown || permissions.length === 0)
|
|
120
|
+
return modules;
|
|
121
|
+
const result = [];
|
|
122
|
+
for (const mod of modules) {
|
|
123
|
+
const children = mod.nav.children;
|
|
124
|
+
if (children?.length) {
|
|
125
|
+
const allowed = children.filter((child) => resolveLevel(permissions, navKey(child.href)) !== "none");
|
|
126
|
+
if (allowed.length === 0)
|
|
127
|
+
continue;
|
|
128
|
+
result.push({ ...mod, nav: { ...mod.nav, children: allowed } });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (resolveLevel(permissions, moduleKey(mod)) === "none")
|
|
132
|
+
continue;
|
|
133
|
+
result.push(mod);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}, [permissions, isUnknown]);
|
|
137
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { getProviderToken, listProviderAccounts, revokeProviderAccount } from "./provider-token.js";
|
|
2
2
|
export type { ProviderAccount } from "./provider-token.js";
|
|
3
|
+
export { exchangeHandoverCode, HandoverExchangeError } from "./provider-token.js";
|
|
4
|
+
export type { HandoverSession } from "./provider-token.js";
|
|
3
5
|
export { CentralAuthConfigError, ProviderNotConnectedError, ProviderUpstreamError, CentralAuthUnavailableError, AmbiguousAccountError, } from "./provider-token.js";
|
|
6
|
+
export { requireModulePermission } from "./permissions.server.js";
|
|
7
|
+
export type { PermissionLevel } from "./permissions.server.js";
|
|
4
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { getProviderToken, listProviderAccounts, revokeProviderAccount } from "./provider-token.js";
|
|
2
|
+
export { exchangeHandoverCode, HandoverExchangeError } from "./provider-token.js";
|
|
2
3
|
export { CentralAuthConfigError, ProviderNotConnectedError, ProviderUpstreamError, CentralAuthUnavailableError, AmbiguousAccountError, } from "./provider-token.js";
|
|
4
|
+
// Server-only rechtencheck. Uitsluitend via het "./server" subpath, nooit via
|
|
5
|
+
// de hoofdbarrel.
|
|
6
|
+
export { requireModulePermission } from "./permissions.server.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
export type PermissionLevel = "none" | "view" | "edit";
|
|
3
|
+
/**
|
|
4
|
+
* Server-side rechtencheck via RPC `effective_module_permission()`.
|
|
5
|
+
*
|
|
6
|
+
* Bedoeld om te combineren met `requireSupabaseAuth`: geef de authenticated
|
|
7
|
+
* client uit `context.supabase` mee.
|
|
8
|
+
*
|
|
9
|
+
* - Ontbreekt de RPC (rechten-add-on niet geïnstalleerd) → toegestaan
|
|
10
|
+
* (default-allow), zodat bestaande shells blijven werken.
|
|
11
|
+
* - Onvoldoende rechten → gooit een `Response` met status 403.
|
|
12
|
+
*
|
|
13
|
+
* Core raakt géén enkele tabel uit het rechtenschema; alleen deze RPC.
|
|
14
|
+
*/
|
|
15
|
+
export declare function requireModulePermission(supabase: SupabaseClient<any, any, any>, moduleId: string, featureId?: string | null, level?: PermissionLevel): Promise<PermissionLevel>;
|
|
16
|
+
//# sourceMappingURL=permissions.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.server.d.ts","sourceRoot":"","sources":["../../src/core/server/permissions.server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAkBvD;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EACvC,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAM,GAAG,IAAW,EAC/B,KAAK,GAAE,eAAwB,GAC9B,OAAO,CAAC,eAAe,CAAC,CAuB1B"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const RANK = { none: 0, view: 1, edit: 2 };
|
|
2
|
+
function isMissingRpc(error) {
|
|
3
|
+
const e = (error ?? {});
|
|
4
|
+
const code = e.code ?? "";
|
|
5
|
+
const message = (e.message ?? "").toLowerCase();
|
|
6
|
+
return (code === "42883" ||
|
|
7
|
+
code === "PGRST202" ||
|
|
8
|
+
code === "PGRST302" ||
|
|
9
|
+
e.status === 404 ||
|
|
10
|
+
message.includes("does not exist") ||
|
|
11
|
+
message.includes("could not find the function"));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Server-side rechtencheck via RPC `effective_module_permission()`.
|
|
15
|
+
*
|
|
16
|
+
* Bedoeld om te combineren met `requireSupabaseAuth`: geef de authenticated
|
|
17
|
+
* client uit `context.supabase` mee.
|
|
18
|
+
*
|
|
19
|
+
* - Ontbreekt de RPC (rechten-add-on niet geïnstalleerd) → toegestaan
|
|
20
|
+
* (default-allow), zodat bestaande shells blijven werken.
|
|
21
|
+
* - Onvoldoende rechten → gooit een `Response` met status 403.
|
|
22
|
+
*
|
|
23
|
+
* Core raakt géén enkele tabel uit het rechtenschema; alleen deze RPC.
|
|
24
|
+
*/
|
|
25
|
+
export async function requireModulePermission(supabase, moduleId, featureId = null, level = "view") {
|
|
26
|
+
const { data, error } = await supabase.rpc("effective_module_permission", {
|
|
27
|
+
_module_id: moduleId,
|
|
28
|
+
_feature_id: featureId,
|
|
29
|
+
});
|
|
30
|
+
if (error) {
|
|
31
|
+
if (isMissingRpc(error))
|
|
32
|
+
return "edit";
|
|
33
|
+
throw new Response("Rechtencheck mislukt", { status: 500 });
|
|
34
|
+
}
|
|
35
|
+
const raw = typeof data === "string" ? data.toLowerCase() : "";
|
|
36
|
+
const effective = raw === "none" || raw === "view" || raw === "edit" ? raw : "edit";
|
|
37
|
+
if (RANK[effective] < RANK[level]) {
|
|
38
|
+
throw new Response("Forbidden: geen rechten voor deze module", { status: 403 });
|
|
39
|
+
}
|
|
40
|
+
return effective;
|
|
41
|
+
}
|
|
@@ -33,4 +33,21 @@ export declare function listProviderAccounts(provider: string, opts?: {
|
|
|
33
33
|
export declare function revokeProviderAccount(provider: string, accountKey: string, opts?: {
|
|
34
34
|
environment?: "staging" | "live";
|
|
35
35
|
}): Promise<void>;
|
|
36
|
+
export declare class HandoverExchangeError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
export interface HandoverSession {
|
|
40
|
+
accessToken: string;
|
|
41
|
+
refreshToken: string;
|
|
42
|
+
/** Kan ongeldig zijn als central geen expires_at meestuurt; alleen informatief. */
|
|
43
|
+
expiresAt: Date;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* POST {CENTRAL_AUTH_URL}/api/session/exchange met body { code }.
|
|
47
|
+
*
|
|
48
|
+
* Fail-closed: elke fout gooit. De code komt NOOIT in een logregel of
|
|
49
|
+
* foutmelding terecht, en het secret verlaat deze module niet.
|
|
50
|
+
* Uitsluitend beschikbaar via het "./server"-subpath.
|
|
51
|
+
*/
|
|
52
|
+
export declare function exchangeHandoverCode(code: string): Promise<HandoverSession>;
|
|
36
53
|
//# sourceMappingURL=provider-token.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-token.d.ts","sourceRoot":"","sources":["../../src/core/server/provider-token.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAOD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"provider-token.d.ts","sourceRoot":"","sources":["../../src/core/server/provider-token.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAOD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAqMD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC,CA6BnD;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,eAAe,EAAE,CAAC,CA4C5B;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAMD,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAmCjF"}
|
|
@@ -49,10 +49,12 @@ function getCentralAuthConfig() {
|
|
|
49
49
|
}
|
|
50
50
|
return { baseUrl, secret };
|
|
51
51
|
}
|
|
52
|
-
// segment is always one of the
|
|
52
|
+
// segment is always one of the literals above, never caller input —
|
|
53
53
|
// the URL path must never be built from anything a caller supplies.
|
|
54
|
+
// provider === undefined betekent: provider-less endpoint, geen extra segment.
|
|
54
55
|
function buildCentralUrl(baseUrl, segment, provider, query) {
|
|
55
|
-
const
|
|
56
|
+
const path = provider === undefined ? segment : `${segment}/${encodeURIComponent(provider)}`;
|
|
57
|
+
const base = `${baseUrl.replace(/\/+$/, "")}/api/${path}`;
|
|
56
58
|
if (!query)
|
|
57
59
|
return base;
|
|
58
60
|
const qs = new URLSearchParams(query).toString();
|
|
@@ -97,9 +99,13 @@ async function mapCentralError(response, provider, environment, allowAmbiguousAc
|
|
|
97
99
|
}
|
|
98
100
|
throw new CentralAuthUnavailableError(`Central auth unavailable (provider=${provider}, environment=${environment}, status=${response.status}).`);
|
|
99
101
|
}
|
|
100
|
-
async function requestCentral(segment,
|
|
102
|
+
async function requestCentral(segment,
|
|
103
|
+
// undefined bij provider-less endpoints; alleen voor de URL relevant.
|
|
104
|
+
provider, environment, init, allowAmbiguousAccount) {
|
|
101
105
|
const { baseUrl, secret } = getCentralAuthConfig();
|
|
102
106
|
const url = buildCentralUrl(baseUrl, segment, provider, init.query);
|
|
107
|
+
// Label uitsluitend voor logging en foutmeldingen — nooit voor de URL.
|
|
108
|
+
const providerLabel = provider ?? segment;
|
|
103
109
|
const controller = new AbortController();
|
|
104
110
|
let timedOut = false;
|
|
105
111
|
const timeout = setTimeout(() => {
|
|
@@ -119,14 +125,14 @@ async function requestCentral(segment, provider, environment, init, allowAmbiguo
|
|
|
119
125
|
});
|
|
120
126
|
}
|
|
121
127
|
catch {
|
|
122
|
-
logAuthFailure(
|
|
123
|
-
throw new CentralAuthUnavailableError(`Central auth request failed (provider=${
|
|
128
|
+
logAuthFailure(providerLabel, environment, timedOut ? "timeout" : "network");
|
|
129
|
+
throw new CentralAuthUnavailableError(`Central auth request failed (provider=${providerLabel}, environment=${environment}, reason=${timedOut ? "timeout" : "network"}).`);
|
|
124
130
|
}
|
|
125
131
|
finally {
|
|
126
132
|
clearTimeout(timeout);
|
|
127
133
|
}
|
|
128
134
|
if (!response.ok) {
|
|
129
|
-
await mapCentralError(response,
|
|
135
|
+
await mapCentralError(response, providerLabel, environment, allowAmbiguousAccount);
|
|
130
136
|
}
|
|
131
137
|
return response;
|
|
132
138
|
}
|
|
@@ -231,3 +237,44 @@ export async function revokeProviderAccount(provider, accountKey, opts) {
|
|
|
231
237
|
cache.delete(key);
|
|
232
238
|
}
|
|
233
239
|
}
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Handover: eenmalige code van central-auth inwisselen voor een Supabase-sessie.
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
export class HandoverExchangeError extends Error {
|
|
244
|
+
constructor(message) {
|
|
245
|
+
super(message);
|
|
246
|
+
this.name = "HandoverExchangeError";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* POST {CENTRAL_AUTH_URL}/api/session/exchange met body { code }.
|
|
251
|
+
*
|
|
252
|
+
* Fail-closed: elke fout gooit. De code komt NOOIT in een logregel of
|
|
253
|
+
* foutmelding terecht, en het secret verlaat deze module niet.
|
|
254
|
+
* Uitsluitend beschikbaar via het "./server"-subpath.
|
|
255
|
+
*/
|
|
256
|
+
export async function exchangeHandoverCode(code) {
|
|
257
|
+
if (typeof code !== "string" || code.trim() === "") {
|
|
258
|
+
throw new HandoverExchangeError("Handover code is missing.");
|
|
259
|
+
}
|
|
260
|
+
const response = await requestCentral("session/exchange",
|
|
261
|
+
/* provider */ undefined, "live", { method: "POST", body: { code } },
|
|
262
|
+
/* allowAmbiguousAccount */ false);
|
|
263
|
+
let body;
|
|
264
|
+
try {
|
|
265
|
+
body = (await response.json());
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
logAuthFailure("session/exchange", "live", response.status);
|
|
269
|
+
throw new CentralAuthUnavailableError(`Central auth returned an unparseable response (endpoint=session/exchange, status=${response.status}).`);
|
|
270
|
+
}
|
|
271
|
+
if (typeof body.access_token !== "string" || typeof body.refresh_token !== "string") {
|
|
272
|
+
logAuthFailure("session/exchange", "live", response.status);
|
|
273
|
+
throw new HandoverExchangeError(`Central auth returned an incomplete session (endpoint=session/exchange, status=${response.status}).`);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
accessToken: body.access_token,
|
|
277
|
+
refreshToken: body.refresh_token,
|
|
278
|
+
expiresAt: new Date(typeof body.expires_at === "string" ? body.expires_at : Date.now()),
|
|
279
|
+
};
|
|
280
|
+
}
|
package/dist/types/module.d.ts
CHANGED
|
@@ -16,5 +16,28 @@ export interface FlowModule {
|
|
|
16
16
|
version: string;
|
|
17
17
|
nav: NavItem;
|
|
18
18
|
settingsCards: SettingsCard[];
|
|
19
|
+
/**
|
|
20
|
+
* Optionele expliciete sleutel waarmee deze module extern (module_registry,
|
|
21
|
+
* rechten) wordt geïdentificeerd. Zet deze wanneer de afgeleide sleutel niet
|
|
22
|
+
* gelijk is aan de registry-slug.
|
|
23
|
+
*/
|
|
24
|
+
registryKey?: string;
|
|
19
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* De externe modulesleutel.
|
|
28
|
+
*
|
|
29
|
+
* Regel (generiek, core kent geen enkele module- of domeinnaam):
|
|
30
|
+
* 1. `registryKey` als die is opgegeven;
|
|
31
|
+
* 2. anders het eerste padsegment van `nav.href`;
|
|
32
|
+
* 3. afsluitende underscores worden gestript.
|
|
33
|
+
*
|
|
34
|
+
* Waarom (3): een afsluitende underscore in een routepad is een TanStack
|
|
35
|
+
* un-nesting-conventie (`/voorraad_/detail`), geen betekenisvol segment. Zonder
|
|
36
|
+
* strippen levert dezelfde module twee sleutels op (`voorraad` en `voorraad_`)
|
|
37
|
+
* en verdwijnt hij half uit de zijbalk. Strippen is gekozen boven "registryKey
|
|
38
|
+
* verplicht maken", omdat dat laatste bestaande modules stil zou breken.
|
|
39
|
+
*/
|
|
40
|
+
export declare function moduleKey(mod: FlowModule): string;
|
|
41
|
+
/** Zelfde afleidingsregel, maar voor een los pad (nav-children, URL-pathname). */
|
|
42
|
+
export declare function navKey(href: string): string;
|
|
20
43
|
//# sourceMappingURL=module.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/core/types/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,aAAa,EAAE,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/core/types/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAGjD;AAED,kFAAkF;AAClF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3C"}
|
package/dist/types/module.js
CHANGED
|
@@ -1 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* De externe modulesleutel.
|
|
3
|
+
*
|
|
4
|
+
* Regel (generiek, core kent geen enkele module- of domeinnaam):
|
|
5
|
+
* 1. `registryKey` als die is opgegeven;
|
|
6
|
+
* 2. anders het eerste padsegment van `nav.href`;
|
|
7
|
+
* 3. afsluitende underscores worden gestript.
|
|
8
|
+
*
|
|
9
|
+
* Waarom (3): een afsluitende underscore in een routepad is een TanStack
|
|
10
|
+
* un-nesting-conventie (`/voorraad_/detail`), geen betekenisvol segment. Zonder
|
|
11
|
+
* strippen levert dezelfde module twee sleutels op (`voorraad` en `voorraad_`)
|
|
12
|
+
* en verdwijnt hij half uit de zijbalk. Strippen is gekozen boven "registryKey
|
|
13
|
+
* verplicht maken", omdat dat laatste bestaande modules stil zou breken.
|
|
14
|
+
*/
|
|
15
|
+
export function moduleKey(mod) {
|
|
16
|
+
if (mod.registryKey)
|
|
17
|
+
return mod.registryKey;
|
|
18
|
+
return navKey(mod.nav.href);
|
|
19
|
+
}
|
|
20
|
+
/** Zelfde afleidingsregel, maar voor een los pad (nav-children, URL-pathname). */
|
|
21
|
+
export function navKey(href) {
|
|
22
|
+
const segment = href.split("?")[0].split("#")[0].split("/").filter(Boolean)[0] ?? "";
|
|
23
|
+
return segment.replace(/_+$/, "");
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"default": "./dist/server/index.js"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
"files": [
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
20
22
|
"scripts": {
|
|
21
23
|
"dev": "vite dev",
|
|
22
24
|
"build": "vite build",
|
|
@@ -70,6 +72,7 @@
|
|
|
70
72
|
"embla-carousel-react": "^8.6.0",
|
|
71
73
|
"input-otp": "^1.4.2",
|
|
72
74
|
"lucide-react": "^0.575.0",
|
|
75
|
+
"nitro": "3.0.260603-beta",
|
|
73
76
|
"react": "^19.2.0",
|
|
74
77
|
"react-barcode": "^1.6.1",
|
|
75
78
|
"react-day-picker": "^9.14.0",
|
|
@@ -88,7 +91,7 @@
|
|
|
88
91
|
},
|
|
89
92
|
"devDependencies": {
|
|
90
93
|
"@eslint/js": "^9.32.0",
|
|
91
|
-
"@lovable.dev/vite-tanstack-config": "
|
|
94
|
+
"@lovable.dev/vite-tanstack-config": "2.13.1",
|
|
92
95
|
"@types/node": "^22.16.5",
|
|
93
96
|
"@types/react": "^19.2.0",
|
|
94
97
|
"@types/react-dom": "^19.2.0",
|