@pyric/ui 0.1.0-alpha.11 → 0.1.0-alpha.13

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 (152) hide show
  1. package/package.json +5 -4
  2. package/src/agents/ContextWindowUsage.tsx +514 -0
  3. package/src/agents/EmptyState.tsx +27 -0
  4. package/src/agents/Fold.tsx +64 -0
  5. package/src/agents/Modal.tsx +65 -0
  6. package/src/agents/PulsingDot.tsx +28 -0
  7. package/src/agents/inbrowser-agent-usage.d.ts +141 -0
  8. package/src/agents/index.ts +28 -0
  9. package/src/auth/authApi.ts +72 -0
  10. package/src/auth/claims.ts +63 -0
  11. package/src/auth/components/AuthProviderToggles.tsx +147 -0
  12. package/src/auth/components/AuthSignInHelper.tsx +197 -0
  13. package/src/auth/components/AuthUserForm.tsx +328 -0
  14. package/src/auth/components/AuthUserList.tsx +219 -0
  15. package/src/auth/components/ClaimsField.tsx +50 -0
  16. package/src/auth/components/confirmActions.tsx +114 -0
  17. package/src/auth/controller.ts +173 -0
  18. package/src/auth/hooks/index.ts +36 -0
  19. package/src/auth/hooks/useAuthFlowHelper.ts +55 -0
  20. package/src/auth/hooks/useAuthProviderConfig.ts +139 -0
  21. package/src/auth/hooks/useAuthUserEditor.ts +76 -0
  22. package/src/auth/hooks/useAuthUsers.ts +154 -0
  23. package/src/auth/index.ts +42 -0
  24. package/src/auth/providers.ts +28 -0
  25. package/src/auth/reducers/userEditor.ts +186 -0
  26. package/src/events/components/ActivityActionItems.tsx +136 -0
  27. package/src/events/components/ActivityGrid.tsx +197 -0
  28. package/src/events/components/ActivityGridRow.tsx +65 -0
  29. package/src/events/components/ProposedChangeDiff.tsx +175 -0
  30. package/src/events/components/format.ts +21 -0
  31. package/src/events/components/index.ts +17 -0
  32. package/src/events/digest.ts +630 -0
  33. package/src/events/hooks/index.ts +9 -0
  34. package/src/events/hooks/useActivityDigest.ts +42 -0
  35. package/src/events/hooks/useActivityStream.ts +86 -0
  36. package/src/events/index.ts +39 -0
  37. package/src/events/types.ts +152 -0
  38. package/src/firestore/components/CollectionList.tsx +78 -0
  39. package/src/firestore/components/DeleteWithConfirm.tsx +88 -0
  40. package/src/firestore/components/DocumentEditor.tsx +350 -0
  41. package/src/firestore/components/DocumentList.tsx +217 -0
  42. package/src/firestore/components/DocumentPreview.tsx +265 -0
  43. package/src/firestore/components/FieldRenderer.tsx +25 -0
  44. package/src/firestore/components/QueryBuilder.tsx +181 -0
  45. package/src/firestore/components/ReferencePicker.tsx +212 -0
  46. package/src/firestore/components/TreeEntry.tsx +58 -0
  47. package/src/firestore/components/context.ts +25 -0
  48. package/src/firestore/fieldEditors/array.tsx +30 -0
  49. package/src/firestore/fieldEditors/boolean.tsx +39 -0
  50. package/src/firestore/fieldEditors/bytes.tsx +53 -0
  51. package/src/firestore/fieldEditors/geopoint.tsx +71 -0
  52. package/src/firestore/fieldEditors/map.tsx +33 -0
  53. package/src/firestore/fieldEditors/null.tsx +27 -0
  54. package/src/firestore/fieldEditors/number.tsx +43 -0
  55. package/src/firestore/fieldEditors/reference.tsx +87 -0
  56. package/src/firestore/fieldEditors/registry.ts +45 -0
  57. package/src/firestore/fieldEditors/string.tsx +33 -0
  58. package/src/firestore/fieldEditors/timestamp.tsx +75 -0
  59. package/src/firestore/fieldEditors/types.ts +68 -0
  60. package/src/firestore/fieldEditors/vector.tsx +142 -0
  61. package/src/firestore/firestoreApi.ts +86 -0
  62. package/src/firestore/hooks/coerceError.ts +34 -0
  63. package/src/firestore/hooks/index.ts +46 -0
  64. package/src/firestore/hooks/useCollectionList.ts +102 -0
  65. package/src/firestore/hooks/useDocumentEditor.ts +161 -0
  66. package/src/firestore/hooks/useDocumentList.ts +242 -0
  67. package/src/firestore/hooks/useDocumentSubcollections.ts +86 -0
  68. package/src/firestore/hooks/useFirestoreCollection.ts +51 -0
  69. package/src/firestore/hooks/useFirestoreDoc.ts +55 -0
  70. package/src/firestore/hooks/useQueryBuilder.ts +188 -0
  71. package/src/firestore/hooks/useRecursiveDelete.ts +77 -0
  72. package/src/firestore/hooks/useReferencePicker.ts +228 -0
  73. package/src/firestore/import/parseImport.ts +137 -0
  74. package/src/firestore/index.ts +87 -0
  75. package/src/firestore/reducers/defaults.ts +38 -0
  76. package/src/firestore/reducers/documentEditor.ts +239 -0
  77. package/src/firestore/reducers/tree.ts +140 -0
  78. package/src/firestore/reducers/types.ts +92 -0
  79. package/src/firestore/reducers/validation.ts +129 -0
  80. package/src/firestore/types.ts +231 -0
  81. package/src/firestore/validation/ids.ts +45 -0
  82. package/src/firestore/valueEquality.ts +43 -0
  83. package/src/index.ts +10 -0
  84. package/src/primitives/Badge.tsx +40 -0
  85. package/src/primitives/ConfirmDialog.tsx +137 -0
  86. package/src/primitives/CopyButton.tsx +62 -0
  87. package/src/primitives/JsonView.tsx +151 -0
  88. package/src/primitives/SegmentedControl.tsx +72 -0
  89. package/src/primitives/Toast.tsx +161 -0
  90. package/src/primitives/VirtualList.tsx +104 -0
  91. package/src/primitives/hooks/useContainerSize.ts +53 -0
  92. package/src/primitives/hooks/useUpdateHighlights.ts +109 -0
  93. package/src/primitives/index.ts +39 -0
  94. package/src/primitives/useConfirm.tsx +113 -0
  95. package/src/rtdb/components/RtdbPathBar.tsx +135 -0
  96. package/src/rtdb/components/RtdbTree.tsx +409 -0
  97. package/src/rtdb/editor.ts +79 -0
  98. package/src/rtdb/hooks/useRtdbTree.ts +137 -0
  99. package/src/rtdb/index.ts +66 -0
  100. package/src/rtdb/pathInput.ts +47 -0
  101. package/src/rtdb/reducers/tree.ts +191 -0
  102. package/src/rtdb/rtdbApi.ts +23 -0
  103. package/src/rtdb/values.ts +188 -0
  104. package/src/rules/components/DenialInspector.tsx +227 -0
  105. package/src/rules/components/format.ts +171 -0
  106. package/src/rules/components/index.ts +12 -0
  107. package/src/rules/components/scope.ts +75 -0
  108. package/src/rules/hooks/index.ts +5 -0
  109. package/src/rules/hooks/useDenialTrace.ts +100 -0
  110. package/src/rules/index.ts +24 -0
  111. package/src/rules/types.ts +91 -0
  112. package/src/storage/collisionRename.ts +114 -0
  113. package/src/storage/components/DeleteSelectionWithConfirm.tsx +193 -0
  114. package/src/storage/components/ObjectBrowser.tsx +219 -0
  115. package/src/storage/components/ObjectInspector.tsx +169 -0
  116. package/src/storage/components/PathBreadcrumb.tsx +84 -0
  117. package/src/storage/components/UploadDropzone.tsx +182 -0
  118. package/src/storage/folderPlaceholder.ts +40 -0
  119. package/src/storage/hooks/index.ts +59 -0
  120. package/src/storage/hooks/useMetadataEditor.ts +329 -0
  121. package/src/storage/hooks/useObjectUpload.ts +262 -0
  122. package/src/storage/hooks/usePathState.ts +94 -0
  123. package/src/storage/hooks/useStorageDelete.ts +195 -0
  124. package/src/storage/hooks/useStorageList.ts +261 -0
  125. package/src/storage/hooks/useStorageObject.ts +162 -0
  126. package/src/storage/hooks/useStorageRulesGate.ts +270 -0
  127. package/src/storage/hooks/useStorageSelection.ts +90 -0
  128. package/src/storage/index.ts +59 -0
  129. package/src/storage/pendingPrefixes.ts +125 -0
  130. package/src/storage/previews.tsx +120 -0
  131. package/src/storage/storageApi.ts +54 -0
  132. package/src/traffic/components/RuleHeatmap.tsx +97 -0
  133. package/src/traffic/components/TrafficDetail.tsx +160 -0
  134. package/src/traffic/components/TrafficGroupRow.tsx +91 -0
  135. package/src/traffic/components/TrafficLineChart.tsx +139 -0
  136. package/src/traffic/components/TrafficLog.tsx +175 -0
  137. package/src/traffic/components/TrafficMetricCards.tsx +77 -0
  138. package/src/traffic/components/TrafficRow.tsx +69 -0
  139. package/src/traffic/components/TrafficStats.tsx +73 -0
  140. package/src/traffic/components/TrafficTimeline.tsx +289 -0
  141. package/src/traffic/components/format.ts +22 -0
  142. package/src/traffic/components/index.ts +22 -0
  143. package/src/traffic/hooks/index.ts +60 -0
  144. package/src/traffic/hooks/useRuleHeatmap.ts +92 -0
  145. package/src/traffic/hooks/useTrafficBuckets.ts +146 -0
  146. package/src/traffic/hooks/useTrafficFilter.ts +74 -0
  147. package/src/traffic/hooks/useTrafficGroups.ts +126 -0
  148. package/src/traffic/hooks/useTrafficMetrics.ts +250 -0
  149. package/src/traffic/hooks/useTrafficMonitor.ts +111 -0
  150. package/src/traffic/hooks/useTrafficStats.ts +77 -0
  151. package/src/traffic/index.ts +13 -0
  152. package/src/traffic/types.ts +85 -0
