@zealamic/payload-auth-rbac-plugin 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/components/role-permission-matrix-client/default-data.js +2 -2
  2. package/dist/components/role-permission-matrix-client/default-data.js.map +1 -1
  3. package/dist/components/role-permission-matrix-client/index.js +2 -2
  4. package/dist/components/role-permission-matrix-client/index.js.map +1 -1
  5. package/dist/components/role-permission-matrix-client/matrix.module.scss +1 -4
  6. package/dist/components/role-permission-matrix-client/types.d.ts +4 -6
  7. package/dist/components/role-permission-matrix-client/types.js.map +1 -1
  8. package/docs/TRANSLATIONS.md +12 -5
  9. package/package.json +4 -27
  10. package/src/collections/permission-actions/default-data.ts +36 -0
  11. package/src/collections/permission-actions/index.ts +144 -0
  12. package/src/collections/permission-actions/types.ts +56 -0
  13. package/src/collections/permission-features/default-data.ts +30 -0
  14. package/src/collections/permission-features/index.ts +122 -0
  15. package/src/collections/permission-features/types.ts +47 -0
  16. package/src/collections/permissions/default-data.ts +38 -0
  17. package/src/collections/permissions/index.ts +160 -0
  18. package/src/collections/permissions/types.ts +57 -0
  19. package/src/collections/roles/default-data.ts +44 -0
  20. package/src/collections/roles/hooks/sync-permission-matrix-draft.ts +73 -0
  21. package/src/collections/roles/index.ts +178 -0
  22. package/src/collections/roles/types.ts +56 -0
  23. package/src/collections/roles-permissions/default-data.ts +28 -0
  24. package/src/collections/roles-permissions/index.ts +107 -0
  25. package/src/collections/roles-permissions/types.ts +42 -0
  26. package/src/collections/users/default-data.ts +19 -0
  27. package/src/collections/users/index.ts +148 -0
  28. package/src/collections/users/parent-path.ts +310 -0
  29. package/src/collections/users/types.ts +25 -0
  30. package/src/components/role-permission-matrix-client/default-data.ts +25 -0
  31. package/src/components/role-permission-matrix-client/index.tsx +369 -0
  32. package/src/components/role-permission-matrix-client/matrix.module.scss +66 -0
  33. package/src/components/role-permission-matrix-client/types.ts +16 -0
  34. package/src/endpoints/customEndpointHandler.ts +5 -0
  35. package/src/exports/client.ts +1 -0
  36. package/src/exports/rsc.ts +0 -0
  37. package/src/general-types.d.ts +5 -0
  38. package/src/index.ts +249 -0
  39. package/src/lib/constants/general.ts +1 -0
  40. package/src/lib/constants/index.ts +15 -0
  41. package/src/lib/constants/permission-action.ts +9 -0
  42. package/src/lib/constants/permission-feature.ts +4 -0
  43. package/src/lib/constants/permission.ts +4 -0
  44. package/src/lib/constants/role.ts +10 -0
  45. package/src/lib/constants/user.ts +1 -0
  46. package/src/lib/utils/access.ts +611 -0
  47. package/src/lib/utils/data.ts +7 -0
  48. package/src/lib/utils/fields.ts +62 -0
  49. package/src/lib/utils/index.ts +4 -0
  50. package/src/lib/utils/localization.ts +106 -0
  51. package/src/styles/variables.scss +1 -0
  52. package/src/types.ts +64 -0
