akanjs 2.0.0-beta.5 → 2.0.0-beta.7

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.
@@ -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
 
@@ -7,7 +7,9 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
7
7
  return {
8
8
  filename: "_index.tsx",
9
9
  content: `
10
- import { Link } from "akanjs/ui";
10
+ import { getEnv } from "akanjs/base";
11
+ import { usePage } from "akanjs/client";
12
+ import { Link, System } from "akanjs/ui";
11
13
  import {
12
14
  FaBookOpen,
13
15
  FaBoxOpen,
@@ -21,82 +23,87 @@ import {
21
23
  } from "react-icons/fa";
22
24
 
23
25
  export default function Page() {
24
- const appName = ${JSON.stringify(dict.appName)};
25
-
26
- const highlights = [
27
- {
28
- icon: <FaLayerGroup />,
29
- title: "One Codebase",
30
- description: "Design pages, services, server contracts, and deployment surfaces in one Akan workspace.",
31
- },
32
- {
33
- icon: <FaShieldAlt />,
34
- title: "Type-Safe Flow",
35
- description: "Keep application contracts close to the business logic they protect.",
36
- },
37
- {
38
- icon: <FaRocket />,
39
- title: "Ready To Ship",
40
- description: "Start locally, build production artifacts, and grow without changing the app shape.",
41
- },
42
- ];
43
-
26
+ const appName = getEnv().appName;
27
+ const { l } = usePage();
44
28
  return (
45
29
  <main className="relative min-h-screen overflow-hidden bg-base-100 text-base-content">
46
- <div className="absolute -left-48 -top-48 h-96 w-96 rounded-full bg-primary/25 blur-3xl" />
47
- <div className="absolute -bottom-56 -right-40 h-112 w-md rounded-full bg-accent/20 blur-3xl" />
30
+ <div className="absolute -top-48 -left-48 h-96 w-96 rounded-full bg-primary/25 blur-3xl" />
31
+ <div className="absolute -right-40 -bottom-56 h-112 w-md rounded-full bg-accent/20 blur-3xl" />
48
32
  <div className="absolute inset-x-0 top-0 h-px bg-linear-to-r from-transparent via-primary/60 to-transparent" />
49
33
 
50
34
  <section className="relative mx-auto flex min-h-screen w-full max-w-7xl flex-col px-6 py-8 lg:px-8">
51
35
  <nav className="flex items-center justify-between">
52
36
  <div className="flex items-center gap-3">
53
- <div className="flex h-11 w-11 items-center justify-center overflow-hidden rounded-2xl bg-black shadow-lg shadow-primary/20">
54
- <img src="/logo.png" alt="Akan.js logo" className="h-full w-full object-cover" />
37
+ <div className="flex h-11 w-11 items-center justify-center overflow-hidden rounded-2xl bg-base-100 shadow-lg shadow-primary/20">
38
+ <img
39
+ src="/logo.png"
40
+ alt={l.trans({ en: "Akan.js logo", ko: "Akan.js 로고" })}
41
+ className="h-full w-full object-cover"
42
+ />
55
43
  </div>
56
44
  <div>
57
- <p className="text-sm font-semibold uppercase tracking-[0.3em] text-primary">Akan.js</p>
58
- <p className="text-xs text-base-content/60">Full-stack TypeScript framework</p>
45
+ <p className="font-semibold text-primary text-sm tracking-[0.25em]">Akan.js</p>
46
+ <p className="text-base-content/60 text-xs">
47
+ {l.trans({ en: "Full-stack TypeScript framework", ko: "풀스택 타입스크립트 프레임워크" })}
48
+ </p>
59
49
  </div>
60
50
  </div>
61
- <Link href="https://akanjs.com" target="_blank">
62
- <button className="btn btn-sm border-base-content/10 bg-base-content/10 text-base-content hover:border-primary hover:bg-primary hover:text-base-100">
63
- Official Site
64
- <FaExternalLinkAlt />
65
- </button>
66
- </Link>
51
+ <div className="flex items-center gap-2">
52
+ <System.ThemeToggle themes={["light", "dark"]} />
53
+ <System.SelectLanguage languages={["en", "ko"]} />
54
+ <Link href="https://akanjs.com" target="_blank">
55
+ <button className="btn btn-sm border-base-content/10 bg-base-content/10 text-base-content hover:border-primary hover:bg-primary hover:text-base-100">
56
+ {l.trans({ en: "Official Site", ko: "공식 사이트" })}
57
+ <FaExternalLinkAlt />
58
+ </button>
59
+ </Link>
60
+ </div>
67
61
  </nav>
68
62
 
69
63
  <div className="grid flex-1 items-center gap-10 py-16 lg:grid-cols-[1.04fr_0.96fr] lg:py-10">
70
64
  <div>
71
65
  <div className="badge mb-6 border-primary/20 bg-primary/10 px-4 py-3 text-primary">
72
66
  <FaCheckCircle />
73
- Your app is running
67
+ {l.trans({ en: "Your app is running", ko: "앱이 실행 중입니다" })}
74
68
  </div>
75
- <h1 className="max-w-4xl text-5xl font-black tracking-tight text-base-content sm:text-6xl lg:text-7xl">
76
- Build your business app as one connected system.
69
+ <h1 className="max-w-4xl font-black text-5xl text-base-content tracking-tight sm:text-6xl lg:text-7xl">
70
+ {l.trans({
71
+ en: "Akan turns business intent into the whole product.",
72
+ ko: "Akan은 비즈니스 의도를 제품 전체로 바꿉니다.",
73
+ })}
77
74
  </h1>
78
- <p className="mt-6 max-w-2xl text-lg leading-8 text-base-content/70">
79
- Akan.js helps solo developers and small teams create web, server, database, and deployment-ready
80
- surfaces from a single TypeScript workspace.
75
+ <p className="mt-6 max-w-2xl text-base-content/70 text-lg leading-8">
76
+ {l.trans({
77
+ en: "Agents Write. Keep It Minimal. Always Readable. Nice To Review. Build with less code, fewer repeated decisions, and a clearer path from idea to production.",
78
+ ko: "Agents Write. Keep It Minimal. Always Readable. Nice To Review. 더 적은 코드, 더 적은 반복 결정, 더 선명한 출시 경로로 만드세요.",
79
+ })}
81
80
  </p>
82
81
 
83
82
  <div className="mt-8 flex flex-wrap gap-3">
84
- <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">All-in-one</span>
85
- <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">Type-safe</span>
86
- <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">Minimal code</span>
87
- <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">Bun-first</span>
83
+ <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">
84
+ Agent-ready
85
+ </span>
86
+ <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">
87
+ Minimal code
88
+ </span>
89
+ <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">
90
+ Readable by default
91
+ </span>
92
+ <span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">
93
+ Review-friendly
94
+ </span>
88
95
  </div>
89
96
 
90
97
  <div className="mt-10 flex flex-col gap-3 sm:flex-row">
91
98
  <Link href="https://akanjs.com/docs/intro/quickstart" target="_blank">
92
99
  <button className="btn border-none bg-primary text-base-100 hover:bg-primary/80">
93
- Read Quick Start
100
+ {l.trans({ en: "Read Quick Start", ko: "빠른 시작 읽기" })}
94
101
  <FaBookOpen />
95
102
  </button>
96
103
  </Link>
97
104
  <Link href="https://akanjs.com/docs/intro/practice" target="_blank">
98
105
  <button className="btn border-base-content/10 bg-base-content/10 text-base-content hover:border-base-content/20 hover:bg-base-content/15">
99
- Learn By Building
106
+ {l.trans({ en: "Learn By Building", ko: "만들면서 배우기" })}
100
107
  <FaCodeBranch />
101
108
  </button>
102
109
  </Link>
@@ -108,60 +115,177 @@ export default function Page() {
108
115
  <div className="relative overflow-hidden rounded-4xl border border-base-content/10 bg-base-content/6 p-5 shadow-2xl backdrop-blur">
109
116
  <div className="mb-5 flex items-center justify-between rounded-2xl border border-base-content/10 bg-base-100/70 px-4 py-3">
110
117
  <div>
111
- <p className="text-xs uppercase tracking-[0.24em] text-base-content/40">Current App</p>
112
- <p className="text-lg font-semibold text-base-content">{appName}</p>
118
+ <p className="text-base-content/40 text-xs uppercase tracking-[0.24em]">
119
+ {l.trans({ en: "Akan Acrostic", ko: "Akan 사행시" })}
120
+ </p>
121
+ <p className="font-semibold text-base-content text-lg">
122
+ {l.trans({ en: "A framework for focused builders", ko: "집중하는 빌더를 위한 프레임워크" })}
123
+ </p>
124
+ </div>
125
+ <div className="rounded-xl bg-primary/10 px-3 py-2 font-medium text-primary text-sm">{appName}</div>
126
+ </div>
127
+
128
+ <div className="grid gap-3">
129
+ <div className="rounded-2xl border border-base-content/10 bg-base-100/80 p-4">
130
+ <div className="flex gap-4">
131
+ <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-base-100 font-black text-primary text-xl">
132
+ A
133
+ </div>
134
+ <div>
135
+ <p className="font-bold text-base-content">Agents Write</p>
136
+ <p className="mt-1 text-base-content/60 text-sm leading-6">
137
+ {l.trans({
138
+ en: "Business definitions become the source code, so teams and agents can focus on what to build.",
139
+ ko: "비즈니스 정의가 소스 코드가 되므로, 팀과 에이전트는 무엇을 만들지에 집중할 수 있습니다.",
140
+ })}
141
+ </p>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ <div className="rounded-2xl border border-base-content/10 bg-base-100/80 p-4">
146
+ <div className="flex gap-4">
147
+ <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-base-100 font-black text-primary text-xl">
148
+ K
149
+ </div>
150
+ <div>
151
+ <p className="font-bold text-base-content">Keep It Minimal</p>
152
+ <p className="mt-1 text-base-content/60 text-sm leading-6">
153
+ {l.trans({
154
+ en: "One definition flows into web, app, server, database, and infrastructure without repeated logic.",
155
+ ko: "하나의 정의가 반복 로직 없이 웹, 앱, 서버, 데이터베이스, 인프라로 이어집니다.",
156
+ })}
157
+ </p>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ <div className="rounded-2xl border border-base-content/10 bg-base-100/80 p-4">
162
+ <div className="flex gap-4">
163
+ <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-base-100 font-black text-primary text-xl">
164
+ A
165
+ </div>
166
+ <div>
167
+ <p className="font-bold text-base-content">Always Readable</p>
168
+ <p className="mt-1 text-base-content/60 text-sm leading-6">
169
+ {l.trans({
170
+ en: "Strict conventions keep the app easy to understand long after the first version ships.",
171
+ ko: "엄격한 컨벤션은 첫 버전 출시 후에도 앱을 쉽게 이해할 수 있게 지켜줍니다.",
172
+ })}
173
+ </p>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ <div className="rounded-2xl border border-base-content/10 bg-base-100/80 p-4">
178
+ <div className="flex gap-4">
179
+ <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-base-100 font-black text-primary text-xl">
180
+ N
181
+ </div>
182
+ <div>
183
+ <p className="font-bold text-base-content">Nice To Review</p>
184
+ <p className="mt-1 text-base-content/60 text-sm leading-6">
185
+ {l.trans({
186
+ en: "Focused changes make business intent clear, so reviews stay fast and releases stay calm.",
187
+ ko: "집중된 변경은 비즈니스 의도를 선명하게 만들어 리뷰를 빠르게 하고 배포를 안정적으로 유지합니다.",
188
+ })}
189
+ </p>
190
+ </div>
191
+ </div>
113
192
  </div>
114
- <div className="rounded-xl bg-primary/10 px-3 py-2 text-sm font-medium text-primary">Live</div>
115
193
  </div>
116
194
 
117
- <div className="mockup-code border border-base-content/10 bg-base-100 text-sm shadow-none">
195
+ <div className="mockup-code mt-5 border border-base-content/10 bg-base-100 text-sm shadow-none">
118
196
  <pre data-prefix="$">
119
197
  <code className="text-primary">bun run akan start {appName}</code>
120
198
  </pre>
121
199
  <pre data-prefix="✓">
122
- <code className="text-accent">server, web, and app surfaces ready</code>
200
+ <code className="text-accent">
201
+ {l.trans({ en: "web, app, server, db, and infra ready", ko: "웹, 앱, 서버, DB, 인프라 준비 완료" })}
202
+ </code>
123
203
  </pre>
124
204
  <pre data-prefix="→">
125
- <code className="text-base-content/70">edit page/_index.tsx to begin</code>
205
+ <code className="text-base-content/70">
206
+ {l.trans({
207
+ en: "edit page/_index.tsx around your business",
208
+ ko: "비즈니스에 맞게 page/_index.tsx를 수정하세요",
209
+ })}
210
+ </code>
126
211
  </pre>
127
212
  </div>
128
-
129
- <div className="mt-5 grid gap-3 sm:grid-cols-3">
130
- {[
131
- ["Pages", "UI routes"],
132
- ["Services", "Typed APIs"],
133
- ["Deploy", "Artifacts"],
134
- ].map(([title, description]) => (
135
- <div key={title} className="rounded-2xl border border-base-content/10 bg-base-content/4 p-4">
136
- <p className="text-sm font-semibold text-base-content">{title}</p>
137
- <p className="mt-1 text-xs text-base-content/60">{description}</p>
138
- </div>
139
- ))}
140
- </div>
141
213
  </div>
142
214
  </div>
143
215
  </div>
144
216
 
145
- <div className="grid gap-4 pb-8 md:grid-cols-3">
146
- {highlights.map((item) => (
147
- <div key={item.title} className="rounded-3xl border border-base-content/10 bg-base-content/4 p-6 backdrop-blur">
148
- <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-xl text-primary">
149
- {item.icon}
150
- </div>
151
- <h2 className="text-lg font-bold text-base-content">{item.title}</h2>
152
- <p className="mt-2 text-sm leading-6 text-base-content/60">{item.description}</p>
217
+ <div className="grid gap-4 pb-8 md:grid-cols-2 xl:grid-cols-4">
218
+ <div className="rounded-3xl border border-base-content/10 bg-base-content/4 p-6 backdrop-blur">
219
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary text-xl">
220
+ <FaTerminal />
221
+ </div>
222
+ <h2 className="font-bold text-base-content text-lg">
223
+ {l.trans({ en: "Agentic By Design", ko: "에이전틱 설계" })}
224
+ </h2>
225
+ <p className="mt-2 text-base-content/60 text-sm leading-6">
226
+ {l.trans({
227
+ en: "Describe the business once and let Akan shape the application surfaces around it.",
228
+ ko: "비즈니스를 한 번 설명하면 Akan이 그 주변의 애플리케이션 표면을 구성합니다.",
229
+ })}
230
+ </p>
231
+ </div>
232
+ <div className="rounded-3xl border border-base-content/10 bg-base-content/4 p-6 backdrop-blur">
233
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary text-xl">
234
+ <FaLayerGroup />
235
+ </div>
236
+ <h2 className="font-bold text-base-content text-lg">
237
+ {l.trans({ en: "One Definition", ko: "하나의 정의" })}
238
+ </h2>
239
+ <p className="mt-2 text-base-content/60 text-sm leading-6">
240
+ {l.trans({
241
+ en: "Pages, services, database models, and deployment artifacts stay connected in one workspace.",
242
+ ko: "페이지, 서비스, 데이터베이스 모델, 배포 산출물이 하나의 워크스페이스에서 연결됩니다.",
243
+ })}
244
+ </p>
245
+ </div>
246
+ <div className="rounded-3xl border border-base-content/10 bg-base-content/4 p-6 backdrop-blur">
247
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary text-xl">
248
+ <FaRocket />
153
249
  </div>
154
- ))}
250
+ <h2 className="font-bold text-base-content text-lg">
251
+ {l.trans({ en: "Less To Review", ko: "리뷰할 것이 적습니다" })}
252
+ </h2>
253
+ <p className="mt-2 text-base-content/60 text-sm leading-6">
254
+ {l.trans({
255
+ en: "Smaller code surfaces make intent easier to inspect, approve, and ship.",
256
+ ko: "더 작은 코드 표면은 의도를 확인하고 승인하고 배포하기 쉽게 만듭니다.",
257
+ })}
258
+ </p>
259
+ </div>
260
+ <div className="rounded-3xl border border-base-content/10 bg-base-content/4 p-6 backdrop-blur">
261
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary text-xl">
262
+ <FaShieldAlt />
263
+ </div>
264
+ <h2 className="font-bold text-base-content text-lg">
265
+ {l.trans({ en: "Type-Safe Growth", ko: "타입 안전한 성장" })}
266
+ </h2>
267
+ <p className="mt-2 text-base-content/60 text-sm leading-6">
268
+ {l.trans({
269
+ en: "Conventions and contracts keep the stack readable as the product expands.",
270
+ ko: "컨벤션과 계약은 제품이 확장되어도 스택을 읽기 쉽게 유지합니다.",
271
+ })}
272
+ </p>
273
+ </div>
155
274
  </div>
156
275
 
157
- <div className="flex flex-col items-start justify-between gap-4 rounded-3xl border border-base-content/10 bg-base-content/4 p-5 text-sm text-base-content/70 md:flex-row md:items-center">
276
+ <div className="flex flex-col items-start justify-between gap-4 rounded-3xl border border-base-content/10 bg-base-content/4 p-5 text-base-content/70 text-sm md:flex-row md:items-center">
158
277
  <div className="flex items-center gap-3">
159
278
  <FaTerminal className="text-primary" />
160
- <span>Next: open your workspace and start shaping the product around your business model.</span>
279
+ <span>
280
+ {l.trans({
281
+ en: "Next: define the business once, then let Akan carry it across every surface.",
282
+ ko: "다음 단계: 비즈니스를 한 번 정의하고, Akan이 모든 표면으로 이어가게 하세요.",
283
+ })}
284
+ </span>
161
285
  </div>
162
286
  <div className="flex items-center gap-2 text-base-content/40">
163
287
  <FaBoxOpen />
164
- <span>Akan.js template</span>
288
+ <span>{l.trans({ en: "Akan.js template", ko: "Akan.js 템플릿" })}</span>
165
289
  </div>
166
290
  </div>
167
291
  </section>
@@ -7,7 +7,7 @@ interface Dict {
7
7
  }
8
8
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
9
  return `
10
- import { beyond, by, from, into, type SchemaOf } from "akanjs/document";
10
+ import { by, from, into, type SchemaOf } from "akanjs/document";
11
11
 
12
12
  import * as cnst from "../cnst";
13
13
 
@@ -142,6 +142,22 @@
142
142
  "includes": ["**/*.constant.ts", "**/*.document.ts", "**/*.service.ts", "**/*.store.ts"],
143
143
  "plugins": ["./node_modules/akanjs/devkit/lint/no-js-private-class-method.grit"]
144
144
  },
