@donotdev/cli 0.0.6 → 0.0.7
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.
- package/dependencies-matrix.json +25 -7
- package/dist/bin/commands/build.js +156 -158
- package/dist/bin/commands/bump.js +153 -153
- package/dist/bin/commands/cacheout.js +154 -154
- package/dist/bin/commands/create-app.js +184 -156
- package/dist/bin/commands/create-project.js +154 -154
- package/dist/bin/commands/deploy.js +470 -470
- package/dist/bin/commands/dev.js +155 -155
- package/dist/bin/commands/emu.js +155 -155
- package/dist/bin/commands/format.js +154 -154
- package/dist/bin/commands/lint.js +157 -154
- package/dist/bin/commands/preview.js +155 -155
- package/dist/bin/commands/sync-secrets.js +155 -155
- package/dist/bin/commands/wai.d.ts +11 -0
- package/dist/bin/commands/wai.d.ts.map +1 -0
- package/dist/bin/commands/wai.js +12 -0
- package/dist/bin/commands/wai.js.map +1 -0
- package/dist/bin/dndev.js +24 -8
- package/dist/bin/donotdev.js +24 -8
- package/dist/index.js +524 -497
- package/package.json +1 -1
- package/templates/app-demo/src/config/app.ts.example +12 -0
- package/templates/app-next/src/config/app.ts.example +75 -48
- package/templates/app-vite/index.html.example +71 -37
- package/templates/app-vite/src/config/app.ts.example +75 -47
- package/templates/app-vite/src/pages/FormPageExample.tsx.example +152 -0
- package/templates/app-vite/src/pages/HomePage.tsx.example +81 -134
- package/templates/app-vite/src/pages/ListPageExample.tsx.example +88 -0
- package/templates/functions-firebase/build.mjs.example +8 -1
- package/templates/functions-firebase/functions-firebase/build.mjs.example +8 -1
- package/templates/functions-firebase/functions-firebase/src/index.ts.example +19 -25
- package/templates/functions-firebase/functions.config.js.example +35 -0
- package/templates/root-consumer/entities/ExampleEntity.ts.example +223 -0
- package/templates/root-consumer/entities/demo.ts.example +562 -0
- package/templates/root-consumer/entities/index.ts.example +15 -0
- package/templates/root-consumer/guides/{AGENT_START_HERE.md.example → dndev/AGENT_START_HERE.md.example} +22 -0
- package/templates/root-consumer/guides/dndev/COMPONENTS_CRUD.md.example +231 -0
- package/templates/root-consumer/guides/{SETUP_AUTH.md.example → dndev/SETUP_AUTH.md.example} +30 -0
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +473 -0
- package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +116 -0
- package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +404 -0
- package/templates/root-consumer/guides/wai-way/agents/architect.md.example +78 -0
- package/templates/root-consumer/guides/wai-way/agents/builder.md.example +87 -0
- package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +325 -0
- package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +100 -0
- package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +281 -0
- package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +77 -0
- package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +104 -0
- package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +124 -0
- package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +165 -0
- package/templates/root-consumer/guides/wai-way/context_map.json.example +95 -0
- package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +840 -0
- package/templates/root-consumer/guides/wai-way/page_patterns.md.example +686 -0
- package/templates/root-consumer/guides/wai-way/presets_guide.md.example +217 -0
- package/templates/root-consumer/guides/wai-way/spec_template.md.example +312 -0
- package/templates/functions-firebase/functions-firebase/src/crud/createEntity.ts.example +0 -19
- package/templates/functions-firebase/functions-firebase/src/crud/deleteEntity.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/getEntity.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/index.ts.example +0 -12
- package/templates/functions-firebase/functions-firebase/src/crud/listEntities.ts.example +0 -14
- package/templates/functions-firebase/functions-firebase/src/crud/updateEntity.ts.example +0 -14
- package/templates/root-consumer/guides/COMPONENTS_CRUD.md.example +0 -70
- package/templates/root-consumer/guides/SETUP_CRUD.md.example +0 -1244
- package/templates/root-consumer/guides/SETUP_FUNCTIONS.md.example +0 -114
- /package/templates/root-consumer/guides/{COMPONENTS_ADV.md.example → dndev/COMPONENTS_ADV.md.example} +0 -0
- /package/templates/root-consumer/guides/{COMPONENTS_ATOMIC.md.example → dndev/COMPONENTS_ATOMIC.md.example} +0 -0
- /package/templates/root-consumer/guides/{COMPONENTS_UI.md.example → dndev/COMPONENTS_UI.md.example} +0 -0
- /package/templates/root-consumer/guides/{ENV_SETUP.md.example → dndev/ENV_SETUP.md.example} +0 -0
- /package/templates/root-consumer/guides/{INDEX.md.example → dndev/INDEX.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_APP_CONFIG.md.example → dndev/SETUP_APP_CONFIG.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_BILLING.md.example → dndev/SETUP_BILLING.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_I18N.md.example → dndev/SETUP_I18N.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_LAYOUTS.md.example → dndev/SETUP_LAYOUTS.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_OAUTH.md.example → dndev/SETUP_OAUTH.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_PAGES.md.example → dndev/SETUP_PAGES.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_PWA.md.example → dndev/SETUP_PWA.md.example} +0 -0
- /package/templates/root-consumer/guides/{SETUP_THEMES.md.example → dndev/SETUP_THEMES.md.example} +0 -0
- /package/templates/root-consumer/guides/{USE_ROUTING.md.example → dndev/USE_ROUTING.md.example} +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/APP_CHECK.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/COOKIE_REFERENCE.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/EMULATORS.md.example +0 -0
- /package/templates/root-consumer/guides/{advanced → dndev/advanced}/VERSION_CONTROL.md.example +0 -0
|
@@ -0,0 +1,473 @@
|
|
|
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
|
+
fields: {
|
|
17
|
+
name: {
|
|
18
|
+
name: 'name',
|
|
19
|
+
label: 'name',
|
|
20
|
+
type: 'text',
|
|
21
|
+
visibility: 'guest',
|
|
22
|
+
validation: { required: true }
|
|
23
|
+
},
|
|
24
|
+
price: {
|
|
25
|
+
name: 'price',
|
|
26
|
+
label: 'price',
|
|
27
|
+
type: 'number',
|
|
28
|
+
visibility: 'guest',
|
|
29
|
+
validation: { required: true }
|
|
30
|
+
},
|
|
31
|
+
image: {
|
|
32
|
+
name: 'image',
|
|
33
|
+
label: 'image',
|
|
34
|
+
type: 'image',
|
|
35
|
+
visibility: 'guest'
|
|
36
|
+
},
|
|
37
|
+
customerContact: {
|
|
38
|
+
name: 'customerContact',
|
|
39
|
+
label: 'customerContact',
|
|
40
|
+
type: 'email',
|
|
41
|
+
visibility: 'admin',
|
|
42
|
+
editable: 'admin'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Auto-added fields:** `id`, `createdAt`, `updatedAt`, `createdById`, `updatedById`, `status`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Visibility & Access
|
|
53
|
+
|
|
54
|
+
### Field Visibility (who SEES)
|
|
55
|
+
| Level | Who |
|
|
56
|
+
|-------|-----|
|
|
57
|
+
| `'guest'` | Everyone |
|
|
58
|
+
| `'user'` | Authenticated |
|
|
59
|
+
| `'admin'` | Admins |
|
|
60
|
+
| `'super'` | Super admins |
|
|
61
|
+
| `'hidden'` | Never |
|
|
62
|
+
|
|
63
|
+
### Entity Access (who CAN DO)
|
|
64
|
+
```typescript
|
|
65
|
+
access: {
|
|
66
|
+
create: 'admin', // default
|
|
67
|
+
read: 'guest', // default
|
|
68
|
+
update: 'admin', // default
|
|
69
|
+
delete: 'admin', // default
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Override:**
|
|
74
|
+
```typescript
|
|
75
|
+
// Contact form - guests submit, admins read
|
|
76
|
+
access: { create: 'guest', read: 'admin' }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Register Functions (Backend)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// functions/src/index.ts
|
|
85
|
+
import { initializeApp } from 'firebase-admin/app';
|
|
86
|
+
import { createCrudFunctions } from '@donotdev/functions/firebase';
|
|
87
|
+
import * as entities from 'entities';
|
|
88
|
+
|
|
89
|
+
initializeApp();
|
|
90
|
+
export const crud = createCrudFunctions(entities);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Deploy:** `dndev deploy`
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 4. Frontend Usage
|
|
98
|
+
|
|
99
|
+
### Create/Edit Page
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { EntityFormRenderer, useCrud } from '@donotdev/crud';
|
|
103
|
+
import { productEntity } from 'entities';
|
|
104
|
+
|
|
105
|
+
export default function ProductPage() {
|
|
106
|
+
const { id } = useParams<{ id: string }>();
|
|
107
|
+
const { add, update, get } = useCrud(productEntity);
|
|
108
|
+
const isNew = id === 'new';
|
|
109
|
+
const [data, setData] = useState<any>(null);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!isNew && id) get(id).then(setData);
|
|
113
|
+
}, [id]);
|
|
114
|
+
|
|
115
|
+
const handleSubmit = async (formData: any) => {
|
|
116
|
+
isNew ? await add(formData) : await update(id!, formData);
|
|
117
|
+
navigate('/products');
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// DON'T mount form until data ready (edit mode)
|
|
121
|
+
if (!isNew && !data) return <Spinner />;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<EntityFormRenderer
|
|
125
|
+
entity={productEntity}
|
|
126
|
+
operation={isNew ? 'create' : 'edit'}
|
|
127
|
+
defaultValues={data}
|
|
128
|
+
onSubmit={handleSubmit}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### List Page
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { EntityList } from '@donotdev/crud';
|
|
138
|
+
import { productEntity } from 'entities';
|
|
139
|
+
|
|
140
|
+
export default function ProductsPage() {
|
|
141
|
+
return <EntityList entity={productEntity} onRowClick={(p) => navigate(`/products/${p.id}`)} />;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Card Grid
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { EntityCardList } from '@donotdev/crud';
|
|
149
|
+
|
|
150
|
+
export default function ShopPage() {
|
|
151
|
+
return <EntityCardList entity={productEntity} />;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 5. Field Types
|
|
158
|
+
|
|
159
|
+
### Text Inputs
|
|
160
|
+
- `text` - Single-line text input
|
|
161
|
+
- `email` - Email input with validation
|
|
162
|
+
- `tel` - Phone number input
|
|
163
|
+
- `url` - URL input
|
|
164
|
+
- `color` - Color picker
|
|
165
|
+
- `password` - Password input (masked)
|
|
166
|
+
- `textarea` - Multi-line text input
|
|
167
|
+
- `richtext` - Rich text editor
|
|
168
|
+
|
|
169
|
+
### Numbers
|
|
170
|
+
- `number` - Numeric input
|
|
171
|
+
- `range` - Slider input
|
|
172
|
+
|
|
173
|
+
### Boolean
|
|
174
|
+
- `checkbox` - Checkbox input
|
|
175
|
+
- `boolean` - Alias for checkbox
|
|
176
|
+
- `switch` - Toggle switch
|
|
177
|
+
|
|
178
|
+
### Dates & Time
|
|
179
|
+
- `date` - Date picker
|
|
180
|
+
- `datetime-local` - Date and time picker
|
|
181
|
+
- `time` - Time picker
|
|
182
|
+
- `week` - Week picker
|
|
183
|
+
- `month` - Month picker
|
|
184
|
+
- `timestamp` - Timestamp (Firestore Timestamp)
|
|
185
|
+
|
|
186
|
+
### Selection
|
|
187
|
+
- `select` - Dropdown select
|
|
188
|
+
- `combobox` - Searchable dropdown
|
|
189
|
+
- `multiselect` - Multiple selection dropdown
|
|
190
|
+
- `radio` - Radio button group
|
|
191
|
+
|
|
192
|
+
### Files & Media
|
|
193
|
+
- `file` - Single file upload (deferred: uploads on form submit)
|
|
194
|
+
- `files` - Multiple file uploads (deferred: uploads on form submit)
|
|
195
|
+
- `document` - Document upload (PDF, etc.) (deferred: uploads on form submit)
|
|
196
|
+
- `documents` - Multiple document uploads (deferred: uploads on form submit)
|
|
197
|
+
- `image` - Single image upload (deferred: uploads on form submit, optimistic UI updates)
|
|
198
|
+
- `images` - Multiple image uploads (deferred: uploads on form submit, optimistic UI updates)
|
|
199
|
+
|
|
200
|
+
**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.
|
|
201
|
+
|
|
202
|
+
### Complex Types
|
|
203
|
+
- `geopoint` - Geographic coordinates (lat/lng)
|
|
204
|
+
- `address` - Address input with autocomplete
|
|
205
|
+
- `map` - Map picker
|
|
206
|
+
- `array` - Array of text inputs
|
|
207
|
+
|
|
208
|
+
### Special
|
|
209
|
+
- `avatar` - Avatar image upload
|
|
210
|
+
- `badge` - Badge display
|
|
211
|
+
- `hidden` - Hidden field (not displayed)
|
|
212
|
+
- `submit` - Submit button (uncontrolled)
|
|
213
|
+
- `reset` - Reset button (uncontrolled)
|
|
214
|
+
|
|
215
|
+
### Select Options
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
category: {
|
|
219
|
+
type: 'select',
|
|
220
|
+
validation: {
|
|
221
|
+
options: [
|
|
222
|
+
{ value: 'electronics', label: 'Electronics' },
|
|
223
|
+
{ value: 'clothing', label: 'Clothing' }
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Year Dropdown
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { yearOptions } from '@donotdev/crud';
|
|
233
|
+
|
|
234
|
+
year: {
|
|
235
|
+
type: 'select',
|
|
236
|
+
validation: { options: yearOptions(1990) } // 1990 to now, descending
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Switch with Custom Values
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
transmission: {
|
|
244
|
+
type: 'switch',
|
|
245
|
+
options: {
|
|
246
|
+
fieldSpecific: {
|
|
247
|
+
uncheckedValue: 'Manual',
|
|
248
|
+
checkedValue: 'Automatic',
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 6. Custom Fields & Schemas
|
|
257
|
+
|
|
258
|
+
### Custom Field Types with UI Components
|
|
259
|
+
|
|
260
|
+
For custom field types that need custom UI components:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { registerFieldType, type ControlledFieldProps } from '@donotdev/crud';
|
|
264
|
+
import { useController } from 'react-hook-form';
|
|
265
|
+
import * as v from 'valibot';
|
|
266
|
+
|
|
267
|
+
// Custom controlled component MUST use framework's useController
|
|
268
|
+
import { useController, registerFieldType } from '@donotdev/crud';
|
|
269
|
+
import type { ControlledFieldProps } from '@donotdev/crud';
|
|
270
|
+
|
|
271
|
+
function RepairOperationsField({
|
|
272
|
+
fieldConfig,
|
|
273
|
+
control,
|
|
274
|
+
errors,
|
|
275
|
+
t,
|
|
276
|
+
}: ControlledFieldProps) {
|
|
277
|
+
// REQUIRED: Use framework's useController (not react-hook-form's) - ensures type compatibility
|
|
278
|
+
const { field, fieldState } = useController({
|
|
279
|
+
name: fieldConfig.name,
|
|
280
|
+
control: control,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Use field.value and field.onChange for form state
|
|
284
|
+
const value = (field.value as any) || [];
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div>
|
|
288
|
+
<label>{t(fieldConfig.label)}</label>
|
|
289
|
+
{/* Your custom UI here */}
|
|
290
|
+
{fieldState?.error && (
|
|
291
|
+
<span className="error">{fieldState.error.message}</span>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Register UI component
|
|
298
|
+
registerFieldType({
|
|
299
|
+
type: 'repairOperations',
|
|
300
|
+
controlledComponent: RepairOperationsField,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Then define schema in entity
|
|
304
|
+
export const carEntity = defineEntity({
|
|
305
|
+
name: 'Car',
|
|
306
|
+
collection: 'cars',
|
|
307
|
+
fields: {
|
|
308
|
+
repairs: {
|
|
309
|
+
name: 'repairs',
|
|
310
|
+
label: 'repairs',
|
|
311
|
+
type: 'repairOperations' as any,
|
|
312
|
+
visibility: 'admin',
|
|
313
|
+
validation: {
|
|
314
|
+
required: false,
|
|
315
|
+
// Custom Valibot schema - single source of truth
|
|
316
|
+
schema: v.nullish(v.array(v.object({
|
|
317
|
+
operation: v.string(),
|
|
318
|
+
cost: v.number(),
|
|
319
|
+
})))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Important:**
|
|
327
|
+
- Custom controlled components receive `control` prop, NOT `field` prop
|
|
328
|
+
- You MUST use `useController` hook to get `field` and `fieldState`
|
|
329
|
+
- Define schema in `validation.schema` - it's the single source of truth
|
|
330
|
+
|
|
331
|
+
### Custom Schemas (No Custom UI)
|
|
332
|
+
|
|
333
|
+
For custom validation without custom UI, just define the schema:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import * as v from 'valibot';
|
|
337
|
+
|
|
338
|
+
export const carEntity = defineEntity({
|
|
339
|
+
name: 'Car',
|
|
340
|
+
collection: 'cars',
|
|
341
|
+
fields: {
|
|
342
|
+
// Custom array field with inline schema
|
|
343
|
+
repairs: {
|
|
344
|
+
name: 'repairs',
|
|
345
|
+
label: 'repairs',
|
|
346
|
+
type: 'text', // Can be any type, schema takes precedence
|
|
347
|
+
visibility: 'admin',
|
|
348
|
+
validation: {
|
|
349
|
+
required: false,
|
|
350
|
+
// Custom Valibot schema - takes priority over type-based schema
|
|
351
|
+
schema: v.array(
|
|
352
|
+
v.object({
|
|
353
|
+
operation: v.string(),
|
|
354
|
+
cost: v.number(),
|
|
355
|
+
date: v.string(), // ISO date string
|
|
356
|
+
})
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
// Custom object field
|
|
362
|
+
metadata: {
|
|
363
|
+
name: 'metadata',
|
|
364
|
+
label: 'metadata',
|
|
365
|
+
type: 'map',
|
|
366
|
+
visibility: 'admin',
|
|
367
|
+
validation: {
|
|
368
|
+
schema: v.object({
|
|
369
|
+
source: v.string(),
|
|
370
|
+
importedAt: v.string(),
|
|
371
|
+
tags: v.array(v.string()),
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**How It Works:**
|
|
380
|
+
- `validation.schema` is the **single source of truth** - works everywhere (client + server)
|
|
381
|
+
- Takes priority over built-in type schemas
|
|
382
|
+
- Entity is self-contained - no separate registrations needed
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 7. Hooks API
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Actions
|
|
390
|
+
const { add, update, delete: remove, get } = useCrud(productEntity);
|
|
391
|
+
|
|
392
|
+
// List (auto-fetch for tables)
|
|
393
|
+
const { items, loading } = useCrudList(productEntity);
|
|
394
|
+
|
|
395
|
+
// Cards (optimized for public)
|
|
396
|
+
const { items, loading, loadMore } = useCrudCardList(productEntity);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 8. Custom Form Layouts
|
|
402
|
+
|
|
403
|
+
Need a custom layout instead of `EntityFormRenderer`? Use `useEntityForm` directly.
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
import { useId, useEffect } from 'react';
|
|
407
|
+
import { useEntityForm, useCrud, UploadProvider } from '@donotdev/crud';
|
|
408
|
+
import { productEntity } from 'entities';
|
|
409
|
+
|
|
410
|
+
export function CustomProductForm() {
|
|
411
|
+
const formId = useId();
|
|
412
|
+
const { add } = useCrud(productEntity);
|
|
413
|
+
const {
|
|
414
|
+
control,
|
|
415
|
+
handleSubmit,
|
|
416
|
+
fields,
|
|
417
|
+
formStatus, // 'idle' | 'uploading' | 'submitting' | ...
|
|
418
|
+
uploadProgress, // 0-100 during uploads
|
|
419
|
+
cleanup
|
|
420
|
+
} = useEntityForm(productEntity, { formId });
|
|
421
|
+
|
|
422
|
+
useEffect(() => cleanup, [cleanup]);
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<UploadProvider formId={formId}>
|
|
426
|
+
<form onSubmit={handleSubmit(add)}>
|
|
427
|
+
{/* Your custom layout here */}
|
|
428
|
+
<Controller control={control} name="name" render={({ field }) => (
|
|
429
|
+
<input {...field} placeholder="Product name" />
|
|
430
|
+
)} />
|
|
431
|
+
|
|
432
|
+
<button type="submit" disabled={formStatus !== 'idle'}>
|
|
433
|
+
{formStatus === 'uploading' ? `Uploading ${uploadProgress}%...` : 'Save'}
|
|
434
|
+
</button>
|
|
435
|
+
</form>
|
|
436
|
+
</UploadProvider>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Key points:**
|
|
442
|
+
- Pass `formId` to get automatic upload handling and loading states
|
|
443
|
+
- Wrap with `UploadProvider` if using file/image fields
|
|
444
|
+
- `handleSubmit` automatically uploads files before validation
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 9. i18n
|
|
449
|
+
|
|
450
|
+
**Namespace:** `entity-${entity.name.toLowerCase()}`
|
|
451
|
+
|
|
452
|
+
**File:** `locales/entity-product_en.json`
|
|
453
|
+
```json
|
|
454
|
+
{
|
|
455
|
+
"fields": {
|
|
456
|
+
"name": "Product Name",
|
|
457
|
+
"price": "Price"
|
|
458
|
+
},
|
|
459
|
+
"name": "Product"
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Field `label` → translation key in `fields.*`
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 10. Routing Convention
|
|
468
|
+
|
|
469
|
+
| Route | Purpose |
|
|
470
|
+
|-------|---------|
|
|
471
|
+
| `/${collection}` | List |
|
|
472
|
+
| `/${collection}/new` | Create |
|
|
473
|
+
| `/${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.**
|