@enterprisestandard/react 0.0.5-beta.20260114.3 → 0.0.5-beta.20260115.2

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.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Group storage for persisting group data.
3
+ *
4
+ * Group stores are an optional extension for the IAM Groups functionality.
5
+ * They enable:
6
+ * - Caching group data locally for fast lookups
7
+ * - Receiving group provisioning from external IAM providers (SCIM server)
8
+ * - Storing groups close to your application (in-memory, Redis, database)
9
+ *
10
+ * ## Example Usage
11
+ *
12
+ * ```typescript
13
+ * import { InMemoryGroupStore } from '@enterprisestandard/react';
14
+ *
15
+ * const groupStore = new InMemoryGroupStore();
16
+ *
17
+ * // Store a group
18
+ * await groupStore.upsert({
19
+ * id: 'group-123',
20
+ * displayName: 'Administrators',
21
+ * createdAt: new Date(),
22
+ * updatedAt: new Date(),
23
+ * });
24
+ *
25
+ * // Look up groups
26
+ * const group = await groupStore.get('group-123');
27
+ * const allGroups = await groupStore.list();
28
+ * ```
29
+ */
30
+ /**
31
+ * In-memory group store implementation using Maps.
32
+ *
33
+ * Suitable for:
34
+ * - Development and testing
35
+ * - Single-server deployments
36
+ * - Applications without high availability requirements
37
+ *
38
+ * NOT suitable for:
39
+ * - Multi-server deployments (groups not shared)
40
+ * - High availability scenarios (groups lost on restart)
41
+ * - Production applications with distributed architecture
42
+ *
43
+ * For production, implement GroupStore with Redis or a database.
44
+ *
45
+ * @template TExtended - Type-safe custom data that consumers can add to groups
46
+ */
47
+ export class InMemoryGroupStore {
48
+ constructor() {
49
+ /** Primary storage: id -> group */
50
+ this.groups = new Map();
51
+ /** Secondary index: externalId -> id */
52
+ this.externalIdIndex = new Map();
53
+ /** Secondary index: displayName (lowercase) -> id */
54
+ this.displayNameIndex = new Map();
55
+ }
56
+ async get(id) {
57
+ return this.groups.get(id) ?? null;
58
+ }
59
+ async getByExternalId(externalId) {
60
+ const id = this.externalIdIndex.get(externalId);
61
+ if (!id)
62
+ return null;
63
+ return this.groups.get(id) ?? null;
64
+ }
65
+ async getByDisplayName(displayName) {
66
+ const id = this.displayNameIndex.get(displayName.toLowerCase());
67
+ if (!id)
68
+ return null;
69
+ return this.groups.get(id) ?? null;
70
+ }
71
+ async list() {
72
+ return Array.from(this.groups.values());
73
+ }
74
+ async upsert(group) {
75
+ const existing = this.groups.get(group.id);
76
+ // Clean up old indexes if externalId or displayName changed
77
+ if (existing) {
78
+ if (existing.externalId && existing.externalId !== group.externalId) {
79
+ this.externalIdIndex.delete(existing.externalId);
80
+ }
81
+ if (existing.displayName.toLowerCase() !== group.displayName.toLowerCase()) {
82
+ this.displayNameIndex.delete(existing.displayName.toLowerCase());
83
+ }
84
+ }
85
+ // Store the group
86
+ this.groups.set(group.id, group);
87
+ // Update indexes
88
+ if (group.externalId) {
89
+ this.externalIdIndex.set(group.externalId, group.id);
90
+ }
91
+ this.displayNameIndex.set(group.displayName.toLowerCase(), group.id);
92
+ }
93
+ async delete(id) {
94
+ const group = this.groups.get(id);
95
+ if (group) {
96
+ // Clean up indexes
97
+ if (group.externalId) {
98
+ this.externalIdIndex.delete(group.externalId);
99
+ }
100
+ this.displayNameIndex.delete(group.displayName.toLowerCase());
101
+ }
102
+ this.groups.delete(id);
103
+ }
104
+ async addMember(groupId, member) {
105
+ const group = this.groups.get(groupId);
106
+ if (!group) {
107
+ throw new Error(`Group ${groupId} not found`);
108
+ }
109
+ const members = group.members ?? [];
110
+ // Check if member already exists
111
+ if (!members.some((m) => m.value === member.value)) {
112
+ members.push(member);
113
+ group.members = members;
114
+ group.updatedAt = new Date();
115
+ }
116
+ }
117
+ async removeMember(groupId, memberId) {
118
+ const group = this.groups.get(groupId);
119
+ if (!group) {
120
+ throw new Error(`Group ${groupId} not found`);
121
+ }
122
+ if (group.members) {
123
+ group.members = group.members.filter((m) => m.value !== memberId);
124
+ group.updatedAt = new Date();
125
+ }
126
+ }
127
+ }