@object-ui/app-shell 3.3.0 → 3.3.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.
Files changed (175) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +22 -2
  3. package/dist/chrome/CommandPalette.d.ts +16 -0
  4. package/dist/chrome/CommandPalette.js +74 -0
  5. package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
  6. package/dist/chrome/ConditionalAuthWrapper.js +135 -0
  7. package/dist/chrome/ConsoleToaster.d.ts +11 -0
  8. package/dist/chrome/ConsoleToaster.js +28 -0
  9. package/dist/chrome/ErrorBoundary.d.ts +39 -0
  10. package/dist/chrome/ErrorBoundary.js +64 -0
  11. package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
  12. package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
  13. package/dist/chrome/LoadingScreen.d.ts +12 -0
  14. package/dist/chrome/LoadingScreen.js +44 -0
  15. package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
  16. package/dist/chrome/OnboardingWalkthrough.js +11 -0
  17. package/dist/chrome/ThemeProvider.d.ts +13 -0
  18. package/dist/chrome/ThemeProvider.js +37 -0
  19. package/dist/chrome/index.d.ts +8 -0
  20. package/dist/chrome/index.js +8 -0
  21. package/dist/console/AppContent.d.ts +26 -0
  22. package/dist/console/AppContent.js +266 -0
  23. package/dist/console/ConsoleShell.d.ts +66 -0
  24. package/dist/console/ConsoleShell.js +97 -0
  25. package/dist/console/auth/AuthPageLayout.d.ts +9 -0
  26. package/dist/console/auth/AuthPageLayout.js +4 -0
  27. package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
  28. package/dist/console/auth/ForgotPasswordPage.js +23 -0
  29. package/dist/console/auth/LoginPage.d.ts +4 -0
  30. package/dist/console/auth/LoginPage.js +24 -0
  31. package/dist/console/auth/RegisterPage.d.ts +4 -0
  32. package/dist/console/auth/RegisterPage.js +29 -0
  33. package/dist/console/auth/index.d.ts +4 -0
  34. package/dist/console/auth/index.js +4 -0
  35. package/dist/console/home/AppCard.d.ts +14 -0
  36. package/dist/console/home/AppCard.js +36 -0
  37. package/dist/console/home/HomeLayout.d.ts +15 -0
  38. package/dist/console/home/HomeLayout.js +20 -0
  39. package/dist/console/home/HomePage.d.ts +18 -0
  40. package/dist/console/home/HomePage.js +54 -0
  41. package/dist/console/home/QuickActions.d.ts +9 -0
  42. package/dist/console/home/QuickActions.js +53 -0
  43. package/dist/console/home/RecentApps.d.ts +13 -0
  44. package/dist/console/home/RecentApps.js +32 -0
  45. package/dist/console/home/StarredApps.d.ts +13 -0
  46. package/dist/console/home/StarredApps.js +29 -0
  47. package/dist/console/home/index.d.ts +11 -0
  48. package/dist/console/home/index.js +11 -0
  49. package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
  50. package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
  51. package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
  52. package/dist/console/organizations/OrganizationsLayout.js +20 -0
  53. package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
  54. package/dist/console/organizations/OrganizationsPage.js +76 -0
  55. package/dist/console/organizations/index.d.ts +8 -0
  56. package/dist/console/organizations/index.js +8 -0
  57. package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
  58. package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
  59. package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
  60. package/dist/console/organizations/manage/InvitationsPage.js +107 -0
  61. package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
  62. package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
  63. package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
  64. package/dist/console/organizations/manage/MembersPage.js +89 -0
  65. package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
  66. package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
  67. package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
  68. package/dist/console/organizations/manage/SettingsPage.js +141 -0
  69. package/dist/console/organizations/manage/orgContext.d.ts +5 -0
  70. package/dist/console/organizations/manage/orgContext.js +4 -0
  71. package/dist/context/FavoritesProvider.d.ts +50 -0
  72. package/dist/context/FavoritesProvider.js +129 -0
  73. package/dist/context/NavigationContext.d.ts +46 -0
  74. package/dist/context/NavigationContext.js +59 -0
  75. package/dist/context/index.d.ts +3 -0
  76. package/dist/context/index.js +2 -0
  77. package/dist/hooks/index.d.ts +7 -0
  78. package/dist/hooks/index.js +7 -0
  79. package/dist/hooks/useFavorites.d.ts +13 -0
  80. package/dist/hooks/useFavorites.js +13 -0
  81. package/dist/hooks/useMetadataService.d.ts +13 -0
  82. package/dist/hooks/useMetadataService.js +18 -0
  83. package/dist/hooks/useNavPins.d.ts +16 -0
  84. package/dist/hooks/useNavPins.js +72 -0
  85. package/dist/hooks/useNavigationSync.d.ts +82 -0
  86. package/dist/hooks/useNavigationSync.js +495 -0
  87. package/dist/hooks/useObjectActions.d.ts +38 -0
  88. package/dist/hooks/useObjectActions.js +92 -0
  89. package/dist/hooks/useRecentItems.d.ts +21 -0
  90. package/dist/hooks/useRecentItems.js +50 -0
  91. package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
  92. package/dist/hooks/useResponsiveSidebar.js +28 -0
  93. package/dist/index.d.ts +29 -0
  94. package/dist/index.js +35 -0
  95. package/dist/layout/ActivityFeed.d.ts +22 -0
  96. package/dist/layout/ActivityFeed.js +57 -0
  97. package/dist/layout/AppHeader.d.ts +33 -0
  98. package/dist/layout/AppHeader.js +182 -0
  99. package/dist/layout/AppSidebar.d.ts +14 -0
  100. package/dist/layout/AppSidebar.js +202 -0
  101. package/dist/layout/AppSwitcher.d.ts +12 -0
  102. package/dist/layout/AppSwitcher.js +24 -0
  103. package/dist/layout/AuthPageLayout.d.ts +9 -0
  104. package/dist/layout/AuthPageLayout.js +4 -0
  105. package/dist/layout/ConnectionStatus.d.ts +14 -0
  106. package/dist/layout/ConnectionStatus.js +36 -0
  107. package/dist/layout/ConsoleFloatingChatbot.d.ts +10 -0
  108. package/dist/layout/ConsoleFloatingChatbot.js +27 -0
  109. package/dist/layout/ConsoleLayout.d.ts +22 -0
  110. package/dist/layout/ConsoleLayout.js +49 -0
  111. package/dist/layout/LocaleSwitcher.d.ts +7 -0
  112. package/dist/layout/LocaleSwitcher.js +27 -0
  113. package/dist/layout/ModeToggle.d.ts +1 -0
  114. package/dist/layout/ModeToggle.js +10 -0
  115. package/dist/layout/UnifiedSidebar.d.ts +27 -0
  116. package/dist/layout/UnifiedSidebar.js +186 -0
  117. package/dist/layout/index.d.ts +11 -0
  118. package/dist/layout/index.js +10 -0
  119. package/dist/providers/AdapterProvider.d.ts +21 -0
  120. package/dist/providers/AdapterProvider.js +52 -0
  121. package/dist/providers/ExpressionProvider.d.ts +50 -0
  122. package/dist/providers/ExpressionProvider.js +67 -0
  123. package/dist/providers/MetadataProvider.d.ts +18 -0
  124. package/dist/providers/MetadataProvider.js +324 -0
  125. package/dist/services/MetadataService.d.ts +119 -0
  126. package/dist/services/MetadataService.js +211 -0
  127. package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
  128. package/dist/skeletons/SkeletonDashboard.js +12 -0
  129. package/dist/skeletons/SkeletonDetail.d.ts +15 -0
  130. package/dist/skeletons/SkeletonDetail.js +12 -0
  131. package/dist/skeletons/SkeletonGrid.d.ts +15 -0
  132. package/dist/skeletons/SkeletonGrid.js +12 -0
  133. package/dist/skeletons/index.d.ts +3 -0
  134. package/dist/skeletons/index.js +3 -0
  135. package/dist/utils/getIcon.d.ts +24 -0
  136. package/dist/utils/getIcon.js +87 -0
  137. package/dist/utils/index.d.ts +33 -0
  138. package/dist/utils/index.js +62 -0
  139. package/dist/utils/metadataConverters.d.ts +78 -0
  140. package/dist/utils/metadataConverters.js +74 -0
  141. package/dist/views/ActionConfirmDialog.d.ts +22 -0
  142. package/dist/views/ActionConfirmDialog.js +22 -0
  143. package/dist/views/ActionParamDialog.d.ts +23 -0
  144. package/dist/views/ActionParamDialog.js +67 -0
  145. package/dist/views/DashboardView.d.ts +9 -0
  146. package/dist/views/DashboardView.js +357 -0
  147. package/dist/views/DesignDrawer.d.ts +28 -0
  148. package/dist/views/DesignDrawer.js +51 -0
  149. package/dist/views/MetadataInspector.d.ts +40 -0
  150. package/dist/views/MetadataInspector.js +69 -0
  151. package/dist/views/ObjectView.d.ts +11 -0
  152. package/dist/views/ObjectView.js +769 -0
  153. package/dist/views/PageView.d.ts +6 -0
  154. package/dist/views/PageView.js +49 -0
  155. package/dist/views/RecordDetailView.d.ts +14 -0
  156. package/dist/views/RecordDetailView.js +508 -0
  157. package/dist/views/ReportView.d.ts +4 -0
  158. package/dist/views/ReportView.js +285 -0
  159. package/dist/views/SearchResultsPage.d.ts +9 -0
  160. package/dist/views/SearchResultsPage.js +107 -0
  161. package/dist/views/ViewConfigPanel.d.ts +56 -0
  162. package/dist/views/ViewConfigPanel.js +91 -0
  163. package/dist/views/index.d.ts +11 -0
  164. package/dist/views/index.js +11 -0
  165. package/package.json +59 -11
  166. package/.turbo/turbo-build.log +0 -4
  167. package/src/components/AppShell.tsx +0 -31
  168. package/src/components/DashboardRenderer.tsx +0 -36
  169. package/src/components/FormRenderer.tsx +0 -71
  170. package/src/components/ObjectRenderer.tsx +0 -122
  171. package/src/components/PageRenderer.tsx +0 -28
  172. package/src/index.ts +0 -20
  173. package/src/types.ts +0 -78
  174. package/tsconfig.json +0 -20
  175. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,324 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useRef, useMemo, } from 'react';
