@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.
Files changed (34) hide show
  1. package/README.md +7 -26
  2. package/cjs/index.js +191 -28
  3. package/cjs/src/guidelines/componentRule/componentRule.instruction.md +3 -81
  4. package/cjs/src/guidelines/cssRule/cssRule.instruction.md +435 -0
  5. package/cjs/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
  6. package/cjs/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
  7. package/cjs/src/guidelines/modelStore/modelStore.instruction.md +2 -1
  8. package/cjs/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
  9. package/cjs/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
  10. package/cjs/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
  11. package/cjs/src/templates/app/main.js +1 -2
  12. package/esm/index.js +199 -36
  13. package/esm/src/guidelines/componentRule/componentRule.instruction.md +3 -81
  14. package/esm/src/guidelines/cssRule/cssRule.instruction.md +435 -0
  15. package/esm/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
  16. package/esm/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
  17. package/esm/src/guidelines/modelStore/modelStore.instruction.md +2 -1
  18. package/esm/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
  19. package/esm/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
  20. package/esm/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
  21. package/esm/src/templates/app/main.js +1 -2
  22. package/package.json +1 -1
  23. package/src/guideline/guideline.command.d.ts +3 -1
  24. package/src/guideline/guideline.prompt.d.ts +15 -1
  25. package/src/guideline/guideline.runner.d.ts +17 -3
  26. package/src/guideline/guideline.script.d.ts +8 -2
  27. package/src/guidelines/componentRule/componentRule.instruction.md +3 -81
  28. package/src/guidelines/cssRule/cssRule.instruction.md +435 -0
  29. package/src/guidelines/docPageRule/docPageRule.instruction.md +389 -0
  30. package/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
  31. package/src/guidelines/modelStore/modelStore.instruction.md +2 -1
  32. package/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
  33. package/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
  34. 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
- Akan.js uses a multi-tiered model architecture with clear inheritance patterns:
18
-
19
- ```
20
- ┌───────────────┐
21
- │ ModelInput │
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**: User-provided data for create/update operations
45
- 2. **Object Model**: Input + system-managed fields
46
- 3. **Light Model**: Subset of Object for listings/references
47
- 4. **Full Model**: Complete model for database schema
48
- 5. **Filter Model**: Query methods and sorting options
49
- 6. **Insight Model**: Aggregation fields for analytics
50
- 7. **Summary Model**: Dashboard statistics and counters
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, Filter, Model, sortOf, via } from "@akanjs/constant";
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("ProductInput")
66
- export class ProductInput {
67
- // User-editable fields
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("ProductObject")
72
- export class ProductObject extends via(ProductInput) {
73
- // System fields
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("LightProduct")
78
- export class LightProduct extends via(ProductObject, ["id", "name", "price"] as const) {}
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("Product")
82
- export class Product extends via(ProductObject, LightProduct) {}
83
-
84
- // 7. Insight Model (optional)
85
- @Model.Insight("ProductInsight")
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
- // 8. Summary Model (optional)
91
- @Model.Summary("ProductSummary")
92
- export class ProductSummary {
93
- // Summary statistics
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
- // 9. Filter Model
97
- @Model.Filter("ProductFilter")
98
- export class ProductFilter extends sortOf(Product, {
99
- newest: { createdAt: -1 },
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, Filter, Model, sortOf, via } from "@akanjs/constant";
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 that users can create or update:
120
+ The Input model defines fields required for data creation:
144
121
 
145
122
  ```typescript
