@donotdev/cli 0.0.6 → 0.0.8

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 (87) hide show
  1. package/README.md +3 -18
  2. package/dependencies-matrix.json +64 -121
  3. package/dist/bin/commands/build.js +173 -161
  4. package/dist/bin/commands/bump.js +181 -156
  5. package/dist/bin/commands/cacheout.js +188 -171
  6. package/dist/bin/commands/create-app.js +213 -156
  7. package/dist/bin/commands/create-project.js +183 -154
  8. package/dist/bin/commands/deploy.js +491 -477
  9. package/dist/bin/commands/dev.js +176 -160
  10. package/dist/bin/commands/emu.js +181 -165
  11. package/dist/bin/commands/format.js +191 -174
  12. package/dist/bin/commands/lint.js +191 -171
  13. package/dist/bin/commands/preview.js +177 -161
  14. package/dist/bin/commands/sync-secrets.js +172 -158
  15. package/dist/bin/commands/wai.d.ts +11 -0
  16. package/dist/bin/commands/wai.d.ts.map +1 -0
  17. package/dist/bin/commands/wai.js +12 -0
  18. package/dist/bin/commands/wai.js.map +1 -0
  19. package/dist/bin/dndev.js +24 -24
  20. package/dist/bin/donotdev.js +24 -24
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +661 -669
  24. package/dist/index.js.map +1 -1
  25. package/package.json +1 -1
  26. package/templates/app-demo/src/config/app.ts.example +12 -0
  27. package/templates/app-next/src/config/app.ts.example +75 -48
  28. package/templates/app-vite/index.html.example +71 -37
  29. package/templates/app-vite/src/config/app.ts.example +75 -47
  30. package/templates/app-vite/src/pages/FormPageExample.tsx.example +152 -0
  31. package/templates/app-vite/src/pages/HomePage.tsx.example +81 -134
  32. package/templates/app-vite/src/pages/ListPageExample.tsx.example +88 -0
  33. package/templates/functions-firebase/build.mjs.example +8 -1
  34. package/templates/functions-firebase/functions-firebase/build.mjs.example +8 -1
  35. package/templates/functions-firebase/functions-firebase/src/index.ts.example +19 -25
  36. package/templates/functions-firebase/functions.config.js.example +35 -0
  37. package/templates/root-consumer/entities/ExampleEntity.ts.example +223 -0
  38. package/templates/root-consumer/entities/demo.ts.example +576 -0
  39. package/templates/root-consumer/entities/index.ts.example +15 -0
  40. package/templates/root-consumer/eslint.config.js.example +2 -80
  41. package/templates/root-consumer/guides/{AGENT_START_HERE.md.example → dndev/AGENT_START_HERE.md.example} +22 -0
  42. package/templates/root-consumer/guides/dndev/COMPONENTS_CRUD.md.example +231 -0
  43. package/templates/root-consumer/guides/{SETUP_AUTH.md.example → dndev/SETUP_AUTH.md.example} +30 -0
  44. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +539 -0
  45. package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +116 -0
  46. package/templates/root-consumer/guides/{SETUP_I18N.md.example → dndev/SETUP_I18N.md.example} +46 -0
  47. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +404 -0
  48. package/templates/root-consumer/guides/wai-way/agents/architect.md.example +78 -0
  49. package/templates/root-consumer/guides/wai-way/agents/builder.md.example +87 -0
  50. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +325 -0
  51. package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +100 -0
  52. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +281 -0
  53. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +77 -0
  54. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +104 -0
  55. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +124 -0
  56. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +165 -0
  57. package/templates/root-consumer/guides/wai-way/context_map.json.example +95 -0
  58. package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +840 -0
  59. package/templates/root-consumer/guides/wai-way/page_patterns.md.example +686 -0
  60. package/templates/root-consumer/guides/wai-way/presets_guide.md.example +217 -0
  61. package/templates/root-consumer/guides/wai-way/spec_template.md.example +312 -0
  62. package/templates/functions-firebase/functions-firebase/src/crud/createEntity.ts.example +0 -19
  63. package/templates/functions-firebase/functions-firebase/src/crud/deleteEntity.ts.example +0 -14
  64. package/templates/functions-firebase/functions-firebase/src/crud/getEntity.ts.example +0 -14
  65. package/templates/functions-firebase/functions-firebase/src/crud/index.ts.example +0 -12
  66. package/templates/functions-firebase/functions-firebase/src/crud/listEntities.ts.example +0 -14
  67. package/templates/functions-firebase/functions-firebase/src/crud/updateEntity.ts.example +0 -14
  68. package/templates/root-consumer/guides/COMPONENTS_CRUD.md.example +0 -70
  69. package/templates/root-consumer/guides/SETUP_CRUD.md.example +0 -1244
  70. package/templates/root-consumer/guides/SETUP_FUNCTIONS.md.example +0 -114
  71. /package/templates/root-consumer/guides/{COMPONENTS_ADV.md.example → dndev/COMPONENTS_ADV.md.example} +0 -0
  72. /package/templates/root-consumer/guides/{COMPONENTS_ATOMIC.md.example → dndev/COMPONENTS_ATOMIC.md.example} +0 -0
  73. /package/templates/root-consumer/guides/{COMPONENTS_UI.md.example → dndev/COMPONENTS_UI.md.example} +0 -0
  74. /package/templates/root-consumer/guides/{ENV_SETUP.md.example → dndev/ENV_SETUP.md.example} +0 -0
  75. /package/templates/root-consumer/guides/{INDEX.md.example → dndev/INDEX.md.example} +0 -0
  76. /package/templates/root-consumer/guides/{SETUP_APP_CONFIG.md.example → dndev/SETUP_APP_CONFIG.md.example} +0 -0
  77. /package/templates/root-consumer/guides/{SETUP_BILLING.md.example → dndev/SETUP_BILLING.md.example} +0 -0
  78. /package/templates/root-consumer/guides/{SETUP_LAYOUTS.md.example → dndev/SETUP_LAYOUTS.md.example} +0 -0
  79. /package/templates/root-consumer/guides/{SETUP_OAUTH.md.example → dndev/SETUP_OAUTH.md.example} +0 -0
  80. /package/templates/root-consumer/guides/{SETUP_PAGES.md.example → dndev/SETUP_PAGES.md.example} +0 -0
  81. /package/templates/root-consumer/guides/{SETUP_PWA.md.example → dndev/SETUP_PWA.md.example} +0 -0
  82. /package/templates/root-consumer/guides/{SETUP_THEMES.md.example → dndev/SETUP_THEMES.md.example} +0 -0
  83. /package/templates/root-consumer/guides/{USE_ROUTING.md.example → dndev/USE_ROUTING.md.example} +0 -0
  84. /package/templates/root-consumer/guides/{advanced → dndev/advanced}/APP_CHECK.md.example +0 -0
  85. /package/templates/root-consumer/guides/{advanced → dndev/advanced}/COOKIE_REFERENCE.md.example +0 -0
  86. /package/templates/root-consumer/guides/{advanced → dndev/advanced}/EMULATORS.md.example +0 -0
  87. /package/templates/root-consumer/guides/{advanced → dndev/advanced}/VERSION_CONTROL.md.example +0 -0
