akanjs 2.0.0-beta.4 → 2.0.0-beta.6
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/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 +1 -1
- package/cli/package/package.runner.ts +1 -1
- package/cli/templates/app/page/_index.tsx +200 -76
- package/cli/templates/module/__model__.document.ts +1 -1
- package/constant/fieldInfo.ts +1 -1
- package/package.json +1 -1
- package/server/resolver/resolver.contract.fixture.ts +1 -1
|
@@ -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
|
|
package/cli/index.js
CHANGED
|
@@ -9951,7 +9951,7 @@ var {$: $2 } = globalThis.Bun;
|
|
|
9951
9951
|
|
|
9952
9952
|
class PackageRunner extends runner("package") {
|
|
9953
9953
|
async version(workspace, { log = true } = {}) {
|
|
9954
|
-
const pkgJson = await FileSys.readJson(process.env.USE_AKANJS_PKGS === "true" ? `${workspace.workspaceRoot}/pkgs/akanjs/package.json` : `${path36.dirname(Bun.main)}
|
|
9954
|
+
const pkgJson = await FileSys.readJson(process.env.USE_AKANJS_PKGS === "true" ? `${workspace.workspaceRoot}/pkgs/akanjs/package.json` : `${path36.dirname(Bun.main)}/../package.json`);
|
|
9955
9955
|
const version = pkgJson.version;
|
|
9956
9956
|
if (log)
|
|
9957
9957
|
Logger.rawLog(`${pkgJson.name}@${version}`);
|
|
@@ -15,7 +15,7 @@ export class PackageRunner extends runner("package") {
|
|
|
15
15
|
const pkgJson = await FileSys.readJson<PackageJson>(
|
|
16
16
|
process.env.USE_AKANJS_PKGS === "true"
|
|
17
17
|
? `${workspace.workspaceRoot}/pkgs/akanjs/package.json`
|
|
18
|
-
: `${path.dirname(Bun.main)}
|
|
18
|
+
: `${path.dirname(Bun.main)}/../package.json`,
|
|
19
19
|
);
|
|
20
20
|
const version = pkgJson.version;
|
|
21
21
|
if (log) Logger.rawLog(`${pkgJson.name}@${version}`);
|
|
@@ -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 {
|
|
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 =
|
|
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 -
|
|
47
|
-
<div className="absolute -
|
|
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-
|
|
54
|
-
<img
|
|
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-
|
|
58
|
-
<p className="text-
|
|
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
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
76
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
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">
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<span className="badge badge-lg border-base-content/10 bg-base-content/10 text-base-content">
|
|
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]
|
|
112
|
-
|
|
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">
|
|
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">
|
|
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-
|
|
146
|
-
|
|
147
|
-
<div
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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-
|
|
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>
|
|
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 {
|
|
10
|
+
import { by, from, into, type SchemaOf } from "akanjs/document";
|
|
11
11
|
|
|
12
12
|
import * as cnst from "../cnst";
|
|
13
13
|
|
package/constant/fieldInfo.ts
CHANGED
|
@@ -190,7 +190,7 @@ export class ConstantField<
|
|
|
190
190
|
}
|
|
191
191
|
static getBaseInsightField(): FieldObject {
|
|
192
192
|
return {
|
|
193
|
-
count: field(Int, { default: 0, accumulate: {
|
|
193
|
+
count: field(Int, { default: 0, accumulate: {} }).toField(),
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
readonly nullable: Nullable;
|
package/package.json
CHANGED
|
@@ -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: {
|
|
34
|
+
total: f(Int, { default: 0, accumulate: {} }),
|
|
35
35
|
}));
|
|
36
36
|
export const serverResolverTestConstant = ConstantRegistry.buildModel(
|
|
37
37
|
"serverResolverTestItem",
|