@akanjs/cli 0.0.145 → 0.0.147
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/README.md +7 -26
- package/cjs/index.js +191 -28
- package/cjs/src/guidelines/componentRule/componentRule.instruction.md +3 -81
- package/cjs/src/guidelines/cssRule/cssRule.instruction.md +435 -0
- package/cjs/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
- package/cjs/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/cjs/src/guidelines/modelStore/modelStore.instruction.md +2 -1
- package/cjs/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/cjs/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/cjs/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
- package/cjs/src/templates/app/main.js +1 -2
- package/esm/index.js +199 -36
- package/esm/src/guidelines/componentRule/componentRule.instruction.md +3 -81
- package/esm/src/guidelines/cssRule/cssRule.instruction.md +435 -0
- package/esm/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
- package/esm/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/esm/src/guidelines/modelStore/modelStore.instruction.md +2 -1
- package/esm/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/esm/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/esm/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
- package/esm/src/templates/app/main.js +1 -2
- package/package.json +1 -1
- package/src/guideline/guideline.command.d.ts +3 -1
- package/src/guideline/guideline.prompt.d.ts +15 -1
- package/src/guideline/guideline.runner.d.ts +17 -3
- package/src/guideline/guideline.script.d.ts +8 -2
- package/src/guidelines/componentRule/componentRule.instruction.md +3 -81
- package/src/guidelines/cssRule/cssRule.instruction.md +435 -0
- package/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
- package/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/src/guidelines/modelStore/modelStore.instruction.md +2 -1
- package/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
|
@@ -14,92 +14,92 @@ Model constant files (`model.constant.ts`) serve as the foundation of the Akan.j
|
|
|
14
14
|
|
|
15
15
|
## Model Class Hierarchy and Relationships
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
│ ModelObject │
|
|
27
|
-
└───┬───────────┘
|
|
28
|
-
│
|
|
29
|
-
┌─────────────────┬─┴───┬─────────────────┐
|
|
30
|
-
│ │ │ │
|
|
31
|
-
▼ ▼ ▼ ▼
|
|
32
|
-
┌───────────────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐
|
|
33
|
-
│ ModelFilter │ │ Model │ │LightMod.│ │ModelInsight │
|
|
34
|
-
└───────────────────┘ └─────────┘ └─────────┘ └─────────────┘
|
|
35
|
-
│
|
|
36
|
-
▼
|
|
37
|
-
┌───────────────┐
|
|
38
|
-
│ModelSummary │
|
|
39
|
-
└───────────────┘
|
|
17
|
+
```mermaid
|
|
18
|
+
graph TD
|
|
19
|
+
Input[Model.Input] --> Object[Model.Object]
|
|
20
|
+
Object --> Light[Model.Light]
|
|
21
|
+
Object --> Full[Model.Full]
|
|
22
|
+
Object --> Insight[Model.Insight]
|
|
23
|
+
Insight --> Summary[Model.Summary]
|
|
24
|
+
Object --> Query[Model.Query]
|
|
25
|
+
Object --> Sort[Model.Sort]
|
|
40
26
|
```
|
|
41
27
|
|
|
42
28
|
Each model type serves a specific purpose in the data lifecycle:
|
|
43
29
|
|
|
44
|
-
1. **Input Model**:
|
|
45
|
-
2. **Object Model**:
|
|
46
|
-
3. **Light Model**:
|
|
47
|
-
4. **Full Model**:
|
|
48
|
-
5. **
|
|
49
|
-
6. **
|
|
50
|
-
7. **
|
|
30
|
+
1. **Input Model**: Data required to create a schema
|
|
31
|
+
2. **Object Model**: Schema completed when data is created
|
|
32
|
+
3. **Light Model**: Lightweight schema used when querying multiple data
|
|
33
|
+
4. **Full Model**: Class-based complete form of data
|
|
34
|
+
5. **Insight Model**: Statistical data extractable during queries
|
|
35
|
+
6. **Summary Model**: Statistical data extracted periodically during monitoring
|
|
36
|
+
7. **Query Model**: Defines query methods and statements
|
|
37
|
+
8. **Sort Model**: Defines sort keys and values for data ordering
|
|
51
38
|
|
|
52
39
|
## File Structure and Organization
|
|
53
40
|
|
|
54
41
|
```typescript
|
|
55
42
|
// 1. Imports
|
|
56
43
|
import { enumOf, ID, Int } from "@akanjs/base";
|
|
57
|
-
import { Field,
|
|
58
|
-
import { LightRelatedModel } from "../related/related.constant";
|
|
44
|
+
import { Field, Model } from "@akanjs/constant";
|
|
59
45
|
|
|
60
46
|
// 2. Enums
|
|
61
47
|
export const StatusEnum = enumOf(["active", "inactive"] as const);
|
|
62
48
|
export type StatusEnum = enumOf<typeof StatusEnum>;
|
|
63
49
|
|
|
64
50
|
// 3. Input Model
|
|
65
|
-
@Model.Input("
|
|
66
|
-
export class
|
|
67
|
-
|
|
51
|
+
@Model.Input("DroneInput")
|
|
52
|
+
export class DroneInput {
|
|
53
|
+
@Field.Prop(() => String)
|
|
54
|
+
name: string;
|
|
68
55
|
}
|
|
69
56
|
|
|
70
57
|
// 4. Object Model
|
|
71
|
-
@Model.Object("
|
|
72
|
-
export class
|
|
73
|
-
|
|
58
|
+
@Model.Object("DroneObject")
|
|
59
|
+
export class DroneObject extends BaseModel(DroneInput) {
|
|
60
|
+
@Field.Prop(() => String, { enum: StatusEnum, default: "offline" })
|
|
61
|
+
status: StatusEnum;
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
// 5. Light Model
|
|
77
|
-
@Model.Light("
|
|
78
|
-
export class
|
|
65
|
+
@Model.Light("LightDrone")
|
|
66
|
+
export class LightDrone extends Light(DroneObject, ["id", "name", "status"] as const) {
|
|
67
|
+
isConnected() {
|
|
68
|
+
return this.status !== "offline";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
79
71
|
|
|
80
72
|
// 6. Full Model
|
|
81
|
-
@Model.Full("
|
|
82
|
-
export class
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
export class ProductInsight {
|
|
87
|
-
// Aggregation fields
|
|
73
|
+
@Model.Full("Drone")
|
|
74
|
+
export class Drone extends Full(DroneObject, LightDrone) {
|
|
75
|
+
isAvailable() {
|
|
76
|
+
return this.isConnected() && this.wsUri.startsWith("ws://");
|
|
77
|
+
}
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
//
|
|
91
|
-
@Model.
|
|
92
|
-
export class
|
|
93
|
-
|
|
80
|
+
// 7. Insight Model
|
|
81
|
+
@Model.Insight("DroneInsight")
|
|
82
|
+
export class DroneInsight {
|
|
83
|
+
@Field.Prop(() => Int, { default: 0, accumulate: { $sum: 1 } })
|
|
84
|
+
count: number;
|
|
94
85
|
}
|
|
95
86
|
|
|
96
|
-
//
|
|
97
|
-
@Model.
|
|
98
|
-
export class
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Query methods
|
|
87
|
+
// 8. Summary Model
|
|
88
|
+
@Model.Summary("DroneSummary")
|
|
89
|
+
export class DroneSummary {
|
|
90
|
+
@Field.Prop(() => Int, { min: 0, default: 0, query: { status: { $ne: "inactive" } } })
|
|
91
|
+
totalDrone: number;
|
|
102
92
|
}
|
|
93
|
+
|
|
94
|
+
// 9. Query Model
|
|
95
|
+
export const droneQuery = {
|
|
96
|
+
byName: (name: string) => ({ name }),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// 10. Sort Model
|
|
100
|
+
export const droneSort = {
|
|
101
|
+
alphabetical: { name: 1 },
|
|
102
|
+
};
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
## Required Imports
|
|
@@ -109,850 +109,433 @@ export class ProductFilter extends sortOf(Product, {
|
|
|
109
109
|
import { enumOf, ID, Int, Float, String, Boolean, Date, type Dayjs, dayjs, JSON } from "@akanjs/base";
|
|
110
110
|
|
|
111
111
|
// Model decorators
|
|
112
|
-
import { Field,
|
|
112
|
+
import { Field, Model } from "@akanjs/constant";
|
|
113
113
|
|
|
114
114
|
// Related models (use Light models to prevent circular dependencies)
|
|
115
115
|
import { LightCategory } from "../category/category.constant";
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
## Enum Definition Patterns
|
|
119
|
-
|
|
120
|
-
Enums should be defined with `enumOf()` and exported as both const and type:
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// Define enum with camelCase values (preferred)
|
|
124
|
-
export const StatusEnum = enumOf([
|
|
125
|
-
"active",
|
|
126
|
-
"inactive",
|
|
127
|
-
"pending"
|
|
128
|
-
] as const);
|
|
129
|
-
|
|
130
|
-
// Export as type for TypeScript
|
|
131
|
-
export type StatusEnum = enumOf<typeof StatusEnum>;
|
|
132
|
-
|
|
133
|
-
// Usage in fields
|
|
134
|
-
@Field.Prop(() => String, {
|
|
135
|
-
enum: StatusEnum,
|
|
136
|
-
default: "active"
|
|
137
|
-
})
|
|
138
|
-
status: StatusEnum;
|
|
139
|
-
```
|
|
140
|
-
|
|
141
118
|
## ModelInput Implementation
|
|
142
119
|
|
|
143
|
-
The Input model defines fields
|
|
120
|
+
The Input model defines fields required for data creation:
|
|
144
121
|
|
|
145
122
|
```typescript
|
|
146
|
-
@Model.Input("
|
|
147
|
-
export class
|
|
148
|
-
@Field.Prop(() => String
|
|
149
|
-
minlength: 3,
|
|
150
|
-
maxlength: 100,
|
|
151
|
-
})
|
|
123
|
+
@Model.Input("DroneInput")
|
|
124
|
+
export class DroneInput {
|
|
125
|
+
@Field.Prop(() => String)
|
|
152
126
|
name: string;
|
|
153
127
|
|
|
154
|
-
@Field.Prop(() =>
|
|
155
|
-
|
|
156
|
-
default: 0,
|
|
157
|
-
})
|
|
158
|
-
price: number;
|
|
159
|
-
|
|
160
|
-
@Field.Prop(() => String, {
|
|
161
|
-
nullable: true,
|
|
162
|
-
maxlength: 1000,
|
|
163
|
-
})
|
|
164
|
-
description: string | null;
|
|
165
|
-
|
|
166
|
-
@Field.Prop(() => LightCategory, {
|
|
167
|
-
ref: "Category",
|
|
168
|
-
})
|
|
169
|
-
category: LightCategory;
|
|
128
|
+
@Field.Prop(() => String, { default: "ws://10.10.150.10:9091" })
|
|
129
|
+
wsUri: string;
|
|
170
130
|
}
|
|
171
131
|
```
|
|
172
132
|
|
|
173
|
-
|
|
133
|
+
**Usage Examples**:
|
|
174
134
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
135
|
+
```typescript
|
|
136
|
+
// Document creation
|
|
137
|
+
new this.Drone({ name: "myDrone", wsUri: "ws://10.10.150.10:9091" });
|
|
138
|
+
|
|
139
|
+
// Service logic
|
|
140
|
+
this.createDrone({ name: "myDrone", wsUri: "ws://10.10.150.10:9091" });
|
|
141
|
+
|
|
142
|
+
// API call
|
|
143
|
+
fetch.createDrone({ name: "myDrone", wsUri: "ws://10.10.150.10:9091" });
|
|
144
|
+
```
|
|
180
145
|
|
|
181
146
|
## ModelObject Implementation
|
|
182
147
|
|
|
183
|
-
The Object model
|
|
148
|
+
The Object model adds system-managed fields:
|
|
184
149
|
|
|
185
150
|
```typescript
|
|
186
|
-
|
|
187
|
-
export
|
|
188
|
-
@Field.Prop(() => String, {
|
|
189
|
-
enum: StatusEnum,
|
|
190
|
-
default: "active",
|
|
191
|
-
})
|
|
192
|
-
status: StatusEnum;
|
|
151
|
+
export const droneStatuses = ["active", "offline", "inactive"] as const;
|
|
152
|
+
export type DroneStatus = (typeof droneStatuses)[number];
|
|
193
153
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
viewCount: number;
|
|
199
|
-
|
|
200
|
-
@Field.Hidden(() => String, {
|
|
201
|
-
nullable: true,
|
|
202
|
-
})
|
|
203
|
-
internalNotes: string | null;
|
|
204
|
-
|
|
205
|
-
@Field.Secret(() => String, {
|
|
206
|
-
nullable: true,
|
|
207
|
-
})
|
|
208
|
-
apiKey: string | null;
|
|
154
|
+
@Model.Object("DroneObject")
|
|
155
|
+
export class DroneObject extends BaseModel(DroneInput) {
|
|
156
|
+
@Field.Prop(() => String, { enum: droneStatuses, default: "offline" })
|
|
157
|
+
status: DroneStatus;
|
|
209
158
|
}
|
|
210
159
|
```
|
|
211
160
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- Inherits all Input model fields via `via()`
|
|
215
|
-
- Adds system-managed fields like status
|
|
216
|
-
- Can include counters and metrics
|
|
217
|
-
- May include hidden fields with `Field.Hidden`
|
|
218
|
-
- May include sensitive data with `Field.Secret`
|
|
219
|
-
|
|
220
|
-
## LightModel Implementation
|
|
221
|
-
|
|
222
|
-
The Light model defines a subset of fields for listings and references:
|
|
161
|
+
**Usage Examples**:
|
|
223
162
|
|
|
224
163
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Key characteristics:
|
|
164
|
+
// Document lookup
|
|
165
|
+
const drone = await this.Drone.pickById("droneId"); // drone.status
|
|
230
166
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- Always includes `id` field
|
|
234
|
-
- Optimized for listings and relationships
|
|
235
|
-
- No additional fields, only selection
|
|
167
|
+
// Service lookup
|
|
168
|
+
const drone = await this.getDrone("droneId"); // drone.status
|
|
236
169
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
The Full model combines Object and Light models for database schema:
|
|
170
|
+
// Store lookup
|
|
171
|
+
const drone = await fetch.drone("droneId"); // drone.status
|
|
240
172
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
export class Product extends via(ProductObject, LightProduct) {}
|
|
173
|
+
// Component usage
|
|
174
|
+
const drone = st.use.drone(); // drone.status
|
|
244
175
|
```
|
|
245
176
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
- Usually empty implementation
|
|
249
|
-
- Combines Object and Light models
|
|
250
|
-
- Used for database schema generation
|
|
251
|
-
- Primary model for CRUD operations
|
|
252
|
-
- Name should match the base entity (no suffix)
|
|
253
|
-
|
|
254
|
-
## Insight Model Implementation
|
|
177
|
+
## LightModel Implementation
|
|
255
178
|
|
|
256
|
-
|
|
179
|
+
Light model defines lightweight schema for list queries:
|
|
257
180
|
|
|
258
181
|
```typescript
|
|
259
|
-
@Model.
|
|
260
|
-
export class
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
})
|
|
265
|
-
count: number;
|
|
266
|
-
|
|
267
|
-
@Field.Prop(() => Int, {
|
|
268
|
-
default: 0,
|
|
269
|
-
accumulate: { $sum: "$price" },
|
|
270
|
-
})
|
|
271
|
-
totalSales: number;
|
|
272
|
-
|
|
273
|
-
@Field.Prop(() => Float, {
|
|
274
|
-
default: 0,
|
|
275
|
-
accumulate: { $avg: "$price" },
|
|
276
|
-
})
|
|
277
|
-
averagePrice: number;
|
|
182
|
+
@Model.Light("LightDrone")
|
|
183
|
+
export class LightDrone extends Light(DroneObject, ["name", "status"] as const) {
|
|
184
|
+
isConnected() {
|
|
185
|
+
return this.status !== "offline";
|
|
186
|
+
}
|
|
278
187
|
}
|
|
279
188
|
```
|
|
280
189
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- Uses MongoDB accumulator operators
|
|
284
|
-
- Defines metrics and KPIs
|
|
285
|
-
- Supports data analytics
|
|
286
|
-
- Fields typically have numeric types
|
|
287
|
-
- Often has count, sum, avg operations
|
|
288
|
-
|
|
289
|
-
## Summary Model Implementation
|
|
290
|
-
|
|
291
|
-
The Summary model defines dashboard statistics:
|
|
190
|
+
**Usage Examples**:
|
|
292
191
|
|
|
293
192
|
```typescript
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
min: 0,
|
|
298
|
-
default: 0,
|
|
299
|
-
query: {},
|
|
300
|
-
})
|
|
301
|
-
totalProducts: number;
|
|
302
|
-
|
|
303
|
-
@Field.Prop(() => Int, {
|
|
304
|
-
min: 0,
|
|
305
|
-
default: 0,
|
|
306
|
-
query: { status: "active" },
|
|
307
|
-
})
|
|
308
|
-
activeProducts: number;
|
|
309
|
-
|
|
310
|
-
@Field.Prop(() => Float, {
|
|
311
|
-
default: 0,
|
|
312
|
-
query: { price: { $gt: 0 } },
|
|
313
|
-
})
|
|
314
|
-
averagePrice: number;
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Key characteristics:
|
|
193
|
+
// Store query
|
|
194
|
+
const droneList = await fetch.droneList({ status: "active" });
|
|
195
|
+
// drone.name available, drone.wsUri not available
|
|
319
196
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- May combine data from multiple models
|
|
324
|
-
- Often uses queries to filter record sets
|
|
197
|
+
// Component usage
|
|
198
|
+
const droneMap = st.use.droneMap(); // Map<string, LightDrone>
|
|
199
|
+
```
|
|
325
200
|
|
|
326
|
-
##
|
|
201
|
+
## Full Model Implementation
|
|
327
202
|
|
|
328
|
-
|
|
203
|
+
Full model adds convenience functions:
|
|
329
204
|
|
|
330
205
|
```typescript
|
|
331
|
-
@Model.
|
|
332
|
-
export class
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
cheapest: { price: 1 },
|
|
336
|
-
expensive: { price: -1 },
|
|
337
|
-
}) {
|
|
338
|
-
@Filter.Mongo()
|
|
339
|
-
byStatus(
|
|
340
|
-
@Filter.Arg("status", () => String, {
|
|
341
|
-
enum: StatusEnum,
|
|
342
|
-
default: "active",
|
|
343
|
-
})
|
|
344
|
-
status: StatusEnum
|
|
345
|
-
) {
|
|
346
|
-
return { status };
|
|
206
|
+
@Model.Full("Drone")
|
|
207
|
+
export class Drone extends Full(DroneObject, LightDrone) {
|
|
208
|
+
static isDronesAllConnected(droneList: LightDrone[]) {
|
|
209
|
+
return droneList.every((drone) => drone.isConnected());
|
|
347
210
|
}
|
|
348
211
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
@Filter.Arg("categoryId", () => ID, {
|
|
352
|
-
ref: "Category",
|
|
353
|
-
renderOption: (cat: LightCategory) => cat.name,
|
|
354
|
-
})
|
|
355
|
-
categoryId: string
|
|
356
|
-
) {
|
|
357
|
-
return { category: categoryId };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
@Filter.Mongo()
|
|
361
|
-
priceRange(
|
|
362
|
-
@Filter.Arg("min", () => Int, { min: 0, default: 0 })
|
|
363
|
-
min: number,
|
|
364
|
-
|
|
365
|
-
@Filter.Arg("max", () => Int, { min: 0, nullable: true })
|
|
366
|
-
max: number | null
|
|
367
|
-
) {
|
|
368
|
-
const query: Record<string, any> = { price: { $gte: min } };
|
|
369
|
-
if (max !== null) {
|
|
370
|
-
query.price.$lte = max;
|
|
371
|
-
}
|
|
372
|
-
return query;
|
|
212
|
+
isAvailable() {
|
|
213
|
+
return this.isConnected() && this.wsUri.startsWith("ws://");
|
|
373
214
|
}
|
|
374
215
|
}
|
|
375
216
|
```
|
|
376
217
|
|
|
377
|
-
|
|
218
|
+
**Usage Examples**:
|
|
378
219
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
220
|
+
```typescript
|
|
221
|
+
// API response handling
|
|
222
|
+
const drone = await fetch.drone("droneId");
|
|
223
|
+
drone.isAvailable();
|
|
224
|
+
|
|
225
|
+
// Component logic
|
|
226
|
+
const droneMap = st.use.droneMap();
|
|
227
|
+
const droneList = [...droneMap.values()];
|
|
228
|
+
const isAllConnected = cnst.Drone.isDroneAllConnected(droneList);
|
|
229
|
+
```
|
|
385
230
|
|
|
386
|
-
##
|
|
231
|
+
## Insight Model Implementation
|
|
387
232
|
|
|
388
|
-
|
|
233
|
+
Insight model defines statistical fields:
|
|
389
234
|
|
|
390
235
|
```typescript
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
// Hidden field (DB only, not in API)
|
|
396
|
-
@Field.Hidden(() => JSON, { options })
|
|
397
|
-
metadata: Record<string, any>;
|
|
398
|
-
|
|
399
|
-
// Sensitive field (not selected by default)
|
|
400
|
-
@Field.Secret(() => String, { options })
|
|
401
|
-
apiKey: string;
|
|
402
|
-
|
|
403
|
-
// Computed field (not stored in DB)
|
|
404
|
-
@Field.Resolve(() => Int)
|
|
405
|
-
get total(): number {
|
|
406
|
-
return this.quantity * this.price;
|
|
236
|
+
@Model.Insight("DroneInsight")
|
|
237
|
+
export class DroneInsight {
|
|
238
|
+
@Field.Prop(() => Int, { default: 0, accumulate: { $sum: 1 } })
|
|
239
|
+
count: number;
|
|
407
240
|
}
|
|
408
241
|
```
|
|
409
242
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
| Option | Type | Description | Example |
|
|
413
|
-
| ----------- | ---------- | ------------------------ | -------------------------- |
|
|
414
|
-
| `default` | `any` | Default value | `{ default: 0 }` |
|
|
415
|
-
| `nullable` | `boolean` | Allows null | `{ nullable: true }` |
|
|
416
|
-
| `enum` | `Enum` | Restricts to enum values | `{ enum: StatusEnum }` |
|
|
417
|
-
| `min` | `number` | Minimum value | `{ min: 0 }` |
|
|
418
|
-
| `max` | `number` | Maximum value | `{ max: 100 }` |
|
|
419
|
-
| `minlength` | `number` | Minimum string length | `{ minlength: 3 }` |
|
|
420
|
-
| `maxlength` | `number` | Maximum string length | `{ maxlength: 255 }` |
|
|
421
|
-
| `ref` | `string` | Reference to model | `{ ref: "Category" }` |
|
|
422
|
-
| `refPath` | `string` | Dynamic reference path | `{ refPath: "modelType" }` |
|
|
423
|
-
| `immutable` | `boolean` | Cannot be changed | `{ immutable: true }` |
|
|
424
|
-
| `validate` | `function` | Custom validation | `{ validate: isEmail }` |
|
|
425
|
-
| `text` | `string` | Search configuration | `{ text: "search" }` |
|
|
426
|
-
|
|
427
|
-
## Filter Decorators and Arguments
|
|
428
|
-
|
|
429
|
-
Filter decorators define query methods:
|
|
243
|
+
**Usage Examples**:
|
|
430
244
|
|
|
431
245
|
```typescript
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
byStatus(
|
|
435
|
-
@Filter.Arg("status", () => String, {
|
|
436
|
-
enum: StatusEnum,
|
|
437
|
-
default: "active"
|
|
438
|
-
})
|
|
439
|
-
status: StatusEnum
|
|
440
|
-
) {
|
|
441
|
-
return { status };
|
|
442
|
-
}
|
|
246
|
+
// Service usage
|
|
247
|
+
const droneInsight = await this.insight({ ...query });
|
|
443
248
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
) {
|
|
450
|
-
return { q: query };
|
|
451
|
-
}
|
|
249
|
+
// API call
|
|
250
|
+
const droneInsight = await fetch.droneInsight({ ...query });
|
|
251
|
+
|
|
252
|
+
// Component usage
|
|
253
|
+
const droneInsight = st.use.droneInsight(); // droneInsight.count
|
|
452
254
|
```
|
|
453
255
|
|
|
454
|
-
|
|
256
|
+
## Summary Model Implementation
|
|
455
257
|
|
|
456
|
-
|
|
457
|
-
| -------------- | ------------------------ | --------------------------------- |
|
|
458
|
-
| `enum` | Restricts to enum values | `{ enum: StatusEnum }` |
|
|
459
|
-
| `default` | Default value | `{ default: "active" }` |
|
|
460
|
-
| `nullable` | Allows null | `{ nullable: true }` |
|
|
461
|
-
| `ref` | Reference to model | `{ ref: "Category" }` |
|
|
462
|
-
| `renderOption` | Display function | `{ renderOption: (x) => x.name }` |
|
|
258
|
+
Summary model defines periodic statistics:
|
|
463
259
|
|
|
464
|
-
|
|
260
|
+
```typescript
|
|
261
|
+
@Model.Summary("DroneSummary")
|
|
262
|
+
export class DroneSummary {
|
|
263
|
+
@Field.Prop(() => Int, { min: 0, default: 0, query: { status: { $ne: "inactive" } } })
|
|
264
|
+
totalDrone: number;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
465
267
|
|
|
466
|
-
|
|
268
|
+
**Usage Examples**:
|
|
467
269
|
|
|
468
270
|
```typescript
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// Inherit specific fields from Object model
|
|
473
|
-
extends via(ProductObject, [
|
|
474
|
-
"id", "name", "price"
|
|
475
|
-
] as const)
|
|
476
|
-
|
|
477
|
-
// Inherit both Object and Light models
|
|
478
|
-
extends via(ProductObject, LightProduct)
|
|
479
|
-
|
|
480
|
-
// Define sorting options
|
|
481
|
-
extends sortOf(Product, {
|
|
482
|
-
newest: { createdAt: -1 },
|
|
483
|
-
cheapest: { price: 1 }
|
|
484
|
-
})
|
|
485
|
-
```
|
|
271
|
+
// Service usage
|
|
272
|
+
const summary = await this.getActiveSummary();
|
|
486
273
|
|
|
487
|
-
|
|
274
|
+
// API call
|
|
275
|
+
const summary = await fetch.getActiveSummary();
|
|
488
276
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
- Sort order: 1 for ascending, -1 for descending
|
|
277
|
+
// Component usage
|
|
278
|
+
const summary = st.use.summary(); // summary.totalDrone
|
|
279
|
+
```
|
|
493
280
|
|
|
494
|
-
##
|
|
281
|
+
## Query Model Implementation
|
|
495
282
|
|
|
496
|
-
|
|
283
|
+
Query model defines query methods:
|
|
497
284
|
|
|
498
285
|
```typescript
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
// Array of references
|
|
504
|
-
@Field.Prop(() => [LightTag], { ref: "Tag" })
|
|
505
|
-
tags: LightTag[];
|
|
506
|
-
|
|
507
|
-
// Optional reference
|
|
508
|
-
@Field.Prop(() => LightUser, {
|
|
509
|
-
ref: "User",
|
|
510
|
-
nullable: true
|
|
511
|
-
})
|
|
512
|
-
assignedTo: LightUser | null;
|
|
513
|
-
|
|
514
|
-
// Dynamic reference (polymorphic)
|
|
515
|
-
@Field.Prop(() => String, { enum: ModelType })
|
|
516
|
-
modelType: ModelType;
|
|
517
|
-
|
|
518
|
-
@Field.Prop(() => LightModel, { refPath: "modelType" })
|
|
519
|
-
model: LightModel;
|
|
520
|
-
|
|
521
|
-
// Parent-child relationship
|
|
522
|
-
@Field.Prop(() => LightProject, {
|
|
523
|
-
ref: "Project",
|
|
524
|
-
refType: "parent"
|
|
525
|
-
})
|
|
526
|
-
project: LightProject;
|
|
286
|
+
export const droneQuery = {
|
|
287
|
+
...baseQueries,
|
|
288
|
+
byName: (name: string) => ({ name }),
|
|
289
|
+
};
|
|
527
290
|
```
|
|
528
291
|
|
|
529
|
-
|
|
292
|
+
**Usage Examples**:
|
|
530
293
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
294
|
+
```typescript
|
|
295
|
+
// Document queries
|
|
296
|
+
const drone = await this.findByName("myDrone");
|
|
297
|
+
const drones = await this.listByName("myDrone");
|
|
298
|
+
const droneCount = await this.countByName("myDrone");
|
|
299
|
+
|
|
300
|
+
// Service queries
|
|
301
|
+
const drone = await this.findByName("myDrone");
|
|
302
|
+
const drones = await this.listByName("myDrone");
|
|
303
|
+
```
|
|
536
304
|
|
|
537
|
-
##
|
|
305
|
+
## Sort Model Implementation
|
|
538
306
|
|
|
539
|
-
|
|
307
|
+
Sort model defines sorting options:
|
|
540
308
|
|
|
541
309
|
```typescript
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
// String length validation
|
|
547
|
-
@Field.Prop(() => String, { minlength: 3, maxlength: 50 })
|
|
548
|
-
username: string;
|
|
549
|
-
|
|
550
|
-
// Format validation
|
|
551
|
-
@Field.Prop(() => String, { type: "email" })
|
|
552
|
-
email: string;
|
|
553
|
-
|
|
554
|
-
// Custom validation function
|
|
555
|
-
@Field.Prop(() => String, {
|
|
556
|
-
validate: (value, model) => {
|
|
557
|
-
return /^[A-Z][a-z]+$/.test(value);
|
|
558
|
-
}
|
|
559
|
-
})
|
|
560
|
-
properName: string;
|
|
310
|
+
export const droneSort = {
|
|
311
|
+
...baseSorts,
|
|
312
|
+
alphabetical: { name: 1 },
|
|
313
|
+
};
|
|
561
314
|
```
|
|
562
315
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
Ensure type safety across the model:
|
|
316
|
+
**Usage Examples**:
|
|
566
317
|
|
|
567
318
|
```typescript
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
count: number; // TypeScript type matches decorator type
|
|
571
|
-
|
|
572
|
-
// 2. Mark arrays as const for Light models
|
|
573
|
-
extends via(ProductObject, [
|
|
574
|
-
"id", "name"
|
|
575
|
-
] as const) // Type-safe field selection
|
|
319
|
+
// Document sorting
|
|
320
|
+
const drones = await this.listByName("myDrone", { sort: "alphabetical" });
|
|
576
321
|
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
|
|
322
|
+
// Service sorting
|
|
323
|
+
const drones = await this.listByName("myDrone", { sort: "alphabetical" });
|
|
324
|
+
```
|
|
580
325
|
|
|
581
|
-
|
|
582
|
-
export type StatusEnum = enumOf<typeof StatusEnum>;
|
|
326
|
+
## Field Decorators and Options
|
|
583
327
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
328
|
+
| Option | Type | Default | Description | Example |
|
|
329
|
+
| ------------ | -------------------- | ------- | -------------------------------------- | ---------------------------------------------------------------------- |
|
|
330
|
+
| `nullable` | `boolean` | `false` | Field required status | `@Field.Prop(()=> String, { default: "untitled" }) title: string;` |
|
|
331
|
+
| `ref` | `string` | - | Reference collection name | `@Field.Prop(()=> ID, { ref: "drone" }) drone: string;` |
|
|
332
|
+
| `refPath` | `string` | - | Dynamic reference collection name | `@Field.Prop(()=> ID, { refPath: "rootType" }) root: string;` |
|
|
333
|
+
| `default` | `any` | - | Default value when no value entered | `@Field.Prop(()=> String, { default: "untitled" }) title: string;` |
|
|
334
|
+
| `type` | `string` | - | Preset validation (email/password/url) | `@Field.Prop(()=> String, { type: "email" }) email: string;` |
|
|
335
|
+
| `immutable` | `boolean` | `false` | Cannot be changed after creation | `@Field.Prop(()=> ID, { immutable: true }) creator: string;` |
|
|
336
|
+
| `min` | `number` | - | Minimum value for Int/Float fields | `@Field.Prop(()=> Int, { min: 0 }) progress: number;` |
|
|
337
|
+
| `max` | `number` | - | Maximum value for Int/Float fields | `@Field.Prop(()=> Float, { min: 0, max: 1 }) ratio: number;` |
|
|
338
|
+
| `enum` | `any[]` | - | Enum values restriction | `@Field.Prop(()=> String, { enum: ["active","inactive"] }) status;` |
|
|
339
|
+
| `minlength` | `number` | - | Minimum string length | `@Field.Prop(()=> String, { minlength: 2 }) name: string;` |
|
|
340
|
+
| `maxlength` | `number` | - | Maximum string length | `@Field.Prop(()=> String, { maxlength: 30 }) title: string;` |
|
|
341
|
+
| `query` | `object` | - | Query value for Summary fields | `@Field.Prop(() => Int, { query: { status: { $ne: 'inactive' } })` |
|
|
342
|
+
| `accumulate` | `object` | - | Aggregation value for Insight fields | `@Field.Prop(() => Int, { accumulate: { $sum: 1 } }) count: number;` |
|
|
343
|
+
| `example` | `any` | - | Example value for API docs | `@Field.Prop(()=> String, { example: "hello@akanjs.com" }) email;` |
|
|
344
|
+
| `of` | `any` | - | Value type for Map fields | `@Field.Prop(()=> Map, { of: Date }) readAts: Map<string, Dayjs>;` |
|
|
345
|
+
| `validate` | `(value) => boolean` | - | Custom validation function | `@Field.Prop(()=> String, { validate: (v)=> v.includes("@") }) email;` |
|
|
346
|
+
|
|
347
|
+
## Advanced Field Types
|
|
348
|
+
|
|
349
|
+
```mermaid
|
|
350
|
+
graph LR
|
|
351
|
+
Prop[Field.Prop] -->|General purpose| Standard
|
|
352
|
+
Hidden[Field.Hidden] -->|Backend only| Restricted
|
|
353
|
+
Secret[Field.Secret] -->|Security restricted| Sensitive
|
|
354
|
+
Resolve[Field.Resolve] -->|Computed| Virtual
|
|
587
355
|
```
|
|
588
356
|
|
|
589
|
-
|
|
357
|
+
1. **Field.Prop**
|
|
358
|
+
General schema field declaration, visible in both backend and frontend
|
|
590
359
|
|
|
591
|
-
|
|
360
|
+
```typescript
|
|
361
|
+
@Field.Prop(() => String)
|
|
362
|
+
name: string;
|
|
363
|
+
```
|
|
592
364
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
@Field.Prop(String)
|
|
596
|
-
name: string;
|
|
365
|
+
2. **Field.Hidden**
|
|
366
|
+
Backend-only fields, restricted in frontend for security
|
|
597
367
|
|
|
598
|
-
|
|
599
|
-
@Field.
|
|
600
|
-
|
|
601
|
-
```
|
|
368
|
+
```typescript
|
|
369
|
+
@Field.Hidden(() => String)
|
|
370
|
+
internalCode: string;
|
|
371
|
+
```
|
|
602
372
|
|
|
603
|
-
|
|
373
|
+
3. **Field.Secret**
|
|
374
|
+
Security-sensitive fields with query restrictions
|
|
604
375
|
|
|
605
|
-
```typescript
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
376
|
+
```typescript
|
|
377
|
+
@Field.Secret(() => String)
|
|
378
|
+
apiKey: string;
|
|
379
|
+
```
|
|
609
380
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
381
|
+
4. **Field.Resolve**
|
|
382
|
+
Computed fields not stored in database
|
|
383
|
+
```typescript
|
|
384
|
+
@Field.Resolve(() => Int)
|
|
385
|
+
get total() { return this.items.length }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Advanced - Relation Patterns
|
|
614
389
|
|
|
615
|
-
###
|
|
390
|
+
### 1. Scalar Embedded Style
|
|
616
391
|
|
|
617
392
|
```typescript
|
|
618
|
-
|
|
619
|
-
|
|
393
|
+
@Model.Scalar("DronePhysicalState")
|
|
394
|
+
export class DronePhysicalState {
|
|
395
|
+
@Field.Prop(() => [Float], { default: [0, 0, 0] })
|
|
396
|
+
rpy: [number, number, number];
|
|
397
|
+
}
|
|
620
398
|
|
|
621
|
-
|
|
622
|
-
|
|
399
|
+
@Model.Object("DroneObject")
|
|
400
|
+
export class DroneObject {
|
|
401
|
+
@Field.Prop(() => DronePhysicalState)
|
|
402
|
+
physicalState: DronePhysicalState;
|
|
403
|
+
}
|
|
623
404
|
```
|
|
624
405
|
|
|
625
|
-
###
|
|
406
|
+
### 2. Reference ID Style
|
|
626
407
|
|
|
627
408
|
```typescript
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
@Field.Prop(() => String, { nullable: true })
|
|
634
|
-
description: string | null;
|
|
409
|
+
@Model.Input("MissionInput")
|
|
410
|
+
export class MissionInput {
|
|
411
|
+
@Field.Prop(() => ID, { ref: "drone" })
|
|
412
|
+
drone: string;
|
|
413
|
+
}
|
|
635
414
|
```
|
|
636
415
|
|
|
637
|
-
###
|
|
416
|
+
### 3. Resolved Reference Style
|
|
638
417
|
|
|
639
418
|
```typescript
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
419
|
+
@Model.Object("DroneObject")
|
|
420
|
+
export class DroneObject {
|
|
421
|
+
@Field.Prop(() => LightMission, { nullable: true })
|
|
422
|
+
mission: LightMission | null;
|
|
423
|
+
}
|
|
645
424
|
```
|
|
646
425
|
|
|
647
|
-
|
|
426
|
+
**Important**: Avoid circular dependencies by importing directly from constant files:
|
|
648
427
|
|
|
649
428
|
```typescript
|
|
650
|
-
//
|
|
651
|
-
|
|
652
|
-
tags: string[];
|
|
429
|
+
// Correct
|
|
430
|
+
import { LightMission } from "../mission/mission.constant";
|
|
653
431
|
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
tags: string[];
|
|
432
|
+
// Incorrect
|
|
433
|
+
import { LightMission } from "../cnst_";
|
|
657
434
|
```
|
|
658
435
|
|
|
659
436
|
## Best Practices for Maintainable Models
|
|
660
437
|
|
|
661
|
-
1. **
|
|
662
|
-
|
|
663
|
-
- Create all model types in the recommended order
|
|
664
|
-
- Use inheritance properly with `via()` and `sortOf()`
|
|
438
|
+
1. **Consistent Naming**:
|
|
665
439
|
|
|
666
|
-
|
|
440
|
+
- Input: `DroneInput`
|
|
441
|
+
- Object: `DroneObject`
|
|
442
|
+
- Light: `LightDrone`
|
|
443
|
+
- Query: `droneQuery`
|
|
444
|
+
- Sort: `droneSort`
|
|
667
445
|
|
|
668
|
-
|
|
669
|
-
- Split complex models into related entities
|
|
446
|
+
2. **Reference Handling**:
|
|
670
447
|
|
|
671
|
-
|
|
448
|
+
- Use Light models for references
|
|
449
|
+
- Avoid circular dependencies
|
|
450
|
+
- Prefer direct imports over barrel files
|
|
672
451
|
|
|
673
|
-
|
|
674
|
-
- Classes: `PascalCase` with type suffix
|
|
675
|
-
- Fields: `camelCase`
|
|
676
|
-
- Enums: `PascalCase` with `Enum` suffix
|
|
452
|
+
3. **Field Design**:
|
|
677
453
|
|
|
678
|
-
|
|
454
|
+
- Use proper field types (Prop, Hidden, Secret, Resolve)
|
|
455
|
+
- Set appropriate defaults (especially for arrays)
|
|
456
|
+
- Add validation where needed
|
|
679
457
|
|
|
680
|
-
|
|
681
|
-
- Use empty arrays for array fields: `{ default: [] }`
|
|
682
|
-
- Use function defaults for dates: `{ default: () => dayjs() }`
|
|
458
|
+
4. **Performance Optimization**:
|
|
683
459
|
|
|
684
|
-
|
|
460
|
+
- Use Light models for list queries
|
|
461
|
+
- Keep Insight/Summary models lean
|
|
462
|
+
- Use appropriate indexes for queries
|
|
685
463
|
|
|
686
|
-
|
|
687
|
-
-
|
|
688
|
-
-
|
|
464
|
+
5. **Documentation**:
|
|
465
|
+
- Comment complex fields
|
|
466
|
+
- Explain non-obvious relationships
|
|
467
|
+
- Document query/sort usage patterns
|
|
689
468
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
- Include only essential fields
|
|
693
|
-
- Always include `id` field
|
|
694
|
-
- Keep them small for efficient API responses
|
|
695
|
-
|
|
696
|
-
7. **Document complex fields**
|
|
697
|
-
|
|
698
|
-
- Add comments for non-obvious fields
|
|
699
|
-
- Explain validation rules when complex
|
|
700
|
-
|
|
701
|
-
8. **Use appropriate Field decorators**
|
|
702
|
-
|
|
703
|
-
- `Field.Prop` for standard fields
|
|
704
|
-
- `Field.Hidden` for internal data
|
|
705
|
-
- `Field.Secret` for sensitive data
|
|
706
|
-
- `Field.Resolve` for computed properties
|
|
707
|
-
|
|
708
|
-
9. **Handle references correctly**
|
|
709
|
-
|
|
710
|
-
- Always use Light models for references
|
|
711
|
-
- Set proper `ref` option to model name
|
|
712
|
-
- Use `refPath` for polymorphic references
|
|
713
|
-
|
|
714
|
-
10. **Export everything needed**
|
|
715
|
-
- Export all models and enums
|
|
716
|
-
- Export type definitions for enums
|
|
717
|
-
|
|
718
|
-
## Complete Example with All Model Types
|
|
469
|
+
## Complete Example
|
|
719
470
|
|
|
720
471
|
```typescript
|
|
721
|
-
|
|
722
|
-
import {
|
|
723
|
-
import { Field, Filter, Model, sortOf, via } from "@akanjs/constant";
|
|
724
|
-
|
|
725
|
-
import { LightCategory } from "../category/category.constant";
|
|
726
|
-
import { LightUser } from "../user/user.constant";
|
|
727
|
-
|
|
728
|
-
// Enums
|
|
729
|
-
export const ProductStatus = enumOf(["draft", "active", "outOfStock", "discontinued"] as const);
|
|
730
|
-
export type ProductStatus = enumOf<typeof ProductStatus>;
|
|
472
|
+
import { ID, Int, String } from "@akanjs/base";
|
|
473
|
+
import { Field, Model } from "@akanjs/constant";
|
|
731
474
|
|
|
732
475
|
// Input Model
|
|
733
|
-
@Model.Input("
|
|
734
|
-
export class
|
|
735
|
-
@Field.Prop(() => String
|
|
736
|
-
minlength: 3,
|
|
737
|
-
maxlength: 100,
|
|
738
|
-
})
|
|
476
|
+
@Model.Input("DroneInput")
|
|
477
|
+
export class DroneInput {
|
|
478
|
+
@Field.Prop(() => String)
|
|
739
479
|
name: string;
|
|
740
|
-
|
|
741
|
-
@Field.Prop(() => String, {
|
|
742
|
-
nullable: true,
|
|
743
|
-
maxlength: 1000,
|
|
744
|
-
})
|
|
745
|
-
description: string | null;
|
|
746
|
-
|
|
747
|
-
@Field.Prop(() => Float, {
|
|
748
|
-
min: 0,
|
|
749
|
-
default: 0,
|
|
750
|
-
})
|
|
751
|
-
price: number;
|
|
752
|
-
|
|
753
|
-
@Field.Prop(() => Int, {
|
|
754
|
-
min: 0,
|
|
755
|
-
default: 0,
|
|
756
|
-
})
|
|
757
|
-
stock: number;
|
|
758
|
-
|
|
759
|
-
@Field.Prop(() => LightCategory, {
|
|
760
|
-
ref: "Category",
|
|
761
|
-
})
|
|
762
|
-
category: LightCategory;
|
|
763
|
-
|
|
764
|
-
@Field.Prop(() => [String], {
|
|
765
|
-
default: [],
|
|
766
|
-
})
|
|
767
|
-
tags: string[];
|
|
768
|
-
|
|
769
|
-
@Field.Prop(() => String, {
|
|
770
|
-
nullable: true,
|
|
771
|
-
})
|
|
772
|
-
imageUrl: string | null;
|
|
773
480
|
}
|
|
774
481
|
|
|
775
482
|
// Object Model
|
|
776
|
-
@Model.Object("
|
|
777
|
-
export class
|
|
778
|
-
@Field.Prop(() => String, {
|
|
779
|
-
|
|
780
|
-
default: "draft",
|
|
781
|
-
})
|
|
782
|
-
status: ProductStatus;
|
|
783
|
-
|
|
784
|
-
@Field.Prop(() => LightUser, {
|
|
785
|
-
ref: "User",
|
|
786
|
-
})
|
|
787
|
-
createdBy: LightUser;
|
|
788
|
-
|
|
789
|
-
@Field.Prop(() => Int, {
|
|
790
|
-
default: 0,
|
|
791
|
-
min: 0,
|
|
792
|
-
})
|
|
793
|
-
viewCount: number;
|
|
794
|
-
|
|
795
|
-
@Field.Prop(() => Date, {
|
|
796
|
-
default: () => dayjs(),
|
|
797
|
-
immutable: true,
|
|
798
|
-
})
|
|
799
|
-
launchDate: Dayjs;
|
|
800
|
-
|
|
801
|
-
@Field.Hidden(() => String, {
|
|
802
|
-
nullable: true,
|
|
803
|
-
})
|
|
804
|
-
internalNotes: string | null;
|
|
483
|
+
@Model.Object("DroneObject")
|
|
484
|
+
export class DroneObject extends BaseModel(DroneInput) {
|
|
485
|
+
@Field.Prop(() => String, { enum: ["active", "offline"], default: "offline" })
|
|
486
|
+
status: string;
|
|
805
487
|
}
|
|
806
488
|
|
|
807
489
|
// Light Model
|
|
808
|
-
@Model.Light("
|
|
809
|
-
export class
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
"status",
|
|
815
|
-
"category",
|
|
816
|
-
] as const) {}
|
|
490
|
+
@Model.Light("LightDrone")
|
|
491
|
+
export class LightDrone extends Light(DroneObject, ["id", "name", "status"] as const) {
|
|
492
|
+
isConnected() {
|
|
493
|
+
return this.status === "active";
|
|
494
|
+
}
|
|
495
|
+
}
|
|
817
496
|
|
|
818
497
|
// Full Model
|
|
819
|
-
@Model.Full("
|
|
820
|
-
export class
|
|
498
|
+
@Model.Full("Drone")
|
|
499
|
+
export class Drone extends Full(DroneObject, LightDrone) {
|
|
500
|
+
isAvailable() {
|
|
501
|
+
return this.isConnected() && this.battery > 20;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
821
504
|
|
|
822
505
|
// Insight Model
|
|
823
|
-
@Model.Insight("
|
|
824
|
-
export class
|
|
825
|
-
@Field.Prop(() => Int, {
|
|
826
|
-
default: 0,
|
|
827
|
-
accumulate: { $sum: 1 },
|
|
828
|
-
})
|
|
506
|
+
@Model.Insight("DroneInsight")
|
|
507
|
+
export class DroneInsight {
|
|
508
|
+
@Field.Prop(() => Int, { accumulate: { $sum: 1 } })
|
|
829
509
|
count: number;
|
|
830
|
-
|
|
831
|
-
@Field.Prop(() => Float, {
|
|
832
|
-
default: 0,
|
|
833
|
-
accumulate: { $sum: "$price" },
|
|
834
|
-
})
|
|
835
|
-
totalValue: number;
|
|
836
|
-
|
|
837
|
-
@Field.Prop(() => Int, {
|
|
838
|
-
default: 0,
|
|
839
|
-
accumulate: { $sum: "$viewCount" },
|
|
840
|
-
})
|
|
841
|
-
totalViews: number;
|
|
842
510
|
}
|
|
843
511
|
|
|
844
512
|
// Summary Model
|
|
845
|
-
@Model.Summary("
|
|
846
|
-
export class
|
|
847
|
-
@Field.Prop(() => Int, {
|
|
848
|
-
|
|
849
|
-
default: 0,
|
|
850
|
-
query: {},
|
|
851
|
-
})
|
|
852
|
-
totalProducts: number;
|
|
853
|
-
|
|
854
|
-
@Field.Prop(() => Int, {
|
|
855
|
-
min: 0,
|
|
856
|
-
default: 0,
|
|
857
|
-
query: { status: "active" },
|
|
858
|
-
})
|
|
859
|
-
activeProducts: number;
|
|
860
|
-
|
|
861
|
-
@Field.Prop(() => Int, {
|
|
862
|
-
min: 0,
|
|
863
|
-
default: 0,
|
|
864
|
-
query: { stock: { $lte: 0 } },
|
|
865
|
-
})
|
|
866
|
-
outOfStockProducts: number;
|
|
513
|
+
@Model.Summary("DroneSummary")
|
|
514
|
+
export class DroneSummary {
|
|
515
|
+
@Field.Prop(() => Int, { query: { status: "active" } })
|
|
516
|
+
activeDrones: number;
|
|
867
517
|
}
|
|
868
518
|
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
oldest: { createdAt: 1 },
|
|
874
|
-
cheapest: { price: 1 },
|
|
875
|
-
expensive: { price: -1 },
|
|
876
|
-
popular: { viewCount: -1 },
|
|
877
|
-
}) {
|
|
878
|
-
@Filter.Mongo()
|
|
879
|
-
byStatus(
|
|
880
|
-
@Filter.Arg("status", () => String, {
|
|
881
|
-
enum: ProductStatus,
|
|
882
|
-
default: "active",
|
|
883
|
-
})
|
|
884
|
-
status: ProductStatus
|
|
885
|
-
) {
|
|
886
|
-
return { status };
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
@Filter.Mongo()
|
|
890
|
-
inCategory(
|
|
891
|
-
@Filter.Arg("categoryId", () => ID, {
|
|
892
|
-
ref: "Category",
|
|
893
|
-
renderOption: (cat: LightCategory) => cat.name,
|
|
894
|
-
})
|
|
895
|
-
categoryId: string
|
|
896
|
-
) {
|
|
897
|
-
return { category: categoryId };
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
@Filter.Mongo()
|
|
901
|
-
hasTag(
|
|
902
|
-
@Filter.Arg("tag", () => String)
|
|
903
|
-
tag: string
|
|
904
|
-
) {
|
|
905
|
-
return { tags: tag };
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
@Filter.Mongo()
|
|
909
|
-
priceRange(
|
|
910
|
-
@Filter.Arg("min", () => Float, {
|
|
911
|
-
min: 0,
|
|
912
|
-
default: 0,
|
|
913
|
-
})
|
|
914
|
-
min: number,
|
|
915
|
-
|
|
916
|
-
@Filter.Arg("max", () => Float, {
|
|
917
|
-
min: 0,
|
|
918
|
-
nullable: true,
|
|
919
|
-
})
|
|
920
|
-
max: number | null
|
|
921
|
-
) {
|
|
922
|
-
const query: Record<string, any> = { price: { $gte: min } };
|
|
923
|
-
if (max !== null) {
|
|
924
|
-
query.price.$lte = max;
|
|
925
|
-
}
|
|
926
|
-
return query;
|
|
927
|
-
}
|
|
519
|
+
// Query Model
|
|
520
|
+
export const droneQuery = {
|
|
521
|
+
byStatus: (status: string) => ({ status }),
|
|
522
|
+
};
|
|
928
523
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
@Filter.Meili()
|
|
935
|
-
searchText(
|
|
936
|
-
@Filter.Arg("query", () => String)
|
|
937
|
-
query: string
|
|
938
|
-
) {
|
|
939
|
-
return { q: query };
|
|
940
|
-
}
|
|
941
|
-
}
|
|
524
|
+
// Sort Model
|
|
525
|
+
export const droneSort = {
|
|
526
|
+
byName: { name: 1 },
|
|
527
|
+
};
|
|
942
528
|
```
|
|
943
529
|
|
|
944
530
|
## Summary Checklist
|
|
945
531
|
|
|
946
|
-
1.
|
|
947
|
-
2.
|
|
948
|
-
3.
|
|
949
|
-
4.
|
|
950
|
-
5.
|
|
951
|
-
6.
|
|
952
|
-
7.
|
|
953
|
-
8.
|
|
954
|
-
9.
|
|
955
|
-
10.
|
|
956
|
-
11. **Insight/Summary**: Add aggregation and summary fields
|
|
957
|
-
12. **Type Safety**: Ensure TypeScript types match field definitions
|
|
958
|
-
13. **Exports**: Export all models and enums
|
|
532
|
+
1. [ ] Implement all required model types (Input, Object, Light, Full, Insight, Summary, Query, Sort)
|
|
533
|
+
2. [ ] Define field options properly (nullable, ref, default, etc.)
|
|
534
|
+
3. [ ] Add necessary validation rules
|
|
535
|
+
4. [ ] Implement relation patterns where needed
|
|
536
|
+
5. [ ] Use proper field types (Prop, Hidden, Secret, Resolve)
|
|
537
|
+
6. [ ] Add convenience functions to Light/Full models
|
|
538
|
+
7. [ ] Prevent circular dependencies in references
|
|
539
|
+
8. [ ] Add comprehensive JSDoc comments
|
|
540
|
+
9. [ ] Verify all examples in usage contexts
|
|
541
|
+
10. [ ] Test query/sort functionality
|