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.
Files changed (92) hide show
  1. package/base/primitiveRegistry.ts +28 -2
  2. package/cli/application/application.command.ts +11 -3
  3. package/cli/application/application.runner.ts +17 -1
  4. package/cli/guidelines/databaseModule/databaseModule.instruction.md +1 -1
  5. package/cli/guidelines/modelConstant/modelConstant.instruction.md +5 -5
  6. package/cli/guidelines/modelDocument/modelDocument.instruction.md +34 -61
  7. package/cli/guidelines/modelService/modelService.instruction.md +1 -1
  8. package/cli/index.js +9321 -19222
  9. package/cli/library/library.runner.ts +14 -13
  10. package/cli/package/package.runner.ts +31 -6
  11. package/cli/package/package.script.ts +2 -2
  12. package/cli/templates/app/page/_index.tsx +200 -79
  13. package/cli/templates/app/page/_layout.tsx +0 -1
  14. package/cli/templates/app/public/favicon.ico.template +0 -0
  15. package/cli/templates/app/public/logo.png.template +0 -0
  16. package/cli/templates/module/__Model__.Zone.tsx +1 -1
  17. package/cli/templates/module/__model__.document.ts +1 -1
  18. package/cli/templates/workspaceRoot/.gitignore.template +1 -11
  19. package/cli/templates/workspaceRoot/biome.json.template +16 -0
  20. package/cli/templates/workspaceRoot/package.json.template +1 -5
  21. package/cli/templates/workspaceRoot/tsconfig.json.template +24 -21
  22. package/cli/workspace/workspace.command.ts +7 -9
  23. package/cli/workspace/workspace.runner.ts +3 -13
  24. package/cli/workspace/workspace.script.ts +24 -9
  25. package/client/csrTypes.ts +1 -1
  26. package/constant/fieldInfo.ts +1 -1
  27. package/constant/serialize.ts +7 -1
  28. package/devkit/capacitor.base.config.ts +1 -1
  29. package/devkit/capacitorApp.ts +5 -1
  30. package/devkit/commandDecorators/argMeta.ts +28 -14
  31. package/devkit/commandDecorators/command.ts +41 -15
  32. package/devkit/commandDecorators/commandBuilder.ts +78 -42
  33. package/devkit/commandDecorators/helpFormatter.ts +7 -4
  34. package/devkit/dependencyScanner.ts +121 -15
  35. package/devkit/executors.ts +35 -23
  36. package/devkit/frontendBuild/cssCompiler.ts +9 -3
  37. package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
  38. package/devkit/lint/no-deep-internal-import.grit +25 -0
  39. package/devkit/lint/no-import-external-library.grit +1 -0
  40. package/devkit/mobile/mobileTarget.ts +48 -8
  41. package/devkit/scanInfo.ts +4 -1
  42. package/devkit/src/capacitorApp.ts +277 -0
  43. package/devkit/transforms/barrelImportsPlugin.ts +6 -0
  44. package/fetch/client/fetchClient.ts +1 -0
  45. package/fetch/client/httpClient.ts +13 -1
  46. package/package.json +37 -31
  47. package/server/akanServer.ts +21 -7
  48. package/server/hmr/clientScript.ts +8 -5
  49. package/server/resolver/resolver.contract.fixture.ts +1 -1
  50. package/test/index.ts +14 -0
  51. package/test/signalTest.preload.ts +10 -0
  52. package/test/signalTestRuntime.ts +126 -0
  53. package/test/testServer.ts +130 -25
  54. package/ui/Constant/Doc.tsx +696 -0
  55. package/ui/Constant/Mermaid.tsx +149 -0
  56. package/ui/Constant/index.ts +6 -0
  57. package/ui/Constant/schemaDoc.ts +324 -0
  58. package/ui/Field.tsx +0 -1
  59. package/ui/Portal.tsx +2 -0
  60. package/ui/System/CSR.tsx +6 -5
  61. package/ui/System/SSR.tsx +1 -1
  62. package/ui/System/SelectLanguage.tsx +1 -1
  63. package/ui/index.ts +1 -0
  64. package/ui/styles.css +0 -1
  65. package/webkit/bootCsr.tsx +8 -5
  66. package/base/test-globals.d.ts +0 -4
  67. package/cli/templates/app/capacitor.config.ts.template +0 -8
  68. package/cli/templates/app/common/commonLogic.ts +0 -12
  69. package/cli/templates/app/common/index.ts +0 -10
  70. package/cli/templates/app/public/favicon.ico +0 -0
  71. package/cli/templates/app/public/icons/icon-128x128.png +0 -0
  72. package/cli/templates/app/public/icons/icon-144x144.png +0 -0
  73. package/cli/templates/app/public/icons/icon-152x152.png +0 -0
  74. package/cli/templates/app/public/icons/icon-192x192.png +0 -0
  75. package/cli/templates/app/public/icons/icon-256x256.png +0 -0
  76. package/cli/templates/app/public/icons/icon-384x384.png +0 -0
  77. package/cli/templates/app/public/icons/icon-48x48.png +0 -0
  78. package/cli/templates/app/public/icons/icon-512x512.png +0 -0
  79. package/cli/templates/app/public/icons/icon-72x72.png +0 -0
  80. package/cli/templates/app/public/icons/icon-96x96.png +0 -0
  81. package/cli/templates/app/public/logo.svg +0 -70
  82. package/cli/templates/app/public/manifest.json.template +0 -67
  83. package/cli/templates/app/srvkit/backendLogic.ts +0 -12
  84. package/cli/templates/app/srvkit/index.ts +0 -10
  85. package/cli/templates/app/ui/UiComponent.ts +0 -16
  86. package/cli/templates/app/ui/index.ts +0 -10
  87. package/cli/templates/app/webkit/frontendLogic.ts +0 -12
  88. package/cli/templates/app/webkit/index.ts +0 -10
  89. package/cli/templates/module/index.tsx +0 -44
  90. package/cli/templates/workspaceRoot/infra/app/Chart.yaml.template +0 -6
  91. package/cli/templates/workspaceRoot/infra/app/templates/frontend.yaml.template +0 -182
  92. 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