145
+ {
146
+ "includes": [
147
+ "**/*.constant.ts",
148
+ "**/*.dictionary.ts",
149
+ "**/*.document.ts",
150
+ "**/*.service.ts",
151
+ "**/*.signal.ts",
152
+ "**/*.store.ts",
153
+ "**/*.Template.tsx",
154
+ "**/*.Unit.tsx",
155
+ "**/*.Util.tsx",
156
+ "**/*.View.tsx",
157
+ "**/*.Zone.tsx"
158
+ ],
159
+ "plugins": ["./node_modules/akanjs/devkit/lint/no-deep-internal-import.grit"]
160
+ },
145
161
  {
146
162
  "includes": [
147
163
  "**/page/**/*.ts",
@@ -190,7 +190,7 @@ export class ConstantField<
190
190
  }
191
191
  static getBaseInsightField(): FieldObject {
192
192
  return {
193
- count: field(Int, { default: 0, accumulate: { $sum: 1 } }).toField(),
193
+ count: field(Int, { default: 0, accumulate: {} }).toField(),
194
194
  };
195
195
  }
196
196
  readonly nullable: Nullable;
@@ -0,0 +1,25 @@
1
+ engine biome(1.0)
2
+ language js(typescript, jsx)
3
+
4
+ or {
5
+ JsModuleSource() as $source where {
6
+ $source <: within JsImport(),
7
+ not $filename <: r".*\.(?:test|spec)\.tsx?",
8
+ $source <: r"\"@(?:apps|libs)/[^/]+/[^/]+/.+\"",
9
+ not $source <: r"\"@apps/[^/]+/env/env\.client\"",
10
+ register_diagnostic(
11
+ span = $source,
12
+ message = "@apps and @libs imports should only reference the first two path segments after the alias."
13
+ )
14
+ },
15
+ JsModuleSource() as $source where {
16
+ $source <: within JsImport(),
17
+ not $filename <: r".*\.(?:test|spec)\.tsx?",
18
+ $filename <: r".*apps/akasys/lib/projectBuild/[^/]+\.(?:constant|dictionary|document|service|signal|store)\.ts|.*apps/akasys/lib/projectBuild/[^/]+\.(?:Template|Unit|Util|View|Zone)\.tsx",
19
+ $source <: r"\"\.\./\.\./.*\"",
20
+ register_diagnostic(
21
+ span = $source,
22
+ message = "projectBuild module files should not import from two or more parent directories."
23
+ )
24
+ }
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.0.0-beta.5",
3
+ "version": "2.0.0-beta.7",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@ const ServerResolverTestFull = via(ServerResolverTestObject, ServerResolverTestL
31
31
  resolvedLabel: r(String),
32
32
  }));
33
33
  const ServerResolverTestInsight = via(ServerResolverTestFull, (f) => ({
34
- total: f(Int, { default: 0, accumulate: { $sum: "$count" } }),
34
+ total: f(Int, { default: 0, accumulate: {} }),
35
35
  }));
36
36
  export const serverResolverTestConstant = ConstantRegistry.buildModel(
37
37
  "serverResolverTestItem",
@@ -22,7 +22,7 @@ export const SelectLanguage = ({ className, languages = parseAkanI18nEnv().local
22
22
  id="select-language"
23
23
  tabIndex={0}
24
24
  role="button"
25
- className="btn btn-ghost btn-sm mx-2 my-auto min-h-0 rounded-btn px-3 font-medium text-xs md:mx-4"
25
+ className="btn btn-ghost btn-sm mx-2 my-auto min-h-0 border-none px-3 font-medium text-xs md:mx-4"
26
26
  >
27
27
  {languageNames[lang as keyof typeof languageNames]}
28
28
  </div>