@akanjs/cli 2.3.11-rc.1 → 2.3.11-rc.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.11-rc.1",
3
+ "version": "2.3.11-rc.10",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -34,7 +34,7 @@
34
34
  "@langchain/openai": "^1.4.6",
35
35
  "@tailwindcss/node": "^4.3.0",
36
36
  "@trapezedev/project": "^7.1.4",
37
- "akanjs": "2.3.11-rc.1",
37
+ "akanjs": "2.3.11-rc.10",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "daisyui": "5.5.23",
@@ -4,12 +4,10 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
4
4
  return {
5
5
  filename: "Task.Zone.tsx",
6
6
  content: `"use client";
7
- import { Task, usePage } from "@apps/${dict.appName}/client";
7
+ import { type cnst, Task, usePage } from "@apps/${dict.appName}/client";
8
8
  import type { ClientInit, ClientView } from "akanjs/fetch";
9
9
  import { Link, Load } from "akanjs/ui";
10
10
 
11
- import * as cnst from "../cnst";
12
-
13
11
  // ===== Task.Zone.tsx =====
14
12
  // Convention: lib/<module>/ — PascalCase .tsx, Zone suffix = composition layer between pages and UI.
15
13
  // Zone components use Load.Units / Load.View from akanjs/ui — the framework convention for data-bound zones.
@@ -10,7 +10,7 @@ import * as db from "../db";
10
10
  // Convention: <module>.service.ts — business logic orchestration for a database module.
11
11
  // Extends serve(db.<module>, depsCallback) from akanjs/service — binds to the DB model, receives DI deps.
12
12
  // Auto-generated by akan sync (do not write manually):
13
- // getTask(id), createModel(data), updateModel(id, data), removeModel(id),
13
+ // getTask(id), createTask(data), updateTask(id, data), removeTask(id),
14
14
  // listByStatus(status), searchDocs(text), and all filter+query methods from document.ts.
15
15
  // Manual below: lifecycle hooks, custom business logic methods.
16
16
  // Registered by akan sync into srv.ts barrel.
@@ -10,14 +10,14 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  filename: "_index.tsx",
11
11
  content: `
12
12
  import { Load } from "akanjs/ui";
13
- import { fetch, usePage, ${dict.Model} } from "${dict.appName}/client";
13
+ import { fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
14
14
  import type { PageConfig } from "akanjs/client";
15
15
 
16
16
  interface PageProps {
17
17
  params: { ${dict.model}Id: string };
18
18
  }
19
19
 
20
- export default function Page({ params }: PageProps) {
20
+ export default async function Page({ params }: PageProps) {
21
21
  const { l } = usePage();
22
22
  const { ${dict.model}Id } = params;
23
23
  const { ${dict.model}, ${dict.model}Edit } = await fetch.edit${dict.Model}(${dict.model}Id);
@@ -9,8 +9,8 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
9
9
  return {
10
10
  filename: "_index.tsx",
11
11
  content: `
12
- import { ${dict.Model}, fetch, usePage } from "${dict.appName}/client";
13
- import { Link, Load } from "akanjs/ui";
12
+ import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { Link } from "akanjs/ui";
14
14
  import type { PageConfig } from "akanjs/client";
15
15
 
16
16
  interface PageProps {
@@ -29,7 +29,7 @@ export async function generateHead({ params }: PageProps) {
29
29
  </>
30
30
  );
31
31
  }
32
- export default function Page({ params }: PageProps) {
32
+ export default async function Page({ params }: PageProps) {
33
33
  const { l } = usePage();
34
34
  const { ${dict.model}Id } = params;
35
35
  const { ${dict.model}, ${dict.model}View } = await fetch.view${dict.Model}(${dict.model}Id);
@@ -10,10 +10,10 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  filename: "_index.tsx",
11
11
  content: `
12
12
  import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
- import { Link, Load } from "akanjs/ui";
13
+ import { Link } from "akanjs/ui";
14
14
  import type { PageConfig } from "akanjs/client";
15
15
 
16
- export default function Page() {
16
+ export default async function Page() {
17
17
  const { l } = usePage();
18
18
  const { ${dict.model}InitInPublic } = await fetch.init${dict.Model}InPublic();
19
19
  return (
@@ -10,10 +10,10 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  filename: "_index.tsx",
11
11
  content: `
12
12
  import type { PageConfig } from "akanjs/client";
13
- import { Load, Model } from "akanjs/ui";
14
- import { ${dict.Model}, type cnst, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { Model } from "akanjs/ui";
14
+ import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
15
15
 
16
- export default function Page() {
16
+ export default async function Page() {
17
17
  const { l } = usePage();
18
18
  const { ${dict.model}InitInPublic } = await fetch.init${dict.Model}InPublic();
19
19
  return (
@@ -11,7 +11,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
11
11
  content: `
12
12
  "use client";
13
13
  import { Field, Layout } from "akanjs/ui";
14
- import { cnst, st, usePage } from "@${scanInfo?.type ?? "apps"}/${dict.sysName}/client";
14
+ import { st, usePage } from "@${scanInfo?.type ?? "apps"}/${dict.sysName}/client";
15
15
 
16
16
  interface GeneralProps {
17
17
  className?: string;
@@ -23,10 +23,9 @@ export const General = ({ className }: GeneralProps) => {
23
23
  return (
24
24
  <Layout.Template className={className}>
25
25
  <Field.Text
26
- label={l("${dict.model}.id")}
27
- desc={l("${dict.model}.id.desc")}
28
- value={${dict.model}Form.id}
29
- onChange={st.do.setIdOn${dict.Model}}
26
+ label={l("${dict.model}.name")}
27
+ value={${dict.model}Form.name}
28
+ onChange={st.do.setNameOn${dict.Model}}
30
29
  />
31
30
  </Layout.Template>
32
31
  );
@@ -17,7 +17,7 @@ export const Card = ({ ${dict.model}, href }: ModelProps<"${dict.model}", cnst.L
17
17
  const { l } = usePage();
18
18
  return (
19
19
  <Link href={href} className="w-full">
20
- <div>{l("${dict.model}.id")}:{${dict.model}.id}</div>
20
+ <div>{l("${dict.model}.name")}: {${dict.model}.name}</div>
21
21
  </Link>
22
22
  );
23
23
  };
@@ -20,7 +20,7 @@ export const General = ({ className, ${dict.model} }: GeneralProps) => {
20
20
  const { l } = usePage();
21
21
  return (
22
22
  <div className={clsx("w-full", className)}>
23
- <div>{l("${dict.model}.id")}: {${dict.model}.id}</div>
23
+ <div>{l("${dict.model}.name")}: {${dict.model}.name}</div>
24
24
  </div>
25
25
  );
26
26
  };
@@ -11,7 +11,9 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
11
11
  content: `
12
12
  "use client";
13
13
  import { Load } from "akanjs/ui";
14
- import { cnst, ${dict.Model} } from "@${scanInfo?.type ?? "apps"}/${dict.sysName}/client";
14
+ // Alias the domain namespace so the Card/View exports below never collide with the model name
15
+ // (a model literally named "card" or "view" would otherwise shadow this import).
16
+ import { type cnst, ${dict.Model} as ${dict.Model}Domain } from "@${scanInfo?.type ?? "apps"}/${dict.sysName}/client";
15
17
  import type { ClientInit, ClientView, SliceMeta } from "akanjs/fetch";
16
18
 
17
19
  interface CardProps {
@@ -25,7 +27,7 @@ export const Card = ({ className, init, slice }: CardProps) => {
25
27
  className={className}
26
28
  init={init}
27
29
  renderItem={(${dict.model}) => (
28
- <${dict.Model}.Unit.Card key={${dict.model}.id} href={\`/${dict.model}/\${${dict.model}.id}\`} ${dict.model}={${dict.model}} />
30
+ <${dict.Model}Domain.Unit.Card key={${dict.model}.id} href={\`/${dict.model}/\${${dict.model}.id}\`} ${dict.model}={${dict.model}} />
29
31
  )}
30
32
  />
31
33
  );
@@ -36,7 +38,7 @@ interface ViewProps {
36
38
  view: ClientView<"${dict.model}", cnst.${dict.Model}>;
37
39
  }
38
40
  export const View = ({ view }: ViewProps) => {
39
- return <Load.View view={view} renderView={(${dict.model}) => <${dict.Model}.View.General ${dict.model}={${dict.model}} />} />;
41
+ return <Load.View view={view} renderView={(${dict.model}) => <${dict.Model}Domain.View.General ${dict.model}={${dict.model}} />} />;
40
42
  };
41
43
  `,
42
44
  };
@@ -10,11 +10,12 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  import { via } from "akanjs/constant";
11
11
 
12
12
  export class ${dict.Model}Input extends via((field) => ({
13
+ name: field(String),
13
14
  })) {}
14
15
 
15
16
  export class ${dict.Model}Object extends via(${dict.Model}Input, (field) => ({})) {}
16
17
 
17
- export class Light${dict.Model} extends via(${dict.Model}Object, [] as const, (resolve) => ({})) {}
18
+ export class Light${dict.Model} extends via(${dict.Model}Object, ["name"] as const, (resolve) => ({})) {}
18
19
 
19
20
  export class ${dict.Model} extends via(${dict.Model}Object, Light${dict.Model}, (resolve) => ({})) {}
20
21
 
@@ -24,7 +24,9 @@ export const dictionary = modelDictionary(["en", "ko"])
24
24
  .of((t) =>
25
25
  t(["${modelLabelEn}", "${modelLabelKo}"]).desc(["${modelDescEn}", "${modelDescKo}"])
26
26
  )
27
- .model<${dict.Model}>((t) => ({}))
27
+ .model<${dict.Model}>((t) => ({
28
+ name: t(["Name", "이름"]),
29
+ }))
28
30
  .insight<${dict.Model}Insight>((t) => ({}))
29
31
  .slice<${dict.Model}Slice>((fn) => ({
30
32
  inPublic: fn(["${dict.Model} In Public", "${dict.Model} 공개"]).arg((t) => ({})),
@@ -1,38 +1,9 @@
1
1
  ---
2
- description: Akan.js workspace conventions for coding agents
2
+ description: Akan workspace agent guide
3
3
  alwaysApply: true
4
4
  ---
5
5
 
6
- # Akan.js Workspace Rules
6
+ Follow the workspace agent guide, which is the single source of truth for Akan conventions,
7
+ generated-file rules, and the MCP workflow policy.
7
8
 
8
- - Keep edits inside the established Akan structure: `apps/<app>`, `libs/<lib>`, `pkgs/*`, and `infra`.
9
- - Pages live under `apps/<app>/page`; index routes use `_index.tsx`, and nested layouts use `_layout.tsx`.
10
- - Database domain modules live under `lib/<model>`.
11
- - Service modules live under `lib/_<service>`.
12
- - Scalar modules live under `lib/__scalar/<scalar>`.
13
- - Module abstracts live beside module code as `<model>.abstract.md`, `<service>.abstract.md`, or
14
- `<scalar>.abstract.md`. Read them before changing module behavior.
15
- - Do not hand-edit generated Akan files such as `akan.app.json`, `client.ts`, `server.ts`, generated facet indexes,
16
- `lib/cnst.ts`, `lib/dict.ts`, `lib/db.ts`, `lib/srv.ts`, `lib/st.ts`, `lib/sig.ts`, `lib/useClient.ts`, or
17
- `lib/useServer.ts`.
18
- - Prefer Akan MCP workflows before direct source edits: use `akan mcp --mode plan` for workflow discovery and
19
- planning, then `akan mcp --mode apply` only for allowlisted apply, validation, and repair tools.
20
- - If `plan_workflow` returns `planPath` or `next.tool=apply_workflow`, call `apply_workflow({ planPath })` before
21
- editing source files directly.
22
- - After `apply_workflow`, run `run_validation` with `validationTarget` when present; otherwise use `applyReportPath`.
23
- - Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
24
- - Direct edits are fallback only after `list_workflows`/`explain_workflow` show no matching workflow, or after
25
- `apply_workflow` reports unsupported/no-op/failed diagnostics that require manual action.
26
- - For compound requests, split the request into workflows and apply each `planPath` in order, such as `create-module`
27
- followed by `add-field`.
28
- - If generated output is stale or broken, update the owning source file and run `akan repair generated` or
29
- `akan sync <app-or-lib>` instead of patching generated files.
30
- - For new domain behavior, inspect sibling `constant`, `dictionary`, `signal`, `document`, `service`, `store`, and UI
31
- module files before changing shape.
32
- - Update `*.abstract.md` when business invariants, workflows, or public behavior change. Do not update it for
33
- formatting-only, import-only, or style-only changes.
34
- - Respect server/client import boundaries. Use `akanjs/server` only in server-side code and `akanjs/client` only in
35
- client/page runtime code.
36
- - Treat `AKAN_PUBLIC_*` env vars as public values. Do not store secrets in them.
37
- - Verify changes with the smallest relevant command: `akan lint <target>`, `akan test <target>`, or
38
- `akan build <app-name>`.
9
+ @AGENTS.md
@@ -10,7 +10,7 @@ alwaysApply: true
10
10
  - To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
11
11
 
12
12
  ```bash
13
- cd dist/apps/<appName> && USE_AKANJS_PKGS=true AKAN_PUBLIC_REPO_NAME=akansoft AKAN_PUBLIC_SERVE_DOMAIN="akamir.com" AKAN_PUBLIC_APP_NAME=seon AKAN_PUBLIC_ENV=local AKAN_PUBLIC_OPERATION_MODE=local SERVER_MODE=federation AKAN_PUBLIC_BASE_PATHS=neul,apptest,seon bun main.js
13
+ cd dist/apps/<appName> && USE_AKANJS_PKGS=true AKAN_PUBLIC_REPO_NAME=akansoft AKAN_PUBLIC_SERVE_DOMAIN="akanjs.com" AKAN_PUBLIC_APP_NAME=seon AKAN_PUBLIC_ENV=local AKAN_PUBLIC_OPERATION_MODE=local SERVER_MODE=federation AKAN_PUBLIC_BASE_PATHS=operator,office,homepage bun main.js
14
14
  ```
15
15
 
16
16
  - Adjust `<appName>`, `AKAN_PUBLIC_APP_NAME`, and `AKAN_PUBLIC_BASE_PATHS` to match the app being tested.
@@ -9,5 +9,5 @@ alwaysApply: false
9
9
  - Use Bun and ESM assumptions from the root `tsconfig.json`.
10
10
  - Prefer path aliases over deep relative imports when crossing package boundaries.
11
11
  - Use `akanjs/*` for framework facets, `@apps/*` for apps, `@libs/*` for shared libs, and `@contract/*` for contract code.
12
- - Respect existing client/server entrypoints such as `@libs/shared/client`, `@libs/shared/server`, `@apps/puffinplace/client`, and `@apps/puffinplace/server`.
12
+ - Respect existing client/server entrypoints such as `@libs/shared/client`, `@libs/shared/server`, `@apps/<% appName %>/client`, and `@apps/<% appName %>/server`.
13
13
  - Let Biome organize imports instead of manually reshuffling unrelated imports.
@@ -67,7 +67,9 @@ local.properties
67
67
  **/public/fallback-*.js
68
68
  **/public/libs
69
69
  **/private/libs
70
+ **/public/firebase-messaging-sw.js
70
71
 
72
+ **/android/keystore.properties
71
73
  **/vendor/bundle/
72
74
  **/ios/App/App/public
73
75
  **/ios/App/App/Podfile.lock
@@ -118,4 +120,5 @@ libs/*/client.ts
118
120
  libs/*/server.ts
119
121
  libs/*/index.ts
120
122
  **/.akan
121
- **/bun.lock
123
+ **/bun.lock
124
+ **/tsconfig.tsbuildinfo
@@ -28,7 +28,6 @@ Common generated files include:
28
28
  - `*/lib/cnst.ts`
29
29
  - `*/lib/db.ts`
30
30
  - `*/lib/dict.ts`
31
- - `*/lib/option.ts`
32
31
  - `*/lib/sig.ts`
33
32
  - `*/lib/srv.ts`
34
33
  - `*/lib/st.ts`
@@ -87,7 +86,8 @@ When adding a new database-backed domain module (e.g., product, user):
87
86
 
88
87
  ```bash
89
88
  # 1. Scaffold the module with Akan CLI (creates constant, service, signal, store, document files)
90
- akan create-module <module-name> --app <%= appName %>
89
+ # The target app/lib is a POSITIONAL argument, not a --app flag.
90
+ akan create-module <module-name> <%= appName %>
91
91
 
92
92
  # 2. Start dev server with HMR and type checking at http://localhost:8282
93
93
  akan start <%= appName %>
@@ -111,15 +111,41 @@ akan test <%= appName %>
111
111
  akan build <%= appName %>
112
112
  ```
113
113
 
114
+ **Verify endpoints with signal tests, not raw HTTP.** The canonical way to check a query/mutation/slice
115
+ contract is an in-memory signal test (`<model>.signal.test.ts`), using the test fetch harness
116
+ (`getOrSetupSignalTestFetch`) — it is fast, needs no running server, and exercises `fetch.*`, `view/edit/merge<Model>`,
117
+ and slice `init`/`list`/`insight` directly. Prefer it over `curl`: the dev gateway locale-prefixes routes (`/en/...`),
118
+ so hand-rolled HTTP calls against a raw path can redirect unexpectedly. See `akan test <%= appName %>`.
119
+
114
120
  ### Other Frequently Used Commands
115
121
 
116
122
  ```bash
117
- akan create-scalar <scalar-name> --app <%= appName %> # Add a scalar module (lib/__scalar/<scalar-name>/)
118
- akan create-service <service-name> --app <%= appName %> # Add a service module (lib/_<service-name>/)
123
+ akan create-scalar <scalar-name> <%= appName %> # Add a scalar module (lib/__scalar/<scalar-name>/)
124
+ akan create-service <service-name> <%= appName %> # Add a service module (lib/_<service-name>/)
119
125
  akan test <%= appName %> # Run the test code (lib/*/*.signal.test.ts or others)
120
126
  akan lint <%= appName %> # Lint only (no typecheck)
121
127
  ```
122
128
 
129
+ **CLI argument conventions.** Two argument styles, and mixing them up is a common mistake:
130
+
131
+ - Scaffolding and whole-app commands take the target app/lib as a **positional** argument, not a flag:
132
+
133
+ ```bash
134
+ akan create-module photo <%= appName %>
135
+ akan create-scalar money <%= appName %>
136
+ akan create-service billing <%= appName %>
137
+ akan sync <%= appName %>
138
+ ```
139
+
140
+ - Only the source-limited field commands use `--app`/`--module` flags:
141
+
142
+ ```bash
143
+ akan add-field --app <%= appName %> --module photo --field width --type Int
144
+ akan add-enum-field --app <%= appName %> --module photo --field status --values draft,active
145
+ ```
146
+
147
+ Passing `--app` to `create-module` is not recognized, and the target app will not resolve.
148
+
123
149
  For the default generated app, start with:
124
150
 
125
151
  ```bash
@@ -130,6 +156,11 @@ akan start <%= appName %>
130
156
 
131
157
  Almost every Akan.js change follows this pattern. **Missing sync or repair is the #1 cause of agent confusion.**
132
158
 
159
+ > **If the Akan MCP tools are not connected in your agent, skip straight to the CLI-only fallback below.**
160
+ > `akan mcp --mode plan/apply` starts a stdio MCP server that only works when your agent is wired to it as an
161
+ > MCP client. When those `list_workflows` / `plan_workflow` / `apply_workflow` tools are not available, the CLI
162
+ > commands are a fully supported, first-class path — you are not losing any capability by using them.
163
+
133
164
  1. **Plan** — Ask the Akan MCP server for the workflow first.
134
165
  ```
135
166
  akan mcp --mode plan
@@ -171,12 +202,31 @@ Almost every Akan.js change follows this pattern. **Missing sync or repair is th
171
202
 
172
203
  If `akan sync` gives errors, try:
173
204
  - `akan build <%= appName %>` — full rebuild catches type errors sync may miss
174
- - Re-run `akan create-module <name> --app <%= appName %>` if the scaffold is corrupted
205
+ - Re-run `akan create-module <name> <%= appName %>` if the scaffold is corrupted
175
206
 
176
207
  For compound natural-language requests, split the request into workflows and apply each artifact in order. For example,
177
208
  "create a project module and add a budget field" should run `create-module` plan/apply first, then `add-field`
178
209
  plan/apply, then validation/doctor on the returned `validationTarget`.
179
210
 
211
+ ### CLI-Only Fallback (MCP Not Connected)
212
+
213
+ When the Akan MCP tools are not loaded, run the CLI commands directly. Each MCP tool maps 1:1 to a CLI command,
214
+ and the CLI emits the same structured report via `--format json`:
215
+
216
+ | MCP tool | CLI-only equivalent |
217
+ |----------|---------------------|
218
+ | `list_workflows` | `akan workflow list` |
219
+ | `explain_workflow <name>` | `akan workflow explain <name>` |
220
+ | `plan_workflow <name> ...` | `akan workflow plan <name> ... --format json --out <planPath>` |
221
+ | `apply_workflow { planPath }` | `akan workflow apply <planPath> --format json` (add `--dry-run` to preview) |
222
+ | `run_validation { validationTarget }` | `akan doctor --strict --format json` (or `akan typecheck <%= appName %>`) |
223
+ | `repair_generated` / `repair_imports` / `repair_module_shape` | `akan repair generated\|imports\|module-shape --app <%= appName %> --format json` |
224
+
225
+ The scaffolding primitives (`akan create-module`, `akan create-scalar`, `akan create-service`, `akan add-field`,
226
+ `akan add-enum-field`) are the same primitives the workflows call, so `create-module <name> <%= appName %>` followed
227
+ by `akan sync <%= appName %>` is equivalent to running the `create-module` workflow. Direct source edits remain the
228
+ final fallback when no CLI command covers the change.
229
+
180
230
  ## Quick Decision Matrix — "Where do I put this code?"
181
231
 
182
232
  | You want to... | Create in... | Run after... |
@@ -185,6 +235,7 @@ plan/apply, then validation/doctor on the returned `validationTarget`.
185
235
  | Add a pure workflow / integration (e.g., Payment, Email) | `lib/_<service>/` → service, signal, store, dictionary, abstract | `akan sync <name>` |
186
236
  | Add a reusable value type (e.g., Address, WorkHistory) | `lib/__scalar/<type>/` → constant, dictionary, abstract | `akan sync <name>` |
187
237
  | Create a new URL-visitable page | `page/` → `_index.tsx`, `_layout.tsx`, `[param]/_index.tsx` | Rebuild (akan start auto-detects) |
238
+ | Change the app color theme / design tokens | `apps/<app>/page/styles.css` → edit the daisyUI `@plugin "daisyui/theme"` blocks (`light`/`dark`, the `--color-*` variables) | akan start hot-reloads |
188
239
  | Add a form or reusable UI component | `ui/` → PascalCase `.tsx` with `"use client"` if needed | `akan sync <name>` |
189
240
  | Add a React hook or browser helper | `webkit/` → camelCase `.ts` with `"use client"` | `akan sync <name>` |
190
241
  | Add a server-only guard, middleware, or adaptor | `srvkit/` → PascalCase `.ts` | `akan sync <name>` |
@@ -202,6 +253,7 @@ plan/apply, then validation/doctor on the returned `validationTarget`.
202
253
  | Skip running `akan sync` after deleting a file | Deleted files remain referenced in barrel exports, causing import errors everywhere. | Run `akan sync <name>` after every file add, remove, or rename |
203
254
  | Use "use client" or `useState`/`useEffect` in pages/*.tsx, *.Unit.tsx, and *.View.tsx files | Server code cannot use React hooks. Wrap in a separate `"use client"` component. | Move hook logic to `webkit/` or a `"use client"` UI component |
204
255
  | Use `<a>` tag for internal navigation between pages | Akan.js uses `<Link>` from `akanjs/ui` for client-side navigation — avoids full page reloads. | `import { Link } from "akanjs/ui"` and use `<Link href="/task">...</Link>` |
256
+ | Name a custom `Endpoint`/`Slice` like a generated CRUD op — `create<Model>`, `update<Model>`, `remove<Model>`, `view<Model>`, `edit<Model>`, `merge<Model>` | These names are already auto-generated. A collision can pass sync/typecheck/build and only fail at runtime. | Pick a distinct verb, e.g. `startTask`/`archiveTask`, never `createTask` for a custom endpoint |
205
257
 
206
258
  ## Generated File Tracker (Quick Reference)
207
259
 
@@ -212,7 +264,6 @@ These files are regenerated by `akan sync` and overwritten on every sync. **Do n
212
264
  | `*/lib/cnst.ts` | All `*/lib/*/**.constant.ts` | Barrel for all constants |
213
265
  | `*/lib/db.ts` | All `*/lib/<model>/*.document.ts` | Barrel for all document models |
214
266
  | `*/lib/dict.ts` | All `*/lib/*/**.dictionary.ts` | Barrel for all dictionaries |
215
- | `*/lib/option.ts` | Generated option helpers | Option helper entry |
216
267
  | `*/lib/sig.ts` | All `*/lib/**/**.signal.ts` | Barrel for all signals |
217
268
  | `*/lib/srv.ts` | All `*/lib/**/**.service.ts` | Barrel for all services |
218
269
  | `*/lib/st.ts` | All `*/lib/**/**.store.ts` | Barrel for all stores |
@@ -289,7 +340,13 @@ const form = st.use.taskForm();
289
340
 
290
341
  ### Recipe 2: Injecting a Dependency into a Service
291
342
 
292
- Two patterns: injecting an **adapter** (external client) or injecting another **module's service**.
343
+ Three patterns: injecting an **external adapter** (`use<>()`), another **module's service** (`service<>()`),
344
+ or a **predefined framework adapter** (`plug()`). A field named `<refName>Service` resolves to the service
345
+ registered under `<refName>` — the `Service`/`Signal` suffix is required and stripped to derive the lookup key.
346
+
347
+ > `apps/<app>/lib/option.ts` is a **user-owned** file scaffolded once — edit it to register adapters/DI. Unlike the
348
+ > barrels (`cnst.ts`, `db.ts`, `srv.ts`, …) it is **not** overwritten by `akan sync`, so your `.use(...)` registrations
349
+ > are safe.
293
350
 
294
351
  **A. Adapter injection via `use<>()` (for external clients / global singletons)**
295
352
 
@@ -332,18 +389,51 @@ export class TaskService extends serve(db.task, ({ service }) => ({
332
389
  }
333
390
  ```
334
391
 
392
+ **C. Predefined framework adapter injection via `plug()` (storage, cache, queue, schedule, …)**
393
+
394
+ Akan ships predefined adapter roles from `akanjs/service`: `StorageAdaptorRole`, `CacheAdaptorRole`,
395
+ `QueueAdaptorRole`, `ScheduleAdaptorRole`, `DatabaseAdaptorRole`, `WebsocketAdaptorRole`,
396
+ `LoggingAdaptorRole`, `CompressAdaptorRole`. `plug()` injects the concrete adapter bound to that role (the
397
+ default `StorageAdaptor` binding is `BlobStorage`). `plug()` also accepts a concrete adapter class directly.
398
+
399
+ ```typescript
400
+ // In <model>.service.ts — inject the framework storage adapter by role
401
+ import { plug, serve, StorageAdaptorRole } from "akanjs/service";
402
+
403
+ export class TaskService extends serve(db.task, ({ plug }) => ({
404
+ storage: plug(StorageAdaptorRole),
405
+ })) {
406
+ async attach(taskId: string, path: string, localPath: string) {
407
+ // BlobStorage returns a URL under blobStorage.urlPrefix (default "/api/localFile/getBlob").
408
+ return await this.storage.uploadDataFromLocal({ path, localPath });
409
+ }
410
+ }
411
+ ```
412
+
413
+ For a custom adapter class (not a predefined role), pass the class itself, e.g. `ipfsApi: plug(IpfsApi)`
414
+ (see `libs/shared/lib/file/file.service.ts`). Injecting a file/image field is usually simpler than calling
415
+ storage directly: declare `image: field(File).optional()` (or `images: field([File])`) on the model and let the
416
+ store's generated `upload<Field>On<Model>(fileList)` action handle the upload.
417
+
335
418
  ---
336
419
 
337
420
  ### Recipe 3: Creating and Using a Slice
338
421
 
339
422
  A Slice is a named, filtered data view. Add file entries and connect from a page.
340
423
 
424
+ > **Silent failure — a slice `exec` must return a query descriptor, never an executed list.**
425
+ > Return `this.taskService.queryByStatuses(...)` (the `query<Filter>` builder), **not**
426
+ > `this.taskService.listByStatuses(...)` / `listBy...(...)` (which returns a `Promise<Doc[]>`).
427
+ > Returning an array type-checks but throws at runtime during insight aggregation with the opaque
428
+ > `Error: Unknown document field path: 0`. If you see that error, your slice is returning a list, not a query.
429
+
341
430
  ```typescript
342
431
  // 1. apps/<app>/lib/<model>/<model>.signal.ts — Define the slice
343
432
  export class TaskSlice extends slice(srv.task, (init) => ({
344
433
  inTodo: init()
345
434
  .search("statuses", [cnst.TaskStatus])
346
435
  .exec(function (statuses?) {
436
+ // ✅ query<Filter> — a query descriptor. ❌ listByStatuses(...) returns an array and fails at runtime.
347
437
  return this.taskService.queryByStatuses(statuses ?? ["todo", "inProgress"]);
348
438
  }),
349
439
  })) {}
@@ -522,6 +612,41 @@ For each business question, follow this chain:
522
612
  | What client state is shared? | `store.ts` — `store()` with auto-generated form/insight state + custom actions |
523
613
  | What should users see? | `View.tsx` + `Zone.tsx` (detail/container), `Template.tsx` (forms), `Unit.tsx` (cards), `Util.tsx` (buttons) |
524
614
 
615
+ ## Modeling & Query Gotchas
616
+
617
+ A short list of things the type system does not always catch:
618
+
619
+ - **Slices return a query, not a list.** A slice `exec` must return `this.<model>Service.query<Filter>(...)`, never
620
+ a `list<Filter>(...)` / `listBy...(...)` array. Returning an array type-checks but fails at runtime with
621
+ `Unknown document field path: 0`. (See Recipe 3.)
622
+ - **Custom endpoint names must not collide with generated CRUD.** `create/update/remove/view/edit/merge<Model>`
623
+ already exist. A collision can build green and fail only at runtime — pick a distinct verb.
624
+ - **Numbers are `Int` or `Float`, never `Number`.** `field(Number)` / `.body("x", Number)` fail to typecheck. Use
625
+ `Int` for counts, `Float` for decimals.
626
+ - **Array fields use `field([T])`.** e.g. `tags: field([String])`, `images: field([File])` — not `field(String)` with
627
+ a suffix.
628
+ - **Reading a secret field needs an explicit select.** `field(...).secret()` values (e.g. `passwordHash`) are stripped
629
+ from query results by default. Fetch them with `{ select: { <field>: true } }`, e.g.
630
+ `this.userModel.pickById(id, { select: { passwordHash: true } })`.
631
+
632
+ ## Current User, Guards & Auth-Gated Pages
633
+
634
+ Built-in user authentication (session / JWT / password hashing) ships as a separate Akan auth library, not in the
635
+ core framework. The core framework gives you the composition points below; wire the auth library through them.
636
+
637
+ - **Guards** attach at the signal declaration, not per-method:
638
+ `endpoint(srv.task, { guards: { root: SignedIn } }, ({ mutation }) => ({...}))` or
639
+ `slice(srv.task, { guards: { root: SignedIn, get: Public, cru: Public } }, ...)`. `Public` always allows; other
640
+ guards implement the `Guard` interface in `srvkit/` (server-only) and read the request context.
641
+ - **Read the current user inside a custom endpoint** by injecting an `InternalArg` with `.with(...)`:
642
+ `mutation(cnst.Task).with(CurrentUserId).exec(async function (currentUserId) { ... })`. The `Guard` /
643
+ `InternalArg` helpers live in `srvkit/` and read `context.getHttpContext().req.user`.
644
+ - **Auto-generated CRUD and `serve()` service methods / lifecycle hooks do not receive session context.** If an
645
+ operation needs the acting user, expose a custom endpoint that takes it via `.with(CurrentUserId)` — never trust a
646
+ client-supplied user id.
647
+ - **SSR auth-gated pages: guard at the layout.** Check the session in the `_layout.tsx` loader and redirect when it is
648
+ absent, so nested pages never render for signed-out users.
649
+
525
650
  ## Auto-Generated API Reference
526
651
 
527
652
  akan sync automatically generates APIs across all layers. Only write custom logic — never hand-write what the framework generates.
@@ -545,7 +670,7 @@ akan sync automatically generates APIs across all layers. Only write custom logi
545
670
  |---------------|-------------|
546
671
  | `this.<model>Model` | Auto-injected model adaptor |
547
672
  | `get<Model>(id)`, `load<Model>(id)` | Single document lookup |
548
- | `createModel(data)`, `updateModel(id, data)`, `removeModel(id)` | CRUD operations |
673
+ | `create<Model>(data)`, `update<Model>(id, data)`, `remove<Model>(id)` | CRUD operations — named after the model, e.g. `createTask`/`updateTask`/`removeTask` (there is no literal `createModel`) |
549
674
  | `list<Query>(args)`, `find<Query>(args)`, `pick<Query>(args)` | Filter-based queries |
550
675
  | `exists<Query>(args)`, `count<Query>(args)`, `insight<Query>(args)` | Filter-based helpers |
551
676
  | `_preCreate`, `_postCreate`, `_preUpdate`, `_postUpdate`, `_preRemove`, `_postRemove` | Lifecycle hooks (override to add logic) |
@@ -576,3 +701,13 @@ akan sync automatically generates APIs across all layers. Only write custom logi
576
701
  | `insight[Query](args)`, `query[Query](args)` | Insight and raw query |
577
702
 
578
703
  **Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan sync auto-generates all 10 query helper methods per filter. Write `Document` chain methods only for state transitions with validation.
704
+
705
+ ## Generated Context
706
+
707
+ The section below is regenerated by `akan agent install` (run `bun run setup:agent`). It lists this
708
+ workspace's apps, generated files, validation commands, and framework guide. Edit anything outside the
709
+ markers freely; content between them is overwritten on the next install.
710
+
711
+ <!-- akan:agent:start -->
712
+ <!-- Populated by `akan agent install`. Run `bun run setup:agent` to refresh. -->
713
+ <!-- akan:agent:end -->
@@ -50,7 +50,8 @@
50
50
  "!!**/dist",
51
51
  "!!**/.akan",
52
52
  "!!**/dump",
53
- "!!**/local"
53
+ "!!**/local",
54
+ "!!**/ios/DerivedData"
54
55
  ]
55
56
  },
56
57
  "formatter": {
@@ -131,6 +132,20 @@
131
132
  }
132
133
  },
133
134
  "overrides": [
135
+ {
136
+ "includes": [
137
+ "apps/**/*.ts",
138
+ "apps/**/*.tsx",
139
+ "libs/**/*.ts",
140
+ "libs/**/*.tsx",
141
+ "!**/*.test.ts",
142
+ "!**/*.test.tsx",
143
+ "!**/*.spec.ts",
144
+ "!**/*.spec.tsx",
145
+ "!**/common/**"
146
+ ],
147
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-throw-raw-error.grit"]
148
+ },
134
149
  {
135
150
  "includes": ["**/page/**/*.ts", "**/page/**/*.tsx", "**/*.Unit.tsx", "**/*.View.tsx"],
136
151
  "plugins": [
@@ -146,6 +161,10 @@
146
161
  "includes": ["**/*.constant.ts", "**/*.document.ts", "**/*.service.ts", "**/*.store.ts"],
147
162
  "plugins": ["./node_modules/@akanjs/devkit/lint/no-js-private-class-method.grit"]
148
163
  },
164
+ {
165
+ "includes": ["**/*.signal.ts"],
166
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-redeclare-predefined-endpoint.grit"]
167
+ },
149
168
  {
150
169
  "includes": [
151
170
  "**/*.constant.ts",
@@ -17,7 +17,6 @@ sync or build can overwrite local changes.
17
17
  | `apps/*/lib/cnst.ts` | Re-exports constants and model shapes from module `*.constant.ts` files. |
18
18
  | `apps/*/lib/db.ts` | Re-exports database models from module `*.document.ts` files. |
19
19
  | `apps/*/lib/dict.ts` | Re-exports dictionaries from module `*.dictionary.ts` files. |
20
- | `apps/*/lib/option.ts` | Re-exports generated option helpers. |
21
20
  | `apps/*/lib/srv.ts` | Re-exports services from module `*.service.ts` files. |
22
21
  | `apps/*/lib/sig.ts` | Re-exports endpoints, slices, and internals from module `*.signal.ts` files. |
23
22
  | `apps/*/lib/st.ts` | Re-exports stores from module `*.store.ts` files. |
@@ -13,9 +13,9 @@
13
13
  "agent:setup": "akan agent install all --force",
14
14
  "agent:doctor": "akan doctor --strict --format json",
15
15
  "agent:context": "akan context --format json",
16
- "agent:mcp:readonly": "akan mcp-install cursor --mode readonly --force",
17
- "agent:mcp:plan": "akan mcp-install cursor --mode plan --force",
18
- "agent:mcp:apply": "akan mcp-install cursor --mode apply --force",
16
+ "agent:mcp:readonly": "akan mcp-install all --mode readonly --force",
17
+ "agent:mcp:plan": "akan mcp-install all --mode plan --force",
18
+ "agent:mcp:apply": "akan mcp-install all --mode apply --force",
19
19
  "agent:workflows": "akan workflow list",
20
20
  "agent:sample:context": "akan context --format markdown --app <%= appName %> --module project",
21
21
  "agent:sample:service": "akan create-service billing <%= appName %> --format json",