146
- @Model.Input("ProductInput")
147
- export class ProductInput {
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(() => Int, {
155
- min: 0,
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
- Key characteristics:
133
+ **Usage Examples**:
174
134
 
175
- - Contains only user-editable fields
176
- - Includes validation rules (min, max, etc.)
177
- - Uses nullable fields for optional data
178
- - References other models with Light variants
179
- - No system-generated fields (id, timestamps)
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 extends Input with system-managed fields:
148
+ The Object model adds system-managed fields:
184
149
 
185
150
  ```typescript
186
- @Model.Object("ProductObject")
187
- export class ProductObject extends via(ProductInput) {
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
- @Field.Prop(() => Int, {
195
- default: 0,
196
- min: 0,
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
- Key characteristics:
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
- @Model.Light("LightProduct")
226
- export class LightProduct extends via(ProductObject, ["id", "name", "price", "status", "category"] as const) {}
227
- ```
228
-
229
- Key characteristics:
164
+ // Document lookup
165
+ const drone = await this.Drone.pickById("droneId"); // drone.status
230
166
 
231
- - Selects essential fields from Object model
232
- - Uses `as const` assertion for type safety
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
- ## Full Model Implementation
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
- ```typescript
242
- @Model.Full("Product")
243
- export class Product extends via(ProductObject, LightProduct) {}
173
+ // Component usage
174
+ const drone = st.use.drone(); // drone.status
244
175
  ```
245
176
 
246
- Key characteristics:
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
- The Insight model defines fields for analytics and aggregation:
179
+ Light model defines lightweight schema for list queries:
257
180
 
258
181
  ```typescript
259
- @Model.Insight("ProductInsight")
260
- export class ProductInsight {
261
- @Field.Prop(() => Int, {
262
- default: 0,
263
- accumulate: { $sum: 1 },
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
- Key characteristics:
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
- @Model.Summary("ProductSummary")
295
- export class ProductSummary {
296
- @Field.Prop(() => Int, {
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
- - Defines summary statistics for dashboards
321
- - Uses `query` option to filter records
322
- - Provides high-level metrics
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
- ## Filter Model Implementation
201
+ ## Full Model Implementation
327
202
 
328
- The Filter model defines query operations and sorting options:
203
+ Full model adds convenience functions:
329
204
 
330
205
  ```typescript
331
- @Model.Filter("ProductFilter")
332
- export class ProductFilter extends sortOf(Product, {
333
- newest: { createdAt: -1 },
334
- oldest: { createdAt: 1 },
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
- @Filter.Mongo()
350
- inCategory(
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
- Key characteristics:
218
+ **Usage Examples**:
378
219
 
379
- - Extends with `sortOf()` to define sorting options
380
- - Uses `@Filter.Mongo()` for query methods
381
- - Defines parameters with `@Filter.Arg()`
382
- - Returns MongoDB query objects
383
- - Can have complex query logic
384
- - Supports references with `ref` option
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
- ## Field Decorators and Options
231
+ ## Insight Model Implementation
387
232
 
388
- Field decorators configure property behavior:
233
+ Insight model defines statistical fields:
389
234
 
390
235
  ```typescript
391
- // Standard field (visible in API and DB)
392
- @Field.Prop(() => String, { options })
393
- name: string;
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
- Common field options:
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
- // MongoDB query
433
- @Filter.Mongo()
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
- // Meilisearch query
445
- @Filter.Meili()
446
- searchText(
447
- @Filter.Arg("query", () => String)
448
- query: string
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
- Filter argument options:
256
+ ## Summary Model Implementation
455
257
 
456
- | Option | Description | Example |
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
- ## Inheritance with via() and sortOf()
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
- Akan.js uses utility functions for model inheritance:
268
+ **Usage Examples**:
467
269
 
468
270
  ```typescript
469
- // Inherit all fields from Input model
470
- extends via(ProductInput)
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
- Key points:
274
+ // API call
275
+ const summary = await fetch.getActiveSummary();
488
276
 
489
- - `via()` handles field inheritance
490
- - Field selection uses `as const` for type safety
491
- - `sortOf()` configures sorting options for filters
492
- - Sort order: 1 for ascending, -1 for descending
277
+ // Component usage
278
+ const summary = st.use.summary(); // summary.totalDrone
279
+ ```
493
280
 
494
- ## Working with References to Other Models
281
+ ## Query Model Implementation
495
282
 
496
- Models can reference other models in several ways:
283
+ Query model defines query methods:
497
284
 
498
285
  ```typescript
499
- // Basic reference
500
- @Field.Prop(() => LightCategory, { ref: "Category" })
501
- category: LightCategory;
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
- Key points:
292
+ **Usage Examples**:
530
293
 
531
- - Always use Light models for references
532
- - Set `ref` to the model name string
533
- - Use arrays for one-to-many relationships
534
- - Use `refPath` for polymorphic relationships
535
- - Set `refType` for relationship semantics
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
- ## Validation Rules and Constraints
305
+ ## Sort Model Implementation
538
306
 
539
- Add validation to ensure data integrity:
307
+ Sort model defines sorting options:
540
308
 
541
309
  ```typescript
542
- // Numeric range validation
543
- @Field.Prop(() => Int, { min: 0, max: 100 })
544
- score: number;
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
- ## Type Safety Considerations
564
-
565
- Ensure type safety across the model:
316
+ **Usage Examples**:
566
317
 
567
318
  ```typescript
568
- // 1. Use proper types for fields
569
- @Field.Prop(() => Int)
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
- // 3. Handle nullable fields correctly
578
- @Field.Prop(() => String, { nullable: true })
579
- description: string | null; // Include union with null
322
+ // Service sorting
323
+ const drones = await this.listByName("myDrone", { sort: "alphabetical" });
324
+ ```
580
325
 
581
- // 4. Export enum types
582
- export type StatusEnum = enumOf<typeof StatusEnum>;
326
+ ## Field Decorators and Options
583
327
 
584
- // 5. Use precise array types
585
- @Field.Prop(() => [Int])
586
- scores: number[]; // Array of integers
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
- ## Common Mistakes and Solutions
357
+ 1. **Field.Prop**
358
+ General schema field declaration, visible in both backend and frontend
590
359
 
591
- ### 1. Missing Field Type
360
+ ```typescript
361
+ @Field.Prop(() => String)
362
+ name: string;
363
+ ```
592
364
 
593
- ```typescript
594
- // Wrong (missing type function)
595
- @Field.Prop(String)
596
- name: string;
365
+ 2. **Field.Hidden**
366
+ Backend-only fields, restricted in frontend for security
597
367
 
598
- // ✅ Correct
599
- @Field.Prop(() => String)
600
- name: string;
601
- ```
368
+ ```typescript
369
+ @Field.Hidden(() => String)
370
+ internalCode: string;
371
+ ```
602
372
 
603
- ### 2. Incorrect Array Type
373
+ 3. **Field.Secret**
374
+ Security-sensitive fields with query restrictions
604
375
 
605
- ```typescript
606
- // ❌ Wrong (using Array<T> syntax)
607
- @Field.Prop(() => Array<String>)
608
- tags: string[];
376
+ ```typescript
377
+ @Field.Secret(() => String)
378
+ apiKey: string;
379
+ ```
609
380
 
610
- // ✅ Correct
611
- @Field.Prop(() => [String])
612
- tags: string[];
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
- ### 3. Missing Const Assertion
390
+ ### 1. Scalar Embedded Style
616
391
 
617
392
  ```typescript
618
- // ❌ Wrong (missing as const)
619
- extends via(ProductObject, ["id", "name"])
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
- // ✅ Correct
622
- extends via(ProductObject, ["id", "name"] as const)
399
+ @Model.Object("DroneObject")
400
+ export class DroneObject {
401
+ @Field.Prop(() => DronePhysicalState)
402
+ physicalState: DronePhysicalState;
403
+ }
623
404
  ```
624
405
 
625
- ### 4. Missing Nullable Type
406
+ ### 2. Reference ID Style
626
407
 
627
408
  ```typescript
628
- // ❌ Wrong (missing null in type)
629
- @Field.Prop(() => String, { nullable: true })
630
- description: string;
631
-
632
- // ✅ Correct
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
- ### 5. Circular Dependencies
416
+ ### 3. Resolved Reference Style
638
417
 
639
418
  ```typescript
640
- // ❌ Wrong (circular dependency)
641
- import { Product } from "../product/product.constant";
642
-
643
- // Correct
644
- import { LightProduct } from "../product/product.constant";
419
+ @Model.Object("DroneObject")
420
+ export class DroneObject {
421
+ @Field.Prop(() => LightMission, { nullable: true })
422
+ mission: LightMission | null;
423
+ }
645
424
  ```
646
425
 
647
- ### 6. Missing Default for Arrays
426
+ **Important**: Avoid circular dependencies by importing directly from constant files:
648
427
 
649
428
  ```typescript
650
- // ❌ Wrong (no default for array)
651
- @Field.Prop(() => [String])
652
- tags: string[];
429
+ // Correct
430
+ import { LightMission } from "../mission/mission.constant";
653
431
 
654
- // ✅ Correct
655
- @Field.Prop(() => [String], { default: [] })
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. **Follow the standard model hierarchy**
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
- 2. **Keep models focused**
440
+ - Input: `DroneInput`
441
+ - Object: `DroneObject`
442
+ - Light: `LightDrone`
443
+ - Query: `droneQuery`
444
+ - Sort: `droneSort`
667
445
 
668
- - Each model should represent a single entity
669
- - Split complex models into related entities
446
+ 2. **Reference Handling**:
670
447
 
671
- 3. **Use proper naming conventions**
448
+ - Use Light models for references
449
+ - Avoid circular dependencies
450
+ - Prefer direct imports over barrel files
672
451
 
673
- - File: `camelCase.constant.ts`
674
- - Classes: `PascalCase` with type suffix
675
- - Fields: `camelCase`
676
- - Enums: `PascalCase` with `Enum` suffix
452
+ 3. **Field Design**:
677
453
 
678
- 4. **Add appropriate defaults**
454
+ - Use proper field types (Prop, Hidden, Secret, Resolve)
455
+ - Set appropriate defaults (especially for arrays)
456
+ - Add validation where needed
679
457
 
680
- - Set reasonable defaults for required fields
681
- - Use empty arrays for array fields: `{ default: [] }`
682
- - Use function defaults for dates: `{ default: () => dayjs() }`
458
+ 4. **Performance Optimization**:
683
459
 
684
- 5. **Implement proper validation**
460
+ - Use Light models for list queries
461
+ - Keep Insight/Summary models lean
462
+ - Use appropriate indexes for queries
685
463
 
686
- - Add min/max for numbers
687
- - Add minlength/maxlength for strings
688
- - Use custom validation for complex rules
464
+ 5. **Documentation**:
465
+ - Comment complex fields
466
+ - Explain non-obvious relationships
467
+ - Document query/sort usage patterns
689
468
 
690
- 6. **Optimize Light models**
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
- // File: apps/ecommerce/lib/product/product.constant.ts
722
- import { enumOf, ID, Int, Float, type Dayjs, dayjs } from "@akanjs/base";
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("ProductInput")
734
- export class ProductInput {
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("ProductObject")
777
- export class ProductObject extends via(ProductInput) {
778
- @Field.Prop(() => String, {
779
- enum: ProductStatus,
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("LightProduct")
809
- export class LightProduct extends via(ProductObject, [
810
- "id",
811
- "name",
812
- "price",
813
- "imageUrl",
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("Product")
820
- export class Product extends via(ProductObject, LightProduct) {}
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("ProductInsight")
824
- export class ProductInsight {
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("ProductSummary")
846
- export class ProductSummary {
847
- @Field.Prop(() => Int, {
848
- min: 0,
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
- // Filter Model
870
- @Model.Filter("ProductFilter")
871
- export class ProductFilter extends sortOf(Product, {
872
- newest: { createdAt: -1 },
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
- @Filter.Mongo()
930
- inStock() {
931
- return { stock: { $gt: 0 } };
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. **Structure**: Follow the model hierarchy (Input Object Light/Full Filter/Summary/Insight)
947
- 2. **Naming**: Use consistent class naming (ModelInput, ModelObject, LightModel, Model)
948
- 3. **Enums**: Define with `enumOf()` and export both const and type
949
- 4. **Fields**: Use `Field.Prop` with appropriate options
950
- 5. **Validation**: Add constraints (min, max, minlength, maxlength)
951
- 6. **Defaults**: Set appropriate defaults, especially for arrays
952
- 7. **References**: Use Light models with proper `ref` option
953
- 8. **Light Models**: Select essential fields with `as const` assertion
954
- 9. **Full Models**: Combine Object and Light models
955
- 10. **Filter Models**: Define sort options and query methods
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