3
+ import { MetadataCtx, useMetadata } from '@object-ui/react';
4
+ export { useMetadataItem } from '@object-ui/react';
5
+ export { useMetadata };
6
+ // ---------------------------------------------------------------------------
7
+ // Constants
8
+ // ---------------------------------------------------------------------------
9
+ const DEFAULT_TTL_MS = 5 * 60 * 1000;
10
+ const EAGER_TYPES = ['app'];
11
+ const TYPE_BY_STATE_KEY = {
12
+ apps: 'app',
13
+ objects: 'object',
14
+ dashboards: 'dashboard',
15
+ reports: 'report',
16
+ pages: 'page',
17
+ };
18
+ const SESSION_STORAGE_PREFIX = 'objectui:metadata:';
19
+ function isDev() {
20
+ try {
21
+ const meta = import.meta;
22
+ if (meta && meta.env && typeof meta.env.MODE === 'string') {
23
+ return meta.env.MODE !== 'production';
24
+ }
25
+ }
26
+ catch {
27
+ /* import.meta unavailable */
28
+ }
29
+ if (typeof process !== 'undefined' && process.env && typeof process.env.NODE_ENV === 'string') {
30
+ return process.env.NODE_ENV !== 'production';
31
+ }
32
+ return false;
33
+ }
34
+ const DEV = isDev();
35
+ function debug(...args) {
36
+ if (DEV) {
37
+ // eslint-disable-next-line no-console
38
+ console.debug('[MetadataProvider]', ...args);
39
+ }
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Helpers
43
+ // ---------------------------------------------------------------------------
44
+ function extractItems(res) {
45
+ if (res && typeof res === 'object' && 'items' in res && Array.isArray(res.items)) {
46
+ return res.items;
47
+ }
48
+ return [];
49
+ }
50
+ function extractItem(res) {
51
+ if (res == null)
52
+ return null;
53
+ if (typeof res === 'object' && 'item' in res) {
54
+ return res.item ?? null;
55
+ }
56
+ return res;
57
+ }
58
+ function isNamedItem(item) {
59
+ return (!!item &&
60
+ typeof item === 'object' &&
61
+ 'name' in item &&
62
+ typeof item.name === 'string');
63
+ }
64
+ function emptyEntry() {
65
+ return {
66
+ status: 'idle',
67
+ items: [],
68
+ byName: new Map(),
69
+ error: null,
70
+ fetchedAt: 0,
71
+ promise: null,
72
+ };
73
+ }
74
+ function loadFromSession(type) {
75
+ if (typeof sessionStorage === 'undefined')
76
+ return null;
77
+ try {
78
+ const raw = sessionStorage.getItem(SESSION_STORAGE_PREFIX + type);
79
+ if (!raw)
80
+ return null;
81
+ const parsed = JSON.parse(raw);
82
+ return Array.isArray(parsed) ? parsed : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ function saveToSession(type, items) {
89
+ if (typeof sessionStorage === 'undefined')
90
+ return;
91
+ try {
92
+ sessionStorage.setItem(SESSION_STORAGE_PREFIX + type, JSON.stringify(items));
93
+ }
94
+ catch {
95
+ /* quota or serialization failure */
96
+ }
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Provider
100
+ // ---------------------------------------------------------------------------
101
+ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }) {
102
+ const cacheRef = useRef(new Map());
103
+ const itemPromisesRef = useRef(new Map());
104
+ const adapterRef = useRef(adapter);
105
+ adapterRef.current = adapter;
106
+ const [version, setVersion] = useState(0);
107
+ // Defer state bumps so they never occur synchronously during a consumer's
108
+ // render phase. `ensureType` may be invoked from inside `useMemo` getters
109
+ // (e.g. when a list-page renders and triggers a lazy fetch), and React
110
+ // forbids cross-component setState during render. queueMicrotask schedules
111
+ // the update after the current render has committed.
112
+ const bump = useCallback(() => {
113
+ if (typeof queueMicrotask === 'function') {
114
+ queueMicrotask(() => setVersion(v => v + 1));
115
+ }
116
+ else {
117
+ Promise.resolve().then(() => setVersion(v => v + 1));
118
+ }
119
+ }, []);
120
+ const getEntry = useCallback((type) => {
121
+ let entry = cacheRef.current.get(type);
122
+ if (!entry) {
123
+ entry = emptyEntry();
124
+ cacheRef.current.set(type, entry);
125
+ }
126
+ return entry;
127
+ }, []);
128
+ const ensureType = useCallback((type) => {
129
+ const entry = getEntry(type);
130
+ if (entry.promise)
131
+ return entry.promise;
132
+ if (entry.status === 'ready' && Date.now() - entry.fetchedAt < ttlMs) {
133
+ debug(`cache hit (fresh) type=${type} items=${entry.items.length}`);
134
+ return Promise.resolve(entry.items);
135
+ }
136
+ const started = Date.now();
137
+ entry.status = 'loading';
138
+ entry.error = null;
139
+ const client = adapterRef.current.getClient();
140
+ const promise = client.meta
141
+ .getItems(type)
142
+ .then((res) => {
143
+ const items = extractItems(res);
144
+ entry.items = items;
145
+ entry.status = 'ready';
146
+ entry.error = null;
147
+ entry.fetchedAt = Date.now();
148
+ entry.promise = null;
149
+ entry.byName.clear();
150
+ for (const it of items) {
151
+ if (isNamedItem(it)) {
152
+ entry.byName.set(it.name, it);
153
+ }
154
+ }
155
+ if (type === 'app')
156
+ saveToSession(type, items);
157
+ debug(`fetched type=${type} items=${items.length} in ${Date.now() - started}ms`);
158
+ bump();
159
+ return items;
160
+ })
161
+ .catch((err) => {
162
+ const error = err instanceof Error ? err : new Error(String(err));
163
+ entry.status = 'error';
164
+ entry.error = error;
165
+ entry.promise = null;
166
+ debug(`fetch failed type=${type}`, error);
167
+ bump();
168
+ return [];
169
+ });
170
+ entry.promise = promise;
171
+ // No synchronous bump here: the only externally-visible state change at
172
+ // this point is `status: 'loading'`, which no consumer reads. Bumping
173
+ // from inside a render-phase getter (see useMemo below) would trigger
174
+ // React's "Cannot update a component while rendering" warning.
175
+ return promise;
176
+ }, [bump, getEntry, ttlMs]);
177
+ const getItem = useCallback((type, name) => {
178
+ const entry = getEntry(type);
179
+ if (entry.byName.has(name)) {
180
+ debug(`item cache hit type=${type} name=${name}`);
181
+ return Promise.resolve(entry.byName.get(name) ?? null);
182
+ }
183
+ let pending = itemPromisesRef.current.get(type);
184
+ if (!pending) {
185
+ pending = new Map();
186
+ itemPromisesRef.current.set(type, pending);
187
+ }
188
+ const existing = pending.get(name);
189
+ if (existing)
190
+ return existing;
191
+ const started = Date.now();
192
+ const client = adapterRef.current.getClient();
193
+ const promise = client.meta
194
+ .getItem(type, name)
195
+ .then((res) => {
196
+ const item = extractItem(res);
197
+ if (item)
198
+ entry.byName.set(name, item);
199
+ debug(`fetched item type=${type} name=${name} in ${Date.now() - started}ms`);
200
+ pending.delete(name);
201
+ return item;
202
+ })
203
+ .catch((err) => {
204
+ pending.delete(name);
205
+ debug(`fetch item failed type=${type} name=${name}`, err);
206
+ return null;
207
+ });
208
+ pending.set(name, promise);
209
+ return promise;
210
+ }, [getEntry]);
211
+ const refresh = useCallback(async (type) => {
212
+ if (type) {
213
+ const entry = getEntry(type);
214
+ entry.fetchedAt = 0;
215
+ entry.byName.clear();
216
+ await ensureType(type);
217
+ return;
218
+ }
219
+ const types = Array.from(cacheRef.current.keys()).filter(t => cacheRef.current.get(t).status !== 'idle');
220
+ await Promise.all(types.map(t => {
221
+ const entry = cacheRef.current.get(t);
222
+ entry.fetchedAt = 0;
223
+ entry.byName.clear();
224
+ return ensureType(t);
225
+ }));
226
+ }, [ensureType, getEntry]);
227
+ const invalidate = useCallback((type, name) => {
228
+ const entry = cacheRef.current.get(type);
229
+ if (!entry)
230
+ return;
231
+ if (name) {
232
+ entry.byName.delete(name);
233
+ entry.items = entry.items.filter((it) => it?.name !== name);
234
+ debug(`invalidated type=${type} name=${name}`);
235
+ }
236
+ else {
237
+ entry.status = 'idle';
238
+ entry.items = [];
239
+ entry.byName.clear();
240
+ entry.error = null;
241
+ entry.fetchedAt = 0;
242
+ debug(`invalidated type=${type}`);
243
+ }
244
+ bump();
245
+ }, [bump]);
246
+ const [initialLoading, setInitialLoading] = useState(true);
247
+ const [initialError, setInitialError] = useState(null);
248
+ useEffect(() => {
249
+ let cancelled = false;
250
+ const cached = loadFromSession('app');
251
+ if (cached) {
252
+ const entry = getEntry('app');
253
+ entry.items = cached;
254
+ entry.status = 'ready';
255
+ entry.fetchedAt = 0;
256
+ for (const it of cached) {
257
+ if (isNamedItem(it)) {
258
+ entry.byName.set(it.name, it);
259
+ }
260
+ }
261
+ bump();
262
+ setInitialLoading(false);
263
+ }
264
+ (async () => {
265
+ for (const type of EAGER_TYPES) {
266
+ try {
267
+ await ensureType(type);
268
+ if (!cancelled)
269
+ setInitialError(null);
270
+ }
271
+ catch (err) {
272
+ if (!cancelled) {
273
+ setInitialError(err instanceof Error ? err : new Error(String(err)));
274
+ }
275
+ }
276
+ }
277
+ if (!cancelled)
278
+ setInitialLoading(false);
279
+ })();
280
+ return () => {
281
+ cancelled = true;
282
+ };
283
+ }, [adapter, ensureType, getEntry, bump]);
284
+ const value = useMemo(() => {
285
+ void version;
286
+ const readType = (type) => {
287
+ const entry = getEntry(type);
288
+ if (entry.status === 'idle') {
289
+ void ensureType(type);
290
+ }
291
+ return entry.items;
292
+ };
293
+ const getItemsByType = (type) => readType(type);
294
+ const base = {
295
+ apps: getEntry('app').items,
296
+ get objects() {
297
+ return readType(TYPE_BY_STATE_KEY.objects);
298
+ },
299
+ get dashboards() {
300
+ return readType(TYPE_BY_STATE_KEY.dashboards);
301
+ },
302
+ get reports() {
303
+ return readType(TYPE_BY_STATE_KEY.reports);
304
+ },
305
+ get pages() {
306
+ return readType(TYPE_BY_STATE_KEY.pages);
307
+ },
308
+ loading: initialLoading,
309
+ error: initialError ?? getEntry('app').error,
310
+ refresh,
311
+ invalidate,
312
+ ensureType,
313
+ getItem,
314
+ getItemsByType,
315
+ };
316
+ return base;
317
+ }, [version, initialLoading, initialError, ensureType, getItem, getEntry, refresh, invalidate]);
318
+ return _jsx(MetadataCtx.Provider, { value: value, children: children });
319
+ }
320
+ export function useMetadataType(type) {
321
+ const ctx = useMetadata();
322
+ const items = ctx.getItemsByType(type);
323
+ return { items, loading: ctx.loading && type === 'app', error: ctx.error };
324
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * MetadataService
3
+ *
4
+ * Encapsulates CRUD operations for object definitions and field definitions
5
+ * against the ObjectStack metadata API (`client.meta.saveItem`).
6
+ *
7
+ * This service bridges the gap between the local-state-only ObjectManager /
8
+ * FieldDesigner components and the backend persistence layer.
9
+ *
10
+ * Pattern:
11
+ * 1. Optimistically update local UI state
12
+ * 2. Persist via `client.meta.saveItem('object', name, data)`
13
+ * 3. Refresh MetadataProvider cache on success
14
+ * 4. Rollback local state on failure
15
+ *
16
+ * @module services/MetadataService
17
+ */
18
+ import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
19
+ import type { ObjectDefinition, DesignerFieldDefinition } from '@object-ui/types';
20
+ /** Shape written to the metadata API for an object definition. */
21
+ export interface ObjectMetadataPayload {
22
+ name: string;
23
+ label?: string;
24
+ pluralLabel?: string;
25
+ description?: string;
26
+ icon?: string;
27
+ group?: string;
28
+ sortOrder?: number;
29
+ enabled?: boolean;
30
+ fields?: FieldMetadataPayload[];
31
+ relationships?: Array<{
32
+ relatedObject: string;
33
+ type: string;
34
+ label?: string;
35
+ foreignKey?: string;
36
+ }>;
37
+ }
38
+ /** Shape written to the metadata API for a field definition. */
39
+ export interface FieldMetadataPayload {
40
+ name: string;
41
+ label?: string;
42
+ type: string;
43
+ group?: string;
44
+ description?: string;
45
+ required?: boolean;
46
+ unique?: boolean;
47
+ readonly?: boolean;
48
+ hidden?: boolean;
49
+ defaultValue?: string;
50
+ placeholder?: string;
51
+ options?: Array<{
52
+ label: string;
53
+ value: string;
54
+ color?: string;
55
+ }>;
56
+ externalId?: boolean;
57
+ trackHistory?: boolean;
58
+ indexed?: boolean;
59
+ referenceTo?: string;
60
+ formula?: string;
61
+ sortOrder?: number;
62
+ }
63
+ export declare class MetadataService {
64
+ private adapter;
65
+ constructor(adapter: ObjectStackAdapter);
66
+ /**
67
+ * Fetch all items for a given metadata category.
68
+ * Returns the items array from the API response, defaulting to `[]`.
69
+ */
70
+ getItems(category: string): Promise<Record<string, unknown>[]>;
71
+ /**
72
+ * Persist a metadata item (upsert) for any category.
73
+ */
74
+ saveMetadataItem(category: string, name: string, data: Record<string, unknown>): Promise<void>;
75
+ /**
76
+ * Soft-delete a metadata item by persisting it with `enabled: false` and
77
+ * `_deleted: true`. Works for any metadata category.
78
+ */
79
+ deleteMetadataItem(category: string, name: string): Promise<void>;
80
+ /**
81
+ * Persist an object definition to the backend.
82
+ * Works for both create and update (the API is an upsert).
83
+ */
84
+ saveObject(obj: ObjectDefinition, existingFields?: FieldMetadataPayload[]): Promise<void>;
85
+ /**
86
+ * Delete an object definition from the backend.
87
+ *
88
+ * NOTE: The ObjectStack metadata API currently exposes `saveItem` but no
89
+ * dedicated `deleteItem`. We persist the object with `enabled: false` so
90
+ * the intent is recorded and the object is hidden from active use.
91
+ * A full hard-delete can be added once the backend supports it.
92
+ */
93
+ deleteObject(objectName: string): Promise<void>;
94
+ /**
95
+ * Persist updated fields for an object.
96
+ *
97
+ * Fetches the current object metadata, replaces its `fields` array with the
98
+ * provided designer fields, and saves the whole object back.
99
+ */
100
+ saveFields(objectName: string, fields: DesignerFieldDefinition[]): Promise<void>;
101
+ /**
102
+ * Detect changes between previous and next object arrays.
103
+ *
104
+ * Returns the single object that was created, updated, or deleted.
105
+ * If multiple objects changed simultaneously the function returns `null`
106
+ * (callers should treat this as a bulk save of the entire array).
107
+ */
108
+ static diffObjects(prev: ObjectDefinition[], next: ObjectDefinition[]): {
109
+ type: 'create' | 'update' | 'delete';
110
+ object: ObjectDefinition;
111
+ } | null;
112
+ /**
113
+ * Detect changes between previous and next field arrays.
114
+ */
115
+ static diffFields(prev: DesignerFieldDefinition[], next: DesignerFieldDefinition[]): {
116
+ type: 'create' | 'update' | 'delete';
117
+ field: DesignerFieldDefinition;
118
+ } | null;
119
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * MetadataService
3
+ *
4
+ * Encapsulates CRUD operations for object definitions and field definitions
5
+ * against the ObjectStack metadata API (`client.meta.saveItem`).
6
+ *
7
+ * This service bridges the gap between the local-state-only ObjectManager /
8
+ * FieldDesigner components and the backend persistence layer.
9
+ *
10
+ * Pattern:
11
+ * 1. Optimistically update local UI state
12
+ * 2. Persist via `client.meta.saveItem('object', name, data)`
13
+ * 3. Refresh MetadataProvider cache on success
14
+ * 4. Rollback local state on failure
15
+ *
16
+ * @module services/MetadataService
17
+ */
18
+ // ---------------------------------------------------------------------------
19
+ // Converters: UI types → API payloads
20
+ // ---------------------------------------------------------------------------
21
+ /** Convert an `ObjectDefinition` (UI) to the API payload shape. */
22
+ function toObjectPayload(obj, fields) {
23
+ return {
24
+ name: obj.name,
25
+ label: obj.label,
26
+ pluralLabel: obj.pluralLabel,
27
+ description: obj.description,
28
+ icon: obj.icon,
29
+ group: obj.group,
30
+ sortOrder: obj.sortOrder,
31
+ enabled: obj.enabled,
32
+ fields,
33
+ relationships: obj.relationships,
34
+ };
35
+ }
36
+ /** Convert a `DesignerFieldDefinition` (UI) to the API payload shape. */
37
+ function toFieldPayload(field) {
38
+ return {
39
+ name: field.name,
40
+ label: field.label,
41
+ type: field.type,
42
+ group: field.group,
43
+ description: field.description,
44
+ required: field.required,
45
+ unique: field.unique,
46
+ readonly: field.readonly,
47
+ hidden: field.hidden,
48
+ defaultValue: field.defaultValue,
49
+ placeholder: field.placeholder,
50
+ options: field.options,
51
+ externalId: field.externalId,
52
+ trackHistory: field.trackHistory,
53
+ indexed: field.indexed,
54
+ referenceTo: field.referenceTo,
55
+ formula: field.formula,
56
+ sortOrder: field.sortOrder,
57
+ };
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Service
61
+ // ---------------------------------------------------------------------------
62
+ export class MetadataService {
63
+ constructor(adapter) {
64
+ Object.defineProperty(this, "adapter", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: adapter
69
+ });
70
+ }
71
+ // -----------------------------------------------------------------------
72
+ // Generic metadata operations (any type)
73
+ // -----------------------------------------------------------------------
74
+ /**
75
+ * Fetch all items for a given metadata category.
76
+ * Returns the items array from the API response, defaulting to `[]`.
77
+ */
78
+ async getItems(category) {
79
+ const client = this.adapter.getClient();
80
+ const res = await client.meta.getItems(category);
81
+ if (res && typeof res === 'object' && 'items' in res && Array.isArray(res.items)) {
82
+ return res.items;
83
+ }
84
+ return [];
85
+ }
86
+ /**
87
+ * Persist a metadata item (upsert) for any category.
88
+ */
89
+ async saveMetadataItem(category, name, data) {
90
+ const client = this.adapter.getClient();
91
+ await client.meta.saveItem(category, name, data);
92
+ this.adapter.invalidateCache(`${category}:${name}`);
93
+ }
94
+ /**
95
+ * Soft-delete a metadata item by persisting it with `enabled: false` and
96
+ * `_deleted: true`. Works for any metadata category.
97
+ */
98
+ async deleteMetadataItem(category, name) {
99
+ const client = this.adapter.getClient();
100
+ await client.meta.saveItem(category, name, { name, enabled: false, _deleted: true });
101
+ this.adapter.invalidateCache(`${category}:${name}`);
102
+ }
103
+ // -----------------------------------------------------------------------
104
+ // Object operations
105
+ // -----------------------------------------------------------------------
106
+ /**
107
+ * Persist an object definition to the backend.
108
+ * Works for both create and update (the API is an upsert).
109
+ */
110
+ async saveObject(obj, existingFields) {
111
+ const client = this.adapter.getClient();
112
+ const payload = toObjectPayload(obj, existingFields);
113
+ await client.meta.saveItem('object', obj.name, payload);
114
+ this.adapter.invalidateCache(`object:${obj.name}`);
115
+ }
116
+ /**
117
+ * Delete an object definition from the backend.
118
+ *
119
+ * NOTE: The ObjectStack metadata API currently exposes `saveItem` but no
120
+ * dedicated `deleteItem`. We persist the object with `enabled: false` so
121
+ * the intent is recorded and the object is hidden from active use.
122
+ * A full hard-delete can be added once the backend supports it.
123
+ */
124
+ async deleteObject(objectName) {
125
+ const client = this.adapter.getClient();
126
+ await client.meta.saveItem('object', objectName, { name: objectName, enabled: false, _deleted: true });
127
+ this.adapter.invalidateCache(`object:${objectName}`);
128
+ }
129
+ // -----------------------------------------------------------------------
130
+ // Field operations (fields are stored as part of their parent object)
131
+ // -----------------------------------------------------------------------
132
+ /**
133
+ * Persist updated fields for an object.
134
+ *
135
+ * Fetches the current object metadata, replaces its `fields` array with the
136
+ * provided designer fields, and saves the whole object back.
137
+ */
138
+ async saveFields(objectName, fields) {
139
+ const client = this.adapter.getClient();
140
+ // Fetch current object metadata to preserve non-field properties
141
+ let existingObject = {};
142
+ try {
143
+ const raw = await client.meta.getItem('object', objectName);
144
+ existingObject = raw?.item ?? raw ?? {};
145
+ }
146
+ catch {
147
+ // Object may not exist yet on the backend; proceed with fields-only save
148
+ }
149
+ const updatedObject = {
150
+ ...existingObject,
151
+ name: objectName,
152
+ fields: fields.map(toFieldPayload),
153
+ };
154
+ await client.meta.saveItem('object', objectName, updatedObject);
155
+ this.adapter.invalidateCache(`object:${objectName}`);
156
+ }
157
+ // -----------------------------------------------------------------------
158
+ // Diff helpers — determine what changed between two arrays
159
+ // -----------------------------------------------------------------------
160
+ /**
161
+ * Detect changes between previous and next object arrays.
162
+ *
163
+ * Returns the single object that was created, updated, or deleted.
164
+ * If multiple objects changed simultaneously the function returns `null`
165
+ * (callers should treat this as a bulk save of the entire array).
166
+ */
167
+ static diffObjects(prev, next) {
168
+ const prevMap = new Map(prev.map((o) => [o.id, o]));
169
+ const nextMap = new Map(next.map((o) => [o.id, o]));
170
+ // Detect creation (exists in next but not prev)
171
+ for (const [id, obj] of nextMap) {
172
+ if (!prevMap.has(id))
173
+ return { type: 'create', object: obj };
174
+ }
175
+ // Detect deletion (exists in prev but not next)
176
+ for (const [id, obj] of prevMap) {
177
+ if (!nextMap.has(id))
178
+ return { type: 'delete', object: obj };
179
+ }
180
+ // Detect update (same id but different content)
181
+ for (const [id, nextObj] of nextMap) {
182
+ const prevObj = prevMap.get(id);
183
+ if (prevObj && JSON.stringify(prevObj) !== JSON.stringify(nextObj)) {
184
+ return { type: 'update', object: nextObj };
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+ /**
190
+ * Detect changes between previous and next field arrays.
191
+ */
192
+ static diffFields(prev, next) {
193
+ const prevMap = new Map(prev.map((f) => [f.id, f]));
194
+ const nextMap = new Map(next.map((f) => [f.id, f]));
195
+ for (const [id, field] of nextMap) {
196
+ if (!prevMap.has(id))
197
+ return { type: 'create', field };
198
+ }
199
+ for (const [id, field] of prevMap) {
200
+ if (!nextMap.has(id))
201
+ return { type: 'delete', field };
202
+ }
203
+ for (const [id, nextField] of nextMap) {
204
+ const prevField = prevMap.get(id);
205
+ if (prevField && JSON.stringify(prevField) !== JSON.stringify(nextField)) {
206
+ return { type: 'update', field: nextField };
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SkeletonDashboard
3
+ *
4
+ * Skeleton loading placeholder for dashboard views.
5
+ * Renders animated pulse cards that mimic a dashboard grid layout.
6
+ * @module
7
+ */
8
+ interface SkeletonDashboardProps {
9
+ /** Number of widget cards to render */
10
+ cards?: number;
11
+ }
12
+ export declare function SkeletonDashboard({ cards }: SkeletonDashboardProps): import("react/jsx-runtime").JSX.Element;
13
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * SkeletonDashboard
4
+ *
5
+ * Skeleton loading placeholder for dashboard views.
6
+ * Renders animated pulse cards that mimic a dashboard grid layout.
7
+ * @module
8
+ */
9
+ import { Skeleton } from '@object-ui/components';
10
+ export function SkeletonDashboard({ cards = 6 }) {
11
+ return (_jsxs("div", { className: "w-full space-y-6 p-4 sm:p-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-56" }), _jsx(Skeleton, { className: "h-4 w-80" })] }), _jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "rounded-lg border p-4 space-y-2", children: [_jsx(Skeleton, { className: "h-4 w-20" }), _jsx(Skeleton, { className: "h-8 w-24" }), _jsx(Skeleton, { className: "h-3 w-16" })] }, `stat-${i}`))) }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: Array.from({ length: cards }).map((_, i) => (_jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-5 w-32" }), _jsx(Skeleton, { className: "h-5 w-5 rounded-full" })] }), _jsx(Skeleton, { className: "h-40 w-full" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3 w-16" }), _jsx(Skeleton, { className: "h-3 w-12" })] })] }, `widget-${i}`))) })] }));
12
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SkeletonDetail
3
+ *
4
+ * Skeleton loading placeholder for record detail views.
5
+ * Renders animated pulse fields that mimic a detail form layout.
6
+ * @module
7
+ */
8
+ interface SkeletonDetailProps {
9
+ /** Number of field rows to render */
10
+ fields?: number;
11
+ /** Number of columns */
12
+ columns?: number;
13
+ }
14
+ export declare function SkeletonDetail({ fields, columns }: SkeletonDetailProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};