@akanjs/cli 3.0.0-alpha.1 → 3.0.0-alpha.3

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/.build-stamp CHANGED
@@ -1 +1 @@
1
- 8978ecc29bed4933e395dd03d27e5e3f76ba864e8e303fd3500ea45c948ca8de
1
+ 5f4cb4962f5027c269281e372337f2349defcfca97ac7c862b17e452e9749c5d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.3",
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": "3.0.0-alpha.1",
37
+ "akanjs": "3.0.0-alpha.3",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "dayjs": "^1.11.20",
@@ -16,7 +16,9 @@ ${databaseModules.map((module) => `import * as ${module}Db from "./${module}/${m
16
16
  ${libs.map((lib) => `export { db as ${lib} } from "@libs/${lib}/server";`).join("\n")}
17
17
 
18
18
  ${databaseModules.map((module) => `class ${capitalize(module)}Input extends by(cnst.${capitalize(module)}Input) {}`).join("\n")}
19
+ ${databaseModules.map((module) => `class ${capitalize(module)}Insight extends by(cnst.${capitalize(module)}Insight) {}`).join("\n")}
19
20
  ${databaseModules.length ? `export type { ${databaseModules.map((module) => `${capitalize(module)}Input`).join(", ")} };` : ""}
21
+ ${databaseModules.length ? `export type { ${databaseModules.map((module) => `${capitalize(module)}Insight`).join(", ")} };` : ""}
20
22
 
21
23
  ${scalarModules.map((module) => `export type * from "./__scalar/${module}/${module}.document";`).join("\n")}
22
24
  ${databaseModules.map((module) => `export type * from "./${module}/${module}.document";`).join("\n")}
@@ -24,7 +26,7 @@ ${databaseModules.map((module) => `export type * from "./${module}/${module}.doc
24
26
  ${databaseModules
25
27
  .map((module) => {
26
28
  const names = { Module: module.charAt(0).toUpperCase() + module.slice(1) };
27
- return `export const ${module} = DatabaseRegistry.buildModel("${module}" as const, ${names.Module}Input, ${module}Db.${names.Module}, ${module}Db.${names.Module}Model, cnst.${names.Module}, cnst.${names.Module}Insight, ${module}Db.${names.Module}Filter);`;
29
+ return `export const ${module} = DatabaseRegistry.buildModel("${module}" as const, ${names.Module}Input, ${module}Db.${names.Module}, ${module}Db.${names.Module}Model, cnst.${names.Module}, ${names.Module}Insight, ${module}Db.${names.Module}Filter);`;
28
30
  })
29
31
  .join("\n")}
30
32
  ${scalarModules.map((module) => `export const ${module} = DatabaseRegistry.buildScalar("${module}" as const, ${capitalize(module)});`).join("\n")}
@@ -650,7 +650,7 @@ export class TaskService extends serve(db.task, ({ plug }) => ({
650
650
  For a custom adapter class (not a predefined role), pass the class itself, e.g. `ipfsApi: plug(IpfsApi)`
651
651
  (see `libs/shared/lib/file/file.service.ts`). Injecting a file/image field is usually simpler than calling
652
652
  storage directly: declare `image: field(File).optional()` (or `images: field([File])`) on the model and let the
653
- store's generated `upload<Field>On<Model>(fileList)` action handle the upload. Add `{ cascade: "remove" }` to that
653
+ store's generated `upload<Field>On<Model>(fileList)` action handle the upload. Add `{ cascade: "removeRef" }` to that
654
654
  field when the file belongs to the model alone, and removing the model removes the file and its stored object.
655
655
 
656
656
  ---
@@ -868,12 +868,23 @@ A short list of things the type system does not always catch:
868
868
  `secret` / `hidden` / `resolve()` fields with `text` throw at class-build time — the mirror is plaintext. Search
869
869
  runs on sqlite/libsql only; `q.search()` against Postgres throws. `thumb` is mirrored for rendering and is not
870
870
  indexed.
871
- - **`cascade: "remove"` takes a relation's target down with its owner.** Declare it on the relation itself
872
- `image: field(File, { cascade: "remove" })`, arrays included. The removal runs through the **target's service**,
873
- so the target's own `_postRemove` runs too; that is how removing a model also deletes the file's stored object.
874
- Only a relation accepts it: a `String`, an `ID`, or a scalar throws while the class is being built. Nothing
875
- checks for other references to the same target, so declaring it asserts exclusive ownership. Document removal is
876
- soft but the storage delete is not, and query-level removal fires no hooks and therefore no cascade.
871
+ - **`cascade` names a direction, and the wrong one is a data loss.** Both actions can sit on the same field shape,
872
+ so the value has to say which end goes away. `cascade: "removeRef"` on the relation an owner holds
873
+ (`image: field(File, { cascade: "removeRef" })`, arrays included) removes the target when the owner is removed;
874
+ only a relation accepts it. `cascade: "removeWith"` on a child's own reference to its owner
875
+ (`field(ID, { ref: "agentSession", cascade: "removeWith" })`, or a relation, or `refPath` for a polymorphic
876
+ owner whose type field must be an `enumOf`) removes the child when the owner is the owner never learns its
877
+ children exist. The removal runs through the **target's service** so its `_postRemove` runs too, unless the
878
+ target provably has no removal side effect, in which case the boot-time plan collapses it into one query. A
879
+ `removeWith` field gets its index automatically. Nothing checks for other references to the same target, so
880
+ `removeRef` asserts exclusive ownership. Removal is soft but a storage delete is not, and a query-level removal
881
+ fires no hooks and therefore no cascade.
882
+ - **Removal is always soft, and `delete` is reserved.** `remove(id)`, the facade's `removeMany(query)`, and the
883
+ store's `removeManyByQuery` all stamp `removedAt`; the framework has no hard delete for a model table.
884
+ - **The model facade spells out `Many`/`One` on its writes** — `updateOne`, `updateMany`, `removeOne`, `removeMany`
885
+ — because a bare `update`/`remove` would read like the document-path `update(id)` / `doc.remove()` while hitting
886
+ every match. Reads keep the short `find`/`findOne` pair, and counting is `count(query)` (`countDocuments` is
887
+ `@deprecated`).
877
888
  - **`q.search()` is a filter node, not a slice requirement.** Prefer
878
889
  `bySearch: filter().arg("text", String).query((text, q) => q.search(text, { prefix: true }))` — the generated
879
890
  `listBySearch` / `countBySearch` / `queryBySearch` / `insightBySearch` come for free. Only add a search slice when
@@ -951,8 +962,12 @@ akan sync automatically generates APIs across all layers. Only write custom logi
951
962
  | `pick[Query](args)`, `pickId[Query](args)` | Find one (throw if not found) |
952
963
  | `exists[Query](args)`, `count[Query](args)` | Existence check and count |
953
964
  | `insight[Query](args)`, `query[Query](args)` | Insight and raw query |
965
+ | `remove[Query](args)`, `removeOne[Query](args)` | Query-level soft remove — all matches, or the newest one (`createdAt` desc, not caller-chosen) |
966
+ | `update[Query](args).set(patch)`, `updateOne[Query](args).set(patch)` | Query-level update — the patch lands on a terminal `.set()`, because a filter's trailing args may be optional |
954
967
 
955
- **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.
968
+ **Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan sync auto-generates all 14 query helper methods per filter. Write `Document` chain methods only for state transitions with validation.
969
+
970
+ **The four query-level writes fire no hooks**, so no `_pre`/`_postRemove` and no cascade run — same as `updateManyByQuery`. Reach for them when the model carries no removal side effect; otherwise remove documents one at a time. A filter keyed after its own model (filter `chat` on model `chat`) is rejected at boot, because `removeChat`/`updateChat` would otherwise shadow the generated single-document CRUD.
956
971
 
957
972
  ## Generated Context
958
973
 
@@ -1,14 +0,0 @@
1
- interface Dict {
2
- libName: string;
3
- }
4
- export default function getContent(scanInfo: null, dict: Dict) {
5
- return `
6
- // 현 디렉토리에서는 프로젝트 내 전체적으로 사용되는 로직을 구현하며, 따라서 백/프론트 비의존적인 pure js 코드만 구현 가능합니다.
7
- // common폴더와 다른점은, base 코드는 시스템 전체에서 import되어 사용되므로, 가장 핵심적인 로직과 추상화된 기능만 구현해야합니다.
8
- // @${dict.libName}/base에서는 서버/클라이언트 관련 라이브러리(@*/server, @*/server, @*/client, @*/client)를 모두 import할 수 없으며, @*/base 라이브러리만 import 가능합니다.
9
-
10
- export const someBaseLogic = () => {
11
- //
12
- };
13
- `;
14
- }
@@ -1,8 +0,0 @@
1
- interface Dict {
2
- [key: string]: string;
3
- }
4
- export default function getContent(scanInfo: null, dict: Dict = {}) {
5
- return `
6
- export * from "./baseLogic";
7
- `;
8
- }