@akanjs/cli 3.0.1 → 3.0.2-rc.0

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.
Files changed (74) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-zfab7pkn.js → CodeAgent-szbjv4gv.js} +2 -2
  3. package/{CodeTui-0zhnr0pw.js → CodeTui-5nw86ab6.js} +1 -1
  4. package/{agent.command-aja6z9mn.js → agent.command-np1dffkb.js} +5 -5
  5. package/akanCode.js +2 -2
  6. package/{application.command-049ehgx3.js → application.command-fyjy9ryc.js} +7 -7
  7. package/{applicationBuildRunner-75n5rpwp.js → applicationBuildRunner-etrzfwjj.js} +3 -3
  8. package/buildBatch.proc.js +3 -3
  9. package/{cloud.command-gw4kqxt8.js → cloud.command-c0rnwy65.js} +9 -9
  10. package/cloud.script-6ba8e9v8.js +20 -0
  11. package/{code.command-x9t6z5nw.js → code.command-hmtx14b2.js} +6 -6
  12. package/{context.command-yfbe1rtd.js → context.command-6aqvdx43.js} +13 -13
  13. package/context.runner-hnkvv3z3.js +21 -0
  14. package/{guideline.command-kq9gp690.js → guideline.command-axvf68gc.js} +2 -2
  15. package/guidelines/conventions/conventions.instruction.md +89 -17
  16. package/guidelines/queryRule/queryRule.instruction.md +4 -2
  17. package/guidelines/runtimeRule/runtimeRule.instruction.md +10 -5
  18. package/guidelines/transportRule/transportRule.instruction.md +40 -4
  19. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +1 -1
  20. package/incrementalBuilder.proc.js +2 -2
  21. package/{index-66yspb67.js → index-1bjkfvm9.js} +29 -27
  22. package/{index-b95yhqjr.js → index-1gs9g12n.js} +3 -3
  23. package/{index-y7hx1ygz.js → index-3xmzr1p1.js} +2 -2
  24. package/{index-mzjb6zp4.js → index-50jjwg8y.js} +1 -1
  25. package/{index-150s07n8.js → index-50wb2a26.js} +313 -93
  26. package/{index-qe71g4ze.js → index-61mmsdjg.js} +6 -6
  27. package/{index-8b639zes.js → index-6keyf4sg.js} +4 -4
  28. package/{index-vs8mefhy.js → index-bsv901fd.js} +7 -7
  29. package/{index-5pvxf7z0.js → index-cb6yc5mb.js} +2 -2
  30. package/{index-fkvb79qe.js → index-d95tfg3n.js} +14 -10
  31. package/{index-rfcz04w9.js → index-f4e08ptp.js} +4 -4
  32. package/{index-25fvrxmy.js → index-g38nhv87.js} +1 -1
  33. package/{index-br8pkrk1.js → index-gvpvxdm1.js} +5 -5
  34. package/{index-mb59b8x0.js → index-h9ht6s60.js} +1 -1
  35. package/{index-5g4rkrvt.js → index-hp3stk1m.js} +3 -3
  36. package/{index-h54yj9wr.js → index-hxnnfy57.js} +6 -5
  37. package/{index-kn5d4jmg.js → index-j38qkvkk.js} +7 -7
  38. package/{index-y5ga5w9t.js → index-jb0hmt3k.js} +3 -3
  39. package/{index-7p4c0yer.js → index-jk9xnck6.js} +33 -11
  40. package/{index-m3xsb1ms.js → index-krjcg4y6.js} +28 -10
  41. package/{index-a37by9hn.js → index-q6ft9fkc.js} +4 -4
  42. package/{index-c14nsvnv.js → index-xwprn9nr.js} +1 -1
  43. package/{index-5q14pwzd.js → index-zwd84m0w.js} +4 -4
  44. package/{index-8kvy8qb4.js → index-zyw622y1.js} +12 -2
  45. package/index.js +21 -21
  46. package/{library.command-keqjevpp.js → library.command-9m2rdwwz.js} +3 -3
  47. package/{localRegistry.command-pydeq5zf.js → localRegistry.command-vacc2njh.js} +8 -8
  48. package/{module.command-dkrnqx3v.js → module.command-q63dffya.js} +5 -5
  49. package/{package.command-wmc0ys5y.js → package.command-y2jh0m99.js} +3 -3
  50. package/package.json +2 -2
  51. package/{page.command-tf3erhrv.js → page.command-f3e4cwhh.js} +3 -3
  52. package/{primitive.command-7hqpegj4.js → primitive.command-r4kq75qj.js} +6 -6
  53. package/{quality.command-8122axq3.js → quality.command-f7gzh26z.js} +9 -2
  54. package/{repair.command-mcy12h0n.js → repair.command-0qmyr6qa.js} +5 -5
  55. package/{scalar.command-vvvbzttc.js → scalar.command-kddn7av4.js} +4 -4
  56. package/{subspace.command-pzvqfy5h.js → subspace.command-j7ayp382.js} +5 -7
  57. package/templates/app/ui/Recipe/jellyButton.ts +1 -0
  58. package/templates/facetIndex/index.ts +21 -6
  59. package/templates/lib/dict.ts +9 -1
  60. package/templates/lib/srv.ts +2 -3
  61. package/templates/libRoot/common/index.ts +2 -7
  62. package/templates/libRoot/srvkit/index.ts +2 -7
  63. package/templates/libRoot/ui/index.ts +2 -7
  64. package/templates/libRoot/webkit/index.ts +2 -7
  65. package/templates/moduleRoot/index.tsx +3 -3
  66. package/{tunnel.command-82zz5xnq.js → tunnel.command-b3ymdk4f.js} +2 -2
  67. package/{workflow.command-y8x3kgjv.js → workflow.command-z1srcdfw.js} +10 -10
  68. package/{workspace.command-jvsb7brc.js → workspace.command-17c46dbp.js} +20 -20
  69. package/cloud.script-c723cpcb.js +0 -20
  70. package/context.runner-erfe68j5.js +0 -21
  71. package/index-bjwxs904.js +0 -77
  72. package/templates/libRoot/common/commonLogic.ts +0 -14
  73. package/templates/libRoot/srvkit/backendLogic.ts +0 -13
  74. package/templates/libRoot/webkit/frontendLogic.ts +0 -13
