@apvee/spfx-react-toolkit 1.2.1 → 2.0.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.
Files changed (138) hide show
  1. package/lib/core/atoms.internal.js +1 -1
  2. package/lib/core/atoms.internal.js.map +1 -1
  3. package/lib/core/context.internal.js +2 -2
  4. package/lib/core/context.internal.js.map +1 -1
  5. package/lib/core/provider-base.internal.js +29 -29
  6. package/lib/core/provider-base.internal.js.map +1 -1
  7. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.d.ts +14 -0
  8. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.d.ts.map +1 -0
  9. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js +20 -0
  10. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js.map +1 -0
  11. package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.manifest.json +17 -0
  12. package/lib/extensions/spFxReactToolkitTest/loc/en-us.js +5 -0
  13. package/lib/hooks/index.d.ts +1 -0
  14. package/lib/hooks/index.d.ts.map +1 -1
  15. package/lib/hooks/index.js +1 -0
  16. package/lib/hooks/index.js.map +1 -1
  17. package/lib/hooks/useAppCatalogUrl.internal.d.ts +26 -0
  18. package/lib/hooks/useAppCatalogUrl.internal.d.ts.map +1 -0
  19. package/lib/hooks/useAppCatalogUrl.internal.js +72 -0
  20. package/lib/hooks/useAppCatalogUrl.internal.js.map +1 -0
  21. package/lib/hooks/useAsyncInvoke.internal.js +27 -75
  22. package/lib/hooks/useAsyncInvoke.internal.js.map +1 -1
  23. package/lib/hooks/useSPFxAadHttpClient.d.ts +46 -0
  24. package/lib/hooks/useSPFxAadHttpClient.d.ts.map +1 -1
  25. package/lib/hooks/useSPFxAadHttpClient.js +65 -20
  26. package/lib/hooks/useSPFxAadHttpClient.js.map +1 -1
  27. package/lib/hooks/useSPFxContainerInfo.js +5 -5
  28. package/lib/hooks/useSPFxContainerInfo.js.map +1 -1
  29. package/lib/hooks/useSPFxContainerSize.js +9 -10
  30. package/lib/hooks/useSPFxContainerSize.js.map +1 -1
  31. package/lib/hooks/useSPFxCorrelationInfo.js +6 -7
  32. package/lib/hooks/useSPFxCorrelationInfo.js.map +1 -1
  33. package/lib/hooks/useSPFxCrossSitePermissions.js +48 -58
  34. package/lib/hooks/useSPFxCrossSitePermissions.js.map +1 -1
  35. package/lib/hooks/useSPFxDisplayMode.js +8 -8
  36. package/lib/hooks/useSPFxDisplayMode.js.map +1 -1
  37. package/lib/hooks/useSPFxEnvironmentInfo.js +17 -18
  38. package/lib/hooks/useSPFxEnvironmentInfo.js.map +1 -1
  39. package/lib/hooks/useSPFxFluent9ThemeInfo.js +4 -4
  40. package/lib/hooks/useSPFxFluent9ThemeInfo.js.map +1 -1
  41. package/lib/hooks/useSPFxHttpClient.d.ts +18 -2
  42. package/lib/hooks/useSPFxHttpClient.d.ts.map +1 -1
  43. package/lib/hooks/useSPFxHttpClient.js +19 -9
  44. package/lib/hooks/useSPFxHttpClient.js.map +1 -1
  45. package/lib/hooks/useSPFxHubSiteInfo.js +21 -24
  46. package/lib/hooks/useSPFxHubSiteInfo.js.map +1 -1
  47. package/lib/hooks/useSPFxInstanceInfo.js +2 -2
  48. package/lib/hooks/useSPFxInstanceInfo.js.map +1 -1
  49. package/lib/hooks/useSPFxListInfo.js +8 -9
  50. package/lib/hooks/useSPFxListInfo.js.map +1 -1
  51. package/lib/hooks/useSPFxLocaleInfo.js +10 -10
  52. package/lib/hooks/useSPFxLocaleInfo.js.map +1 -1
  53. package/lib/hooks/useSPFxLogger.js +26 -26
  54. package/lib/hooks/useSPFxLogger.js.map +1 -1
  55. package/lib/hooks/useSPFxMSGraphClient.d.ts +50 -3
  56. package/lib/hooks/useSPFxMSGraphClient.d.ts.map +1 -1
  57. package/lib/hooks/useSPFxMSGraphClient.js +68 -15
  58. package/lib/hooks/useSPFxMSGraphClient.js.map +1 -1
  59. package/lib/hooks/useSPFxOneDriveAppData.d.ts +0 -1
  60. package/lib/hooks/useSPFxOneDriveAppData.d.ts.map +1 -1
  61. package/lib/hooks/useSPFxOneDriveAppData.js +420 -230
  62. package/lib/hooks/useSPFxOneDriveAppData.js.map +1 -1
  63. package/lib/hooks/useSPFxPageContext.js +2 -2
  64. package/lib/hooks/useSPFxPageContext.js.map +1 -1
  65. package/lib/hooks/useSPFxPageType.js +19 -20
  66. package/lib/hooks/useSPFxPageType.js.map +1 -1
  67. package/lib/hooks/useSPFxPerformance.js +33 -87
  68. package/lib/hooks/useSPFxPerformance.js.map +1 -1
  69. package/lib/hooks/useSPFxPermissions.js +14 -15
  70. package/lib/hooks/useSPFxPermissions.js.map +1 -1
  71. package/lib/hooks/useSPFxPnP.js +62 -119
  72. package/lib/hooks/useSPFxPnP.js.map +1 -1
  73. package/lib/hooks/useSPFxPnPContext.js +22 -25
  74. package/lib/hooks/useSPFxPnPContext.js.map +1 -1
  75. package/lib/hooks/useSPFxPnPList.js +307 -451
  76. package/lib/hooks/useSPFxPnPList.js.map +1 -1
  77. package/lib/hooks/useSPFxPnPSearch.js +262 -353
  78. package/lib/hooks/useSPFxPnPSearch.js.map +1 -1
  79. package/lib/hooks/useSPFxProperties.js +12 -20
  80. package/lib/hooks/useSPFxProperties.js.map +1 -1
  81. package/lib/hooks/useSPFxSPHttpClient.d.ts +18 -2
  82. package/lib/hooks/useSPFxSPHttpClient.d.ts.map +1 -1
  83. package/lib/hooks/useSPFxSPHttpClient.js +28 -18
  84. package/lib/hooks/useSPFxSPHttpClient.js.map +1 -1
  85. package/lib/hooks/useSPFxServiceScope.js +6 -6
  86. package/lib/hooks/useSPFxServiceScope.js.map +1 -1
  87. package/lib/hooks/useSPFxSiteInfo.js +7 -8
  88. package/lib/hooks/useSPFxSiteInfo.js.map +1 -1
  89. package/lib/hooks/useSPFxStorage.js +22 -22
  90. package/lib/hooks/useSPFxStorage.js.map +1 -1
  91. package/lib/hooks/useSPFxTeams.js +37 -92
  92. package/lib/hooks/useSPFxTeams.js.map +1 -1
  93. package/lib/hooks/useSPFxTenantKeyValueStore.d.ts +252 -0
  94. package/lib/hooks/useSPFxTenantKeyValueStore.d.ts.map +1 -0
  95. package/lib/hooks/useSPFxTenantKeyValueStore.js +572 -0
  96. package/lib/hooks/useSPFxTenantKeyValueStore.js.map +1 -0
  97. package/lib/hooks/useSPFxTenantProperty.d.ts +23 -244
  98. package/lib/hooks/useSPFxTenantProperty.d.ts.map +1 -1
  99. package/lib/hooks/useSPFxTenantProperty.js +85 -559
  100. package/lib/hooks/useSPFxTenantProperty.js.map +1 -1
  101. package/lib/hooks/useSPFxUserInfo.js +3 -4
  102. package/lib/hooks/useSPFxUserInfo.js.map +1 -1
  103. package/lib/hooks/useSPFxUserPhoto.js +76 -123
  104. package/lib/hooks/useSPFxUserPhoto.js.map +1 -1
  105. package/lib/utils/resize-observer.internal.js +6 -7
  106. package/lib/utils/resize-observer.internal.js.map +1 -1
  107. package/lib/utils/theme-subscription.internal.js +8 -8
  108. package/lib/utils/theme-subscription.internal.js.map +1 -1
  109. package/lib/utils/type-guards.internal.js +6 -6
  110. package/lib/utils/type-guards.internal.js.map +1 -1
  111. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js +12 -37
  112. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js.map +1 -1
  113. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts.map +1 -1
  114. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js +279 -342
  115. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js.map +1 -1
  116. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js +1 -1
  117. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js.map +1 -1
  118. package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js +26 -86
  119. package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js.map +1 -1
  120. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js +53 -113
  121. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js.map +1 -1
  122. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js +49 -121
  123. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js.map +1 -1
  124. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js +44 -103
  125. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js.map +1 -1
  126. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js +15 -15
  127. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js.map +1 -1
  128. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js +18 -66
  129. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js.map +1 -1
  130. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js +9 -9
  131. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js.map +1 -1
  132. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js +37 -86
  133. package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js.map +1 -1
  134. package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js +6 -9
  135. package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js.map +1 -1
  136. package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js +3 -6
  137. package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js.map +1 -1
  138. package/package.json +8 -6