- return value instanceof Date ? value : value.toDate();
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, { desc: "mobile target name or all" })
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, { desc: "mobile target name or all" })
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
- await exec.spawn("bun", ["test", "--isolate"], { stdio: "inherit" });
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 { beyond, by, Database, into, Loader, type SchemaOf } from "akanjs/document";
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: { $sum: 1 } })
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: { $sum: 1 } })
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: { $ne: 'inactive' } })` |
318
- | `accumulate` | `object` | - | Aggregation value for Insight fields | `@Field.Prop(() => Int, { accumulate: { $sum: 1 } }) count: number;` |
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: { $sum: 1 } })
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
- // Aggregation example
121
+ // Insight-style count example
122
122
  async getOrderStatsByUser(userId: string) {
123
- const result = await this.Order.aggregate([
124
- { $match: { user: userId } },
125
- {
126
- $group: {
127
- _id: "$status",
128
- count: { $sum: 1 },
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 Use MongoDB Model
182
+ ## How to Query Documents
195
183
 
196
- The Akan.js framework provides direct access to the Mongoose model, allowing you to perform standard MongoDB operations:
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.Product.findById(id);
201
- const activeProducts = await productModel.Product.find({ status: "active" });
188
+ const product = await productModel.pickById(id);
189
+ const activeProducts = await productModel.list({ status: "active" });
202
190
 
203
191
  // Update operations
204
- await productModel.Product.updateMany({ category: "electronics" }, { $set: { onSale: true } });
192
+ await productModel.updateManyByQuery({ category: "electronics" }, { set: { onSale: true } });
205
193
 
206
- // Aggregation
207
- const stats = await productModel.Product.aggregate([
208
- { $match: { status: "active" } },
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.Product.find({
220
- price: { $gte: 100, $lte: 500 },
221
- category: { $in: ["electronics", "gadgets"] },
222
- })
223
- .sort({ rating: -1 })
224
- .limit(10);
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.Product.find({
406
+ return this.list(
407
+ {
426
408
  status: "active",
427
- popularity: { $gte: 4 },
428
- })
429
- .sort({ popularity: -1 })
430
- .limit(limit);
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.Product.find({ category: categoryId });
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 = await this.Product.aggregate([
446
- { $match: { status: "active" } },
447
- {
448
- $group: {
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
- $inc: { stock: item.quantity },
647
+ inc: { stock: item.quantity },
648
648
  });
649
649
  }
650
650