@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.20
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 +1 -1
- package/CodeAgent-awv258wb.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-1nqh35ez.js +1457 -0
- package/README.md +3 -0
- package/TunnelShare-scf4e4pd.js +9 -0
- package/{agent.command-d2gfxfck.js → agent.command-1s9ag7sb.js} +8 -9
- package/akanCode.js +102 -0
- package/{application.command-pj2m7584.js → application.command-49hwy6va.js} +51 -78
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-ybyp97pv.js} +63 -64
- package/{applicationReleasePackager-4yasxwv9.js → applicationReleasePackager-esnrphw8.js} +40 -60
- package/buildBatch.proc.js +19 -24
- package/{capacitorApp-36rbr858.js → capacitorApp-1synz4en.js} +3 -4
- package/cloud.command-d7rqtzt9.js +94 -0
- package/cloud.script-bkbpcr96.js +19 -0
- package/code.command-nn48xzej.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-s3mcmvpa.js +48 -0
- package/context.runner-k99nb5pk.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
- package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-5ngz93ga.js +9 -0
- package/guideline.command-sct4qnt5.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +206 -25
- package/guidelines/conventions/conventions.instruction.md +79 -24
- package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +17 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +51 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +31 -48
- package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
- package/index-13ffe9wp.js +69 -0
- package/index-22d57v4c.js +266 -0
- package/{index-fz5q3gd7.js → index-31yr441d.js} +10 -17
- package/index-3hx1z96c.js +87 -0
- package/index-3sq321ak.js +53 -0
- package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
- package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
- package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
- package/{index-259dsnz3.js → index-5pc1qrbv.js} +5 -8
- package/index-7md2g9vw.js +6 -0
- package/index-8mna0947.js +187 -0
- package/{index-zry5nz81.js → index-8sf7wmdr.js} +51 -30
- package/{index-2tv3fdx4.js → index-9d9bgcx4.js} +342 -765
- package/index-9dp031by.js +12 -0
- package/{index-ne2qy0nk.js → index-ahemdr5n.js} +12 -12
- package/{index-ws5e0j51.js → index-as7xmyz8.js} +40 -24
- package/index-axzjaqve.js +1118 -0
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/{index-a64esmtb.js → index-ddxvdd1p.js} +169 -305
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-pnqbkf5p.js → index-dzf3r49a.js} +6 -2
- package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
- package/{index-wt7qrtd4.js → index-mxav6j3c.js} +93 -232
- package/{index-gyd220p2.js → index-myhc3c1j.js} +554 -577
- package/{index-600zhnve.js → index-nv5976x3.js} +56 -197
- package/index-p6pbz9n5.js +25 -0
- package/{index-4edzf1gr.js → index-przybvy5.js} +77 -150
- package/index-smme4r47.js +49 -0
- package/{index-mak3cd28.js → index-w2zdcw3k.js} +137 -255
- package/index-wa5jbxj1.js +760 -0
- package/{index-v0qh106m.js → index-ww4z397j.js} +527 -723
- package/{index-j9yhg404.js → index-xqjwd384.js} +4 -7
- package/{index-7737gxcp.js → index-xqpzbab5.js} +107 -241
- package/index-xsjh6nzh.js +2729 -0
- package/{index-ktb5njgy.js → index-ygn77a5w.js} +47 -99
- package/index-ztp3e73t.js +700 -0
- package/index.js +23 -22
- package/{library.command-nbsky672.js → library.command-1gx04jgn.js} +4 -5
- package/{localRegistry.command-yeq57wjr.js → localRegistry.command-zndxj1q6.js} +22 -42
- package/{module.command-29vqzrh8.js → module.command-3waddk95.js} +8 -11
- package/{package.command-axv0abmn.js → package.command-zrc7br1y.js} +6 -7
- package/package.json +14 -5
- package/{page.command-vs9s3zrh.js → page.command-vpjgardh.js} +4 -5
- package/{primitive.command-cbxmqbtc.js → primitive.command-sz84tk4t.js} +9 -22
- package/prompter-ptaa3nk0.js +8 -0
- package/{quality.command-gmbs7nws.js → quality.command-g869hafs.js} +203 -376
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-bqsjptsj.js → repair.command-chsnjvvv.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-6k1vhe9s.js → scalar.command-2v21newx.js} +7 -10
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-fwjfcts7.js → subspace.command-ckcbwfp1.js} +138 -67
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +1 -1
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +0 -2
- package/templates/service/__model__.abstract.md.template +8 -0
- package/tunnel.command-kd2yr4fx.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-44252cqv.js +79 -0
- package/{workspace.command-8s050m0h.js → workspace.command-fqcztt5p.js} +77 -109
- package/abstractCompactor-mjh5ppsq.js +0 -101
- package/cloud.command-jshwdq22.js +0 -93
- package/context.command-ez8a0zyz.js +0 -80
- package/devTuiView-c08ccbat.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-xmqwx8zm.js +0 -359
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-2hq52jq3.js +0 -465
- package/index-6wwzs1bc.js +0 -7
- package/index-8pm30m73.js +0 -841
- package/index-kn1h7p1x.js +0 -560
- package/index-meh558jy.js +0 -65
- package/index-rwg2aa4f.js +0 -229
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/workflow.command-nwpcdc8p.js +0 -107
|
@@ -3,45 +3,14 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "workHistory.abstract.md",
|
|
6
|
-
content: `#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Standard scalar layers:
|
|
15
|
-
- workHistory.constant.ts — enum (WorkHistoryAction) + entry class (WorkHistory) defined via via() + enumOf()
|
|
16
|
-
- workHistory.dictionary.ts — i18n via scalarDictionary() (fewer layers than modelDictionary)
|
|
17
|
-
- workHistory.abstract.md — embedding rules and intent
|
|
18
|
-
|
|
19
|
-
## Convention: lib/__scalar/<type>/ directory naming
|
|
20
|
-
|
|
21
|
-
- Scalar modules use double-underscore prefix: lib/__scalar/workHistory/
|
|
22
|
-
- Files drop the underscores: workHistory.constant.ts, not __workHistory.constant.ts
|
|
23
|
-
- Models embed scalars via field([WorkHistory]) — list-of-scalar embedding
|
|
24
|
-
|
|
25
|
-
## Embedding Example (in task.constant.ts)
|
|
26
|
-
|
|
27
|
-
TaskObject embeds a list of WorkHistory:
|
|
28
|
-
import { WorkHistory } from "../../__scalar/workHistory/workHistory.constant";
|
|
29
|
-
export class TaskObject extends via(TaskInput, (field) => ({
|
|
30
|
-
workHistory: field([WorkHistory]),
|
|
31
|
-
}))
|
|
32
|
-
|
|
33
|
-
Each status change (create, start, complete) pushes a new entry.
|
|
34
|
-
|
|
35
|
-
## Agent Notes
|
|
36
|
-
|
|
37
|
-
- Read this abstract before modifying the WorkHistory scalar.
|
|
38
|
-
- Update when new action types or fields are added.
|
|
39
|
-
- Do not duplicate workHistory fields in individual models; always reuse this scalar.
|
|
40
|
-
|
|
41
|
-
## Related Modules
|
|
42
|
-
|
|
43
|
-
- lib/task/ — database module embedding workHistory as a list field
|
|
44
|
-
- lib/_noti/ — pure service module (different module type, no scalar usage)
|
|
6
|
+
content: `# workHistory Abstract
|
|
7
|
+
One step in a task's history: which move happened, when, and an optional note.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- Entries are only appended — by \`Task\`'s chain methods and by the task service on create — and never edited or
|
|
11
|
+
removed, so the list reads as the task's moves in order.
|
|
12
|
+
- \`at\` is the moment the entry was built when the writer gives none.
|
|
13
|
+
- \`note\` is empty on every entry the app writes itself.
|
|
45
14
|
`,
|
|
46
15
|
};
|
|
47
16
|
}
|
|
@@ -3,36 +3,13 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "noti.abstract.md",
|
|
6
|
-
content: `#
|
|
6
|
+
content: `# noti Service Abstract
|
|
7
|
+
Noti holds the in-app notification list on the client: the messages the app shows, an unread count, and dismissal.
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Both module types follow the same layer pattern, but service modules have fewer layers:
|
|
14
|
-
no constant.ts (no data shape), no document.ts (no DB queries), no UI components.
|
|
15
|
-
|
|
16
|
-
- noti.service.ts — business logic via serve("noti" as const, ...) (named service, no DB binding)
|
|
17
|
-
- noti.signal.ts — real-time pubsub endpoint bound via endpoint(srv.noti, ...)
|
|
18
|
-
- noti.store.ts — client state via store("noti" as const, ...) (named store, no signal binding)
|
|
19
|
-
- noti.dictionary.ts — i18n via serviceDictionary() (no model field labels)
|
|
20
|
-
|
|
21
|
-
## Convention: lib/_<service>/ directory naming
|
|
22
|
-
|
|
23
|
-
- Service modules live under lib/_<service>/ — the underscore prefix signals "this is a service, not a database model."
|
|
24
|
-
- Files drop the underscore: lib/_noti/noti.service.ts, not lib/_noti/_noti.service.ts.
|
|
25
|
-
- Scan registers into the same barrel files: srv.ts, sig.ts, st.ts, dict.ts.
|
|
26
|
-
|
|
27
|
-
## Convention: named serve/store (no DB binding)
|
|
28
|
-
|
|
29
|
-
- serve("noti" as const, ...) — service name passed as string, no db.<module> binding.
|
|
30
|
-
- store("noti" as const, ...) — store name passed as string, no sig.<module> binding.
|
|
31
|
-
|
|
32
|
-
## Related Modules
|
|
33
|
-
|
|
34
|
-
- lib/task/ — database module reference (compare structure differences)
|
|
35
|
-
- lib/__scalar/workHistory/ — reusable scalar field modules (double-underscore prefix, embedded in models)
|
|
9
|
+
## Rules
|
|
10
|
+
- Notifications live only in the client store; the server side declares nothing yet, so a reload starts empty.
|
|
11
|
+
- \`unreadCount\` counts additions since the last \`markAllRead()\`, not the list's length — removing a notification
|
|
12
|
+
does not lower it.
|
|
36
13
|
`,
|
|
37
14
|
};
|
|
38
15
|
}
|
|
@@ -3,38 +3,18 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "task.abstract.md",
|
|
6
|
-
content: `#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- task.service.ts — business logic orchestration bound to DB via serve(db.task, ...)
|
|
19
|
-
- task.signal.ts — API surface (query, mutation, pubsub endpoints bound via endpoint(srv.task, ...))
|
|
20
|
-
- task.store.ts — client state management bound to signal via store(sig.task, ...)
|
|
21
|
-
- Task.*.tsx — UI components (Zone -> Load -> Unit / View / Template / Util)
|
|
22
|
-
|
|
23
|
-
## Convention: lib/<module>/ directory naming
|
|
24
|
-
|
|
25
|
-
- Database modules live under lib/<module>/ (no underscore prefix)
|
|
26
|
-
- Files follow <module>.<layer>.ts naming — e.g., task.constant.ts, task.service.ts
|
|
27
|
-
- UI files use PascalCase with explicit suffix: Task.Zone.tsx, Task.Unit.tsx, Task.View.tsx
|
|
28
|
-
|
|
29
|
-
## Convention: scan-registered barrels
|
|
30
|
-
|
|
31
|
-
akan sync auto-discovers each file and registers it in the corresponding barrel:
|
|
32
|
-
cnst.ts, db.ts, dict.ts, srv.ts, sig.ts, st.ts
|
|
33
|
-
|
|
34
|
-
## Related Modules
|
|
35
|
-
|
|
36
|
-
- lib/__scalar/workHistory/ — standard scalar modules with double-underscore prefix (reusable field sets)
|
|
37
|
-
- lib/_noti/ — pure service module (compare structure differences: no constant/document, serve without DB binding)
|
|
6
|
+
content: `# task Abstract
|
|
7
|
+
A task is one unit of work that moves from todo through in progress to completed, and keeps a record of each move.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- A task only moves forward: \`start()\` needs \`todo\` and \`complete()\` needs \`inProgress\`; any other state throws.
|
|
11
|
+
- Every move appends one \`workHistory\` entry, and creation writes the first (\`created\`), so the history is never
|
|
12
|
+
empty and its last entry always matches \`status\`.
|
|
13
|
+
- Anyone may read a task; creating, changing, starting and completing one needs a signed-in caller. There is no
|
|
14
|
+
generated admin API (\`root: None\`).
|
|
15
|
+
- Removal is soft: a removed task keeps its row with \`removedAt\` set.
|
|
16
|
+
|
|
17
|
+
todo -> inProgress -> completed
|
|
38
18
|
`,
|
|
39
19
|
};
|
|
40
20
|
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
|
-
export default function getContent(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export default function getContent(
|
|
4
|
+
scanInfo: AppInfo | LibInfo | null,
|
|
5
|
+
dict: { appName: string },
|
|
6
|
+
options: { libs?: string[] } = {},
|
|
7
|
+
) {
|
|
8
|
+
// Task writes are guarded by SignedIn, so the fixtures sign a user in through libs/shared when it is installed.
|
|
9
|
+
const signsIn = options.libs?.includes("shared") ?? false;
|
|
10
|
+
return `${
|
|
11
|
+
signsIn
|
|
12
|
+
? `import * as userSpec from "@libs/shared/lib/user/user.signal.spec";
|
|
13
|
+
`
|
|
14
|
+
: ""
|
|
15
|
+
}import type { DocumentModel } from "akanjs/constant";
|
|
16
|
+
import { ${signsIn ? "" : "getOrSetupSignalTestFetch, "}sampleOf } from "akanjs/test";
|
|
7
17
|
|
|
8
18
|
import * as cnst from "../cnst";
|
|
9
19
|
import type { fetch as appFetch } from "../useServer";
|
|
10
20
|
|
|
11
21
|
type AppFetch = typeof appFetch;
|
|
12
22
|
|
|
13
|
-
const getFetch = async () => await getOrSetupSignalTestFetch<AppFetch>();
|
|
23
|
+
const getFetch = async () => ${signsIn ? "(await userSpec.getUserAgentWithPassword<AppFetch>()).fetch" : "await getOrSetupSignalTestFetch<AppFetch>()"};
|
|
14
24
|
|
|
15
25
|
export interface TaskAgent {
|
|
16
26
|
task: cnst.Task;
|
|
@@ -27,13 +37,6 @@ export const createTask = async (overrides: Partial<DocumentModel<cnst.TaskInput
|
|
|
27
37
|
|
|
28
38
|
const task = await fetch.createTask(taskInput);
|
|
29
39
|
|
|
30
|
-
expect(task).toMatchObject({
|
|
31
|
-
title: taskInput.title,
|
|
32
|
-
content: taskInput.content,
|
|
33
|
-
status: "todo",
|
|
34
|
-
});
|
|
35
|
-
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created"]);
|
|
36
|
-
|
|
37
40
|
return {
|
|
38
41
|
task,
|
|
39
42
|
fetch,
|
|
@@ -45,9 +48,6 @@ export const getStartedTask = async (overrides: Partial<DocumentModel<cnst.TaskI
|
|
|
45
48
|
const agent = await createTask(overrides);
|
|
46
49
|
const task = await agent.fetch.startTask(agent.task.id);
|
|
47
50
|
|
|
48
|
-
expect(task.status).toBe("inProgress");
|
|
49
|
-
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started"]);
|
|
50
|
-
|
|
51
51
|
return {
|
|
52
52
|
...agent,
|
|
53
53
|
task,
|
|
@@ -58,9 +58,6 @@ export const getCompletedTask = async (overrides: Partial<DocumentModel<cnst.Tas
|
|
|
58
58
|
const agent = await getStartedTask(overrides);
|
|
59
59
|
const task = await agent.fetch.completeTask(agent.task.id);
|
|
60
60
|
|
|
61
|
-
expect(task.status).toBe("completed");
|
|
62
|
-
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started", "completed"]);
|
|
63
|
-
|
|
64
61
|
return {
|
|
65
62
|
...agent,
|
|
66
63
|
task,
|
|
@@ -6,9 +6,23 @@ import { configureSignalTest } from "akanjs/test";
|
|
|
6
6
|
|
|
7
7
|
import * as taskSpec from "./task.signal.spec";
|
|
8
8
|
|
|
9
|
-
configureSignalTest({
|
|
9
|
+
configureSignalTest({ storage: "memory" });
|
|
10
10
|
|
|
11
11
|
describe("Task signal smoke", () => {
|
|
12
|
+
test("creates a task in todo", async () => {
|
|
13
|
+
const { task, taskInput } = await taskSpec.createTask({ title: "Signal smoke task" });
|
|
14
|
+
|
|
15
|
+
expect(task).toMatchObject({ title: taskInput.title, content: taskInput.content, status: "todo" });
|
|
16
|
+
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created"]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("starts a task", async () => {
|
|
20
|
+
const { task } = await taskSpec.getStartedTask({ title: "Signal smoke task" });
|
|
21
|
+
|
|
22
|
+
expect(task.status).toBe("inProgress");
|
|
23
|
+
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
12
26
|
test("exposes custom task mutations over fetch", async () => {
|
|
13
27
|
const { task } = await taskSpec.getCompletedTask({ title: "Signal smoke task" });
|
|
14
28
|
|
|
@@ -25,7 +25,7 @@ export class TaskInternal extends internal(srv.task, ({ interval }) => ({})) {}
|
|
|
25
25
|
// Guards, per key: root = the generated admin API (runs any filter with caller-supplied args), get = reads,
|
|
26
26
|
// cru = create/update/remove. root and cru are never Public; None here means "nobody until you name a guard".
|
|
27
27
|
export class TaskSlice extends slice(srv.task, { guards: { root: None, get: Public, cru: SignedIn } }, (init) => ({
|
|
28
|
-
inPublic: init().exec(function () {
|
|
28
|
+
inPublic: init({ guards: [Public] }).exec(function () {
|
|
29
29
|
return this.taskService.queryAny();
|
|
30
30
|
}),
|
|
31
31
|
})) {}
|
|
@@ -2,14 +2,14 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
2
2
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
|
-
filename: "
|
|
5
|
+
filename: "guards.ts",
|
|
6
6
|
content: `import type { Guard, GuardScope, SignalContext } from "akanjs/signal";
|
|
7
7
|
|
|
8
|
-
// =====
|
|
8
|
+
// ===== guards.ts =====
|
|
9
9
|
// Convention: srvkit/ folder — server-only helpers, cannot import from client code.
|
|
10
10
|
// Implements the Guard interface from akanjs/signal.
|
|
11
11
|
// Guards are applied at endpoint/slice declaration: { guards: { root: SignedIn } }.
|
|
12
|
-
// Naming:
|
|
12
|
+
// Naming: camelCase file, PascalCase classes; each static name matches its guard identifier.
|
|
13
13
|
// Scanned by akan sync into srvkit/index.ts barrel automatically.
|
|
14
14
|
|
|
15
15
|
export class SignedIn implements Guard {
|
|
@@ -21,7 +21,10 @@ export class SignedIn implements Guard {
|
|
|
21
21
|
|
|
22
22
|
canPass(context: SignalContext): boolean {
|
|
23
23
|
// Reads the account the middleware resolved, so the same guard answers over HTTP, a websocket and MCP alike.
|
|
24
|
-
|
|
24
|
+
// A guest still has an account — libs/shared resolves one to \`{ appName, environment }\` — so only an
|
|
25
|
+
// identity on it proves a sign-in: your own middleware's \`id\`, or libs/shared's \`self\` (user) / \`me\` (admin).
|
|
26
|
+
const account = context.get<{ id?: string; self?: { id?: string }; me?: { id?: string } }>("account");
|
|
27
|
+
return !!(account?.id ?? account?.self?.id ?? account?.me?.id);
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -2,22 +2,24 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
2
2
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
|
-
filename: "
|
|
5
|
+
filename: "internalArgs.ts",
|
|
6
6
|
content: `import type { InternalArg, SignalContext } from "akanjs/signal";
|
|
7
7
|
|
|
8
|
-
// =====
|
|
8
|
+
// ===== internalArgs.ts =====
|
|
9
9
|
// Convention: srvkit/ folder — server-only helpers.
|
|
10
10
|
// Implements the InternalArg interface from akanjs/signal.
|
|
11
11
|
// InternalArg is an auto-injected argument for resolveField/endpoint .with() chains.
|
|
12
12
|
// Appended to a query/mutation via: .with(CurrentUserId, { nullable: true }).exec(...)
|
|
13
|
-
// Naming:
|
|
13
|
+
// Naming: camelCase file, PascalCase classes; each class name is its arg identifier.
|
|
14
14
|
// Scanned by akan sync into srvkit/index.ts barrel automatically.
|
|
15
15
|
|
|
16
16
|
export class CurrentUserId implements InternalArg<string | null> {
|
|
17
17
|
getArg(context: SignalContext): string | null {
|
|
18
18
|
// \`context.get\` reads what the account middleware resolved, on every transport. Branching on
|
|
19
19
|
// \`getHttpContext()\` would return null for the same caller arriving over a websocket or MCP.
|
|
20
|
-
|
|
20
|
+
// An app's own middleware may put the id on the account itself; libs/shared puts it on \`self\` or \`me\`.
|
|
21
|
+
const account = context.get<{ id?: string; self?: { id?: string }; me?: { id?: string } }>("account");
|
|
22
|
+
return account?.id ?? account?.self?.id ?? account?.me?.id ?? null;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
package/templates/client.ts
CHANGED
|
@@ -4,36 +4,13 @@ const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
4
4
|
|
|
5
5
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
|
|
6
6
|
if (!scanInfo) return null;
|
|
7
|
-
const
|
|
8
|
-
.
|
|
9
|
-
([_, fileTypes]) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
fileTypes.has("zone"),
|
|
15
|
-
)
|
|
16
|
-
.map(([key]) => key);
|
|
17
|
-
const scalarModules = [...scanInfo.scalar.entries()]
|
|
18
|
-
.filter(
|
|
19
|
-
([_, fileTypes]) =>
|
|
20
|
-
fileTypes.has("template") ||
|
|
21
|
-
fileTypes.has("unit") ||
|
|
22
|
-
fileTypes.has("util") ||
|
|
23
|
-
fileTypes.has("view") ||
|
|
24
|
-
fileTypes.has("zone"),
|
|
25
|
-
)
|
|
26
|
-
.map(([key]) => key);
|
|
27
|
-
const serviceModules = [...scanInfo.service.entries()]
|
|
28
|
-
.filter(
|
|
29
|
-
([_, fileTypes]) =>
|
|
30
|
-
fileTypes.has("template") ||
|
|
31
|
-
fileTypes.has("unit") ||
|
|
32
|
-
fileTypes.has("util") ||
|
|
33
|
-
fileTypes.has("view") ||
|
|
34
|
-
fileTypes.has("zone"),
|
|
35
|
-
)
|
|
36
|
-
.map(([key]) => key);
|
|
7
|
+
const uiModules = (modules: Map<string, Set<string>>) =>
|
|
8
|
+
[...modules.entries()]
|
|
9
|
+
.filter(([_, fileTypes]) => ["template", "unit", "util", "view", "zone"].some((type) => fileTypes.has(type)))
|
|
10
|
+
.map(([key]) => key);
|
|
11
|
+
const databaseModules = uiModules(scanInfo.database);
|
|
12
|
+
const scalarModules = uiModules(scanInfo.scalar);
|
|
13
|
+
const serviceModules = uiModules(scanInfo.service);
|
|
37
14
|
return `
|
|
38
15
|
export * as cnst from "./lib/cnst";
|
|
39
16
|
export { msg, Err, usePage, fetch, sig } from "./lib/useClient";
|
|
@@ -4,12 +4,13 @@ interface Dict {
|
|
|
4
4
|
Model: string;
|
|
5
5
|
model: string;
|
|
6
6
|
appName: string;
|
|
7
|
+
clientPath: string;
|
|
7
8
|
}
|
|
8
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
9
10
|
return {
|
|
10
11
|
filename: "_index.tsx",
|
|
11
12
|
content: `
|
|
12
|
-
import { ${dict.Model}, fetch, usePage } from "
|
|
13
|
+
import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
|
|
13
14
|
import { ID } from "akanjs/base";
|
|
14
15
|
import { page } from "akanjs/client";
|
|
15
16
|
import { Load } from "akanjs/ui";
|
|
@@ -4,12 +4,13 @@ interface Dict {
|
|
|
4
4
|
Model: string;
|
|
5
5
|
model: string;
|
|
6
6
|
appName: string;
|
|
7
|
+
clientPath: string;
|
|
7
8
|
}
|
|
8
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
9
10
|
return {
|
|
10
11
|
filename: "_index.tsx",
|
|
11
12
|
content: `
|
|
12
|
-
import { ${dict.Model}, fetch, usePage } from "
|
|
13
|
+
import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
|
|
13
14
|
import { ID } from "akanjs/base";
|
|
14
15
|
import { page } from "akanjs/client";
|
|
15
16
|
import { buttonRecipe, Link } from "akanjs/ui";
|
|
@@ -4,12 +4,13 @@ interface Dict {
|
|
|
4
4
|
Model: string;
|
|
5
5
|
model: string;
|
|
6
6
|
appName: string;
|
|
7
|
+
clientPath: string;
|
|
7
8
|
}
|
|
8
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
9
10
|
return {
|
|
10
11
|
filename: "_index.tsx",
|
|
11
12
|
content: `
|
|
12
|
-
import { type cnst, fetch, usePage, ${dict.Model} } from "
|
|
13
|
+
import { type cnst, fetch, usePage, ${dict.Model} } from "${dict.clientPath}";
|
|
13
14
|
import { page } from "akanjs/client";
|
|
14
15
|
import { Load } from "akanjs/ui";
|
|
15
16
|
|
|
@@ -4,12 +4,13 @@ interface Dict {
|
|
|
4
4
|
Model: string;
|
|
5
5
|
model: string;
|
|
6
6
|
appName: string;
|
|
7
|
+
clientPath: string;
|
|
7
8
|
}
|
|
8
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
9
10
|
return {
|
|
10
11
|
filename: "_index.tsx",
|
|
11
12
|
content: `
|
|
12
|
-
import { ${dict.Model}, fetch, usePage } from "
|
|
13
|
+
import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
|
|
13
14
|
import { page } from "akanjs/client";
|
|
14
15
|
import { buttonRecipe, Link } from "akanjs/ui";
|
|
15
16
|
|
|
@@ -4,12 +4,13 @@ interface Dict {
|
|
|
4
4
|
Model: string;
|
|
5
5
|
model: string;
|
|
6
6
|
appName: string;
|
|
7
|
+
clientPath: string;
|
|
7
8
|
}
|
|
8
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
9
10
|
return {
|
|
10
11
|
filename: "_index.tsx",
|
|
11
12
|
content: `
|
|
12
|
-
import { ${dict.Model}, fetch, usePage } from "
|
|
13
|
+
import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
|
|
13
14
|
import { page } from "akanjs/client";
|
|
14
15
|
import { buttonRecipe, Model } from "akanjs/ui";
|
|
15
16
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
|
-
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
4
|
-
|
|
5
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
|
|
6
4
|
if (!scanInfo) return null;
|
|
7
5
|
const libs = scanInfo.getLibs();
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
|
-
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
4
|
-
|
|
5
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
|
|
6
4
|
if (!scanInfo) return null;
|
|
7
5
|
const libs = scanInfo.getLibs();
|
package/templates/lib/sig.ts
CHANGED
|
@@ -13,6 +13,8 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
|
|
|
13
13
|
const libs = scanInfo.getLibs();
|
|
14
14
|
|
|
15
15
|
const signalNames = [...(libs.length === 0 ? ["base"] : libs), ...databaseModules, ...serviceModules];
|
|
16
|
+
const serverSignalClass = (module: string) =>
|
|
17
|
+
`export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`;
|
|
16
18
|
|
|
17
19
|
return `
|
|
18
20
|
import { FetchClient, type FetchClientType } from "akanjs/fetch";
|
|
@@ -20,38 +22,14 @@ import { SignalRegistry, serverSignal${libs.length === 0 ? ", fetch as base" : "
|
|
|
20
22
|
import { Err } from "./dict";
|
|
21
23
|
${libs.map((lib) => `import { fetch as ${lib} } from "@libs/${lib}/lib/sig";`).join("\n")}
|
|
22
24
|
|
|
23
|
-
${
|
|
24
|
-
|
|
25
|
-
.map(([module]) => `import * as ${module}Sig from "./${module}/${module}.signal";`)
|
|
26
|
-
.join("\n")}
|
|
27
|
-
${[...scanInfo.service.entries()]
|
|
28
|
-
.filter(([_, files]) => files.has("signal"))
|
|
29
|
-
.map(([module]) => `import * as ${module}Sig from "./_${module}/${module}.signal";`)
|
|
30
|
-
.join("\n")}
|
|
25
|
+
${databaseModules.map((module) => `import * as ${module}Sig from "./${module}/${module}.signal";`).join("\n")}
|
|
26
|
+
${serviceModules.map((module) => `import * as ${module}Sig from "./_${module}/${module}.signal";`).join("\n")}
|
|
31
27
|
|
|
32
|
-
${
|
|
33
|
-
|
|
34
|
-
.map(([module]) => `export * from "./${module}/${module}.signal";`)
|
|
35
|
-
.join("\n")}
|
|
36
|
-
${[...scanInfo.service.entries()]
|
|
37
|
-
.filter(([_, files]) => files.has("signal"))
|
|
38
|
-
.map(([module]) => `export * from "./_${module}/${module}.signal";`)
|
|
39
|
-
.join("\n")}
|
|
28
|
+
${databaseModules.map((module) => `export * from "./${module}/${module}.signal";`).join("\n")}
|
|
29
|
+
${serviceModules.map((module) => `export * from "./_${module}/${module}.signal";`).join("\n")}
|
|
40
30
|
|
|
41
|
-
${
|
|
42
|
-
|
|
43
|
-
.map(
|
|
44
|
-
([module]) =>
|
|
45
|
-
`export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`,
|
|
46
|
-
)
|
|
47
|
-
.join("\n")}
|
|
48
|
-
${[...scanInfo.service.entries()]
|
|
49
|
-
.filter(([_, files]) => files.has("signal"))
|
|
50
|
-
.map(
|
|
51
|
-
([module]) =>
|
|
52
|
-
`export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`,
|
|
53
|
-
)
|
|
54
|
-
.join("\n")}
|
|
31
|
+
${databaseModules.map(serverSignalClass).join("\n")}
|
|
32
|
+
${serviceModules.map(serverSignalClass).join("\n")}
|
|
55
33
|
|
|
56
34
|
${databaseModules.map((module) => `export const ${module} = SignalRegistry.registerDatabase("${module}" as const, ${module}Sig.${capitalize(module)}Internal, ${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Slice, ${capitalize(module)});`).join("\n")}
|
|
57
35
|
${serviceModules.map((module) => `export const ${module} = SignalRegistry.registerService("${module}" as const, ${module}Sig.${capitalize(module)}Internal, ${module}Sig.${capitalize(module)}Endpoint, ${capitalize(module)});`).join("\n")}
|
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
version: "3.8"
|
|
2
1
|
networks:
|
|
3
2
|
<%= repoName %>-network:
|
|
4
3
|
driver: bridge
|
|
5
4
|
services:
|
|
6
5
|
redis:
|
|
7
6
|
container_name: <%= repoName %>-redis
|
|
8
|
-
image: redis
|
|
7
|
+
image: redis:8
|
|
9
8
|
restart: unless-stopped
|
|
10
9
|
ports:
|
|
11
10
|
- 6379:6379
|
|
12
11
|
networks:
|
|
13
12
|
- <%= repoName %>-network
|
|
14
|
-
libsql:
|
|
15
|
-
container_name: <%= repoName %>-libsql
|
|
16
|
-
image: ghcr.io/tursodatabase/libsql-server:latest
|
|
17
|
-
restart: unless-stopped
|
|
18
|
-
ports:
|
|
19
|
-
- 8080:8080
|
|
20
|
-
volumes:
|
|
21
|
-
- ./libsql:/var/lib/sqld
|
|
22
|
-
networks:
|
|
23
|
-
- <%= repoName %>-network
|
|
24
13
|
postgres:
|
|
25
14
|
container_name: <%= repoName %>-postgres
|
|
26
|
-
image: postgres:
|
|
15
|
+
image: postgres:18
|
|
27
16
|
restart: unless-stopped
|
|
28
17
|
ports:
|
|
29
18
|
- 5432:5432
|
|
@@ -32,6 +21,6 @@ services:
|
|
|
32
21
|
- POSTGRES_USER=akan
|
|
33
22
|
- POSTGRES_PASSWORD=akan
|
|
34
23
|
volumes:
|
|
35
|
-
- ./postgres:/var/lib/postgresql
|
|
24
|
+
- ./postgres:/var/lib/postgresql
|
|
36
25
|
networks:
|
|
37
26
|
- <%= repoName %>-network
|
|
@@ -6,16 +6,24 @@ interface Dict {
|
|
|
6
6
|
models: string;
|
|
7
7
|
sysName: string;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
interface Options {
|
|
10
|
+
sharedGuards?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict, options: Options = {}) {
|
|
13
|
+
// Admin lives in libs/shared; without it root and cru stay None, shut until the app names a guard of its own.
|
|
14
|
+
const guardImports = options.sharedGuards
|
|
15
|
+
? `import { Admin } from "@libs/shared/srvkit";\nimport { endpoint, internal, Public, slice } from "akanjs/signal";`
|
|
16
|
+
: `import { endpoint, internal, None, Public, slice } from "akanjs/signal";`;
|
|
17
|
+
const writeGuard = options.sharedGuards ? "Admin" : "None";
|
|
10
18
|
return `
|
|
11
|
-
|
|
19
|
+
${guardImports}
|
|
12
20
|
|
|
13
21
|
import * as srv from "../srv";
|
|
14
22
|
|
|
15
23
|
export class ${dict.Model}Internal extends internal(srv.${dict.model}, ({ interval }) => ({})) {}
|
|
16
24
|
|
|
17
|
-
export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root:
|
|
18
|
-
inPublic: init()
|
|
25
|
+
export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root: ${writeGuard}, get: Public, cru: ${writeGuard} } }, (init) => ({
|
|
26
|
+
inPublic: init({ guards: [Public] })
|
|
19
27
|
.exec(function () {
|
|
20
28
|
return this.${dict.model}Service.queryAny();
|
|
21
29
|
}),
|
package/templates/server.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
|
-
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
4
|
-
|
|
5
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
|
|
6
4
|
if (!scanInfo) return null;
|
|
7
5
|
const libs = scanInfo.getLibs();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# <%= model %> Service Abstract
|
|
2
|
+
|
|
3
|
+
<One sentence: the workflow or integration this service owns, stated as fact.>
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- <An invariant the code obeys but cannot explain: a lifetime, a refusal, an ordering, and its reason.>
|
|
8
|
+
- <Two to five of them. If the service moves something through states, end the file with one arrow line.>
|