@@ -0,0 +1,572 @@
1
+ // useSPFxTenantKeyValueStore.ts
2
+ // Hook to manage tenant-wide key-value pairs using a hidden SharePoint list in the tenant app catalog
3
+ import { useState, useCallback, useEffect, useRef } from 'react';
4
+ import { useAppCatalogUrl } from './useAppCatalogUrl.internal';
5
+ import { SPHttpClient } from '@microsoft/sp-http';
6
+ // ═══════════════════════════════════════════════════════════════════════════
7
+ // CONSTANTS
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+ const LIST_TITLE = 'TenantKeyValueStore';
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // PURE FUNCTIONS (extracted for stability and testability)
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ /**
14
+ * Escape a string value for use in OData filter expressions.
15
+ * Single quotes must be doubled to prevent injection.
16
+ *
17
+ * @param value - Raw string value
18
+ * @returns Escaped string safe for OData $filter
19
+ */
20
+ function escapeODataValue(value) {
21
+ return value.replace(/'/g, "''");
22
+ }
23
+ /**
24
+ * Serialize a value for storage.
25
+ * - Primitives (string, number, boolean, null, bigint) → String(value)
26
+ * - Date → ISO 8601 string
27
+ * - Objects/arrays → JSON.stringify()
28
+ *
29
+ * @param value - Value to serialize
30
+ * @returns Serialized string representation
31
+ */
32
+ function serializeValue(value) {
33
+ if (value === null)
34
+ return String(value);
35
+ if (value instanceof Date)
36
+ return value.toISOString();
37
+ const type = typeof value;
38
+ if (type === 'string' || type === 'number' || type === 'boolean' || type === 'bigint') {
39
+ return String(value);
40
+ }
41
+ return JSON.stringify(value);
42
+ }
43
+ /**
44
+ * Deserialize a stored string value back to a typed value.
45
+ * Attempts JSON.parse first; falls back to raw string.
46
+ *
47
+ * @param rawValue - Stored string value
48
+ * @returns Parsed value
49
+ */
50
+ function deserializeValue(rawValue) {
51
+ try {
52
+ return JSON.parse(rawValue);
53
+ }
54
+ catch {
55
+ return rawValue;
56
+ }
57
+ }
58
+ /**
59
+ * Build the list REST API base URL.
60
+ *
61
+ * @param catalogUrl - Tenant app catalog absolute URL
62
+ * @returns REST API URL for the TenantKeyValueStore list
63
+ */
64
+ function getListApiUrl(catalogUrl) {
65
+ return `${catalogUrl}/_api/web/lists/getByTitle('${LIST_TITLE}')`;
66
+ }
67
+ /**
68
+ * Create a multiline text (Note) field on the list.
69
+ *
70
+ * @param client - SPHttpClient instance
71
+ * @param listApiUrl - REST API URL for the target list
72
+ * @param fieldTitle - Internal name / title for the new field
73
+ */
74
+ async function createField(client, listApiUrl, fieldTitle) {
75
+ const response = await client.post(`${listApiUrl}/fields`, SPHttpClient.configurations.v1, {
76
+ body: JSON.stringify({
77
+ FieldTypeKind: 3, // Note (multiline text)
78
+ Title: fieldTitle
79
+ })
80
+ });
81
+ if (!response.ok) {
82
+ const errorText = await response.text();
83
+ throw new Error(`Failed to create ${fieldTitle} field: ${response.statusText}. ${errorText}`);
84
+ }
85
+ }
86
+ // ═══════════════════════════════════════════════════════════════════════════
87
+ // HOOK
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ /**
90
+ * Hook to manage tenant-wide key-value pairs backed by a hidden SharePoint list.
91
+ *
92
+ * The store uses a hidden list named `TenantKeyValueStore` in the tenant app catalog.
93
+ * The list is auto-provisioned on the first operation (get, list, save, or remove), with:
94
+ * - Title column: key (indexed, unique)
95
+ * - Value column: multiline text (Note)
96
+ * - Description column: multiline text (Note)
97
+ *
98
+ * This hook provides an alternative to tenant properties (StorageEntity) for scenarios
99
+ * requiring read/write access via REST, since Microsoft has blocked the
100
+ * SetStorageEntity and RemoveStorageEntity REST API endpoints.
101
+ *
102
+ * Features:
103
+ * - CRUD operations (get, list, save, remove) on tenant-scoped key-value data
104
+ * - Auto-provisioning of the hidden list on first operation (any CRUD call)
105
+ * - Smart provisioning: ref-based fast path (zero API calls after first check),
106
+ * field introspection only when list already exists, Title uniqueness only on creation
107
+ * - Smart serialization for primitives, Date, and complex objects
108
+ * - Permission checking (canWrite flag via IsSiteAdmin)
109
+ * - Unique key enforcement (Indexed + EnforceUniqueValues on Title)
110
+ * - Concurrent provisioning protection (mutex ref)
111
+ * - Memory leak safe with mounted state tracking
112
+ * - Idempotent remove (no-op if key or list doesn't exist)
113
+ *
114
+ * Requirements:
115
+ * - Tenant app catalog must be provisioned
116
+ * - Read: Any authenticated user
117
+ * - Write/Remove: Site Collection Administrator role on tenant app catalog site
118
+ *
119
+ * @returns Object with loading states, error states, permission flag, and CRUD functions
120
+ *
121
+ * @example Basic usage
122
+ * ```tsx
123
+ * function TenantConfigPanel() {
124
+ * const store = useSPFxTenantKeyValueStore();
125
+ *
126
+ * const [endpoint, setEndpoint] = React.useState<string>('');
127
+ *
128
+ * React.useEffect(() => {
129
+ * store.get<string>('apiEndpoint').then(item => {
130
+ * if (item) setEndpoint(item.value);
131
+ * });
132
+ * }, []);
133
+ *
134
+ * const handleSave = async () => {
135
+ * await store.save<string>('apiEndpoint', endpoint, 'Production API endpoint');
136
+ * };
137
+ *
138
+ * if (!store.isReady) return <Spinner />;
139
+ *
140
+ * return (
141
+ * <Stack tokens={{ childrenGap: 10 }}>
142
+ * <TextField value={endpoint} onChange={(_, v) => setEndpoint(v ?? '')} />
143
+ * <PrimaryButton onClick={handleSave} disabled={store.isWriting}>
144
+ * {store.isWriting ? 'Saving...' : 'Save'}
145
+ * </PrimaryButton>
146
+ * </Stack>
147
+ * );
148
+ * }
149
+ * ```
150
+ *
151
+ * @example Heterogeneous value types
152
+ * ```tsx
153
+ * const store = useSPFxTenantKeyValueStore();
154
+ *
155
+ * // String
156
+ * await store.save<string>('appVersion', '2.1.0');
157
+ * const ver = await store.get<string>('appVersion');
158
+ *
159
+ * // Number
160
+ * await store.save<number>('maxUploadSize', 10485760);
161
+ * const size = await store.get<number>('maxUploadSize');
162
+ *
163
+ * // Complex object
164
+ * interface FeatureFlags { enableChat: boolean; maxUsers: number; }
165
+ * await store.save<FeatureFlags>('featureFlags', { enableChat: true, maxUsers: 500 });
166
+ * const flags = await store.get<FeatureFlags>('featureFlags');
167
+ * ```
168
+ *
169
+ * @example Dashboard listing all properties
170
+ * ```tsx
171
+ * function AllPropertiesView() {
172
+ * const store = useSPFxTenantKeyValueStore();
173
+ * const [items, setItems] = React.useState<SPFxTenantKeyValueStoreItem<unknown>[]>([]);
174
+ *
175
+ * React.useEffect(() => {
176
+ * store.list().then(setItems);
177
+ * }, []);
178
+ *
179
+ * return (
180
+ * <DetailsList
181
+ * items={items.map(i => ({
182
+ * key: i.key,
183
+ * value: typeof i.value === 'object' ? JSON.stringify(i.value) : String(i.value),
184
+ * description: i.description ?? '',
185
+ * }))}
186
+ * columns={[
187
+ * { key: 'key', name: 'Key', fieldName: 'key', minWidth: 150 },
188
+ * { key: 'value', name: 'Value', fieldName: 'value', minWidth: 200 },
189
+ * { key: 'desc', name: 'Description', fieldName: 'description', minWidth: 200 },
190
+ * ]}
191
+ * />
192
+ * );
193
+ * }
194
+ * ```
195
+ *
196
+ * @example Permission-aware UI
197
+ * ```tsx
198
+ * function TenantStoreManager() {
199
+ * const store = useSPFxTenantKeyValueStore();
200
+ *
201
+ * if (!store.canWrite) {
202
+ * return (
203
+ * <MessageBar messageBarType={MessageBarType.info}>
204
+ * Read-only access. Contact your SharePoint administrator for write permissions.
205
+ * </MessageBar>
206
+ * );
207
+ * }
208
+ *
209
+ * return <EditablePropertyPanel store={store} />;
210
+ * }
211
+ * ```
212
+ */
213
+ export function useSPFxTenantKeyValueStore() {
214
+ const { spHttpClient, discoverAppCatalogUrl, checkWritePermission, isMountedRef } = useAppCatalogUrl();
215
+ // State management
216
+ const [isLoading, setIsLoading] = useState(false);
217
+ const [error, setError] = useState(undefined);
218
+ const [isWriting, setIsWriting] = useState(false);
219
+ const [writeError, setWriteError] = useState(undefined);
220
+ const [canWrite, setCanWrite] = useState(false);
221
+ // Provisioning state
222
+ const listProvisionedRef = useRef(false);
223
+ const provisioningPromiseRef = useRef(undefined);
224
+ const permissionCheckedRef = useRef(false);
225
+ // ─────────────────────────────────────────────────────────────────────────
226
+ // Internal helpers
227
+ // ─────────────────────────────────────────────────────────────────────────
228
+ /**
229
+ * Check and update write permission (executed once)
230
+ */
231
+ const ensurePermissionChecked = useCallback((catalogUrl) => {
232
+ if (permissionCheckedRef.current)
233
+ return;
234
+ permissionCheckedRef.current = true;
235
+ checkWritePermission(catalogUrl)
236
+ .then(hasPermission => {
237
+ if (isMountedRef.current) {
238
+ setCanWrite(hasPermission);
239
+ }
240
+ })
241
+ .catch(() => {
242
+ if (isMountedRef.current) {
243
+ setCanWrite(false);
244
+ }
245
+ });
246
+ }, [checkWritePermission, isMountedRef]);
247
+ /**
248
+ * Ensure the hidden list exists and has all required fields.
249
+ * Uses a ref-based fast path (zero API calls after first successful check)
250
+ * and a mutex to prevent concurrent provisioning.
251
+ *
252
+ * Flow:
253
+ * 1. Fast path: `listProvisionedRef` is true → return immediately
254
+ * 2. Mutex: reuse in-flight promise if already provisioning
255
+ * 3. Lightweight existence check (1 API call): GET list?$select=Id
256
+ * - 200 (exists) → field introspection, create missing fields only
257
+ * - 404 (missing) → full provision: create list + fields + Title uniqueness
258
+ * 4. Mark ref true on success, cleanup mutex on success or failure
259
+ */
260
+ const ensureListReady = useCallback((client, catalogUrl) => {
261
+ // Fast path: already verified — zero API calls
262
+ if (listProvisionedRef.current)
263
+ return Promise.resolve();
264
+ // Mutex: reuse in-flight provisioning promise
265
+ if (provisioningPromiseRef.current)
266
+ return provisioningPromiseRef.current;
267
+ const doProvision = async () => {
268
+ const listApiUrl = getListApiUrl(catalogUrl);
269
+ // ── Step 1: Lightweight existence check (single API call) ──────
270
+ const listResponse = await client.get(`${listApiUrl}?$select=Id`, SPHttpClient.configurations.v1);
271
+ const listExists = listResponse.status !== 404;
272
+ if (listExists && !listResponse.ok) {
273
+ throw new Error(`Failed to check list existence: ${listResponse.statusText}`);
274
+ }
275
+ if (listExists) {
276
+ // ── Step 2: List exists → field introspection ──────────────
277
+ const fieldsResponse = await client.get(`${listApiUrl}/fields?$filter=InternalName eq 'Value' or InternalName eq 'Description'&$select=InternalName`, SPHttpClient.configurations.v1);
278
+ if (!fieldsResponse.ok) {
279
+ throw new Error(`Failed to check list fields: ${fieldsResponse.statusText}`);
280
+ }
281
+ const fields = await fieldsResponse.json();
282
+ const existingFields = fields.value.map(f => f.InternalName);
283
+ const hasValue = existingFields.includes('Value');
284
+ const hasDescription = existingFields.includes('Description');
285
+ // All fields present → list is fully ready
286
+ if (hasValue && hasDescription) {
287
+ return;
288
+ }
289
+ // Create only the missing fields
290
+ if (!hasValue) {
291
+ await createField(client, listApiUrl, 'Value');
292
+ }
293
+ if (!hasDescription) {
294
+ await createField(client, listApiUrl, 'Description');
295
+ }
296
+ // Skip Title uniqueness — list already existed, constraint is either
297
+ // already set or intentionally not re-applied on field repair
298
+ return;
299
+ }
300
+ // ── Step 3: List does not exist → full provision ─────────────
301
+ // 3a. Create list
302
+ const createListResponse = await client.post(`${catalogUrl}/_api/web/lists`, SPHttpClient.configurations.v1, {
303
+ body: JSON.stringify({
304
+ BaseTemplate: 100,
305
+ Title: LIST_TITLE,
306
+ Hidden: true,
307
+ NoCrawl: true
308
+ })
309
+ });
310
+ if (!createListResponse.ok) {
311
+ const errorText = await createListResponse.text();
312
+ throw new Error(`Failed to create list: ${createListResponse.statusText}. ${errorText}`);
313
+ }
314
+ // 3b. Create Value + Description fields (list is fresh — no need to check)
315
+ await createField(client, getListApiUrl(catalogUrl), 'Value');
316
+ await createField(client, getListApiUrl(catalogUrl), 'Description');
317
+ // 3c. Set Title field as Indexed + EnforceUniqueValues (only on creation)
318
+ const titleResp = await client.post(`${getListApiUrl(catalogUrl)}/fields/getByInternalNameOrTitle('Title')`, SPHttpClient.configurations.v1, {
319
+ headers: {
320
+ 'X-HTTP-Method': 'MERGE',
321
+ 'If-Match': '*'
322
+ },
323
+ body: JSON.stringify({
324
+ Indexed: true,
325
+ EnforceUniqueValues: true
326
+ })
327
+ });
328
+ if (!titleResp.ok) {
329
+ // Non-fatal: unique constraint may already exist
330
+ console.warn('Failed to set Title uniqueness constraint. It may already be configured.');
331
+ }
332
+ };
333
+ // All ref mutations are synchronous — no await between read and write.
334
+ // doProvision() never touches refs; .then() callbacks are separate scopes.
335
+ const cleanupMutex = () => { provisioningPromiseRef.current = undefined; };
336
+ const promise = doProvision()
337
+ .then(() => { listProvisionedRef.current = true; cleanupMutex(); })
338
+ .catch((err) => { cleanupMutex(); throw err; });
339
+ provisioningPromiseRef.current = promise;
340
+ return promise;
341
+ }, []);
342
+ // ─────────────────────────────────────────────────────────────────────────
343
+ // Eager initialization: verify/provision list as soon as SPHttpClient is
344
+ // available, overlapping with component render. By the time the user
345
+ // triggers an operation, listProvisionedRef is already true → zero latency.
346
+ // The mutex in ensureListReady handles the race if an operation fires
347
+ // while this init is still in-flight.
348
+ // ─────────────────────────────────────────────────────────────────────────
349
+ useEffect(() => {
350
+ if (!spHttpClient)
351
+ return;
352
+ discoverAppCatalogUrl()
353
+ .then(catalogUrl => {
354
+ ensurePermissionChecked(catalogUrl);
355
+ return ensureListReady(spHttpClient, catalogUrl);
356
+ })
357
+ .catch(() => {
358
+ // Silently fail — operations will retry on demand via their own ensureListReady call
359
+ });
360
+ }, [spHttpClient, discoverAppCatalogUrl, ensurePermissionChecked, ensureListReady]);
361
+ /**
362
+ * Find an item by key, returning the raw list item or undefined.
363
+ */
364
+ const findItemByKey = useCallback(async (client, catalogUrl, key) => {
365
+ const safeKey = escapeODataValue(key);
366
+ const response = await client.get(`${getListApiUrl(catalogUrl)}/items?$filter=Title eq '${safeKey}'&$select=Id,Title,Value,Description&$top=1`, SPHttpClient.configurations.v1);
367
+ if (!response.ok) {
368
+ throw new Error(`Failed to find item: ${response.statusText}`);
369
+ }
370
+ const data = await response.json();
371
+ return data.value.length > 0 ? data.value[0] : undefined;
372
+ }, []);
373
+ /**
374
+ * Map a raw SharePoint list item to an SPFxTenantKeyValueStoreItem.
375
+ */
376
+ function mapItem(raw) {
377
+ return {
378
+ key: raw.Title,
379
+ value: deserializeValue(raw.Value),
380
+ description: raw.Description || undefined,
381
+ id: raw.Id,
382
+ };
383
+ }
384
+ // ─────────────────────────────────────────────────────────────────────────
385
+ // Public operations
386
+ // ─────────────────────────────────────────────────────────────────────────
387
+ const get = useCallback(async (key) => {
388
+ if (!spHttpClient) {
389
+ throw new Error('SPHttpClient not available. Cannot get property.');
390
+ }
391
+ setIsLoading(true);
392
+ setError(undefined);
393
+ try {
394
+ const catalogUrl = await discoverAppCatalogUrl();
395
+ // Ensure list exists (auto-provision if needed; fallback-safe for read-only users)
396
+ try {
397
+ await ensureListReady(spHttpClient, catalogUrl);
398
+ }
399
+ catch {
400
+ // Provisioning may fail for read-only users — treat as "list not yet created"
401
+ return undefined;
402
+ }
403
+ const raw = await findItemByKey(spHttpClient, catalogUrl, key);
404
+ return raw ? mapItem(raw) : undefined;
405
+ }
406
+ catch (err) {
407
+ if (isMountedRef.current) {
408
+ const capturedError = err instanceof Error ? err : new Error(String(err));
409
+ setError(capturedError);
410
+ console.error('Failed to get tenant key-value:', capturedError);
411
+ }
412
+ return undefined;
413
+ }
414
+ finally {
415
+ if (isMountedRef.current) {
416
+ setIsLoading(false);
417
+ }
418
+ }
419
+ }, [spHttpClient, discoverAppCatalogUrl, ensureListReady, findItemByKey, isMountedRef]);
420
+ const list = useCallback(async () => {
421
+ if (!spHttpClient) {
422
+ throw new Error('SPHttpClient not available. Cannot list properties.');
423
+ }
424
+ setIsLoading(true);
425
+ setError(undefined);
426
+ try {
427
+ const catalogUrl = await discoverAppCatalogUrl();
428
+ // Ensure list exists (auto-provision if needed; fallback-safe for read-only users)
429
+ try {
430
+ await ensureListReady(spHttpClient, catalogUrl);
431
+ }
432
+ catch {
433
+ // Provisioning may fail for read-only users — treat as "list not yet created"
434
+ return [];
435
+ }
436
+ const response = await spHttpClient.get(`${getListApiUrl(catalogUrl)}/items?$select=Id,Title,Value,Description&$orderby=Title`, SPHttpClient.configurations.v1);
437
+ if (!response.ok) {
438
+ throw new Error(`Failed to list items: ${response.statusText}`);
439
+ }
440
+ const data = await response.json();
441
+ return data.value.map(raw => mapItem(raw));
442
+ }
443
+ catch (err) {
444
+ if (isMountedRef.current) {
445
+ const capturedError = err instanceof Error ? err : new Error(String(err));
446
+ setError(capturedError);
447
+ console.error('Failed to list tenant key-values:', capturedError);
448
+ }
449
+ return [];
450
+ }
451
+ finally {
452
+ if (isMountedRef.current) {
453
+ setIsLoading(false);
454
+ }
455
+ }
456
+ }, [spHttpClient, discoverAppCatalogUrl, ensureListReady, isMountedRef]);
457
+ const save = useCallback(async (key, value, description) => {
458
+ if (!spHttpClient) {
459
+ throw new Error('SPHttpClient not available. Cannot save property.');
460
+ }
461
+ setIsWriting(true);
462
+ setWriteError(undefined);
463
+ try {
464
+ const catalogUrl = await discoverAppCatalogUrl();
465
+ // Ensure list is ready (auto-provision on first operation)
466
+ await ensureListReady(spHttpClient, catalogUrl);
467
+ const serializedValue = serializeValue(value);
468
+ // Check if key already exists
469
+ const existing = await findItemByKey(spHttpClient, catalogUrl, key);
470
+ if (existing) {
471
+ // Update existing item
472
+ const updateResponse = await spHttpClient.post(`${getListApiUrl(catalogUrl)}/items(${existing.Id})`, SPHttpClient.configurations.v1, {
473
+ headers: {
474
+ 'X-HTTP-Method': 'MERGE',
475
+ 'If-Match': '*'
476
+ },
477
+ body: JSON.stringify({
478
+ Value: serializedValue,
479
+ Description: description ?? existing.Description ?? ''
480
+ })
481
+ });
482
+ if (!updateResponse.ok) {
483
+ const errorText = await updateResponse.text();
484
+ throw new Error(`Failed to update item: ${updateResponse.statusText}. ${errorText}`);
485
+ }
486
+ }
487
+ else {
488
+ // Create new item
489
+ const createResponse = await spHttpClient.post(`${getListApiUrl(catalogUrl)}/items`, SPHttpClient.configurations.v1, {
490
+ body: JSON.stringify({
491
+ Title: key,
492
+ Value: serializedValue,
493
+ Description: description ?? ''
494
+ })
495
+ });
496
+ if (!createResponse.ok) {
497
+ const errorText = await createResponse.text();
498
+ throw new Error(`Failed to create item: ${createResponse.statusText}. ${errorText}`);
499
+ }
500
+ }
501
+ }
502
+ catch (err) {
503
+ if (isMountedRef.current) {
504
+ const capturedError = err instanceof Error ? err : new Error(String(err));
505
+ setWriteError(capturedError);
506
+ console.error('Failed to save tenant key-value:', capturedError);
507
+ }
508
+ throw err;
509
+ }
510
+ finally {
511
+ if (isMountedRef.current) {
512
+ setIsWriting(false);
513
+ }
514
+ }
515
+ }, [spHttpClient, discoverAppCatalogUrl, ensureListReady, findItemByKey, isMountedRef]);
516
+ const remove = useCallback(async (key) => {
517
+ if (!spHttpClient) {
518
+ throw new Error('SPHttpClient not available. Cannot remove property.');
519
+ }
520
+ setIsWriting(true);
521
+ setWriteError(undefined);
522
+ try {
523
+ const catalogUrl = await discoverAppCatalogUrl();
524
+ // Ensure list is ready (auto-provision on first operation)
525
+ await ensureListReady(spHttpClient, catalogUrl);
526
+ // Find the item
527
+ const existing = await findItemByKey(spHttpClient, catalogUrl, key);
528
+ if (!existing) {
529
+ return; // Item doesn't exist — idempotent no-op
530
+ }
531
+ // Delete the item
532
+ const deleteResponse = await spHttpClient.post(`${getListApiUrl(catalogUrl)}/items(${existing.Id})`, SPHttpClient.configurations.v1, {
533
+ headers: {
534
+ 'X-HTTP-Method': 'DELETE',
535
+ 'If-Match': '*'
536
+ }
537
+ });
538
+ if (!deleteResponse.ok) {
539
+ const errorText = await deleteResponse.text();
540
+ throw new Error(`Failed to remove item: ${deleteResponse.statusText}. ${errorText}`);
541
+ }
542
+ }
543
+ catch (err) {
544
+ if (isMountedRef.current) {
545
+ const capturedError = err instanceof Error ? err : new Error(String(err));
546
+ setWriteError(capturedError);
547
+ console.error('Failed to remove tenant key-value:', capturedError);
548
+ }
549
+ throw err;
550
+ }
551
+ finally {
552
+ if (isMountedRef.current) {
553
+ setIsWriting(false);
554
+ }
555
+ }
556
+ }, [spHttpClient, discoverAppCatalogUrl, ensureListReady, findItemByKey, isMountedRef]);
557
+ // Computed: ready when client is available
558
+ const isReady = spHttpClient !== undefined;
559
+ return {
560
+ isLoading,
561
+ error,
562
+ isWriting,
563
+ writeError,
564
+ canWrite,
565
+ isReady,
566
+ get,
567
+ list,
568
+ save,
569
+ remove,
570
+ };
571
+ }
572
+ //# sourceMappingURL=useSPFxTenantKeyValueStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSPFxTenantKeyValueStore.js","sourceRoot":"","sources":["../../src/hooks/useSPFxTenantKeyValueStore.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,sGAAsG;AAEtG,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEzC,8EAA8E;AAC9E,2DAA2D;AAC3D,8EAA8E;AAE9E;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IACtD,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC;IAC1B,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpF,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAI,QAAgB;IACzC,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAM,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,QAAwB,CAAC;IACpC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,UAAkB;IACrC,OAAO,GAAG,UAAU,+BAA+B,UAAU,IAAI,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,WAAW,CACtB,MAAoB,EACpB,UAAkB,EAClB,UAAkB;IAElB,MAAM,QAAQ,GAAyB,MAAM,MAAM,CAAC,IAAI,CACpD,GAAG,UAAU,SAAS,EACtB,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;QACI,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACjB,aAAa,EAAE,CAAC,EAAE,wBAAwB;YAC1C,KAAK,EAAE,UAAU;SACpB,CAAC;KACL,CACJ,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,oBAAoB,UAAU,WAAW,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;IAClG,CAAC;AACL,CAAC;AAuKD,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2HG;AACH,MAAM,UAAU,0BAA0B;IACtC,MAAM,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAEvG,mBAAmB;IACnB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAoB,SAAS,CAAC,CAAC;IACjE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAoB,SAAS,CAAC,CAAC;IAC3E,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAEzD,qBAAqB;IACrB,MAAM,kBAAkB,GAAG,MAAM,CAAU,KAAK,CAAC,CAAC;IAClD,MAAM,sBAAsB,GAAG,MAAM,CAA4B,SAAS,CAAC,CAAC;IAC5E,MAAM,oBAAoB,GAAG,MAAM,CAAU,KAAK,CAAC,CAAC;IAEpD,4EAA4E;IAC5E,mBAAmB;IACnB,4EAA4E;IAI5E;;OAEG;IACH,MAAM,uBAAuB,GAAG,WAAW,CAAC,CAAC,UAAkB,EAAQ,EAAE;QACrE,IAAI,oBAAoB,CAAC,OAAO;YAAE,OAAO;QACzC,oBAAoB,CAAC,OAAO,GAAG,IAAI,CAAC;QACpC,oBAAoB,CAAC,UAAU,CAAC;aAC3B,IAAI,CAAC,aAAa,CAAC,EAAE;YAClB,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,WAAW,CAAC,aAAa,CAAC,CAAC;YAC/B,CAAC;QACL,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACR,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,WAAW,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,CAAC,CAAC;IACX,CAAC,EAAE,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC,CAAC;IAIzC;;;;;;;;;;;;OAYG;IACH,MAAM,eAAe,GAAG,WAAW,CAAC,CAChC,MAAoB,EACpB,UAAkB,EACL,EAAE;QACf,+CAA+C;QAC/C,IAAI,kBAAkB,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAEzD,8CAA8C;QAC9C,IAAI,sBAAsB,CAAC,OAAO;YAAE,OAAO,sBAAsB,CAAC,OAAO,CAAC;QAE1E,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;YAC1C,MAAM,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAE7C,kEAAkE;YAClE,MAAM,YAAY,GAAyB,MAAM,MAAM,CAAC,GAAG,CACvD,GAAG,UAAU,aAAa,EAC1B,YAAY,CAAC,cAAc,CAAC,EAAE,CACjC,CAAC;YAEF,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,KAAK,GAAG,CAAC;YAE/C,IAAI,UAAU,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,mCAAmC,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC;YAClF,CAAC;YAED,IAAI,UAAU,EAAE,CAAC;gBACb,8DAA8D;gBAC9D,MAAM,cAAc,GAAyB,MAAM,MAAM,CAAC,GAAG,CACzD,GAAG,UAAU,+FAA+F,EAC5G,YAAY,CAAC,cAAc,CAAC,EAAE,CACjC,CAAC;gBAEF,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;oBACrB,MAAM,IAAI,KAAK,CAAC,gCAAgC,cAAc,CAAC,UAAU,EAAE,CAAC,CAAC;gBACjF,CAAC;gBAED,MAAM,MAAM,GAAyB,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;gBACjE,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;gBAC7D,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAClD,MAAM,cAAc,GAAG,cAAc,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;gBAE9D,2CAA2C;gBAC3C,IAAI,QAAQ,IAAI,cAAc,EAAE,CAAC;oBAC7B,OAAO;gBACX,CAAC;gBAED,iCAAiC;gBACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACZ,MAAM,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;gBACnD,CAAC;gBACD,IAAI,CAAC,cAAc,EAAE,CAAC;oBAClB,MAAM,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;gBACzD,CAAC;gBAED,qEAAqE;gBACrE,8DAA8D;gBAC9D,OAAO;YACX,CAAC;YAED,gEAAgE;YAChE,kBAAkB;YAClB,MAAM,kBAAkB,GAAyB,MAAM,MAAM,CAAC,IAAI,CAC9D,GAAG,UAAU,iBAAiB,EAC9B,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;gBACI,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACjB,YAAY,EAAE,GAAG;oBACjB,KAAK,EAAE,UAAU;oBACjB,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,IAAI;iBAChB,CAAC;aACL,CACJ,CAAC;YAEF,IAAI,CAAC,kBAAkB,CAAC,EAAE,EAAE,CAAC;gBACzB,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CAAC,0BAA0B,kBAAkB,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;YAC7F,CAAC;YAED,2EAA2E;YAC3E,MAAM,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;YAC9D,MAAM,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;YAEpE,0EAA0E;YAC1E,MAAM,SAAS,GAAyB,MAAM,MAAM,CAAC,IAAI,CACrD,GAAG,aAAa,CAAC,UAAU,CAAC,2CAA2C,EACvE,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;gBACI,OAAO,EAAE;oBACL,eAAe,EAAE,OAAO;oBACxB,UAAU,EAAE,GAAG;iBAClB;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACjB,OAAO,EAAE,IAAI;oBACb,mBAAmB,EAAE,IAAI;iBAC5B,CAAC;aACL,CACJ,CAAC;YAEF,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;gBAChB,iDAAiD;gBACjD,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;YAC7F,CAAC;QACL,CAAC,CAAC;QAEF,uEAAuE;QACvE,2EAA2E;QAC3E,MAAM,YAAY,GAAG,GAAS,EAAE,GAAG,sBAAsB,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACjF,MAAM,OAAO,GAAG,WAAW,EAAE;aACxB,IAAI,CAAC,GAAG,EAAE,GAAG,kBAAkB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;aAClE,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE,GAAG,YAAY,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,sBAAsB,CAAC,OAAO,GAAG,OAAO,CAAC;QACzC,OAAO,OAAO,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,4EAA4E;IAC5E,yEAAyE;IACzE,qEAAqE;IACrE,4EAA4E;IAC5E,sEAAsE;IACtE,sCAAsC;IACtC,4EAA4E;IAC5E,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,YAAY;YAAE,OAAO;QAE1B,qBAAqB,EAAE;aAClB,IAAI,CAAC,UAAU,CAAC,EAAE;YACf,uBAAuB,CAAC,UAAU,CAAC,CAAC;YACpC,OAAO,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QACrD,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACR,qFAAqF;QACzF,CAAC,CAAC,CAAC;IACX,CAAC,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,eAAe,CAAC,CAAC,CAAC;IAEpF;;OAEG;IACH,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,EACnC,MAAoB,EACpB,UAAkB,EAClB,GAAW,EAC2B,EAAE;QACxC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAyB,MAAM,MAAM,CAAC,GAAG,CACnD,GAAG,aAAa,CAAC,UAAU,CAAC,4BAA4B,OAAO,6CAA6C,EAC5G,YAAY,CAAC,cAAc,CAAC,EAAE,CACjC,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,IAAI,GAAuB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP;;OAEG;IACH,SAAS,OAAO,CAAI,GAAsB;QACtC,OAAO;YACH,GAAG,EAAE,GAAG,CAAC,KAAK;YACd,KAAK,EAAE,gBAAgB,CAAI,GAAG,CAAC,KAAK,CAAC;YACrC,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,SAAS;YACzC,EAAE,EAAE,GAAG,CAAC,EAAE;SACb,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,oBAAoB;IACpB,4EAA4E;IAE5E,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,EAAe,GAAW,EAAuD,EAAE;QAC5G,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACxE,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEpB,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;YAEjD,mFAAmF;YACnF,IAAI,CAAC;gBACD,MAAM,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACpD,CAAC;YAAC,MAAM,CAAC;gBACL,8EAA8E;gBAC9E,OAAO,SAAS,CAAC;YACrB,CAAC;YAED,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAE/D,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,QAAQ,CAAC,aAAa,CAAC,CAAC;gBACxB,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,aAAa,CAAC,CAAC;YACpE,CAAC;YACD,OAAO,SAAS,CAAC;QACrB,CAAC;gBAAS,CAAC;YACP,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAExF,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAqD,EAAE;QACjF,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAC3E,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEpB,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;YAEjD,mFAAmF;YACnF,IAAI,CAAC;gBACD,MAAM,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACpD,CAAC;YAAC,MAAM,CAAC;gBACL,8EAA8E;gBAC9E,OAAO,EAAE,CAAC;YACd,CAAC;YAED,MAAM,QAAQ,GAAyB,MAAM,YAAY,CAAC,GAAG,CACzD,GAAG,aAAa,CAAC,UAAU,CAAC,0DAA0D,EACtF,YAAY,CAAC,cAAc,CAAC,EAAE,CACjC,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,MAAM,IAAI,GAAuB,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAU,GAAG,CAAC,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,QAAQ,CAAC,aAAa,CAAC,CAAC;gBACxB,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,aAAa,CAAC,CAAC;YACtE,CAAC;YACD,OAAO,EAAE,CAAC;QACd,CAAC;gBAAS,CAAC;YACP,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC;IAEzE,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAC1B,GAAW,EACX,KAAQ,EACR,WAAoB,EACP,EAAE;QACf,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACzE,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,aAAa,CAAC,SAAS,CAAC,CAAC;QAEzB,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;YAEjD,2DAA2D;YAC3D,MAAM,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAEhD,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YAE9C,8BAA8B;YAC9B,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAEpE,IAAI,QAAQ,EAAE,CAAC;gBACX,uBAAuB;gBACvB,MAAM,cAAc,GAAyB,MAAM,YAAY,CAAC,IAAI,CAChE,GAAG,aAAa,CAAC,UAAU,CAAC,UAAU,QAAQ,CAAC,EAAE,GAAG,EACpD,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;oBACI,OAAO,EAAE;wBACL,eAAe,EAAE,OAAO;wBACxB,UAAU,EAAE,GAAG;qBAClB;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACjB,KAAK,EAAE,eAAe;wBACtB,WAAW,EAAE,WAAW,IAAI,QAAQ,CAAC,WAAW,IAAI,EAAE;qBACzD,CAAC;iBACL,CACJ,CAAC;gBAEF,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;oBACrB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;oBAC9C,MAAM,IAAI,KAAK,CAAC,0BAA0B,cAAc,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;gBACzF,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,kBAAkB;gBAClB,MAAM,cAAc,GAAyB,MAAM,YAAY,CAAC,IAAI,CAChE,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,EACpC,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;oBACI,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACjB,KAAK,EAAE,GAAG;wBACV,KAAK,EAAE,eAAe;wBACtB,WAAW,EAAE,WAAW,IAAI,EAAE;qBACjC,CAAC;iBACL,CACJ,CAAC;gBAEF,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;oBACrB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;oBAC9C,MAAM,IAAI,KAAK,CAAC,0BAA0B,cAAc,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;gBACzF,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,aAAa,CAAC,aAAa,CAAC,CAAC;gBAC7B,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,aAAa,CAAC,CAAC;YACrE,CAAC;YACD,MAAM,GAAG,CAAC;QACd,CAAC;gBAAS,CAAC;YACP,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAExF,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,GAAW,EAAiB,EAAE;QAC5D,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAC3E,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,aAAa,CAAC,SAAS,CAAC,CAAC;QAEzB,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;YAEjD,2DAA2D;YAC3D,MAAM,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAEhD,gBAAgB;YAChB,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAEpE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACZ,OAAO,CAAC,wCAAwC;YACpD,CAAC;YAED,kBAAkB;YAClB,MAAM,cAAc,GAAyB,MAAM,YAAY,CAAC,IAAI,CAChE,GAAG,aAAa,CAAC,UAAU,CAAC,UAAU,QAAQ,CAAC,EAAE,GAAG,EACpD,YAAY,CAAC,cAAc,CAAC,EAAE,EAC9B;gBACI,OAAO,EAAE;oBACL,eAAe,EAAE,QAAQ;oBACzB,UAAU,EAAE,GAAG;iBAClB;aACJ,CACJ,CAAC;YAEF,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;gBAC9C,MAAM,IAAI,KAAK,CAAC,0BAA0B,cAAc,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;YACzF,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,aAAa,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,aAAa,CAAC,aAAa,CAAC,CAAC;gBAC7B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,aAAa,CAAC,CAAC;YACvE,CAAC;YACD,MAAM,GAAG,CAAC;QACd,CAAC;gBAAS,CAAC;YACP,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAExF,2CAA2C;IAC3C,MAAM,OAAO,GAAG,YAAY,KAAK,SAAS,CAAC;IAE3C,OAAO;QACH,SAAS;QACT,KAAK;QACL,SAAS;QACT,UAAU;QACV,QAAQ;QACR,OAAO;QACP,GAAG;QACH,IAAI;QACJ,IAAI;QACJ,MAAM;KACT,CAAC;AACN,CAAC"}