@@ -0,0 +1,228 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ doc as docFn,
4
+ getDocs,
5
+ limit as limitFn,
6
+ query as queryFn,
7
+ type CollectionReference,
8
+ type DocumentReference,
9
+ type Firestore,
10
+ type QueryDocumentSnapshot,
11
+ } from 'pyric/firestore';
12
+
13
+ export type BrowseLocation =
14
+ | { kind: 'root' }
15
+ | { kind: 'document'; ref: DocumentReference }
16
+ | { kind: 'collection'; ref: CollectionReference };
17
+
18
+ export interface UseReferencePickerOptions {
19
+ firestore: Firestore;
20
+ /**
21
+ * Lister for subcollections under a parent (or root when
22
+ * `parent == null`). The library does not ship a default — see
23
+ * `useCollectionList` for the same rationale (the modular Web SDK
24
+ * can't enumerate collections client-side).
25
+ */
26
+ listCollections: (
27
+ firestore: Firestore,
28
+ parent: DocumentReference | null,
29
+ ) => Promise<CollectionReference[]>;
30
+ /** Default page size for the document list when browsing inside
31
+ * a collection. Default 20. */
32
+ pageSize?: number;
33
+ /** Initial value to pre-populate the text input + parse. */
34
+ initialPath?: string;
35
+ }
36
+
37
+ export interface UseReferencePickerResult {
38
+ /** Current text input value. */
39
+ pathInput: string;
40
+ /** Validated `DocumentReference` parsed from `pathInput`, or
41
+ * `null` when the path is empty / invalid. */
42
+ reference: DocumentReference | null;
43
+ /** Parse error, or `null` when valid. */
44
+ error: string | null;
45
+ /** Current browse position in the tree. */
46
+ browseLocation: BrowseLocation;
47
+ /** Whether `drillBack` has anywhere to go. */
48
+ canDrillBack: boolean;
49
+ /** Collections available at the current browse level. Populated
50
+ * when `browseLocation` is `root` or `document`. */
51
+ collections: CollectionReference[];
52
+ /** First page of documents in the current collection — populated
53
+ * when `browseLocation.kind === 'collection'`. */
54
+ documents: QueryDocumentSnapshot[];
55
+ /** True while a fetch is in flight. */
56
+ isLoading: boolean;
57
+
58
+ /** Set the text-input value. Parses on every change. */
59
+ setPathInput: (path: string) => void;
60
+ /** Commit a chosen reference. Updates `pathInput` (and therefore
61
+ * the parsed `reference`). */
62
+ pick: (ref: DocumentReference) => void;
63
+ /** Drill into a collection — fetches its first page of documents. */
64
+ drillIntoCollection: (ref: CollectionReference) => void;
65
+ /** Drill into a document — fetches its subcollections. */
66
+ drillIntoDocument: (ref: DocumentReference) => void;
67
+ /** Step back one level. No-op when at root. */
68
+ drillBack: () => void;
69
+ /** Clear the path input + reset browse to root. */
70
+ clear: () => void;
71
+ }
72
+
73
+ interface BrowseState {
74
+ current: BrowseLocation;
75
+ history: BrowseLocation[];
76
+ }
77
+
78
+ function parseReferencePath(
79
+ firestore: Firestore,
80
+ path: string,
81
+ ): { ref: DocumentReference | null; error: string | null } {
82
+ const trimmed = path.trim();
83
+ if (!trimmed) return { ref: null, error: null };
84
+ const segments = trimmed.split('/').filter(Boolean);
85
+ if (segments.length === 0) return { ref: null, error: 'Empty path' };
86
+ if (segments.length % 2 !== 0)
87
+ return { ref: null, error: 'Must point to a document (even segment count)' };
88
+ try {
89
+ const ref = docFn(firestore, trimmed) as DocumentReference;
90
+ return { ref, error: null };
91
+ } catch (e) {
92
+ return {
93
+ ref: null,
94
+ error: e instanceof Error ? e.message : 'Invalid path',
95
+ };
96
+ }
97
+ }
98
+
99
+ const ROOT: BrowseLocation = { kind: 'root' };
100
+
101
+ /**
102
+ * Picker state machine. Browses a Firestore tree level-by-level
103
+ * (root → collection → document → collection → ...), maintains a
104
+ * separately-validated text-input path, and commits a chosen
105
+ * reference via `pick`.
106
+ *
107
+ * Headless — consumers compose the resulting state into their own
108
+ * UI, or use the bundled `<ReferencePicker>` component.
109
+ */
110
+ export function useReferencePicker({
111
+ firestore,
112
+ listCollections,
113
+ pageSize = 20,
114
+ initialPath = '',
115
+ }: UseReferencePickerOptions): UseReferencePickerResult {
116
+ const [pathInput, setPathInputState] = useState(initialPath);
117
+ const [browse, setBrowse] = useState<BrowseState>({
118
+ current: ROOT,
119
+ history: [],
120
+ });
121
+ const [collections, setCollections] = useState<CollectionReference[]>([]);
122
+ const [documents, setDocuments] = useState<QueryDocumentSnapshot[]>([]);
123
+ const [isLoading, setIsLoading] = useState(false);
124
+
125
+ const parsed = useMemo(
126
+ () => parseReferencePath(firestore, pathInput),
127
+ [firestore, pathInput],
128
+ );
129
+
130
+ // Keep the injected `listCollections` in a ref so the fetch
131
+ // effect's deps don't include it. Consumers commonly pass an
132
+ // inline arrow function whose identity churns on every render;
133
+ // depending on it would loop the effect forever.
134
+ const listCollectionsRef = useRef(listCollections);
135
+ listCollectionsRef.current = listCollections;
136
+
137
+ // Re-fetch contents whenever the browse location changes.
138
+ useEffect(() => {
139
+ let cancelled = false;
140
+ setIsLoading(true);
141
+ const loc = browse.current;
142
+ if (loc.kind === 'root' || loc.kind === 'document') {
143
+ const parent = loc.kind === 'document' ? loc.ref : null;
144
+ setDocuments([]);
145
+ listCollectionsRef.current(firestore, parent)
146
+ .then((cs) => {
147
+ if (cancelled) return;
148
+ setCollections(cs);
149
+ setIsLoading(false);
150
+ })
151
+ .catch(() => {
152
+ if (cancelled) return;
153
+ setCollections([]);
154
+ setIsLoading(false);
155
+ });
156
+ } else {
157
+ setCollections([]);
158
+ const pagedQuery = queryFn(loc.ref, limitFn(pageSize));
159
+ getDocs(pagedQuery)
160
+ .then((snap) => {
161
+ if (cancelled) return;
162
+ setDocuments([...snap.docs]);
163
+ setIsLoading(false);
164
+ })
165
+ .catch(() => {
166
+ if (cancelled) return;
167
+ setDocuments([]);
168
+ setIsLoading(false);
169
+ });
170
+ }
171
+ return () => {
172
+ cancelled = true;
173
+ };
174
+ }, [browse.current, firestore, pageSize]);
175
+
176
+ const setPathInput = useCallback((path: string) => {
177
+ setPathInputState(path);
178
+ }, []);
179
+
180
+ const pick = useCallback((ref: DocumentReference) => {
181
+ setPathInputState(ref.path);
182
+ }, []);
183
+
184
+ const drillIntoCollection = useCallback((ref: CollectionReference) => {
185
+ setBrowse((prev) => ({
186
+ current: { kind: 'collection', ref },
187
+ history: [...prev.history, prev.current],
188
+ }));
189
+ }, []);
190
+
191
+ const drillIntoDocument = useCallback((ref: DocumentReference) => {
192
+ setBrowse((prev) => ({
193
+ current: { kind: 'document', ref },
194
+ history: [...prev.history, prev.current],
195
+ }));
196
+ }, []);
197
+
198
+ const drillBack = useCallback(() => {
199
+ setBrowse((prev) => {
200
+ if (prev.history.length === 0) return prev;
201
+ const next = prev.history.slice(0, -1);
202
+ const popped = prev.history[prev.history.length - 1];
203
+ return { current: popped, history: next };
204
+ });
205
+ }, []);
206
+
207
+ const clear = useCallback(() => {
208
+ setPathInputState('');
209
+ setBrowse({ current: ROOT, history: [] });
210
+ }, []);
211
+
212
+ return {
213
+ pathInput,
214
+ reference: parsed.ref,
215
+ error: parsed.error,
216
+ browseLocation: browse.current,
217
+ canDrillBack: browse.history.length > 0,
218
+ collections,
219
+ documents,
220
+ isLoading,
221
+ setPathInput,
222
+ pick,
223
+ drillIntoCollection,
224
+ drillIntoDocument,
225
+ drillBack,
226
+ clear,
227
+ };
228
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * JSON import parsing + collision detection for the Firestore "import JSON
3
+ * into a collection" flow.
4
+ *
5
+ * Pure, no React, no I/O: `parseImport` takes the raw pasted/loaded text and
6
+ * returns the documents it would create plus any per-item errors; the caller
7
+ * (the Studio pane) owns showing the "will create N documents" preview,
8
+ * disclosing a collision policy ONLY when {@link detectCollisions} finds an
9
+ * overlap, and actually writing through the existing Firestore write handle.
10
+ *
11
+ * Two accepted shapes:
12
+ * 1. A map: `{ "docId": { ...fields }, "docId2": { ...fields } }`
13
+ * — each key becomes the document id (validated), each value its data.
14
+ * 2. An array: `[ { ...fields }, { ...fields } ]`
15
+ * — each element becomes a document with `id: null` (auto-id at write
16
+ * time), since a bare array carries no natural id.
17
+ */
18
+
19
+ import { validateDocumentId } from '../validation/ids.js';
20
+
21
+ /** One document to create. `id === null` means "let Firestore auto-id it"
22
+ * (only produced by the array shape when no `generateId` option is given —
23
+ * a map key is always a chosen id). */
24
+ export interface ParsedImportDoc {
25
+ id: string | null;
26
+ data: Record<string, unknown>;
27
+ }
28
+
29
+ export interface ParseImportOptions {
30
+ /** When provided, array-shape entries get their auto-id GENERATED AT PARSE
31
+ * TIME (instead of `id: null` / addDoc-at-write-time). Fixing ids at parse
32
+ * makes a retry after a partial failure idempotent: the same parse's ids
33
+ * are reused, so re-running the import cannot duplicate already-written
34
+ * docs. Use {@link firestoreAutoId} for prod-parity ids. */
35
+ generateId?: () => string;
36
+ }
37
+
38
+ const AUTO_ID_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
39
+
40
+ /** A Firestore-style 20-char auto id (same alphabet/length the SDK uses). */
41
+ export function firestoreAutoId(): string {
42
+ let id = '';
43
+ for (let i = 0; i < 20; i++) {
44
+ id += AUTO_ID_ALPHABET.charAt(Math.floor(Math.random() * AUTO_ID_ALPHABET.length));
45
+ }
46
+ return id;
47
+ }
48
+
49
+ export interface ParseImportResult {
50
+ docs: ParsedImportDoc[];
51
+ /** Human-readable problems found while parsing. A non-empty `errors` does
52
+ * NOT necessarily mean `docs` is empty — the parser is per-item tolerant
53
+ * so one bad entry doesn't block the rest; the caller decides whether to
54
+ * block on any error or proceed with the valid subset. */
55
+ errors: string[];
56
+ }
57
+
58
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
59
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
60
+ }
61
+
62
+ /**
63
+ * Parse raw JSON text into the documents it would create. Never throws —
64
+ * a JSON syntax error or a wrong top-level shape becomes an entry in
65
+ * `errors` with an empty `docs` array.
66
+ */
67
+ export function parseImport(input: string, options?: ParseImportOptions): ParseImportResult {
68
+ const trimmed = input.trim();
69
+ if (trimmed === '') {
70
+ return { docs: [], errors: ['Input is empty'] };
71
+ }
72
+
73
+ let parsed: unknown;
74
+ try {
75
+ parsed = JSON.parse(trimmed);
76
+ } catch (e) {
77
+ return { docs: [], errors: [`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`] };
78
+ }
79
+
80
+ const docs: ParsedImportDoc[] = [];
81
+ const errors: string[] = [];
82
+
83
+ if (Array.isArray(parsed)) {
84
+ parsed.forEach((item, i) => {
85
+ if (!isPlainObject(item)) {
86
+ errors.push(`Item ${i}: expected an object, got ${describeType(item)}`);
87
+ return;
88
+ }
89
+ docs.push({ id: options?.generateId ? options.generateId() : null, data: item });
90
+ });
91
+ return { docs, errors };
92
+ }
93
+
94
+ if (isPlainObject(parsed)) {
95
+ for (const [key, value] of Object.entries(parsed)) {
96
+ const idError = validateDocumentId(key);
97
+ if (idError) {
98
+ errors.push(`"${key}": invalid document id — ${idError}`);
99
+ continue;
100
+ }
101
+ if (!isPlainObject(value)) {
102
+ errors.push(`"${key}": expected an object of fields, got ${describeType(value)}`);
103
+ continue;
104
+ }
105
+ docs.push({ id: key, data: value });
106
+ }
107
+ return { docs, errors };
108
+ }
109
+
110
+ return {
111
+ docs: [],
112
+ errors: ['Input must be a JSON object mapping docId -> fields, or an array of objects'],
113
+ };
114
+ }
115
+
116
+ function describeType(value: unknown): string {
117
+ if (value === null) return 'null';
118
+ if (Array.isArray(value)) return 'an array';
119
+ return typeof value;
120
+ }
121
+
122
+ /**
123
+ * Ids in `docs` (map-shape entries only — `id !== null`) that already exist
124
+ * in `existingIds`. The UI shows the skip-or-overwrite policy choice ONLY
125
+ * when this returns a non-empty list.
126
+ */
127
+ export function detectCollisions(
128
+ existingIds: readonly string[],
129
+ docs: readonly ParsedImportDoc[],
130
+ ): string[] {
131
+ const existing = new Set(existingIds);
132
+ const collisions: string[] = [];
133
+ for (const doc of docs) {
134
+ if (doc.id !== null && existing.has(doc.id)) collisions.push(doc.id);
135
+ }
136
+ return collisions;
137
+ }
@@ -0,0 +1,87 @@
1
+ export * from './hooks/index.js';
2
+
3
+ // Injectable Firestore API bundle (Pyric Studio data-backend swap): defaults to
4
+ // in-process `pyric/firestore`; a consumer can provide the SharedWorker client.
5
+ export {
6
+ FirestoreApiProvider,
7
+ useFirestoreApi,
8
+ type FirestoreApi,
9
+ } from './firestoreApi.js';
10
+
11
+ // M2: read-only display surface
12
+ export {
13
+ inferType,
14
+ asVectorView,
15
+ vectorPreview,
16
+ truncateVectorsForDisplay,
17
+ type FieldType,
18
+ type VectorView,
19
+ } from './types.js';
20
+ export { firestoreValuesEqual } from './valueEquality.js';
21
+ export {
22
+ defaultFieldEditors,
23
+ mergeFieldEditors,
24
+ } from './fieldEditors/registry.js';
25
+ export type {
26
+ FieldEditorContract,
27
+ FieldEditorRegistry,
28
+ FieldDisplayProps,
29
+ FieldEditProps,
30
+ } from './fieldEditors/types.js';
31
+ export { FieldRenderer, type FieldRendererProps } from './components/FieldRenderer.js';
32
+ export {
33
+ DocumentPreview,
34
+ type DocumentPreviewProps,
35
+ } from './components/DocumentPreview.js';
36
+
37
+ // M3: editor surface
38
+ export type {
39
+ FieldNode,
40
+ EditorTree,
41
+ DocumentEditorAction,
42
+ DocumentEditorState,
43
+ } from './reducers/types.js';
44
+ export { validateLeaf, validateTree } from './reducers/validation.js';
45
+ export { treeFromData, treeToData } from './reducers/tree.js';
46
+ export { initState, reducer } from './reducers/documentEditor.js';
47
+ export {
48
+ DocumentEditor,
49
+ DocumentEditorRoot,
50
+ DocumentEditorFields,
51
+ useDocumentEditorContext,
52
+ type DocumentEditorRootProps,
53
+ } from './components/DocumentEditor.js';
54
+
55
+ // M4: operational read/write + admin ops
56
+ export { CollectionList, type CollectionListProps } from './components/CollectionList.js';
57
+ export { DocumentList, type DocumentListProps } from './components/DocumentList.js';
58
+ export {
59
+ DeleteWithConfirm,
60
+ type DeleteWithConfirmProps,
61
+ } from './components/DeleteWithConfirm.js';
62
+
63
+ // M5: improvements over firebase-tools-ui
64
+ export {
65
+ ReferencePicker,
66
+ type ReferencePickerProps,
67
+ } from './components/ReferencePicker.js';
68
+
69
+ // M6: query builder + virtualization
70
+ export {
71
+ QueryBuilder,
72
+ type QueryBuilderProps,
73
+ } from './components/QueryBuilder.js';
74
+
75
+ // M7: create-collection / create-document / JSON-import — pure, tested logic.
76
+ // Presentational wiring lives in the consumer (Pyric Studio's FirestorePane)
77
+ // over these + the existing DocumentEditor/hooks, per the disclosure-over-
78
+ // modals design principles.
79
+ export { validateCollectionId, validateDocumentId } from './validation/ids.js';
80
+ export {
81
+ parseImport,
82
+ detectCollisions,
83
+ firestoreAutoId,
84
+ type ParsedImportDoc,
85
+ type ParseImportResult,
86
+ type ParseImportOptions,
87
+ } from './import/parseImport.js';
@@ -0,0 +1,38 @@
1
+ import { Timestamp, GeoPoint, Bytes } from 'pyric/firestore';
2
+ import type { FieldType } from '../types.js';
3
+
4
+ /**
5
+ * Default value used when a new field of this type is added (or
6
+ * when an existing field's type is switched). The reducer applies
7
+ * these so a fresh node always starts in a valid state for its type
8
+ * — except that map/array start empty (children come from add
9
+ * actions).
10
+ */
11
+ export function defaultValueFor(type: FieldType): unknown {
12
+ switch (type) {
13
+ case 'string':
14
+ return '';
15
+ case 'number':
16
+ return 0;
17
+ case 'boolean':
18
+ return false;
19
+ case 'null':
20
+ return null;
21
+ case 'timestamp':
22
+ return Timestamp.now();
23
+ case 'geopoint':
24
+ return new GeoPoint(0, 0);
25
+ case 'reference':
26
+ // No default — a fresh reference is invalid until the user
27
+ // sets a path. The validator will surface that.
28
+ return { path: '', id: '', firestore: {} };
29
+ case 'bytes':
30
+ return Bytes.fromUint8Array(new Uint8Array());
31
+ case 'vector':
32
+ // The wire-sentinel both inferType and the vector editor speak.
33
+ return { __type__: '__vector__', value: [] };
34
+ case 'map':
35
+ case 'array':
36
+ return undefined; // children carry the value
37
+ }
38
+ }