@@ -8,7 +8,8 @@ mutation is mounted under. `conventions` carries the invariants — this is the
8
8
 
9
9
  - Guards run on both HTTP and websocket calls. Read the caller with `context.get<T>("account")` (`pkgs/akanjs/signal/signalContext.ts`) instead of branching on `getHttpContext()` / `getWebSocketContext()`.
10
10
  - Slice-level `guards` only reach the generated query/mutation endpoints. A `pubsub`/`message` endpoint is unguarded unless it declares its own `guards` in its signal option.
11
- - A pubsub room is authorized once, at subscribe. When a socket's credential changes the framework re-runs each room's guards and unsubscribes the ones that now fail (`SignalResolver.revalidateWsRooms`), so guards must stay side-effect free and safe to re-run.
11
+ - A pubsub room is authorized once, at subscribe. When a socket's credential changes the framework re-runs each room's guards and unsubscribes the ones that now fail (`SignalResolver.revalidateWsRooms`), so guards must stay side-effect free and safe to re-run. Each re-check runs on a fresh `SignalContext`, so a guard that memoizes per context answers anew.
12
+ - **Arguments are parsed after the `"account"` guards** (`SignalContext.init` reads the body, `exec` parses): the order is middlewares → `account` guards → parse → `resource` guards → internal arguments → handler, whatever order `guards: [...]` lists them in. A caller an account guard refuses never reaches a scalar parser, which is what keeps a slow parser from being a pre-auth denial of service. A guard or middleware that reads `context.args` / `getArg()` earlier still gets the value, parsed on first read — a mismarked account guard keeps working. One visible consequence: an unauthenticated call with a malformed argument answers 401/403 instead of 400.
12
13
  - A websocket carries its credential in the handshake snapshot on `ws.data` (`AppWsData`); clients that hold the token in memory send it with `fetch.setJwt(...)`, which forwards an auth frame over the socket.
