@akanjs/cli 3.0.0-alpha.62 → 3.0.0-alpha.64
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
|
-
|
|
1
|
+
91051910ed4a6b677266a1e02acdb28b3dd899f6d24b642112cf0de3dcb7c1f2
|
|
@@ -118,7 +118,7 @@ back.
|
|
|
118
118
|
error; it falls back to discovery and aborts on whatever nested config the walk finds. Rename the file to
|
|
119
119
|
`biome.jsonc` to document a disabled rule; `akan lint` pins the config path either way, so it reports the parse
|
|
120
120
|
error on the offending line.
|
|
121
|
-
- **`akan lint` prints up to 200 diagnostics** (`--
|
|
121
|
+
- **`akan lint` prints up to 200 diagnostics** (`--max-diagnostics <n>`, `0` for no limit). Biome's own default is 20
|
|
122
122
|
with no count, which reads as progress when the mix of findings merely changed.
|
|
123
123
|
|
|
124
124
|
## Coding Style (`**/*.{ts,tsx}`)
|
|
@@ -369,6 +369,15 @@ workflow changes.
|
|
|
369
369
|
- 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.
|
|
370
370
|
- 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.
|
|
371
371
|
- 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.
|
|
372
|
+
- **Never read the caller's IP off the socket or the request peer — take `.with(Ip)`.** Every app runs behind
|
|
373
|
+
the federation gateway, so `ws.remoteAddress` and the child's own peer are the *gateway* (`127.0.0.1`) for
|
|
374
|
+
every caller and for the whole life of every socket. `Ip` reads what the gateway recorded
|
|
375
|
+
(`x-real-ip`, then the first `x-forwarded-for` entry) and falls back to the peer only when nothing proxied
|
|
376
|
+
the call; `context.getClientIp()` is the same answer inside a guard or middleware. It arrives unwrapped from
|
|
377
|
+
its `::ffff:` IPv4-mapped form, so it can address a `udp4` socket as well as identify a caller, and it is
|
|
378
|
+
`null` rather than a placeholder when genuinely unknown — a loopback-looking address for an unknown caller
|
|
379
|
+
is the failure this replaced. The gateway also forwards the client port (`x-forwarded-port`, read with
|
|
380
|
+
`context.getClientPort()`) along with host and protocol.
|
|
372
381
|
- **Every socket carries a `socketId`, and only the framework mints one.** `AppWsData` assigns it at the handshake, and a `message` / `pubsub` handler reads it off the `Ws` internal arg — `.with(Ws)` hands `{ ws, socketId, subscribe, on, off }`. Never mint your own from `ws.data`: the room bookkeeping keys on the framework's id, and a second one fails to match it silently. It identifies a **connection**, not a caller — a reconnect gets a new id and the federation gateway's own socket is a different one — so per-user state keys on the account, never on this.
|
|
373
382
|
|
|
374
383
|
### Authorization Defaults
|
|
@@ -389,6 +398,28 @@ workflow changes.
|
|
|
389
398
|
- `.body(...)` / `.param(...)` args accept `ConstantFieldTypeInput` only: scalars, model refs, or `enumOf(...)`.
|
|
390
399
|
- Numbers must use `Int` or `Float` — `Number` is rejected (`pkgs/akanjs/signal/endpointInfo.ts`).
|
|
391
400
|
- `Upload` is valid only inside a mutation flagged for file upload: `mutation([cnst.File], { fileUpload: true }).body("files", [Upload])`, as the `file` module does. It is not a model field type.
|
|
401
|
+
- Bytes are `Binary`, never `Any` — see Scalar & Field Type Reference.
|
|
402
|
+
|
|
403
|
+
### Binary Pubsub
|
|
404
|
+
|
|
405
|
+
- **`pubsub(Binary)` sends its payload in a websocket binary frame**, skipping the JSON `{ type: "pub" }`
|
|
406
|
+
envelope and the base64 a JSON wire would need. The client's subscribe callback receives a `Uint8Array`.
|
|
407
|
+
Nothing else is declared: the return type is the whole switch, and text and binary frames coexist on one
|
|
408
|
+
socket, so every JSON endpoint is untouched. The optimization applies only when the **whole** return is
|
|
409
|
+
`Binary` — `[Binary]`, or a `Binary` inside a model, falls back to base64.
|
|
410
|
+
- **A declared `Binary` room coalesces under backpressure**: while a socket cannot keep up the room keeps only
|
|
411
|
+
its newest frame and drops the rest, which is what a telemetry or video stream wants. Declare
|
|
412
|
+
`pubsub(Binary, { backpressure: "queue" })` when the frames are a sequence a subscriber must see in full, such
|
|
413
|
+
as deltas against a base it already holds; the send buffer then grows with the slowest subscriber. Coalescing
|
|
414
|
+
is keyed by the endpoint, not carried with the frame, so this process, an IPC deliver and a Redis fan-out all
|
|
415
|
+
reach the same answer. Coalesced frames are counted in `pubsubCoalesceCount` on the replica's metrics report.
|
|
416
|
+
- **A `pubsub(Any)` that happens to carry bytes is framed too, and warns once.** `Any` passes a `Buffer` through
|
|
417
|
+
untouched, so the transport sees bytes and sends them whole rather than as a number array — an undeclared
|
|
418
|
+
publisher is fixed rather than left corrupt. It is still not the contract: it queues rather than coalescing
|
|
419
|
+
(nobody declared lossy delivery for that room), and a Redis fan-out still JSON-stringifies it. Declare
|
|
420
|
+
`Binary`.
|
|
421
|
+
- The federation gateway relays a binary frame unchanged, and Bun IPC carries bytes as bytes, so the only hop
|
|
422
|
+
that needed teaching was the socket itself. A cross-server (Redis) fan-out encodes it as protobuf `bytes`.
|
|
392
423
|
|
|
393
424
|
### Mutation HTTP Verb
|
|
394
425
|
|
|
@@ -418,6 +449,13 @@ workflow changes.
|
|
|
418
449
|
ref model's store — that store is a singleton, and loading into it would overwrite whatever listing of the same
|
|
419
450
|
model is already on screen. `ref` is the only UI hint a filter arg carries; there is no render callback, because a
|
|
420
451
|
function cannot cross to the client.
|
|
452
|
+
- **A summary counter names the listing it counts, on the field itself.** `field(Int).meta(getQueryMeta("user")
|
|
453
|
+
.query("byStatuses").args([["prepare"]]))` is what makes the dashboard tile clickable: clicking it applies that
|
|
454
|
+
filter to the listing in place — one request, no navigation — and fills the arg controls under the toolbar, so
|
|
455
|
+
the filter is visible and editable rather than opaque. `args` may be a thunk (`() => [dayjs().subtract(1,
|
|
456
|
+
"hour")]`), read at the click so a relative time is current. A meta naming a different model counts a different
|
|
457
|
+
listing, so its tile stays inert here. `AdminPanel`'s `queryMap` still overrides per column, and is now only for
|
|
458
|
+
a column whose field cannot declare one.
|
|
421
459
|
- **A picker row is labelled by `labelOf`, which reads `Light<Model>.label()` first.** Then the field carrying
|
|
422
460
|
`text: "title"`, then `title` / `name`, with the id as the floor. Write the method on the Light class — that is
|
|
423
461
|
where display logic belongs, and the same label is what `Load.Units` and the in-page agent's screen scope show.
|
|
@@ -627,10 +665,18 @@ commands, transcript compaction, and the built-in tool semantics: `get_guideline
|
|
|
627
665
|
|
|
628
666
|
### Scalar & Field Type Reference
|
|
629
667
|
|
|
630
|
-
- **Import from `akanjs/base`** (real classes/helpers, not globals): `Int`, `Float`, `ID`, `Any`, `Upload`, `enumOf`, and the `dayjs` factory. There is **no `JSON` scalar** — use `Any` for open/flexible payloads.
|
|
668
|
+
- **Import from `akanjs/base`** (real classes/helpers, not globals): `Int`, `Float`, `ID`, `Any`, `Binary`, `Upload`, `enumOf`, and the `dayjs` factory. There is **no `JSON` scalar** — use `Any` for open/flexible payloads.
|
|
631
669
|
- **Use the JS globals directly (no import needed)**: `String`, `Boolean`, `Date`. They are monkey-patched to behave like scalars, so `field(String)` typechecks.
|
|
632
670
|
- **`Number` is not a valid field/body type.** `NumberConstructor` is intentionally not augmented, so `field(Number)` / `.body("x", Number)` fails to typecheck. Use `Int` or `Float` instead.
|
|
633
671
|
- Runtime resolution of every scalar (globals included) goes through `PrimitiveRegistry` by `refName` (`pkgs/akanjs/base/primitiveRegistry.ts`).
|
|
672
|
+
- **`Binary` is raw bytes on the wire, and never a model field.** It is `Uint8Array` on both sides — a Node
|
|
673
|
+
`Buffer` is one, so a server handler may pass one straight in — and it accepts base64 in either direction, so
|
|
674
|
+
the same declaration serves a JSON body and a binary frame. It is **not storable**: the class build refuses
|
|
675
|
+
`field(Binary)` naming the `File` model instead, because every non-base field lives in the `_doc` JSON column
|
|
676
|
+
and bytes would sit there as base64 and ride every read of the row. Never send bytes as `Any` — `Any` passes a
|
|
677
|
+
`Buffer` through untouched and `JSON.stringify` then spells it `{ type: "Buffer", data: number[] }`, which
|
|
678
|
+
`JSON.parse` never restores; the result is 3.6x the wire and a shape that only breaks at the first byte-offset
|
|
679
|
+
read. MCP refuses a `Binary` return for the same reason it refuses `Any`.
|
|
634
680
|
|
|
635
681
|
### Text Search Fields — the `text` role
|
|
636
682
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.64",
|
|
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.
|
|
37
|
+
"akanjs": "3.0.0-alpha.64",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"dayjs": "^1.11.20",
|