@akanjs/cli 0.9.60-canary.8 → 1.0.0
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/cjs/index.js +16 -47
- package/cjs/src/guidelines/scalarConstant/scalarConstant.generate.json +24 -20
- package/cjs/src/guidelines/scalarConstant/scalarConstant.instruction.md +284 -326
- package/cjs/src/guidelines/scalarDictionary/scalarDictionary.generate.json +32 -32
- package/cjs/src/guidelines/scalarDictionary/scalarDictionary.instruction.md +175 -249
- package/cjs/src/templates/app/app/[lang]/page.js +19 -36
- package/cjs/src/templates/workspaceRoot/package.json.template +5 -5
- package/esm/index.js +16 -47
- package/esm/src/guidelines/scalarConstant/scalarConstant.generate.json +24 -20
- package/esm/src/guidelines/scalarConstant/scalarConstant.instruction.md +284 -326
- package/esm/src/guidelines/scalarDictionary/scalarDictionary.generate.json +32 -32
- package/esm/src/guidelines/scalarDictionary/scalarDictionary.instruction.md +175 -249
- package/esm/src/templates/app/app/[lang]/page.js +19 -36
- package/esm/src/templates/workspaceRoot/package.json.template +5 -5
- package/package.json +5 -4
- package/src/guidelines/scalarConstant/scalarConstant.instruction.md +284 -326
- package/src/guidelines/scalarDictionary/scalarDictionary.instruction.md +175 -249
- package/src/library/library.command.d.ts +0 -2
- package/src/library/library.runner.d.ts +0 -2
- package/src/library/library.script.d.ts +0 -2
- package/src/scalar/scalar.command.d.ts +1 -1
- package/cjs/src/guidelines/fieldDecorator/fieldDecorator.generate.json +0 -135
- package/cjs/src/guidelines/fieldDecorator/fieldDecorator.instruction.md +0 -606
- package/esm/src/guidelines/fieldDecorator/fieldDecorator.generate.json +0 -135
- package/esm/src/guidelines/fieldDecorator/fieldDecorator.instruction.md +0 -606
- package/src/guidelines/fieldDecorator/fieldDecorator.instruction.md +0 -606
|
@@ -17,330 +17,328 @@ Scalar constants in Akan.js serve as the foundation for complex data modeling by
|
|
|
17
17
|
```
|
|
18
18
|
{app,lib}/
|
|
19
19
|
└── */lib/__scalar/
|
|
20
|
-
└── <scalarName>/
|
|
21
|
-
└── <scalarName>.constant.ts
|
|
20
|
+
└── <scalarName>/ # camelCase directory
|
|
21
|
+
└── <scalarName>.constant.ts # scalar definition file
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
### Naming Standards
|
|
25
25
|
|
|
26
|
-
- **Directory**: `camelCase` (e.g., `geoLocation`)
|
|
26
|
+
- **Directory**: `camelCase` (e.g., `encourageInfo`, `geoLocation`)
|
|
27
27
|
- **File**: `<scalarName>.constant.ts` (matches directory name)
|
|
28
|
-
- **Class**: `PascalCase`
|
|
29
|
-
- **
|
|
30
|
-
- **Enum Values**: `camelCase` (e.g., `
|
|
31
|
-
|
|
32
|
-
### File Structure
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// 1. Core imports
|
|
36
|
-
import { Float, Int, String, Boolean, Date, type Dayjs, dayjs, enumOf } from "@akanjs/base";
|
|
37
|
-
import { Field, Model } from "@akanjs/constant";
|
|
38
|
-
|
|
39
|
-
// 2. Optional enum definitions (camelCase values)
|
|
40
|
-
export const Status = enumOf(["active", "inactive"] as const);
|
|
41
|
-
export type Status = enumOf<typeof Status>;
|
|
42
|
-
|
|
43
|
-
// 3. Scalar model class (SINGLE CLASS PER FILE)
|
|
44
|
-
// Must match class name
|
|
45
|
-
export class ScalarName {
|
|
46
|
-
// Field definitions
|
|
47
|
-
@Field.Prop(() => FieldType, { ...options })
|
|
48
|
-
fieldName: FieldType;
|
|
49
|
-
}
|
|
50
|
-
```
|
|
28
|
+
- **Scalar Class**: `PascalCase` (e.g., `EncourageInfo`, `GeoLocation`)
|
|
29
|
+
- **Enum Class**: `PascalCase` (e.g., `Journey`, `NotiSetting`)
|
|
30
|
+
- **Enum Values**: `camelCase` (e.g., `firstJoin`, `waitPay`)
|
|
51
31
|
|
|
52
32
|
## Required Imports
|
|
53
33
|
|
|
54
34
|
### Essential Framework Imports
|
|
55
35
|
|
|
56
36
|
```typescript
|
|
57
|
-
import {
|
|
37
|
+
import { via } from "@akanjs/constant";
|
|
58
38
|
```
|
|
59
39
|
|
|
60
40
|
### Common Base Types
|
|
61
41
|
|
|
62
42
|
```typescript
|
|
63
|
-
import { ID, Int, Float,
|
|
43
|
+
import { ID, Int, Float, dayjs, enumOf } from "@akanjs/base";
|
|
64
44
|
```
|
|
65
45
|
|
|
66
46
|
### Cross-Scalar References
|
|
67
47
|
|
|
68
48
|
```typescript
|
|
69
|
-
import { OtherScalar } from "../
|
|
49
|
+
import { OtherScalar } from "../otherScalar/otherScalar.constant";
|
|
70
50
|
```
|
|
71
51
|
|
|
72
|
-
##
|
|
52
|
+
## Basic Syntax with via()
|
|
53
|
+
|
|
54
|
+
The `via()` function is the foundation for defining scalars. It takes a callback that receives the `field()` function, which you use to define each field's type and options.
|
|
73
55
|
|
|
74
56
|
### Basic Structure
|
|
75
57
|
|
|
76
58
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
import { via } from "@akanjs/constant";
|
|
60
|
+
|
|
61
|
+
export class ScalarName extends via((field) => ({
|
|
62
|
+
fieldName: field(FieldType),
|
|
63
|
+
fieldWithOptions: field(FieldType, { ...options }),
|
|
64
|
+
})) {
|
|
65
|
+
// Optional: Add instance methods here
|
|
80
66
|
}
|
|
81
67
|
```
|
|
82
68
|
|
|
83
|
-
###
|
|
84
|
-
|
|
85
|
-
- **One scalar model per file** - Create separate files for different scalars
|
|
86
|
-
- **Class name matches directory name** in PascalCase (e.g., `PaymentInfo` for `payment-info`)
|
|
87
|
-
- **Model names should be domain-specific** and descriptive
|
|
88
|
-
- **No ID/timestamp fields** - Scalars are value objects, not entities
|
|
89
|
-
- **`@Model.Scalar` decorator parameter must match class name** - This is critical for proper metadata registration
|
|
90
|
-
|
|
91
|
-
## Field Definitions
|
|
92
|
-
|
|
93
|
-
### Basic Field Types
|
|
69
|
+
### Simple Example
|
|
94
70
|
|
|
95
71
|
```typescript
|
|
96
|
-
|
|
97
|
-
name: string;
|
|
72
|
+
import { via } from "@akanjs/constant";
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
74
|
+
export class RestrictInfo extends via((field) => ({
|
|
75
|
+
until: field(Date),
|
|
76
|
+
reason: field(String),
|
|
77
|
+
})) {}
|
|
78
|
+
```
|
|
101
79
|
|
|
102
|
-
|
|
103
|
-
percentage: number;
|
|
80
|
+
### Key Patterns
|
|
104
81
|
|
|
105
|
-
|
|
106
|
-
|
|
82
|
+
| Pattern | Description |
|
|
83
|
+
| -------------------------- | -------------------------------------------------------------------------------- |
|
|
84
|
+
| `via((field) => ({...}))` | Creates a class with typed fields. The callback receives the `field()` helper. |
|
|
85
|
+
| `field(Type)` | Defines a single field. First argument is the type (String, Number, Date, etc.). |
|
|
86
|
+
| `field(Type, { options })` | Optional second argument is an options object for defaults, validation, etc. |
|
|
107
87
|
|
|
108
|
-
|
|
109
|
-
timestamp: Dayjs; // Always use Dayjs for dates
|
|
110
|
-
```
|
|
88
|
+
## Available Field Types
|
|
111
89
|
|
|
112
|
-
###
|
|
113
|
-
|
|
114
|
-
| Option | Type | Description | Example |
|
|
115
|
-
| ----------- | -------- | ------------------------------------ | ---------------------------- |
|
|
116
|
-
| `default` | Any | Default field value | `{ default: 0 }` |
|
|
117
|
-
| `nullable` | Boolean | Allows null values | `{ nullable: true }` |
|
|
118
|
-
| `enum` | Enum | Restricts to enum values | `{ enum: Status }` |
|
|
119
|
-
| `min` | Number | Minimum numeric value | `{ min: 0 }` |
|
|
120
|
-
| `max` | Number | Maximum numeric value | `{ max: 100 }` |
|
|
121
|
-
| `minlength` | Number | Minimum string length | `{ minlength: 3 }` |
|
|
122
|
-
| `maxlength` | Number | Maximum string length | `{ maxlength: 255 }` |
|
|
123
|
-
| `example` | Any | Example value for documentation | `{ example: [0,0] }` |
|
|
124
|
-
| `validate` | Function | Custom validation function | `{ validate: (v) => v > 0 }` |
|
|
125
|
-
| `immutable` | Boolean | Prevents modification after creation | `{ immutable: true }` |
|
|
126
|
-
| `select` | Boolean | Includes in query results by default | `{ select: false }` |
|
|
127
|
-
| `text` | String | Enables text search capabilities | `{ text: "search" }` |
|
|
128
|
-
|
|
129
|
-
### Special Field Types
|
|
90
|
+
### Primitive Types
|
|
130
91
|
|
|
131
92
|
```typescript
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
internalCode: string;
|
|
135
|
-
|
|
136
|
-
// Secret field (not selected by default)
|
|
137
|
-
@Field.Secret(() => String)
|
|
138
|
-
apiKey: string;
|
|
139
|
-
|
|
140
|
-
// Resolve field (computed at runtime)
|
|
141
|
-
@Field.Resolve(() => Int)
|
|
142
|
-
get total(): number {
|
|
143
|
-
return this.items.length;
|
|
144
|
-
}
|
|
145
|
-
```
|
|
93
|
+
import { ID, Int, Float } from "@akanjs/base";
|
|
94
|
+
import { via } from "@akanjs/constant";
|
|
146
95
|
|
|
147
|
-
|
|
96
|
+
export class Example extends via((field) => ({
|
|
97
|
+
// String type
|
|
98
|
+
name: field(String),
|
|
148
99
|
|
|
149
|
-
|
|
100
|
+
// Number types
|
|
101
|
+
count: field(Int), // Integer
|
|
102
|
+
price: field(Float), // Floating point
|
|
150
103
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@Field.Prop(() => [String])
|
|
154
|
-
tags: string[];
|
|
104
|
+
// Boolean type
|
|
105
|
+
isActive: field(Boolean),
|
|
155
106
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
107
|
+
// Date type (internally uses Dayjs)
|
|
108
|
+
createdAt: field(Date),
|
|
109
|
+
|
|
110
|
+
// ID type (MongoDB ObjectId)
|
|
111
|
+
referenceId: field(ID),
|
|
112
|
+
})) {}
|
|
159
113
|
```
|
|
160
114
|
|
|
161
|
-
###
|
|
115
|
+
### Array Types
|
|
116
|
+
|
|
117
|
+
Array types are defined by wrapping the type in square brackets:
|
|
162
118
|
|
|
163
119
|
```typescript
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
120
|
+
export class Example extends via((field) => ({
|
|
121
|
+
// Array of strings
|
|
122
|
+
tags: field([String]),
|
|
167
123
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
coordinates: number[][][];
|
|
171
|
-
```
|
|
124
|
+
// Array of numbers
|
|
125
|
+
scores: field([Int]),
|
|
172
126
|
|
|
173
|
-
|
|
127
|
+
// Array of other scalars
|
|
128
|
+
items: field([OtherScalar]),
|
|
174
129
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
130
|
+
// Nested arrays (2D matrix)
|
|
131
|
+
matrix: field([[Int]]),
|
|
132
|
+
})) {}
|
|
178
133
|
```
|
|
179
134
|
|
|
180
|
-
|
|
135
|
+
### Optional Fields
|
|
181
136
|
|
|
182
|
-
|
|
183
|
-
@Field.Prop(() => Map, {
|
|
184
|
-
of: String, // Must specify value type
|
|
185
|
-
default: new Map()
|
|
186
|
-
})
|
|
187
|
-
metadata: Map<string, string>;
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Enum Implementation (camelCase Values)
|
|
137
|
+
Optional fields can be defined using the `.optional()` chain:
|
|
191
138
|
|
|
192
139
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
})
|
|
202
|
-
status: Status;
|
|
140
|
+
import { ID, Int } from "@akanjs/base";
|
|
141
|
+
import { via } from "@akanjs/constant";
|
|
142
|
+
|
|
143
|
+
export class FileMeta extends via((field) => ({
|
|
144
|
+
fileId: field(ID).optional(), // Optional field
|
|
145
|
+
lastModifiedAt: field(Date),
|
|
146
|
+
size: field(Int),
|
|
147
|
+
})) {}
|
|
203
148
|
```
|
|
204
149
|
|
|
205
|
-
##
|
|
150
|
+
## Field Options Reference
|
|
206
151
|
|
|
207
|
-
|
|
152
|
+
| Option | Type | Default | Description | Example |
|
|
153
|
+
| ----------- | -------- | ----------- | ------------------------------------------------ | ----------------------------- |
|
|
154
|
+
| `default` | Any/Func | `undefined` | Default field value (static or factory function) | `{ default: 0 }` |
|
|
155
|
+
| `min` | Number | - | Minimum numeric value | `{ min: 0 }` |
|
|
156
|
+
| `max` | Number | - | Maximum numeric value | `{ max: 100 }` |
|
|
157
|
+
| `minlength` | Number | - | Minimum string length | `{ minlength: 3 }` |
|
|
158
|
+
| `maxlength` | Number | - | Maximum string length | `{ maxlength: 255 }` |
|
|
159
|
+
| `validate` | Function | - | Custom validation function | `{ validate: isPhoneNumber }` |
|
|
160
|
+
| `example` | Any | - | Example value for documentation | `{ example: [0, 0] }` |
|
|
161
|
+
|
|
162
|
+
### Field Options Examples
|
|
208
163
|
|
|
209
164
|
```typescript
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
165
|
+
import { dayjs, Int, Float } from "@akanjs/base";
|
|
166
|
+
import { isPhoneNumber } from "@akanjs/common";
|
|
167
|
+
import { via } from "@akanjs/constant";
|
|
213
168
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const [lon2, lat2] = loc2.coordinates;
|
|
218
|
-
// Distance calculation logic
|
|
219
|
-
return distance;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
169
|
+
export class OrderInfo extends via((field) => ({
|
|
170
|
+
// Static default value
|
|
171
|
+
quantity: field(Int, { default: 1 }),
|
|
223
172
|
|
|
224
|
-
|
|
173
|
+
// Dynamic default using factory function
|
|
174
|
+
orderedAt: field(Date, { default: () => dayjs() }),
|
|
225
175
|
|
|
226
|
-
|
|
176
|
+
// Number range validation
|
|
177
|
+
rating: field(Float, { min: 0, max: 5 }),
|
|
227
178
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- [ ] Class name matches directory name (PascalCase)
|
|
231
|
-
- [ ] All required imports present
|
|
232
|
-
- [ ] The string passed to `@Model.Scalar()` must match the class name exactly
|
|
179
|
+
// String length validation
|
|
180
|
+
description: field(String, { minlength: 10, maxlength: 500 }),
|
|
233
181
|
|
|
234
|
-
|
|
182
|
+
// Custom validation function
|
|
183
|
+
phone: field(String, { validate: isPhoneNumber }),
|
|
235
184
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
185
|
+
// Combined options
|
|
186
|
+
price: field(Float, {
|
|
187
|
+
default: 0,
|
|
188
|
+
min: 0,
|
|
189
|
+
example: 29.99,
|
|
190
|
+
}),
|
|
191
|
+
})) {}
|
|
192
|
+
```
|
|
244
193
|
|
|
245
|
-
###
|
|
194
|
+
### Static vs Dynamic Defaults
|
|
246
195
|
|
|
247
|
-
- **
|
|
248
|
-
- **
|
|
249
|
-
- **Enum Values**: Always use camelCase (`active`, not `ACTIVE`)
|
|
250
|
-
- **Reusability**: Create separate scalars for commonly used structures
|
|
251
|
-
- **Documentation**: Add comments for complex fields
|
|
252
|
-
- **Validation**: Use min/max for numbers, minlength/maxlength for text
|
|
253
|
-
- **Immutability**: Mark fields as `immutable` where appropriate
|
|
254
|
-
- **Default values**: Provide sensible defaults for most fields
|
|
196
|
+
- Use **static values** for constants: `{ default: 0 }`, `{ default: "active" }`
|
|
197
|
+
- Use **factory functions** for values computed at creation time: `{ default: () => dayjs() }`, `{ default: () => new ObjectId() }`
|
|
255
198
|
|
|
256
|
-
##
|
|
199
|
+
## Enum Definition with enumOf()
|
|
200
|
+
|
|
201
|
+
The `enumOf()` function creates typed enum classes. Define enums before using them in your scalar fields.
|
|
257
202
|
|
|
258
|
-
###
|
|
203
|
+
### Basic Enum
|
|
259
204
|
|
|
260
205
|
```typescript
|
|
261
|
-
|
|
262
|
-
|
|
206
|
+
import { enumOf } from "@akanjs/base";
|
|
207
|
+
import { via } from "@akanjs/constant";
|
|
208
|
+
|
|
209
|
+
// Simple enum definition
|
|
210
|
+
export class NotiSetting extends enumOf("notiSetting", ["disagree", "fewer", "normal", "block"]) {}
|
|
263
211
|
|
|
264
|
-
//
|
|
265
|
-
export
|
|
212
|
+
// Using enum in a scalar
|
|
213
|
+
export class NotiInfo extends via((field) => ({
|
|
214
|
+
setting: field(NotiSetting, { default: "normal" }),
|
|
215
|
+
})) {}
|
|
266
216
|
```
|
|
267
217
|
|
|
268
|
-
###
|
|
218
|
+
### Enum with 'as const'
|
|
219
|
+
|
|
220
|
+
For better TypeScript type inference, use `as const`:
|
|
269
221
|
|
|
270
222
|
```typescript
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
223
|
+
import { dayjs, enumOf } from "@akanjs/base";
|
|
224
|
+
import { via } from "@akanjs/constant";
|
|
225
|
+
|
|
226
|
+
// Use "as const" for better type inference
|
|
227
|
+
export class Status extends enumOf("status", ["pending", "active", "completed", "cancelled"] as const) {}
|
|
274
228
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
229
|
+
export class Order extends via((field) => ({
|
|
230
|
+
status: field(Status, { default: "pending" }),
|
|
231
|
+
orderedAt: field(Date, { default: () => dayjs() }),
|
|
232
|
+
})) {}
|
|
278
233
|
```
|
|
279
234
|
|
|
280
|
-
###
|
|
235
|
+
### Enum Key Points
|
|
281
236
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
237
|
+
| Point | Description |
|
|
238
|
+
| ---------------------- | ---------------------------------------------------------------------------------------- |
|
|
239
|
+
| `enumOf(name, values)` | First argument is the enum name (used in dictionary/GraphQL). Second is the value array. |
|
|
240
|
+
| camelCase Values | Always use camelCase for enum values (e.g., `"waitPay"`, not `"WAIT_PAY"`). |
|
|
241
|
+
| `as const` | Add `as const` to the values array for better TypeScript type inference. |
|
|
286
242
|
|
|
287
|
-
|
|
288
|
-
@Field.Prop(() => String, { nullable: true })
|
|
289
|
-
description: string | null;
|
|
290
|
-
```
|
|
243
|
+
## Instance Methods
|
|
291
244
|
|
|
292
|
-
|
|
245
|
+
You can add instance methods directly to the scalar class for computed properties and utility functions:
|
|
293
246
|
|
|
294
247
|
```typescript
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
248
|
+
import { Int } from "@akanjs/base";
|
|
249
|
+
import { via } from "@akanjs/constant";
|
|
250
|
+
|
|
251
|
+
export class Stock extends via((field) => ({
|
|
252
|
+
total: field(Int, { default: 0, min: 0 }),
|
|
253
|
+
current: field(Int, { default: 0, min: 0 }),
|
|
254
|
+
})) {
|
|
255
|
+
getPercentage() {
|
|
256
|
+
if (this.total === 0) return 0;
|
|
257
|
+
return (this.current / this.total) * 100;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
301
260
|
```
|
|
302
261
|
|
|
303
|
-
###
|
|
262
|
+
### Method Guidelines
|
|
263
|
+
|
|
264
|
+
- Instance methods have access to all fields via `this`
|
|
265
|
+
- Use them for calculations based on field values
|
|
266
|
+
- Methods defined in `constant.ts` are available on both server and client
|
|
267
|
+
- For server-only logic, use `document.ts` instead
|
|
268
|
+
|
|
269
|
+
## Static Methods
|
|
270
|
+
|
|
271
|
+
Scalar classes can include static methods for common operations:
|
|
304
272
|
|
|
305
273
|
```typescript
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
class User { ... }
|
|
274
|
+
import { enumOf, Float } from "@akanjs/base";
|
|
275
|
+
import { via } from "@akanjs/constant";
|
|
309
276
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
export class
|
|
277
|
+
export class CoordinateType extends enumOf("coordinateType", ["Point"] as const) {}
|
|
278
|
+
|
|
279
|
+
export class Coordinate extends via((field) => ({
|
|
280
|
+
type: field(CoordinateType, { default: "Point" }),
|
|
281
|
+
coordinates: field([Float], { default: [0, 0], example: [127.114367, 37.497114] }),
|
|
282
|
+
altitude: field(Float, { default: 0 }),
|
|
283
|
+
})) {
|
|
284
|
+
static getDistanceKm(loc1: Coordinate, loc2: Coordinate) {
|
|
285
|
+
const [lon1, lat1] = loc1.coordinates;
|
|
286
|
+
const [lon2, lat2] = loc2.coordinates;
|
|
287
|
+
const R = 6371; // Earth's radius in kilometers
|
|
288
|
+
// Distance calculation logic...
|
|
289
|
+
return distance;
|
|
290
|
+
}
|
|
313
291
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
292
|
+
static moveMeters(loc: Coordinate, x: number, y: number): Coordinate {
|
|
293
|
+
// Calculate new position...
|
|
294
|
+
return { ...loc, coordinates: [newLon, newLat] };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
317
297
|
```
|
|
318
298
|
|
|
319
|
-
|
|
299
|
+
## Common Mistakes and Fixes
|
|
320
300
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
301
|
+
| Issue | Wrong ❌ | Correct ✅ |
|
|
302
|
+
| --------------- | ---------------------------------- | ----------------------------------------- |
|
|
303
|
+
| Enum case | `enumOf("status", ["ACTIVE"])` | `enumOf("status", ["active"])` |
|
|
304
|
+
| Array syntax | `field(Array<Int>)` | `field([Int])` |
|
|
305
|
+
| Dynamic default | `{ default: dayjs() }` | `{ default: () => dayjs() }` |
|
|
306
|
+
| Missing export | `class Status extends enumOf(...)` | `export class Status extends enumOf(...)` |
|
|
307
|
+
| Optional field | `field(ID, { nullable: true })` | `field(ID).optional()` |
|
|
308
|
+
|
|
309
|
+
### Important Note
|
|
325
310
|
|
|
326
|
-
|
|
327
|
-
import { type Dayjs } from "@akanjs/base";
|
|
311
|
+
For Date defaults that should be computed at creation time, **always use a factory function**:
|
|
328
312
|
|
|
329
|
-
|
|
330
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
// ❌ Wrong - creates a single fixed date at module load
|
|
315
|
+
{ default: dayjs() }
|
|
316
|
+
|
|
317
|
+
// ✅ Correct - creates a new date each time
|
|
318
|
+
{ default: () => dayjs() }
|
|
331
319
|
```
|
|
332
320
|
|
|
333
|
-
|
|
321
|
+
## Implementation Checklist
|
|
334
322
|
|
|
335
|
-
|
|
336
|
-
// ❌ Wrong (missing or mismatched parameter)
|
|
337
|
-
@Model.Scalar()
|
|
338
|
-
export class GeoLocation { ... }
|
|
323
|
+
### File-Level
|
|
339
324
|
|
|
340
|
-
|
|
325
|
+
- [ ] File location: `__scalar/<camelCase>/<camelCase>.constant.ts`
|
|
326
|
+
- [ ] Import `via` from `@akanjs/constant`
|
|
327
|
+
- [ ] Import `enumOf` from `@akanjs/base` (if using enums)
|
|
328
|
+
- [ ] Export all classes (scalar and enums)
|
|
341
329
|
|
|
342
|
-
|
|
343
|
-
|
|
330
|
+
### Naming
|
|
331
|
+
|
|
332
|
+
- [ ] Use PascalCase for class names
|
|
333
|
+
- [ ] Use camelCase for enum values
|
|
334
|
+
- [ ] Class name matches directory name in PascalCase
|
|
335
|
+
|
|
336
|
+
### Field Definition
|
|
337
|
+
|
|
338
|
+
- [ ] Use `[Type]` syntax for arrays
|
|
339
|
+
- [ ] Use factory functions for dynamic defaults
|
|
340
|
+
- [ ] Use `.optional()` for nullable fields
|
|
341
|
+
- [ ] Add `as const` for large enum value arrays
|
|
344
342
|
|
|
345
343
|
## Full Examples
|
|
346
344
|
|
|
@@ -349,136 +347,96 @@ export class GeoLocation { ... }
|
|
|
349
347
|
```typescript
|
|
350
348
|
// libs/payment/lib/__scalar/amount/amount.constant.ts
|
|
351
349
|
import { Float } from "@akanjs/base";
|
|
352
|
-
import {
|
|
353
|
-
|
|
354
|
-
export class Amount {
|
|
355
|
-
@Field.Prop(() => Float, { min: 0, default: 0 })
|
|
356
|
-
value: number;
|
|
350
|
+
import { via } from "@akanjs/constant";
|
|
357
351
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
352
|
+
export class Amount extends via((field) => ({
|
|
353
|
+
value: field(Float, { min: 0, default: 0 }),
|
|
354
|
+
currency: field(String, { default: "USD" }),
|
|
355
|
+
})) {}
|
|
361
356
|
```
|
|
362
357
|
|
|
363
|
-
###
|
|
358
|
+
### Scalar with Enum
|
|
364
359
|
|
|
365
360
|
```typescript
|
|
366
|
-
// apps/
|
|
367
|
-
import {
|
|
368
|
-
import {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
export
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
})
|
|
380
|
-
latitude: number;
|
|
381
|
-
|
|
382
|
-
@Field.Prop(() => Float, {
|
|
383
|
-
min: -180,
|
|
384
|
-
max: 180,
|
|
385
|
-
example: -122.4194,
|
|
386
|
-
})
|
|
387
|
-
longitude: number;
|
|
388
|
-
|
|
389
|
-
@Field.Prop(() => Float, {
|
|
390
|
-
nullable: true,
|
|
391
|
-
min: 0,
|
|
392
|
-
example: 12.5,
|
|
393
|
-
})
|
|
394
|
-
elevation: number | null;
|
|
395
|
-
|
|
396
|
-
@Field.Prop(() => String, {
|
|
397
|
-
enum: AccuracyLevel,
|
|
398
|
-
default: "medium",
|
|
399
|
-
})
|
|
400
|
-
accuracy: AccuracyLevel;
|
|
401
|
-
|
|
402
|
-
@Field.Prop(() => Date, {
|
|
403
|
-
default: () => dayjs(),
|
|
404
|
-
immutable: true,
|
|
405
|
-
})
|
|
406
|
-
measuredAt: Dayjs;
|
|
407
|
-
}
|
|
361
|
+
// apps/akasys/lib/__scalar/version/version.constant.ts
|
|
362
|
+
import { dayjs, enumOf } from "@akanjs/base";
|
|
363
|
+
import { via } from "@akanjs/constant";
|
|
364
|
+
|
|
365
|
+
export class VersionStatus extends enumOf("versionStatus", ["active", "expired"] as const) {}
|
|
366
|
+
|
|
367
|
+
export class Version extends via((field) => ({
|
|
368
|
+
source: field(File).optional(),
|
|
369
|
+
appBuild: field(File).optional(),
|
|
370
|
+
build: field(File).optional(),
|
|
371
|
+
status: field(VersionStatus, { default: "active" }),
|
|
372
|
+
at: field(Date, { default: () => dayjs() }),
|
|
373
|
+
})) {}
|
|
408
374
|
```
|
|
409
375
|
|
|
410
|
-
### Scalar with
|
|
376
|
+
### Scalar with Optional Fields
|
|
411
377
|
|
|
412
378
|
```typescript
|
|
413
|
-
// apps/
|
|
414
|
-
import {
|
|
415
|
-
import {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
example: ["red", "blue"],
|
|
425
|
-
})
|
|
426
|
-
colors: string[];
|
|
427
|
-
|
|
428
|
-
@Field.Prop(() => Dimension)
|
|
429
|
-
size: Dimension;
|
|
430
|
-
|
|
431
|
-
@Field.Prop(() => Float, { min: 0 })
|
|
432
|
-
weight: number;
|
|
433
|
-
|
|
434
|
-
@Field.Prop(() => Int, {
|
|
435
|
-
min: 0,
|
|
436
|
-
default: 0,
|
|
437
|
-
})
|
|
438
|
-
stock: number;
|
|
439
|
-
}
|
|
379
|
+
// apps/angelo/lib/__scalar/estimate/estimate.constant.ts
|
|
380
|
+
import { Float, Int } from "@akanjs/base";
|
|
381
|
+
import { via } from "@akanjs/constant";
|
|
382
|
+
|
|
383
|
+
export class Estimate extends via((field) => ({
|
|
384
|
+
name: field(String),
|
|
385
|
+
value: field(Float, { min: 0, default: 0 }),
|
|
386
|
+
num: field(Int, { min: 1, default: 1 }),
|
|
387
|
+
unit: field(String).optional(),
|
|
388
|
+
note: field(String).optional(),
|
|
389
|
+
})) {}
|
|
440
390
|
```
|
|
441
391
|
|
|
442
|
-
### Scalar with Static Methods
|
|
392
|
+
### Complex Scalar with Static Methods
|
|
443
393
|
|
|
444
394
|
```typescript
|
|
445
395
|
// libs/util/lib/__scalar/coordinate/coordinate.constant.ts
|
|
446
|
-
import { Float } from "@akanjs/base";
|
|
447
|
-
import {
|
|
396
|
+
import { enumOf, Float } from "@akanjs/base";
|
|
397
|
+
import { via } from "@akanjs/constant";
|
|
448
398
|
|
|
449
|
-
export class
|
|
450
|
-
@Field.Prop(() => [Float], { default: [0, 0], example: [127.114367, 37.497114] })
|
|
451
|
-
coordinates: number[];
|
|
452
|
-
|
|
453
|
-
@Field.Prop(() => Float, { default: 0 })
|
|
454
|
-
altitude: number;
|
|
399
|
+
export class CoordinateType extends enumOf("coordinateType", ["Point"] as const) {}
|
|
455
400
|
|
|
401
|
+
export class Coordinate extends via((field) => ({
|
|
402
|
+
type: field(CoordinateType, { default: "Point" }),
|
|
403
|
+
coordinates: field([Float], { default: [0, 0], example: [127.114367, 37.497114] }),
|
|
404
|
+
altitude: field(Float, { default: 0 }),
|
|
405
|
+
})) {
|
|
456
406
|
static getDistanceKm(loc1: Coordinate, loc2: Coordinate) {
|
|
457
407
|
const [lon1, lat1] = loc1.coordinates;
|
|
458
408
|
const [lon2, lat2] = loc2.coordinates;
|
|
459
|
-
|
|
409
|
+
const R = 6371;
|
|
410
|
+
// ... calculation logic
|
|
460
411
|
return distance;
|
|
461
412
|
}
|
|
462
413
|
|
|
463
|
-
static moveMeters(loc: Coordinate, x: number, y: number): Coordinate {
|
|
464
|
-
|
|
465
|
-
|
|
414
|
+
static moveMeters(loc: Coordinate, x: number, y: number, z: number = 0): Coordinate {
|
|
415
|
+
const [lon, lat] = loc.coordinates;
|
|
416
|
+
const dx = ((x / 1000 / 6371) * (180 / Math.PI)) / Math.cos(lat * (Math.PI / 180));
|
|
417
|
+
const dy = (y / 1000 / 6371) * (180 / Math.PI);
|
|
418
|
+
return { ...loc, coordinates: [lon + dx, lat + dy], altitude: loc.altitude + z };
|
|
466
419
|
}
|
|
467
420
|
}
|
|
468
421
|
```
|
|
469
422
|
|
|
470
|
-
##
|
|
423
|
+
## Pro Tips
|
|
424
|
+
|
|
425
|
+
- **Keep scalars focused**: If it grows too large, split it into multiple scalars
|
|
426
|
+
- **Value objects only**: Avoid adding ID or timestamp fields (use Models for that)
|
|
427
|
+
- **Define enums first**: Define enums before the scalar class that uses them
|
|
428
|
+
- **Dictionary support**: Don't forget to create `dictionary.ts` for i18n support
|
|
429
|
+
- **Reusability**: Create separate scalars for commonly used structures
|
|
430
|
+
|
|
431
|
+
## Summary
|
|
471
432
|
|
|
472
433
|
1. **Location**: `__scalar/<camelCase>/<camelCase>.constant.ts`
|
|
473
|
-
2. **
|
|
474
|
-
3. **
|
|
475
|
-
4. **
|
|
476
|
-
5. **
|
|
477
|
-
6. **
|
|
478
|
-
7. **Arrays**:
|
|
479
|
-
8. **
|
|
480
|
-
9. **Dates**: Always use `Dayjs` type with `Date` decorator
|
|
481
|
-
10. **Nullability**: Use `| null` with `{ nullable: true }`
|
|
482
|
-
11. **Validation**: Implement field-level constraints
|
|
434
|
+
2. **Import**: `via` from `@akanjs/constant`, `enumOf` from `@akanjs/base`
|
|
435
|
+
3. **Scalar Class**: `export class Name extends via((field) => ({...})) {}`
|
|
436
|
+
4. **Enum Class**: `export class EnumName extends enumOf("enumName", [...] as const) {}`
|
|
437
|
+
5. **Fields**: `field(Type)` or `field(Type, { options })`
|
|
438
|
+
6. **Optional**: Use `.optional()` chain method
|
|
439
|
+
7. **Arrays**: Use `[Type]` syntax
|
|
440
|
+
8. **Enum Values**: Always camelCase
|
|
483
441
|
|
|
484
442
|
Following these patterns ensures type-safe, maintainable scalar definitions that integrate seamlessly with the Akan.js framework's data modeling layer.
|