@@ -0,0 +1,539 @@
1
+ # Setup: CRUD Operations
2
+
3
+ **CRUD = Create, Read, Update, Delete.** Framework handles data ops with built-in security.
4
+
5
+ ---
6
+
7
+ ## 1. Define Entity
8
+
9
+ ```typescript
10
+ // entities/product.ts
11
+ import { defineEntity } from '@donotdev/core';
12
+
13
+ export const productEntity = defineEntity({
14
+ name: 'Product',
15
+ collection: 'products',
16
+ // namespace: 'entity-product', // Optional: defaults to entity-${name.toLowerCase()}
17
+ fields: {
18
+ name: {
19
+ name: 'name',
20
+ label: 'name',
21
+ type: 'text',
22
+ visibility: 'guest',
23
+ validation: { required: true }
24
+ },
25
+ price: {
26
+ name: 'price',
27
+ label: 'price',
28
+ type: 'number',
29
+ visibility: 'guest',
30
+ validation: { required: true }
31
+ },
32
+ image: {
33
+ name: 'image',
34
+ label: 'image',
35
+ type: 'image',
36
+ visibility: 'guest'
37
+ },
38
+ customerContact: {
39
+ name: 'customerContact',
40
+ label: 'customerContact',
41
+ type: 'email',
42
+ visibility: 'admin',
43
+ editable: 'admin'
44
+ }
45
+ }
46
+ });
47
+ ```
48
+
49
+ **Auto-added fields:** `id`, `createdAt`, `updatedAt`, `createdById`, `updatedById`, `status`
50
+
51
+ ---
52
+
53
+ ## 2. Visibility & Access
54
+
55
+ ### Field Visibility (who SEES)
56
+ | Level | Who |
57
+ |-------|-----|
58
+ | `'guest'` | Everyone |
59
+ | `'user'` | Authenticated |
60
+ | `'admin'` | Admins |
61
+ | `'super'` | Super admins |
62
+ | `'hidden'` | Never |
63
+
64
+ ### Entity Access (who CAN DO)
65
+ ```typescript
66
+ access: {
67
+ create: 'admin', // default
68
+ read: 'guest', // default
69
+ update: 'admin', // default
70
+ delete: 'admin', // default
71
+ }
72
+ ```
73
+
74
+ **Override:**
75
+ ```typescript
76
+ // Contact form - guests submit, admins read
77
+ access: { create: 'guest', read: 'admin' }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 3. Register Functions (Backend)
83
+
84
+ ```typescript
85
+ // functions/src/index.ts
86
+ import { initializeApp } from 'firebase-admin/app';
87
+ import { createCrudFunctions } from '@donotdev/functions/firebase';
88
+ import * as entities from 'entities';
89
+
90
+ initializeApp();
91
+ export const crud = createCrudFunctions(entities);
92
+ ```
93
+
94
+ **Deploy:** `dndev deploy`
95
+
96
+ ---
97
+
98
+ ## 4. Frontend Usage
99
+
100
+ ### Create/Edit Page
101
+
102
+ ```tsx
103
+ import { EntityFormRenderer, useCrud } from '@donotdev/crud';
104
+ import { productEntity } from 'entities';
105
+
106
+ export default function ProductPage() {
107
+ const { id } = useParams<{ id: string }>();
108
+ const { add, update, get } = useCrud(productEntity);
109
+ const isNew = id === 'new';
110
+ const [data, setData] = useState<any>(null);
111
+
112
+ useEffect(() => {
113
+ if (!isNew && id) get(id).then(setData);
114
+ }, [id]);
115
+
116
+ const handleSubmit = async (formData: any) => {
117
+ isNew ? await add(formData) : await update(id!, formData);
118
+ navigate('/products');
119
+ };
120
+
121
+ // DON'T mount form until data ready (edit mode)
122
+ if (!isNew && !data) return <Spinner />;
123
+
124
+ return (
125
+ <EntityFormRenderer
126
+ entity={productEntity}
127
+ operation={isNew ? 'create' : 'edit'}
128
+ defaultValues={data}
129
+ onSubmit={handleSubmit}
130
+ />
131
+ );
132
+ }
133
+ ```
134
+
135
+ ### List Page
136
+
137
+ ```tsx
138
+ import { EntityList } from '@donotdev/crud';
139
+ import { useAuth } from '@donotdev/auth';
140
+ import { PageContainer, useNavigate } from '@donotdev/ui';
141
+ import { productEntity } from 'entities';
142
+
143
+ export default function ProductsPage() {
144
+ const user = useAuth('user');
145
+ const navigate = useNavigate();
146
+
147
+ return (
148
+ <PageContainer>
149
+ <EntityList
150
+ entity={productEntity}
151
+ userRole={user?.role}
152
+ // Required: Edit handler - navigates to edit page when edit button clicked
153
+ onEdit={(id) => navigate(`/products/${id}`)}
154
+ // Optional: Create handler - navigates to create page when "Add New" clicked
155
+ onCreate={() => navigate('/products/new')}
156
+ // Optional: View handler - called when row is clicked
157
+ onView={(id) => navigate(`/products/${id}`)}
158
+ />
159
+ </PageContainer>
160
+ );
161
+ }
162
+ ```
163
+
164
+ **Props:**
165
+ - `onEdit` **(required)** - Function `(id: string) => void` called when edit button is clicked. Typically navigates to `/${collection}/${id}` (e.g., `/products/${id}`).
166
+ - `onCreate` **(optional)** - Function `() => void` called when "Add New" button is clicked. Typically navigates to `/${collection}/new`.
167
+ - `onView` **(optional)** - Function `(id: string) => void` called when a table row is clicked. Useful for view-only pages or detail navigation.
168
+ - `userRole` **(optional)** - Current user role for field visibility filtering (backend still enforces security).
169
+
170
+ **Routing Convention:**
171
+ - Default edit route: `/${entity.collection}/${id}` (e.g., `/products/abc123`)
172
+ - You must create a page at `/${collection}/:id` route to handle the edit page
173
+
174
+ ### Card Grid
175
+
176
+ ```tsx
177
+ import { EntityCardList } from '@donotdev/crud';
178
+ import { PageContainer, useNavigate } from '@donotdev/ui';
179
+ import { productEntity } from 'entities';
180
+
181
+ export default function ShopPage() {
182
+ const navigate = useNavigate();
183
+
184
+ return (
185
+ <PageContainer>
186
+ <EntityCardList
187
+ entity={productEntity}
188
+ // Required: View handler - called when card is clicked
189
+ onView={(id) => navigate(`/products/${id}`)}
190
+ />
191
+ </PageContainer>
192
+ );
193
+ }
194
+ ```
195
+
196
+ **Props:**
197
+ - `onView` **(required)** - Function `(id: string) => void` called when a card is clicked. Typically navigates to `/${collection}/${id}` (e.g., `/products/${id}`).
198
+ - `cols` **(optional)** - Responsive column breakpoints `[mobile, tablet, desktop, wide]` (default: `[1, 2, 3, 4]`).
199
+ - `staleTime` **(optional)** - Cache stale time in milliseconds (default: 30 minutes).
200
+ - `filter` **(optional)** - Filter function `(item: any) => boolean` to filter items client-side.
201
+ - `hideFilters` **(optional)** - Hide filters section (default: `false`).
202
+
203
+ ---
204
+
205
+ ## 5. Field Types
206
+
207
+ ### Text Inputs
208
+ - `text` - Single-line text input
209
+ - `email` - Email input with validation
210
+ - `tel` - Phone number input
211
+ - `url` - URL input
212
+ - `color` - Color picker
213
+ - `password` - Password input (masked)
214
+ - `textarea` - Multi-line text input
215
+ - `richtext` - Rich text editor
216
+
217
+ ### Numbers
218
+ - `number` - Numeric input
219
+ - `range` - Slider input
220
+ - `year` - Year input (combobox: type or select from dropdown)
221
+
222
+ ### Boolean
223
+ - `checkbox` - Checkbox input
224
+ - `boolean` - Alias for checkbox
225
+ - `switch` - Toggle switch
226
+
227
+ ### Dates & Time
228
+ - `date` - Date picker
229
+ - `datetime-local` - Date and time picker
230
+ - `time` - Time picker
231
+ - `week` - Week picker
232
+ - `month` - Month picker
233
+ - `timestamp` - Timestamp (Firestore Timestamp)
234
+
235
+ ### Selection
236
+ - `select` - Dropdown select
237
+ - `combobox` - Searchable dropdown
238
+ - `multiselect` - Multiple selection dropdown
239
+ - `radio` - Radio button group
240
+
241
+ ### Files & Media
242
+ - `file` - Single file upload (deferred: uploads on form submit)
243
+ - `files` - Multiple file uploads (deferred: uploads on form submit)
244
+ - `document` - Document upload (PDF, etc.) (deferred: uploads on form submit)
245
+ - `documents` - Multiple document uploads (deferred: uploads on form submit)
246
+ - `image` - Single image upload (deferred: uploads on form submit, optimistic UI updates)
247
+ - `images` - Multiple image uploads (deferred: uploads on form submit, optimistic UI updates)
248
+
249
+ **Note:** File uploads use deferred uploads when inside `EntityFormRenderer` - files upload to storage when you submit the form. Images show immediately with optimistic updates (blob URLs) and automatically replace with storage URLs after upload completes.
250
+
251
+ ### Complex Types
252
+ - `geopoint` - Geographic coordinates (lat/lng)
253
+ - `address` - Address input with autocomplete
254
+ - `map` - Map picker
255
+ - `array` - Array of text inputs
256
+
257
+ ### Special
258
+ - `avatar` - Avatar image upload
259
+ - `badge` - Badge display
260
+ - `hidden` - Hidden field (not displayed)
261
+ - `submit` - Submit button (uncontrolled)
262
+ - `reset` - Reset button (uncontrolled)
263
+
264
+ ### Select Options
265
+
266
+ ```typescript
267
+ category: {
268
+ type: 'select',
269
+ validation: {
270
+ options: [
271
+ { value: 'electronics', label: 'Electronics' },
272
+ { value: 'clothing', label: 'Clothing' }
273
+ ]
274
+ }
275
+ }
276
+ ```
277
+
278
+ ### Year Field
279
+
280
+ Use `type: 'year'` for year inputs (combobox with type-or-select UX):
281
+
282
+ ```typescript
283
+ year: {
284
+ type: 'year',
285
+ validation: {
286
+ required: true,
287
+ min: 1900, // Optional: defaults to 1900
288
+ max: 2100 // Optional: defaults to current year + 10
289
+ }
290
+ }
291
+ ```
292
+
293
+ ### Switch with Custom Values
294
+
295
+ ```typescript
296
+ transmission: {
297
+ type: 'switch',
298
+ options: {
299
+ fieldSpecific: {
300
+ uncheckedValue: 'Manual',
301
+ checkedValue: 'Automatic',
302
+ }
303
+ }
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## 6. Custom Fields & Schemas
310
+
311
+ ### Custom Field Types with UI Components
312
+
313
+ For custom field types that need custom UI components:
314
+
315
+ ```typescript
316
+ import { registerFieldType, type ControlledFieldProps } from '@donotdev/crud';
317
+ import { useController } from 'react-hook-form';
318
+ import * as v from 'valibot';
319
+
320
+ // Custom controlled component MUST use framework's useController
321
+ import { useController, registerFieldType } from '@donotdev/crud';
322
+ import type { ControlledFieldProps } from '@donotdev/crud';
323
+
324
+ function RepairOperationsField({
325
+ fieldConfig,
326
+ control,
327
+ errors,
328
+ t,
329
+ }: ControlledFieldProps) {
330
+ // REQUIRED: Use framework's useController (not react-hook-form's) - ensures type compatibility
331
+ const { field, fieldState } = useController({
332
+ name: fieldConfig.name,
333
+ control: control,
334
+ });
335
+
336
+ // Use field.value and field.onChange for form state
337
+ const value = (field.value as any) || [];
338
+
339
+ return (
340
+ <div>
341
+ <label>{t(fieldConfig.label)}</label>
342
+ {/* Your custom UI here */}
343
+ {fieldState?.error && (
344
+ <span className="error">{fieldState.error.message}</span>
345
+ )}
346
+ </div>
347
+ );
348
+ }
349
+
350
+ // Register UI component
351
+ registerFieldType({
352
+ type: 'repairOperations',
353
+ controlledComponent: RepairOperationsField,
354
+ });
355
+
356
+ // Then define schema in entity
357
+ export const carEntity = defineEntity({
358
+ name: 'Car',
359
+ collection: 'cars',
360
+ fields: {
361
+ repairs: {
362
+ name: 'repairs',
363
+ label: 'repairs',
364
+ type: 'repairOperations' as any,
365
+ visibility: 'admin',
366
+ validation: {
367
+ required: false,
368
+ // Custom Valibot schema - single source of truth
369
+ schema: v.nullish(v.array(v.object({
370
+ operation: v.string(),
371
+ cost: v.number(),
372
+ })))
373
+ }
374
+ }
375
+ }
376
+ });
377
+ ```
378
+
379
+ **Important:**
380
+ - Custom controlled components receive `control` prop, NOT `field` prop
381
+ - You MUST use `useController` hook to get `field` and `fieldState`
382
+ - Define schema in `validation.schema` - it's the single source of truth
383
+
384
+ ### Custom Schemas (No Custom UI)
385
+
386
+ For custom validation without custom UI, just define the schema:
387
+
388
+ ```typescript
389
+ import * as v from 'valibot';
390
+
391
+ export const carEntity = defineEntity({
392
+ name: 'Car',
393
+ collection: 'cars',
394
+ fields: {
395
+ // Custom array field with inline schema
396
+ repairs: {
397
+ name: 'repairs',
398
+ label: 'repairs',
399
+ type: 'text', // Can be any type, schema takes precedence
400
+ visibility: 'admin',
401
+ validation: {
402
+ required: false,
403
+ // Custom Valibot schema - takes priority over type-based schema
404
+ schema: v.array(
405
+ v.object({
406
+ operation: v.string(),
407
+ cost: v.number(),
408
+ date: v.string(), // ISO date string
409
+ })
410
+ )
411
+ }
412
+ },
413
+
414
+ // Custom object field
415
+ metadata: {
416
+ name: 'metadata',
417
+ label: 'metadata',
418
+ type: 'map',
419
+ visibility: 'admin',
420
+ validation: {
421
+ schema: v.object({
422
+ source: v.string(),
423
+ importedAt: v.string(),
424
+ tags: v.array(v.string()),
425
+ })
426
+ }
427
+ }
428
+ }
429
+ });
430
+ ```
431
+
432
+ **How It Works:**
433
+ - `validation.schema` is the **single source of truth** - works everywhere (client + server)
434
+ - Takes priority over built-in type schemas
435
+ - Entity is self-contained - no separate registrations needed
436
+
437
+ ---
438
+
439
+ ## 7. Hooks API
440
+
441
+ ```typescript
442
+ // Actions
443
+ const { add, update, delete: remove, get } = useCrud(productEntity);
444
+
445
+ // List (auto-fetch for tables)
446
+ const { items, loading } = useCrudList(productEntity);
447
+
448
+ // Cards (optimized for public)
449
+ const { items, loading, loadMore } = useCrudCardList(productEntity);
450
+ ```
451
+
452
+ ---
453
+
454
+ ## 8. Custom Form Layouts
455
+
456
+ Need a custom layout instead of `EntityFormRenderer`? Use `useEntityForm` directly.
457
+
458
+ ```tsx
459
+ import { useId, useEffect } from 'react';
460
+ import { useEntityForm, useCrud, UploadProvider } from '@donotdev/crud';
461
+ import { productEntity } from 'entities';
462
+
463
+ export function CustomProductForm() {
464
+ const formId = useId();
465
+ const { add } = useCrud(productEntity);
466
+ const {
467
+ control,
468
+ handleSubmit,
469
+ fields,
470
+ formStatus, // 'idle' | 'uploading' | 'submitting' | ...
471
+ uploadProgress, // 0-100 during uploads
472
+ cleanup
473
+ } = useEntityForm(productEntity, { formId });
474
+
475
+ useEffect(() => cleanup, [cleanup]);
476
+
477
+ return (
478
+ <UploadProvider formId={formId}>
479
+ <form onSubmit={handleSubmit(add)}>
480
+ {/* Your custom layout here */}
481
+ <Controller control={control} name="name" render={({ field }) => (
482
+ <input {...field} placeholder="Product name" />
483
+ )} />
484
+
485
+ <button type="submit" disabled={formStatus !== 'idle'}>
486
+ {formStatus === 'uploading' ? `Uploading ${uploadProgress}%...` : 'Save'}
487
+ </button>
488
+ </form>
489
+ </UploadProvider>
490
+ );
491
+ }
492
+ ```
493
+
494
+ **Key points:**
495
+ - Pass `formId` to get automatic upload handling and loading states
496
+ - Wrap with `UploadProvider` if using file/image fields
497
+ - `handleSubmit` automatically uploads files before validation
498
+
499
+ ---
500
+
501
+ ## 9. i18n
502
+
503
+ **Namespace:** Automatically set to `entity-${entity.name.toLowerCase()}` (e.g., `entity-product`)
504
+
505
+ You can customize it by setting `namespace` in your entity definition:
506
+
507
+ ```typescript
508
+ export const productEntity = defineEntity({
509
+ name: 'Product',
510
+ collection: 'products',
511
+ namespace: 'custom-namespace', // Optional: override default
512
+ fields: { ... }
513
+ });
514
+ ```
515
+
516
+ **Default behavior:** If not specified, the namespace is automatically set to `entity-${entity.name.toLowerCase()}`.
517
+
518
+ **Translation file:** `locales/entity-product_en.json` (or your custom namespace)
519
+ ```json
520
+ {
521
+ "fields": {
522
+ "name": "Product Name",
523
+ "price": "Price"
524
+ },
525
+ "name": "Product"
526
+ }
527
+ ```
528
+
529
+ Field `label` → translation key in `fields.*`
530
+
531
+ ---
532
+
533
+ ## 10. Routing Convention
534
+
535
+ | Route | Purpose |
536
+ |-------|---------|
537
+ | `/${collection}` | List |
538
+ | `/${collection}/new` | Create |
539
+ | `/${collection}/:id` | Detail/Edit |
@@ -0,0 +1,116 @@
1
+ # Setup: Firebase Functions
2
+
3
+ **Most is pre-configured.** Functions scaffolded. Just add config and deploy.
4
+
5
+ ---
6
+
7
+ ## ⚠️ CRITICAL: Import Rules
8
+
9
+ **Functions run on Node.js - you MUST use `/server` imports or your function will crash.**
10
+
11
+ ```typescript
12
+ // ✅ CORRECT
13
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
14
+ import { handleError } from '@donotdev/core/server';
15
+
16
+ // ❌ WRONG - crashes on deploy
17
+ import { getFirestore } from '@donotdev/firebase';
18
+ import { handleError } from '@donotdev/core';
19
+ ```
20
+
21
+ **Rule:** Always use `/server` suffix for:
22
+ - `@donotdev/firebase/server`
23
+ - `@donotdev/core/server`
24
+ - `@donotdev/utils/server`
25
+
26
+ ---
27
+
28
+ ## CRUD Functions Setup
29
+
30
+ **One line to register all entity functions:**
31
+
32
+ ```typescript
33
+ // functions/src/index.ts
34
+ import { initializeApp } from 'firebase-admin/app';
35
+ import { createCrudFunctions } from '@donotdev/functions/firebase';
36
+ import * as entities from 'entities';
37
+
38
+ initializeApp();
39
+ export const crud = createCrudFunctions(entities);
40
+ ```
41
+
42
+ **Result:** Auto-generates `create_products`, `get_products`, `list_products`, `update_products`, `delete_products` for each entity.
43
+
44
+ **Access control:** Configured via `entity.access` (see SETUP_CRUD.md). All functions use `invoker: 'public'` - security enforced via role-based access in code.
45
+
46
+ ---
47
+
48
+ ## Custom Functions
49
+
50
+ **When writing custom functions, use `/server` imports:**
51
+
52
+ ```typescript
53
+ import { onCall } from 'firebase-functions/v2/https';
54
+ import { FUNCTION_CONFIG } from '@donotdev/functions/firebase';
55
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
56
+ import { handleError } from '@donotdev/core/server';
57
+
58
+ export const myFunction = onCall(FUNCTION_CONFIG, async (request) => {
59
+ const db = getFirebaseAdminFirestore();
60
+ // Your logic here
61
+ return { data: 'result' };
62
+ });
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Post-Deployment: Cloud Run IAM
68
+
69
+ **After deploying, you may see `403 Forbidden` on CORS preflight. Fix:**
70
+
71
+ ```bash
72
+ # PowerShell
73
+ gcloud run services update create-products --region=europe-west1 --no-invoker-iam-check --project=myproject
74
+ ```
75
+
76
+ **For all CRUD functions:**
77
+ ```powershell
78
+ $services = @('create-products', 'get-products', 'list-products', 'update-products', 'delete-products');
79
+ foreach ($service in $services) {
80
+ gcloud run services update $service --region=europe-west1 --no-invoker-iam-check --project=myproject
81
+ }
82
+ ```
83
+
84
+ **Why?** Cloud Run blocks unauthenticated OPTIONS (CORS preflight) by default. Your function still validates Firebase Auth - this only allows the preflight to pass.
85
+
86
+ ---
87
+
88
+ ## Function Naming
89
+
90
+ **Export name (camelCase):**
91
+ ```typescript
92
+ export const getDashboardMetrics = onCall(...);
93
+ // Client: httpsCallable(functions, 'getDashboardMetrics')
94
+ ```
95
+
96
+ **Operation ID (snake_case):**
97
+ ```typescript
98
+ createBaseFunction(config, handler, 'get_dashboard_metrics')
99
+ // Used for logging, rate limiting
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Environment
105
+
106
+ ```bash
107
+ # .env.local (local)
108
+ STRIPE_SECRET_KEY=sk_test_...
109
+
110
+ # .env (production, synced via dndev sync-secrets)
111
+ STRIPE_SECRET_KEY=sk_live_...
112
+ ```
113
+
114
+ ---
115
+
116
+ **Add functions, get backend. Framework handles the rest.**
@@ -184,4 +184,50 @@ const { languages, currentLanguage } = useLanguageSelector();
184
184
 
185
185
  ---
186
186
 
187
+ ## Advanced: Shared Entity Translations (Monorepos)
188
+
189
+ **Auto-detected:** The framework automatically scans `../../entities/locales/*_*.json` for shared entity translations. If the path exists, translations are loaded; if not, nothing happens.
190
+
191
+ **File naming:** `entity-car_en.json`, `entity-car_fr.json`
192
+
193
+ **Merge behavior:** Base translations from shared package, app can override any key. Deep merge, app wins.
194
+
195
+ **Example structure:**
196
+ ```
197
+ monorepo/
198
+ entities/
199
+ locales/
200
+ entity-car_en.json # ✅ Auto-detected - no config needed
201
+ entity-car_fr.json
202
+ apps/
203
+ admin/ # ✅ Just works - framework auto-detects
204
+ public/ # ✅ Just works - framework auto-detects
205
+ ```
206
+
207
+ **Custom paths:** If your shared translations are in a different location, use `additionalPaths`:
208
+
209
+ **Vite:**
210
+ ```ts
211
+ // vite.config.ts
212
+ export default defineViteConfig({
213
+ appConfig,
214
+ i18n: {
215
+ additionalPaths: ['../../packages/shared/locales'] // Custom path
216
+ }
217
+ })
218
+ ```
219
+
220
+ **Next.js:**
221
+ ```ts
222
+ // next.config.ts
223
+ export default defineNextConfig({
224
+ appConfig,
225
+ i18n: {
226
+ additionalPaths: ['../../packages/shared/locales'] // Custom path
227
+ }
228
+ })
229
+ ```
230
+
231
+ ---
232
+
187
233
  **Drop files, get languages. Framework handles the rest.**