akanjs 2.0.0-rc.7 → 2.0.1
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/base/primitiveRegistry.ts +28 -2
- package/cli/application/application.command.ts +11 -3
- package/cli/application/application.runner.ts +17 -1
- package/cli/guidelines/databaseModule/databaseModule.instruction.md +1 -1
- package/cli/guidelines/modelConstant/modelConstant.instruction.md +5 -5
- package/cli/guidelines/modelDocument/modelDocument.instruction.md +34 -61
- package/cli/guidelines/modelService/modelService.instruction.md +1 -1
- package/cli/index.js +9321 -19222
- package/cli/library/library.runner.ts +14 -13
- package/cli/package/package.runner.ts +31 -6
- package/cli/package/package.script.ts +2 -2
- package/cli/templates/app/page/_index.tsx +200 -79
- package/cli/templates/app/page/_layout.tsx +0 -1
- package/cli/templates/app/public/favicon.ico.template +0 -0
- package/cli/templates/app/public/logo.png.template +0 -0
- package/cli/templates/module/__Model__.Zone.tsx +1 -1
- package/cli/templates/module/__model__.document.ts +1 -1
- package/cli/templates/workspaceRoot/.gitignore.template +1 -11
- package/cli/templates/workspaceRoot/biome.json.template +16 -0
- package/cli/templates/workspaceRoot/package.json.template +1 -5
- package/cli/templates/workspaceRoot/tsconfig.json.template +24 -21
- package/cli/workspace/workspace.command.ts +7 -9
- package/cli/workspace/workspace.runner.ts +3 -13
- package/cli/workspace/workspace.script.ts +24 -9
- package/client/csrTypes.ts +1 -1
- package/constant/fieldInfo.ts +1 -1
- package/constant/serialize.ts +7 -1
- package/devkit/capacitor.base.config.ts +1 -1
- package/devkit/capacitorApp.ts +5 -1
- package/devkit/commandDecorators/argMeta.ts +28 -14
- package/devkit/commandDecorators/command.ts +41 -15
- package/devkit/commandDecorators/commandBuilder.ts +78 -42
- package/devkit/commandDecorators/helpFormatter.ts +7 -4
- package/devkit/dependencyScanner.ts +121 -15
- package/devkit/executors.ts +35 -23
- package/devkit/frontendBuild/cssCompiler.ts +9 -3
- package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
- package/devkit/lint/no-deep-internal-import.grit +25 -0
- package/devkit/lint/no-import-external-library.grit +1 -0
- package/devkit/mobile/mobileTarget.ts +48 -8
- package/devkit/scanInfo.ts +4 -1
- package/devkit/src/capacitorApp.ts +277 -0
- package/devkit/transforms/barrelImportsPlugin.ts +6 -0
- package/fetch/client/fetchClient.ts +1 -0
- package/fetch/client/httpClient.ts +13 -1
- package/package.json +37 -31
- package/server/akanServer.ts +21 -7
- package/server/hmr/clientScript.ts +8 -5
- package/server/resolver/resolver.contract.fixture.ts +1 -1
- package/test/index.ts +14 -0
- package/test/signalTest.preload.ts +10 -0
- package/test/signalTestRuntime.ts +126 -0
- package/test/testServer.ts +130 -25
- package/ui/Constant/Doc.tsx +696 -0
- package/ui/Constant/Mermaid.tsx +149 -0
- package/ui/Constant/index.ts +6 -0
- package/ui/Constant/schemaDoc.ts +324 -0
- package/ui/Field.tsx +0 -1
- package/ui/Portal.tsx +2 -0
- package/ui/System/CSR.tsx +6 -5
- package/ui/System/SSR.tsx +1 -1
- package/ui/System/SelectLanguage.tsx +1 -1
- package/ui/index.ts +1 -0
- package/ui/styles.css +0 -1
- package/webkit/bootCsr.tsx +8 -5
- package/base/test-globals.d.ts +0 -4
- package/cli/templates/app/capacitor.config.ts.template +0 -8
- package/cli/templates/app/common/commonLogic.ts +0 -12
- package/cli/templates/app/common/index.ts +0 -10
- package/cli/templates/app/public/favicon.ico +0 -0
- package/cli/templates/app/public/icons/icon-128x128.png +0 -0
- package/cli/templates/app/public/icons/icon-144x144.png +0 -0
- package/cli/templates/app/public/icons/icon-152x152.png +0 -0
- package/cli/templates/app/public/icons/icon-192x192.png +0 -0
- package/cli/templates/app/public/icons/icon-256x256.png +0 -0
- package/cli/templates/app/public/icons/icon-384x384.png +0 -0
- package/cli/templates/app/public/icons/icon-48x48.png +0 -0
- package/cli/templates/app/public/icons/icon-512x512.png +0 -0
- package/cli/templates/app/public/icons/icon-72x72.png +0 -0
- package/cli/templates/app/public/icons/icon-96x96.png +0 -0
- package/cli/templates/app/public/logo.svg +0 -70
- package/cli/templates/app/public/manifest.json.template +0 -67
- package/cli/templates/app/srvkit/backendLogic.ts +0 -12
- package/cli/templates/app/srvkit/index.ts +0 -10
- package/cli/templates/app/ui/UiComponent.ts +0 -16
- package/cli/templates/app/ui/index.ts +0 -10
- package/cli/templates/app/webkit/frontendLogic.ts +0 -12
- package/cli/templates/app/webkit/index.ts +0 -10
- package/cli/templates/module/index.tsx +0 -44
- package/cli/templates/workspaceRoot/infra/app/Chart.yaml.template +0 -6
- package/cli/templates/workspaceRoot/infra/app/templates/frontend.yaml.template +0 -182
- package/cli/templates/workspaceRoot/infra/app/values/_common-values.yaml.template +0 -183
|
@@ -244,6 +244,18 @@ const scalarPrimitiveStatics = {
|
|
|
244
244
|
value: PrimitiveValue,
|
|
245
245
|
{ optional = false }: { optional?: boolean } = {},
|
|
246
246
|
): PrimitiveValue {
|
|
247
|
+
if (optional && value === "") return undefined;
|
|
248
|
+
if (this.refName === "Date" && optional && typeof value === "string" && Number.isNaN(new Date(value).getTime()))
|
|
249
|
+
return undefined;
|
|
250
|
+
if (this.refName === "Date" && optional && value instanceof Date && Number.isNaN(value.getTime())) return undefined;
|
|
251
|
+
if (
|
|
252
|
+
optional &&
|
|
253
|
+
value &&
|
|
254
|
+
typeof value === "object" &&
|
|
255
|
+
"isValid" in value &&
|
|
256
|
+
!(value as { isValid: () => boolean }).isValid()
|
|
257
|
+
)
|
|
258
|
+
return undefined;
|
|
247
259
|
this._checkValue(value, { optional });
|
|
248
260
|
if (value === null || value === undefined) return undefined;
|
|
249
261
|
return this.serializeValue(value);
|
|
@@ -257,6 +269,18 @@ const scalarPrimitiveStatics = {
|
|
|
257
269
|
if (optional) return;
|
|
258
270
|
else throw new Error(`Required ${this.refName} value: ${value}`);
|
|
259
271
|
}
|
|
272
|
+
if (optional && value === "") return;
|
|
273
|
+
if (this.refName === "Date" && optional && typeof value === "string" && Number.isNaN(new Date(value).getTime()))
|
|
274
|
+
return;
|
|
275
|
+
if (this.refName === "Date" && optional && value instanceof Date && Number.isNaN(value.getTime())) return;
|
|
276
|
+
if (
|
|
277
|
+
optional &&
|
|
278
|
+
value &&
|
|
279
|
+
typeof value === "object" &&
|
|
280
|
+
"isValid" in value &&
|
|
281
|
+
!(value as { isValid: () => boolean }).isValid()
|
|
282
|
+
)
|
|
283
|
+
return;
|
|
260
284
|
if (!this.validate(value)) throw new Error(`Invalid ${this.refName} value: ${value}`);
|
|
261
285
|
},
|
|
262
286
|
};
|
|
@@ -307,8 +331,10 @@ Object.assign(Date, {
|
|
|
307
331
|
parseValue(input: Date | string | number | Dayjs) {
|
|
308
332
|
return dayjs(input);
|
|
309
333
|
},
|
|
310
|
-
serializeValue(value: Dayjs | Date) {
|
|
311
|
-
|
|
334
|
+
serializeValue(value: Dayjs | Date | string | number) {
|
|
335
|
+
if (value instanceof Date) return value;
|
|
336
|
+
if (typeof value === "string" || typeof value === "number") return dayjs(value).toDate();
|
|
337
|
+
return value.toDate();
|
|
312
338
|
},
|
|
313
339
|
});
|
|
314
340
|
PrimitiveRegistry.register(Date);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { select } from "@inquirer/prompts";
|
|
2
|
-
import { App, command, Exec, Sys, Workspace } from "akanjs/devkit";
|
|
2
|
+
import { App, command, Exec, getMobileTargetChoices, Sys, Workspace } from "akanjs/devkit";
|
|
3
3
|
|
|
4
4
|
import { ApplicationScript } from "./application.script";
|
|
5
5
|
|
|
@@ -53,7 +53,11 @@ export class ApplicationCommand extends command("application", [ApplicationScrip
|
|
|
53
53
|
}),
|
|
54
54
|
buildIos: target({ short: true, desc: "Build iOS app with Capacitor" })
|
|
55
55
|
.with(App)
|
|
56
|
-
.option("target", String, {
|
|
56
|
+
.option("target", String, {
|
|
57
|
+
desc: "mobile target name or all",
|
|
58
|
+
ask: "Select mobile target",
|
|
59
|
+
enum: async ({ app }) => await getMobileTargetChoices(app),
|
|
60
|
+
})
|
|
57
61
|
.option("env", String, {
|
|
58
62
|
enum: ["local", "debug", "develop", "main"],
|
|
59
63
|
desc: "backend environment",
|
|
@@ -66,7 +70,11 @@ export class ApplicationCommand extends command("application", [ApplicationScrip
|
|
|
66
70
|
}),
|
|
67
71
|
buildAndroid: target({ short: true, desc: "Build Android app with Capacitor" })
|
|
68
72
|
.with(App)
|
|
69
|
-
.option("target", String, {
|
|
73
|
+
.option("target", String, {
|
|
74
|
+
desc: "mobile target name or all",
|
|
75
|
+
ask: "Select mobile target",
|
|
76
|
+
enum: async ({ app }) => await getMobileTargetChoices(app),
|
|
77
|
+
})
|
|
70
78
|
.option("env", String, {
|
|
71
79
|
enum: ["local", "debug", "develop", "main"],
|
|
72
80
|
desc: "backend environment",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { confirm, input, select } from "@inquirer/prompts";
|
|
2
3
|
import { StringOutputParser } from "@langchain/core/output_parsers";
|
|
3
4
|
import { PromptTemplate } from "@langchain/core/prompts";
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
CapacitorApp,
|
|
16
17
|
type DatabaseMode,
|
|
17
18
|
type Exec,
|
|
19
|
+
LibExecutor,
|
|
18
20
|
type MobileEnv,
|
|
19
21
|
type ReleaseSourceOptions,
|
|
20
22
|
type ResolvedMobileTarget,
|
|
@@ -73,7 +75,21 @@ export class ApplicationRunner extends runner("application") {
|
|
|
73
75
|
await new ApplicationBuildRunner(app).typecheck(options);
|
|
74
76
|
}
|
|
75
77
|
async test(exec: Exec) {
|
|
76
|
-
|
|
78
|
+
const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
|
|
79
|
+
const preloadPath = path.join(exec.cwdPath, "../../pkgs/akanjs/test/signalTest.preload.ts");
|
|
80
|
+
const env = isSignalTarget
|
|
81
|
+
? {
|
|
82
|
+
AKAN_TEST_SIGNAL: "1",
|
|
83
|
+
AKAN_TEST_TARGET_TYPE: exec.type,
|
|
84
|
+
AKAN_TEST_TARGET_NAME: exec.name,
|
|
85
|
+
AKAN_TEST_LIBS: exec.getScanInfo({ allowEmpty: true })?.getLibs().join(",") ?? "",
|
|
86
|
+
}
|
|
87
|
+
: {};
|
|
88
|
+
const args = isSignalTarget ? ["test", "--isolate", "--preload", preloadPath] : ["test", "--isolate"];
|
|
89
|
+
await exec.spawn("bun", args, {
|
|
90
|
+
...(isSignalTarget ? { env: { ...process.env, ...env } } : {}),
|
|
91
|
+
stdio: "inherit",
|
|
92
|
+
});
|
|
77
93
|
}
|
|
78
94
|
async build(
|
|
79
95
|
app: App,
|
|
@@ -94,7 +94,7 @@ Create the MongoDB schema with document methods and middleware:
|
|
|
94
94
|
|
|
95
95
|
```typescript
|
|
96
96
|
import { dayjs } from "akanjs/base";
|
|
97
|
-
import {
|
|
97
|
+
import { by, Database, into, Loader, type SchemaOf } from "akanjs/document";
|
|
98
98
|
import { hashPassword } from "@libs/shared/server";
|
|
99
99
|
|
|
100
100
|
import * as cnst from "../cnst";
|
|
@@ -80,7 +80,7 @@ export class Drone extends Full(DroneObject, LightDrone) {
|
|
|
80
80
|
// 7. Insight Model
|
|
81
81
|
|
|
82
82
|
export class DroneInsight {
|
|
83
|
-
@Field.Prop(() => Int, { default: 0, accumulate: {
|
|
83
|
+
@Field.Prop(() => Int, { default: 0, accumulate: {} })
|
|
84
84
|
count: number;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -223,7 +223,7 @@ Insight model defines statistical fields:
|
|
|
223
223
|
|
|
224
224
|
```typescript
|
|
225
225
|
export class DroneInsight {
|
|
226
|
-
@Field.Prop(() => Int, { default: 0, accumulate: {
|
|
226
|
+
@Field.Prop(() => Int, { default: 0, accumulate: {} })
|
|
227
227
|
count: number;
|
|
228
228
|
}
|
|
229
229
|
```
|
|
@@ -314,8 +314,8 @@ const drones = await this.listByName("myDrone", { sort: "alphabetical" });
|
|
|
314
314
|
| `enum` | `any[]` | - | Enum values restriction | `@Field.Prop(()=> String, { enum: ["active","inactive"] }) status;` |
|
|
315
315
|
| `minlength` | `number` | - | Minimum string length | `@Field.Prop(()=> String, { minlength: 2 }) name: string;` |
|
|
316
316
|
| `maxlength` | `number` | - | Maximum string length | `@Field.Prop(()=> String, { maxlength: 30 }) title: string;` |
|
|
317
|
-
| `query` | `object` | - | Query value for Summary fields | `@Field.Prop(() => Int, { query: { status: {
|
|
318
|
-
| `accumulate` | `object` | - |
|
|
317
|
+
| `query` | `object` | - | Query value for Summary fields | `@Field.Prop(() => Int, { query: { status: { ne: "inactive" } } })` |
|
|
318
|
+
| `accumulate` | `object` | - | Document query filter for Insight count fields | `@Field.Prop(() => Int, { accumulate: { status: "active" } }) activeCount: number;` |
|
|
319
319
|
| `example` | `any` | - | Example value for API docs | `@Field.Prop(()=> String, { example: "contact@akanjs.com" }) email;` |
|
|
320
320
|
| `of` | `any` | - | Value type for Map fields | `@Field.Prop(()=> Map, { of: Date }) readAts: Map<string, Dayjs>;` |
|
|
321
321
|
| `validate` | `(value) => boolean` | - | Custom validation function | `@Field.Prop(()=> String, { validate: (v)=> v.includes("@") }) email;` |
|
|
@@ -477,7 +477,7 @@ export class Drone extends Full(DroneObject, LightDrone) {
|
|
|
477
477
|
// Insight Model
|
|
478
478
|
|
|
479
479
|
export class DroneInsight {
|
|
480
|
-
@Field.Prop(() => Int, { accumulate: {
|
|
480
|
+
@Field.Prop(() => Int, { accumulate: {} })
|
|
481
481
|
count: number;
|
|
482
482
|
}
|
|
483
483
|
|
|
@@ -118,26 +118,14 @@ export class OrderModel extends into(Order, cnst.orderCnst) {
|
|
|
118
118
|
.save();
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
//
|
|
121
|
+
// Insight-style count example
|
|
122
122
|
async getOrderStatsByUser(userId: string) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
totalAmount: { $sum: "$totalAmount" },
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
return result.reduce((acc, item) => {
|
|
135
|
-
acc[item._id] = {
|
|
136
|
-
count: item.count,
|
|
137
|
-
totalAmount: item.totalAmount,
|
|
138
|
-
};
|
|
139
|
-
return acc;
|
|
140
|
-
}, {});
|
|
123
|
+
const baseQuery = { user: userId };
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
active: await this.count({ ...baseQuery, status: "active" }),
|
|
127
|
+
refunded: await this.count({ ...baseQuery, status: "refunded" }),
|
|
128
|
+
};
|
|
141
129
|
}
|
|
142
130
|
}
|
|
143
131
|
```
|
|
@@ -191,37 +179,30 @@ Indexes are crucial for query performance. Define them in the Middleware's `onSc
|
|
|
191
179
|
- **Unique index** - Ensure field values are unique across the collection
|
|
192
180
|
- **Sparse index** - Only include documents that have the indexed field
|
|
193
181
|
|
|
194
|
-
## How to
|
|
182
|
+
## How to Query Documents
|
|
195
183
|
|
|
196
|
-
The Akan.js framework
|
|
184
|
+
The Akan.js framework uses document query objects for common filtering and counting:
|
|
197
185
|
|
|
198
186
|
```typescript
|
|
199
187
|
// Find operations
|
|
200
|
-
const product = await productModel.
|
|
201
|
-
const activeProducts = await productModel.
|
|
188
|
+
const product = await productModel.pickById(id);
|
|
189
|
+
const activeProducts = await productModel.list({ status: "active" });
|
|
202
190
|
|
|
203
191
|
// Update operations
|
|
204
|
-
await productModel.
|
|
192
|
+
await productModel.updateManyByQuery({ category: "electronics" }, { set: { onSale: true } });
|
|
205
193
|
|
|
206
|
-
//
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
$group: {
|
|
211
|
-
_id: "$category",
|
|
212
|
-
count: { $sum: 1 },
|
|
213
|
-
avgPrice: { $avg: "$price" },
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
]);
|
|
194
|
+
// Insight counts
|
|
195
|
+
const activeCount = await productModel.count({ status: "active" });
|
|
196
|
+
const categoryCount = await productModel.count({ category: "electronics" });
|
|
217
197
|
|
|
218
198
|
// Advanced querying
|
|
219
|
-
const products = await productModel.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
199
|
+
const products = await productModel.list(
|
|
200
|
+
{
|
|
201
|
+
price: { gte: 100, lte: 500 },
|
|
202
|
+
category: { oneOf: ["electronics", "gadgets"] },
|
|
203
|
+
},
|
|
204
|
+
{ sort: { rating: -1 }, limit: 10 },
|
|
205
|
+
);
|
|
225
206
|
```
|
|
226
207
|
|
|
227
208
|
## How to Use DataLoader
|
|
@@ -422,16 +403,17 @@ export class ProductModel extends into(Product, cnst.productCnst) {
|
|
|
422
403
|
tagProductsLoader: Loader<string, Product[]>;
|
|
423
404
|
|
|
424
405
|
async findPopularProducts(limit = 10) {
|
|
425
|
-
return this.
|
|
406
|
+
return this.list(
|
|
407
|
+
{
|
|
426
408
|
status: "active",
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
409
|
+
popularity: { gte: 4 },
|
|
410
|
+
},
|
|
411
|
+
{ sort: { popularity: -1 }, limit },
|
|
412
|
+
);
|
|
431
413
|
}
|
|
432
414
|
|
|
433
415
|
async updatePrices(categoryId: string, increasePercentage: number) {
|
|
434
|
-
const products = await this.
|
|
416
|
+
const products = await this.list({ category: categoryId });
|
|
435
417
|
|
|
436
418
|
const updates = products.map((product) => {
|
|
437
419
|
const newPrice = product.price * (1 + increasePercentage / 100);
|
|
@@ -441,20 +423,11 @@ export class ProductModel extends into(Product, cnst.productCnst) {
|
|
|
441
423
|
return Promise.all(updates);
|
|
442
424
|
}
|
|
443
425
|
|
|
444
|
-
async getCategoryStats() {
|
|
445
|
-
const result =
|
|
446
|
-
|
|
447
|
-
{
|
|
448
|
-
|
|
449
|
-
_id: "$category",
|
|
450
|
-
count: { $sum: 1 },
|
|
451
|
-
avgPrice: { $avg: "$price" },
|
|
452
|
-
minPrice: { $min: "$price" },
|
|
453
|
-
maxPrice: { $max: "$price" },
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
{ $sort: { count: -1 } },
|
|
457
|
-
]);
|
|
426
|
+
async getCategoryStats(categoryId: string) {
|
|
427
|
+
const result = {
|
|
428
|
+
activeCount: await this.count({ status: "active", category: categoryId }),
|
|
429
|
+
popularCount: await this.count({ status: "active", category: categoryId, popularity: { gte: 4 } }),
|
|
430
|
+
};
|
|
458
431
|
|
|
459
432
|
// Cache the results
|
|
460
433
|
await this.ProductCache.set("stats", "categories", JSON.stringify(result), { expireAt: dayjs().add(1, "hour") });
|
|
@@ -644,7 +644,7 @@ export class OrderService extends DbService(db.orderDb) {
|
|
|
644
644
|
// Return stock to inventory
|
|
645
645
|
for (const item of order.items) {
|
|
646
646
|
await this.productService.updateProduct(item.product, {
|
|
647
|
-
|
|
647
|
+
inc: { stock: item.quantity },
|
|
648
648
|
});
|
|
649
649
|
}
|
|
650
650
|
|