@@ -0,0 +1,611 @@
1
+ import { Access, type PayloadRequest, type Where } from "payload";
2
+ import type { DataScope } from "../../collections/roles/types.js";
3
+ import { STATUS as PERMISSION_STATUS } from "../constants/permission.js";
4
+ import { DATA_SCOPE, STATUS as ROLE_STATUS } from "../constants/role.js";
5
+ import { PARENT_PATH_SEPARATOR } from "../constants/user.js";
6
+ import { toID } from "./data.js";
7
+
8
+ type UserRoleRef =
9
+ | number
10
+ | string
11
+ | {
12
+ id?: number | string;
13
+ isSuperAdmin?: boolean | null;
14
+ dataScope?: DataScope;
15
+ };
16
+
17
+ type RequestUser = {
18
+ id?: number | string;
19
+ roles?: UserRoleRef[] | null;
20
+ isSuperAdmin?: boolean | null;
21
+ };
22
+
23
+ export type DataScopeOptions = {
24
+ /** Field on business collections storing the creator user id. Default: `createdBy`. */
25
+ createdByField?: string;
26
+ /** Users collection slug. Default: `users`. */
27
+ usersCollectionSlug?: string;
28
+ };
29
+
30
+ const DEFAULT_CREATED_BY_FIELD = "createdBy";
31
+ const DEFAULT_USERS_COLLECTION = "users";
32
+
33
+ const SCOPE_PRIORITY: Record<DataScope, number> = {
34
+ [DATA_SCOPE.OWN]: 0,
35
+ [DATA_SCOPE.HIERARCHY]: 1,
36
+ [DATA_SCOPE.ALL]: 2,
37
+ };
38
+
39
+ /** Normalize role refs from `req.user` to an always-iterable array. */
40
+ const getRoleRefs = (user: RequestUser): UserRoleRef[] => user.roles ?? [];
41
+
42
+ const getRoleIds = (user: RequestUser): string[] =>
43
+ getRoleRefs(user)
44
+ .map((ref) => toID(ref))
45
+ .filter((id): id is string => Boolean(id));
46
+
47
+ /**
48
+ * Fast-path super-admin check from session payload (`req.user`).
49
+ * Avoids a database round-trip when the field is already present.
50
+ */
51
+ const hasInlineSuperAdmin = (user: RequestUser | null | undefined): boolean => {
52
+ if (!user) {
53
+ return false;
54
+ }
55
+ return Boolean(user.isSuperAdmin);
56
+ };
57
+
58
+ const pickWidestDataScope = (
59
+ scopes: Array<DataScope | null | undefined>,
60
+ ): DataScope => {
61
+ let widest: DataScope = DATA_SCOPE.OWN;
62
+
63
+ for (const scope of scopes) {
64
+ if (!scope) {
65
+ continue;
66
+ }
67
+ if (SCOPE_PRIORITY[scope] > SCOPE_PRIORITY[widest]) {
68
+ widest = scope;
69
+ }
70
+ }
71
+
72
+ return widest;
73
+ };
74
+
75
+ /**
76
+ * Fallback super-admin check against persisted users data.
77
+ * Use when session payload may be stale or missing `isSuperAdmin`.
78
+ */
79
+ const resolveSuperAdminFromUserID = async ({
80
+ req,
81
+ user,
82
+ }: {
83
+ req: PayloadRequest;
84
+ user: RequestUser;
85
+ }): Promise<boolean> => {
86
+ if (!user.id) {
87
+ return false;
88
+ }
89
+
90
+ const userDocs = await req.payload.find({
91
+ collection: "users",
92
+ depth: 0,
93
+ limit: 1,
94
+ pagination: false,
95
+ req,
96
+ where: {
97
+ and: [
98
+ {
99
+ id: { equals: user.id },
100
+ },
101
+ {
102
+ isSuperAdmin: { equals: true },
103
+ },
104
+ ],
105
+ },
106
+ });
107
+
108
+ return userDocs.docs.length > 0;
109
+ };
110
+
111
+ /**
112
+ * Resolve RBAC permission from role assignments:
113
+ * 1) find active `permissions` by feature/action code
114
+ * 2) check an enabled `roles-permissions` row for any user role
115
+ */
116
+ const resolvePermissionFromRoleID = async ({
117
+ req,
118
+ user,
119
+ featureCode,
120
+ actionCode,
121
+ }: {
122
+ req: PayloadRequest;
123
+ user: RequestUser;
124
+ featureCode: string;
125
+ actionCode: string;
126
+ }): Promise<boolean> => {
127
+ const roleIDs = getRoleIds(user);
128
+ if (!roleIDs.length) {
129
+ return false;
130
+ }
131
+
132
+ const permissions = await req.payload.find({
133
+ collection: "permissions",
134
+ depth: 0,
135
+ limit: 0,
136
+ pagination: false,
137
+ req,
138
+ where: {
139
+ and: [
140
+ {
141
+ status: { equals: PERMISSION_STATUS.ACTIVE },
142
+ },
143
+ {
144
+ "permissionFeature.code": { equals: featureCode },
145
+ },
146
+ {
147
+ "permissionAction.code": { equals: actionCode },
148
+ },
149
+ ],
150
+ },
151
+ });
152
+
153
+ if (!permissions.docs.length) {
154
+ return false;
155
+ }
156
+ const permissionIDs = permissions.docs.map((item) => item.id);
157
+ const rolePermissions = await req.payload.find({
158
+ collection: "roles-permissions",
159
+ depth: 0,
160
+ limit: 1,
161
+ pagination: false,
162
+ req,
163
+ where: {
164
+ and: [
165
+ {
166
+ role: { in: roleIDs },
167
+ },
168
+ {
169
+ permission: { in: permissionIDs },
170
+ },
171
+ {
172
+ enabled: { equals: true },
173
+ },
174
+ ],
175
+ },
176
+ });
177
+
178
+ return rolePermissions.docs.length > 0;
179
+ };
180
+
181
+ /**
182
+ * Access helper: allow only super admins.
183
+ * Check session first, then persisted users data.
184
+ */
185
+ export const getSuperAdminAccess = async ({ req }: { req: PayloadRequest }) => {
186
+ const user = req.user as RequestUser | undefined;
187
+ if (!user) {
188
+ return false;
189
+ }
190
+
191
+ if (hasInlineSuperAdmin(user)) {
192
+ return true;
193
+ }
194
+
195
+ return await resolveSuperAdminFromUserID({ req, user });
196
+ };
197
+
198
+ /**
199
+ * Access helper: allow current document owner or super admin.
200
+ */
201
+ export const getAuthenticatedOrSuperAdminAccess:
202
+ | Access
203
+ | Promise<boolean> = async ({ req, id }) => {
204
+ const user = req.user as RequestUser | undefined;
205
+ if (!user?.id) {
206
+ return false;
207
+ }
208
+
209
+ if (String(user.id) === String(id)) {
210
+ return true;
211
+ }
212
+
213
+ if (hasInlineSuperAdmin(user)) {
214
+ return true;
215
+ }
216
+ return await resolveSuperAdminFromUserID({ req, user });
217
+ };
218
+
219
+ /**
220
+ * Core RBAC access function (permission-only).
221
+ * Super admins bypass; others are evaluated via roles + `roles-permissions`.
222
+ */
223
+ const getBasePermissionAccess = ({
224
+ featureCode,
225
+ actionCode,
226
+ }: {
227
+ featureCode: string;
228
+ actionCode: string;
229
+ }) => {
230
+ return async ({ req }: { req: PayloadRequest }) => {
231
+ const user = req.user as RequestUser | undefined;
232
+ if (!user) {
233
+ return false;
234
+ }
235
+
236
+ if (hasInlineSuperAdmin(user)) {
237
+ return true;
238
+ }
239
+
240
+ return await resolvePermissionFromRoleID({
241
+ req,
242
+ user,
243
+ featureCode,
244
+ actionCode,
245
+ });
246
+ };
247
+ };
248
+
249
+ /**
250
+ * Resolve effective data scope from active roles.
251
+ * Widest scope wins: `all` > `hierarchy` > `own`.
252
+ */
253
+ export const resolveEffectiveDataScope = async (
254
+ req: PayloadRequest,
255
+ options: DataScopeOptions = {},
256
+ ): Promise<DataScope> => {
257
+ const user = req.user as RequestUser | undefined;
258
+
259
+ if (!user) {
260
+ return DATA_SCOPE.OWN;
261
+ }
262
+
263
+ if (hasInlineSuperAdmin(user)) {
264
+ return DATA_SCOPE.ALL;
265
+ }
266
+
267
+ const roleRefs = getRoleRefs(user);
268
+ const roleIDs = getRoleIds(user);
269
+
270
+ if (!roleIDs.length) {
271
+ return DATA_SCOPE.OWN;
272
+ }
273
+
274
+ const inlineScopes = roleRefs
275
+ .filter(
276
+ (ref): ref is { id?: string | number; dataScope?: DataScope } =>
277
+ typeof ref === "object" && ref !== null,
278
+ )
279
+ .map((ref) => ref.dataScope)
280
+ .filter((scope): scope is DataScope => Boolean(scope));
281
+
282
+ if (inlineScopes.length === roleRefs.length) {
283
+ return pickWidestDataScope(inlineScopes);
284
+ }
285
+
286
+ const roles = await req.payload.find({
287
+ collection: "roles",
288
+ depth: 0,
289
+ limit: 0,
290
+ pagination: false,
291
+ req,
292
+ where: {
293
+ and: [
294
+ { id: { in: roleIDs } },
295
+ { status: { equals: ROLE_STATUS.ACTIVE } },
296
+ ],
297
+ },
298
+ });
299
+
300
+ return pickWidestDataScope(
301
+ roles.docs.map((role) => (role as { dataScope?: DataScope }).dataScope),
302
+ );
303
+ };
304
+
305
+ /**
306
+ * Collect visible user IDs for hierarchy scope:
307
+ * current user + direct/indirect descendants from `parent` / `parentPath`.
308
+ */
309
+ export const getHierarchyVisibleUserIds = async (
310
+ req: PayloadRequest,
311
+ options: DataScopeOptions = {},
312
+ ): Promise<string[]> => {
313
+ const usersCollectionSlug =
314
+ options.usersCollectionSlug ?? DEFAULT_USERS_COLLECTION;
315
+ const user = req.user as RequestUser | undefined;
316
+ const userId = user?.id;
317
+
318
+ if (!userId) {
319
+ return [];
320
+ }
321
+
322
+ const selfId = String(userId);
323
+
324
+ const descendants = await req.payload.find({
325
+ collection: usersCollectionSlug,
326
+ depth: 0,
327
+ limit: 0,
328
+ pagination: false,
329
+ req,
330
+ where: {
331
+ or: [
332
+ { parent: { equals: selfId } },
333
+ { parentPath: { equals: selfId } },
334
+ { parentPath: { contains: `${selfId}${PARENT_PATH_SEPARATOR}` } },
335
+ ],
336
+ },
337
+ });
338
+
339
+ const ids = new Set<string>([selfId]);
340
+
341
+ for (const doc of descendants.docs) {
342
+ const id = toID(doc as UserRoleRef);
343
+ if (id) {
344
+ ids.add(id);
345
+ }
346
+ }
347
+
348
+ return [...ids];
349
+ };
350
+
351
+ /**
352
+ * Build a read `Where` filter from data scope.
353
+ * Returns `true` when no extra filtering is required (`all` scope / super admin).
354
+ */
355
+ export const getDataScopeReadWhere = async (
356
+ req: PayloadRequest,
357
+ options: DataScopeOptions = {},
358
+ ): Promise<Where | true> => {
359
+ const user = req.user as RequestUser | undefined;
360
+ const createdByField = options.createdByField ?? DEFAULT_CREATED_BY_FIELD;
361
+
362
+ if (!user?.id) {
363
+ return { [createdByField]: { equals: "___none___" } };
364
+ }
365
+
366
+ if (hasInlineSuperAdmin(user)) {
367
+ return true;
368
+ }
369
+
370
+ const scope = await resolveEffectiveDataScope(req, options);
371
+
372
+ if (scope === DATA_SCOPE.ALL) {
373
+ return true;
374
+ }
375
+
376
+ const selfId = String(user.id);
377
+
378
+ if (scope === DATA_SCOPE.OWN) {
379
+ return { [createdByField]: { equals: selfId } };
380
+ }
381
+
382
+ const visibleUserIds = await getHierarchyVisibleUserIds(req, options);
383
+
384
+ return {
385
+ [createdByField]: {
386
+ in: visibleUserIds.length ? visibleUserIds : [selfId],
387
+ },
388
+ };
389
+ };
390
+
391
+ const getCreatedById = (
392
+ doc: Record<string, unknown>,
393
+ createdByField: string,
394
+ ): string | undefined => {
395
+ const value = doc[createdByField];
396
+ return toID(value as UserRoleRef) || undefined;
397
+ };
398
+
399
+ const isUsersCollection = (
400
+ collectionSlug: string,
401
+ usersCollectionSlug: string,
402
+ ) => collectionSlug === usersCollectionSlug;
403
+
404
+ /**
405
+ * Guard for privileged user documents.
406
+ * Non-super-admins cannot mutate users where `isSuperAdmin === true`.
407
+ */
408
+ export const isProtectedSuperAdminUserDoc = (
409
+ doc: Record<string, unknown>,
410
+ ): boolean => Boolean(doc.isSuperAdmin);
411
+
412
+ /**
413
+ * Document-level access check:
414
+ * RBAC permission (`featureCode` + `actionCode`) + data-scope evaluation.
415
+ * Super admins bypass.
416
+ */
417
+ export const canAccessDocumentByDataScope = async ({
418
+ req,
419
+ doc,
420
+ featureCode,
421
+ actionCode,
422
+ collectionSlug,
423
+ options = {},
424
+ }: {
425
+ req: PayloadRequest;
426
+ doc: Record<string, unknown>;
427
+ featureCode: string;
428
+ actionCode: string;
429
+ collectionSlug: string;
430
+ options?: DataScopeOptions;
431
+ }): Promise<boolean> => {
432
+ const user = req.user as RequestUser | undefined;
433
+ const createdByField = options.createdByField ?? DEFAULT_CREATED_BY_FIELD;
434
+ const usersCollectionSlug =
435
+ options.usersCollectionSlug ?? DEFAULT_USERS_COLLECTION;
436
+
437
+ if (!user?.id) {
438
+ return false;
439
+ }
440
+
441
+ if (hasInlineSuperAdmin(user)) {
442
+ return true;
443
+ }
444
+
445
+ const hasPermission = await resolvePermissionFromRoleID({
446
+ req,
447
+ user,
448
+ featureCode,
449
+ actionCode,
450
+ });
451
+
452
+ if (!hasPermission) {
453
+ return false;
454
+ }
455
+
456
+ const scope = await resolveEffectiveDataScope(req, options);
457
+ const creatorId = getCreatedById(doc, createdByField);
458
+
459
+ if (!creatorId) {
460
+ return true;
461
+ }
462
+
463
+ if (
464
+ scope === DATA_SCOPE.ALL &&
465
+ isUsersCollection(collectionSlug, usersCollectionSlug) &&
466
+ isProtectedSuperAdminUserDoc(doc)
467
+ ) {
468
+ return false;
469
+ }
470
+
471
+ if (scope === DATA_SCOPE.ALL) {
472
+ return true;
473
+ }
474
+
475
+ const selfId = String(user.id);
476
+
477
+ if (scope === DATA_SCOPE.OWN) {
478
+ return creatorId === selfId;
479
+ }
480
+
481
+ const visibleUserIds = await getHierarchyVisibleUserIds(req, options);
482
+ return visibleUserIds.includes(creatorId);
483
+ };
484
+
485
+ /**
486
+ * Merge an existing `where` with scope-derived constraints.
487
+ */
488
+ export const mergeDataScopeWhere = (
489
+ base: Where | undefined,
490
+ scopeWhere: Where | true,
491
+ ): Where => {
492
+ if (scopeWhere === true) {
493
+ return base ?? {};
494
+ }
495
+
496
+ if (!base || Object.keys(base).length === 0) {
497
+ return scopeWhere;
498
+ }
499
+
500
+ return {
501
+ and: [base, scopeWhere],
502
+ };
503
+ };
504
+
505
+ /**
506
+ * Access helper for collection `read`:
507
+ * RBAC permission check + data-scope `Where` filter.
508
+ */
509
+ const getPermissionAndDataScopeReadAccess = ({
510
+ featureCode,
511
+ actionCode,
512
+ options = {},
513
+ }: {
514
+ featureCode: string;
515
+ actionCode: string;
516
+ options?: DataScopeOptions;
517
+ }) => {
518
+ return async ({ req }: { req: PayloadRequest }) => {
519
+ const hasPermission = await getBasePermissionAccess({
520
+ featureCode,
521
+ actionCode,
522
+ })({
523
+ req,
524
+ });
525
+
526
+ if (!hasPermission) {
527
+ return false;
528
+ }
529
+
530
+ return getDataScopeReadWhere(req, options);
531
+ };
532
+ };
533
+
534
+ /**
535
+ * Access helper for document mutations (`update`/`delete`):
536
+ * load target document then apply RBAC + data-scope checks.
537
+ */
538
+ const getPermissionAndDataScopeModifyAccess = ({
539
+ featureCode,
540
+ actionCode,
541
+ collectionSlug,
542
+ options = {},
543
+ }: {
544
+ featureCode: string;
545
+ actionCode: string;
546
+ collectionSlug?: string;
547
+ options?: DataScopeOptions;
548
+ }) => {
549
+ return async ({ req, id }: { req: PayloadRequest; id?: string | number }) => {
550
+ if (!id || !collectionSlug) {
551
+ return false;
552
+ }
553
+
554
+ const doc = await req.payload.findByID({
555
+ collection: collectionSlug,
556
+ id,
557
+ depth: 0,
558
+ req,
559
+ });
560
+
561
+ return canAccessDocumentByDataScope({
562
+ req,
563
+ doc: doc as Record<string, unknown>,
564
+ featureCode,
565
+ actionCode,
566
+ collectionSlug,
567
+ options,
568
+ });
569
+ };
570
+ };
571
+
572
+ /**
573
+ * Unified access entrypoint.
574
+ *
575
+ * Modes:
576
+ * - `none`: permission-only (boolean)
577
+ * - `modify`: per-document mutation check (requires `collectionSlug` + runtime `id`)
578
+ * - implicit read mode: pass `options` to get a read `Where` filter after permission check
579
+ */
580
+
581
+ export const getPermissionAccess = ({
582
+ featureCode,
583
+ actionCode,
584
+ mode = "none",
585
+ collectionSlug,
586
+ options,
587
+ }: {
588
+ featureCode: string;
589
+ actionCode: string;
590
+ mode?: "none" | "modify";
591
+ collectionSlug?: string;
592
+ options?: DataScopeOptions;
593
+ }) => {
594
+ if (mode === "modify") {
595
+ return getPermissionAndDataScopeModifyAccess({
596
+ featureCode,
597
+ actionCode,
598
+ collectionSlug,
599
+ options,
600
+ });
601
+ }
602
+
603
+ if (options && Object.keys(options).length > 0) {
604
+ return getPermissionAndDataScopeReadAccess({
605
+ featureCode,
606
+ actionCode,
607
+ options,
608
+ });
609
+ }
610
+ return getBasePermissionAccess({ featureCode, actionCode });
611
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a value to an ID string.
3
+ * @param value - The value to convert.
4
+ * @returns The ID string.
5
+ */
6
+ export const toID = (value?: ItemRef) =>
7
+ value ? (typeof value === "object" ? String(value.id) : String(value)) : "";
@@ -0,0 +1,62 @@
1
+ import type { Field } from "payload";
2
+ import { fieldAffectsData } from "payload/shared";
3
+
4
+ /**
5
+ * Merges one plugin default field with a host override that shares the same `name`.
6
+ * Custom properties win (`{ ...defaultField, ...customField }`). Non-data fields
7
+ * (e.g. tabs, unnamed layout fields) are returned unchanged.
8
+ */
9
+ export const getMergedFieldAffectingData = ({
10
+ fields,
11
+ defaultField,
12
+ }: {
13
+ fields: Field[];
14
+ defaultField: Field;
15
+ }): Field => {
16
+ if (!defaultField || !fieldAffectsData(defaultField)) {
17
+ return defaultField;
18
+ }
19
+
20
+ const defaultName = defaultField.name;
21
+ const customField = fields.find(
22
+ (field) => fieldAffectsData(field) && field.name === defaultName,
23
+ );
24
+
25
+ if (customField) {
26
+ return {
27
+ ...defaultField,
28
+ ...customField,
29
+ } as Field;
30
+ }
31
+
32
+ return defaultField;
33
+ };
34
+
35
+ /**
36
+ * Builds the final `fields` array for a collection: each default is merged by name,
37
+ * then host-only fields are appended (data fields with new names, plus layout fields).
38
+ */
39
+ export const getArrayOfMergedFieldAffectingData = ({
40
+ fields,
41
+ defaultFields,
42
+ }: {
43
+ fields: Field[];
44
+ defaultFields: Field[];
45
+ }): Field[] => {
46
+ const defaultNames = new Set(
47
+ defaultFields.filter(fieldAffectsData).map((field) => field.name),
48
+ );
49
+
50
+ const mergedFromDefaults = defaultFields.map((defaultField) =>
51
+ getMergedFieldAffectingData({ fields, defaultField }),
52
+ );
53
+
54
+ const unMatchedFields = fields.filter((field) => {
55
+ if (!fieldAffectsData(field)) {
56
+ return true;
57
+ }
58
+ return !defaultNames.has(field.name);
59
+ });
60
+
61
+ return [...mergedFromDefaults, ...unMatchedFields];
62
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./access.js"
2
+ export * from "./data.js"
3
+ export * from "./fields.js"
4
+ export * from "./localization.js"