13
14
  - **Never read the caller's IP off the socket or the request peer — take `.with(Ip)`.** Whenever a federation
14
15
  gateway is in front, `ws.remoteAddress` and the child's own peer are the *gateway* (`127.0.0.1`) for every
@@ -74,11 +75,46 @@ mutation is mounted under. `conventions` carries the invariants — this is the
74
75
  never cached, and a relation still costs its own load. The cache saves the handler, not the serialization.
75
76
  - **A cache backend that is down does not take the endpoint down.** A failed read is warned and the call runs
76
77
  uncached; a failed write is warned and dropped.
77
- - The default middleware chain is `Logging → Timeout → <lib middlewares>`, and the call inside it runs guards →
78
- internal arguments → cache → handler: a hung cache backend is bounded by the same deadline as the handler it
79
- stands in for. **`Retry` was removed** — an automatic re-run of an endpoint
78
+ - The default middleware chain is `Logging → Timeout → <lib middlewares>`, and the call inside it runs an
79
+ `account` rate limit → account guards → argument parse → resource guards → internal arguments → cache →
80
+ handler: a hung cache backend is bounded by the same deadline as the handler it stands in for. **`Retry` was removed** — an automatic re-run of an endpoint
80
81
  whose failure it cannot classify replays whatever the first attempt already did.
81
82
 
83
+ ## Rate Limits
84
+
85
+ - **`{ rateLimit: { calls, windowMs?, by? } }` in an endpoint's signal option** counts calls per caller per window
86
+ (`windowMs` 60 000 unless named). `by: "ip"` (the default) is checked in `SignalContext.init`, after the
87
+ cross-site check and before the body is read, so a refused flood costs no parse at all. `by: "account"` is
88
+ checked after the middlewares and before the guards, keyed by `accountKey(account)`; a caller it names nothing
89
+ for — an anonymous one — is counted by address. Leaving a pubsub room is never counted.
90
+ - **The answer is 429 `base.error.tooManyRequests` with `data: { seconds }`** and a `Retry-After` header; a WS
91
+ call gets the same payload as its error frame, and the HTTP client restores a proxy's own non-JSON 429 under the
92
+ same key (seconds from its `Retry-After`, else 60). The first refusal per endpoint per process is warned once;
93
+ later ones log at `debug`, so a flood does not become a log flood.
94
+ - **Keys and addresses.** The address is `context.getClientIp()` — the trusted-proxy answer, never a raw header
95
+ from an untrusted peer. Callers whose address is unknown share one `unknown` budget (fail closed), and the first
96
+ such call is warned about, since it usually means the proxy in front records no `X-Real-IP`/`X-Forwarded-For`
97
+ or is not trusted (`AKAN_TRUSTED_PROXIES`). A page's server-side fetches carry the visitor's address: the page
98
+ renderer hands the RSC worker the address it resolved as `x-real-ip`, and the server-side fetch client forwards
99
+ it on same-origin calls.
100
+ - **Counters live in the cache** (`CacheAdaptor.incr`, epoch-aligned windows), so replicas draw on one budget; a
101
+ cache that fails falls back to counting per process, warned once. The MCP endpoint's own limiter is the same
102
+ `RateLimiter` under its own topic, and keys an anonymous MCP caller by the same trusted address.
103
+ - **App and lib configuration** is `option.setRateLimit(...)` in `lib/option.ts`, merged in mount order with the
104
+ app last: `budget` is the default for every endpoint that declares none (unset leaves those unlimited),
105
+ `endpoints` overrides one by key — a lib's declared budget included — or exempts it with `false`, `accountKey`
106
+ names who `by: "account"` counts (default: `account.id ?? sub ?? sid`; a lib whose accounts carry `self`/`me`
107
+ should name `self.id ?? me.id`, or each session counts apart), and `enabled: false` (or `setRateLimit(false)`)
108
+ turns budgets off for a deployment rate-limited at its edge. `rateLimit: false` on an endpoint exempts it from
109
+ the app's `budget` only.
110
+ - **`AKAN_RATE_LIMIT`**: `off` turns every budget off whatever the code says; `on` turns them on in a `local`
111
+ environment, where they are otherwise off; `<calls>[/<seconds>]` is the default budget wherever the code names
112
+ none. Tests run in the `testing` environment, where budgets apply.
113
+ - **A business key is a service's own count**: `rateLimit: plug(RateLimit)` then
114
+ ``await this.rateLimit.consume(`otp:${phone}`, { calls: 5, windowMs: 3_600_000 })``, which answers
115
+ `{ ok: true } | { ok: false, retryAfterMs }` and is never switched off — throw the module's own `Err` with the
116
+ wait when it refuses.
117
+
82
118
  ## Binary Pubsub
83
119
 
84
120
  - **`pubsub(Binary)` sends its payload in a websocket binary frame**, skipping the JSON `{ type: "pub" }`
@@ -124,7 +124,7 @@ starts it itself. The full contract — targets, updates, what the carried serve
124
124
  ```bash
125
125
  akan start-desktop <%= appName %> # Run the desktop app on this computer
126
126
  akan start-ios <%= appName %> # Run it in a simulator or on an iPhone (start-android alike)
127
- akan build-desktop <%= appName %> # Build it for this computer into .akan/native/<target>/build
127
+ akan build-desktop <%= appName %> # Build it for this computer into dist/native/<%= appName %>/<target>/build
128
128
  akan build-desktop <%= appName %> --installer true # Windows: add a per-user setup program
129
129
  akan update-keygen <%= appName %> # Make the update signing key once; print its public key
130
130
  akan publish-update <%= appName %> # Build and sign a desktop release to upload to updates.url
@@ -8,7 +8,7 @@ import {
8
8
  CodegenLock,
9
9
  WorkspaceExecutor,
10
10
  AppExecutor
11
- } from "./index-150s07n8.js";
11
+ } from "./index-50wb2a26.js";
12
12
  import"./index-52w0fvh8.js";
13
13
  import {
14
14
  GraphClientEntryDiscovery,
@@ -20,7 +20,7 @@ import {
20
20
  DevChangePlanner,
21
21
  DevGeneratedIndexSync,
22
22
  SsrDevBundler
23
- } from "./index-m3xsb1ms.js";
23
+ } from "./index-krjcg4y6.js";
24
24
  import {
25
25
  prepareDevWatchBatch
26
26
  } from "./index-a5vmm6x4.js";
@@ -2,14 +2,12 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-a37by9hn.js";
6
- import {
7
- LibExecutor
8
- } from "./index-150s07n8.js";
5
+ } from "./index-q6ft9fkc.js";
9
6
  import {
10
7
  LibSource,
11
- formatLibStatuses
12
- } from "./index-bjwxs904.js";
8
+ formatLibStatuses,
9
+ LibExecutor
10
+ } from "./index-50wb2a26.js";
13
11
 
14
12
  // pkgs/@akanjs/cli/library/library.script.ts
15
13
  import { Logger } from "akanjs/common";
@@ -86,28 +84,32 @@ class LibraryRunner extends runner("library") {
86
84
  return await Promise.all(libNames.map((libName) => new LibSource(LibExecutor.from(workspace, libName)).status()));
87
85
  }
88
86
  async mergeLibraryDependencies(lib) {
89
- const libPackageJson = await lib.getPackageJson();
90
- const rootPackageJson = await lib.workspace.getPackageJson();
91
- const dependencies = {};
92
- const devDependencies = {};
93
- const libDependencies = { ...libPackageJson.dependencies, ...libPackageJson.devDependencies };
94
- const rootDependencies = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
95
- const allDependencies = Object.fromEntries(Object.keys({ ...libDependencies, ...rootDependencies }).map((dep) => {
96
- const libVersion = libDependencies[dep] ?? "0.0.0";
97
- const rootVersion = rootDependencies[dep] ?? "0.0.0";
98
- const newerVersion = compareSemver(rootVersion, libVersion) > 0 ? rootVersion : libVersion;
99
- return [dep, newerVersion];
100
- }));
101
- Object.keys(allDependencies).sort().forEach((dep) => {
102
- if (libPackageJson.dependencies?.[dep] || rootPackageJson.dependencies?.[dep])
103
- dependencies[dep] = allDependencies[dep];
104
- else
105
- devDependencies[dep] = allDependencies[dep];
87
+ const [libPackageJson, rootPackageJson] = await Promise.all([lib.getPackageJson(), lib.workspace.getPackageJson()]);
88
+ const rootVersionOf = (dep) => rootPackageJson.dependencies?.[dep] ?? rootPackageJson.devDependencies?.[dep];
89
+ const missingFrom = (deps = {}) => Object.entries(deps).filter(([dep]) => !rootVersionOf(dep));
90
+ const dependencies = missingFrom(libPackageJson.dependencies);
91
+ const devDependencies = missingFrom(libPackageJson.devDependencies).filter(([dep]) => !libPackageJson.dependencies?.[dep]);
92
+ const behind = Object.entries({ ...libPackageJson.devDependencies, ...libPackageJson.dependencies }).flatMap(([dep, version]) => {
93
+ const rootVersion = rootVersionOf(dep);
94
+ return rootVersion && compareSemver(version, rootVersion) > 0 ? [`${dep} ${rootVersion} (lib: ${version})`] : [];
106
95
  });
107
- const newRootPackageJson = { ...rootPackageJson, dependencies, devDependencies };
108
- await lib.workspace.setPackageJson(newRootPackageJson);
109
- await lib.workspace.spawn("bun", ["install"]);
110
- await lib.workspace.commit(`Merge ${lib.name} library dependencies`);
96
+ if (behind.length)
97
+ lib.logger.warn(`Kept the root versions of packages libs/${lib.name} lists newer: ${behind.join(", ")}`);
98
+ if (dependencies.length || devDependencies.length) {
99
+ await lib.workspace.setPackageJson({
100
+ ...rootPackageJson,
101
+ dependencies: this.#withAdded(rootPackageJson.dependencies, dependencies),
102
+ devDependencies: this.#withAdded(rootPackageJson.devDependencies, devDependencies)
103
+ });
104
+ await lib.workspace.spawn("bun", ["install"]);
105
+ }
106
+ if (await lib.workspace.hasChanges())
107
+ await lib.workspace.commit(`Merge ${lib.name} library dependencies`);
108
+ }
109
+ #withAdded(deps, added) {
110
+ if (!added.length)
111
+ return deps;
112
+ return Object.fromEntries([...Object.entries(deps ?? {}), ...added].sort(([a], [b]) => a < b ? -1 : 1));
111
113
  }
112
114
  }
113
115
 
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-a37by9hn.js";
5
+ } from "./index-q6ft9fkc.js";
6
6
  import {
7
7
  AGENT_BLOCK_START,
8
8
  AGENT_BLOCK_END,
@@ -13,14 +13,14 @@ import {
13
13
  CLAUDE_COMMENT_RULE,
14
14
  AppExecutor,
15
15
  LibExecutor
16
- } from "./index-150s07n8.js";
16
+ } from "./index-50wb2a26.js";
17
17
  import {
18
18
  collectRecipeSources2,
19
19
  scanRecipes2
20
20
  } from "./index-hj6w4ka1.js";
21
21
  import {
22
22
  AkanContextAnalyzer
23
- } from "./index-8kvy8qb4.js";
23
+ } from "./index-zyw622y1.js";
24
24
  import {
25
25
  Prompter2
26
26
  } from "./index-zzvvc845.js";
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-a37by9hn.js";
5
+ } from "./index-q6ft9fkc.js";
6
6
  import {
7
7
  FileSys2
8
8
  } from "./index-52w0fvh8.js";
9
9
  import {
10
10
  PkgExecutor
11
- } from "./index-150s07n8.js";
11
+ } from "./index-50wb2a26.js";
12
12
  import {
13
13
  TypeScriptDependencyScanner2
14
14
  } from "./index-2ahj896a.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AppInfo
4
- } from "./index-150s07n8.js";
4
+ } from "./index-50wb2a26.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/slicePlanner.ts
7
7
  class SlicePlanner {