@defjs/core 0.3.2 → 0.4.0-beta.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 (6) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +122 -2
  3. package/index.d.ts +1593 -189
  4. package/index.js +5140 -846
  5. package/index.min.js +2 -1
  6. package/package.json +13 -12
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 MunMunMiao
3
+ Copyright (c) 2026 MunMunMiao
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,5 +1,125 @@
1
- ## @defjs/defjs
1
+ # @defjs/core
2
+
3
+ Type-safe HTTP, SSE, and WebSocket commands for TypeScript.
4
+
5
+ This README describes the package version that contains it. Defjs is still pre-1.0, so use the documentation and release notes published for the exact version installed in your application.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add @defjs/core
11
+ ```
12
+
13
+ The package is ESM and declares no runtime dependencies. Its tarball retains this `README.md` and the repository `LICENSE`; repository-wide guides and examples remain outside the package.
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { createClient, defineRequest, struct, withEndpoint } from '@defjs/core'
19
+
20
+ const client = createClient(withEndpoint('https://api.example.com'))
21
+
22
+ const getUser = defineRequest({
23
+ method: 'GET',
24
+ path: '/users/:id',
25
+ input: struct.request({
26
+ path: struct.object({ id: struct.number() }),
27
+ }),
28
+ output: [
29
+ { status: 200, body: struct.object({ id: struct.number(), name: struct.string() }) },
30
+ { status: 404, body: struct.object({ message: struct.string() }) },
31
+ ],
32
+ })
33
+
34
+ const [error, user] = await client.execute(getUser({ path: { id: 1 } }))
35
+
36
+ if (error) {
37
+ console.error(String(error), error.kind, error.code)
38
+ } else {
39
+ console.log(user.id, user.name)
40
+ }
41
+ ```
42
+
43
+ `defineRequest(...)` preserves inline output status literals through a const generic, so the array does not need `as const`.
44
+
45
+ ## Core Boundaries
46
+
47
+ - Commands are typed values created by `defineRequest`, `defineEventStream`, and `defineWebSocket`.
48
+ - Structs validate request input and response data at runtime. `struct.request({ path, query, headers, body })` maps each request part explicitly.
49
+ - Expected HTTP, transport, and definition failures are returned as native `RequestError` instances in an error-first tuple; custom interceptors and application callbacks can still throw. `String(error)` is directly loggable, while `kind`, `code`, and variant metadata remain enumerable for structured logs.
50
+ - Transport and definition errors use the native `Error` cause chain. Narrow `error.cause instanceof StructError` before calling Struct-only helpers such as `format()`, `flatten()`, or `prettify()`.
51
+ - Server clients that capture cookies, authorization, tenant data, or user data must be created inside the owning request scope.
52
+ - Ordinary HTTP work is request-scoped and is bounded with its execute-time timeout or `AbortSignal`; `Client` is not `AsyncDisposable` and has no global `dispose()` lifecycle.
53
+ - Returned SSE and WebSocket handles are `AsyncDisposable`, so `await using` waits for Defjs-owned teardown. SSE disposal stops Defjs reading/reconnect work and releases its reader lock, but does not wait forever for a provider-controlled `cancel()` promise.
54
+ - WebSocket `closed` is the logical terminal result. Its disposer uses a one-second bounded teardown and may reject with `TimeoutError`; it cannot prove physical TCP closure. Existing `close()` and `closed` APIs remain available for manual lifecycle control.
55
+ - A timeout or cancellation does not prove that a server did not receive a write. Preserve operation identity and use an application/server idempotency contract before replaying writes.
56
+ - OpenAPI generation, full SDK generation, query caching, and GraphQL protocol handling are not included in this release. Define contracts by hand or compose purpose-built tools at the application boundary.
57
+
58
+ For client-local tests, inject a Fetch-compatible function with `withHTTPHandle(...)`. This keeps request interception scoped to one client and still exercises command building, interceptors, status dispatch, and response validation.
59
+
60
+ ### WebSocket provider envelopes
61
+
62
+ `defineWebSocket` accepts synchronous, definition-local `normalizeIncoming` and `normalizeOutgoing` boundaries for providers that do not use Defjs's default top-level `type` envelope. Incoming normalization runs after wire JSON decoding and returns a declared dispatch tag plus the payload to validate. Outgoing normalization receives the logical tag and the Struct-encoded payload, then returns the exact pre-transport wire value.
63
+
64
+ ```typescript
65
+ const kraken = defineWebSocket({
66
+ incoming: {
67
+ 'method.subscribe': struct.object({ method: struct.literal('subscribe'), success: struct.boolean() }),
68
+ },
69
+ maxIncomingQueueSize: 16,
70
+ normalizeIncoming(decoded) {
71
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return undefined
72
+ if (Reflect.get(decoded, 'method') !== 'subscribe') return undefined
73
+ return { data: decoded, type: 'method.subscribe' }
74
+ },
75
+ normalizeOutgoing(_type, encodedPayload) {
76
+ if (typeof encodedPayload !== 'object' || encodedPayload === null || Array.isArray(encodedPayload)) {
77
+ throw new TypeError('Expected encoded command')
78
+ }
79
+ return encodedPayload as { readonly [key: string]: unknown }
80
+ },
81
+ outgoing: {
82
+ subscribe: struct.object({
83
+ method: struct.literal('subscribe'),
84
+ params: struct.object({ channel: struct.string() }),
85
+ reqId: struct.number().alias('req_id'),
86
+ }),
87
+ },
88
+ path: '/v2',
89
+ })
90
+
91
+ // session.send({ type: 'subscribe', method: 'subscribe', params: { channel: 'ticker' }, reqId: 1 })
92
+ // wire: {"method":"subscribe","params":{"channel":"ticker"},"req_id":1}
93
+ ```
94
+
95
+ Both adapters are synchronous. An outgoing adapter exception escapes `session.send()` synchronously; heartbeat serialization uses the existing fatal runtime path. Reconnect queues retain the already-normalized serialized string. If a provider payload itself has a wire `type`, declare it under a non-dispatch property such as `providerType: struct.literal('update').alias('type')`.
96
+
97
+ The adapters do not replace Struct validation, WebSocket subprotocols, reconnect policy, or session lifecycle. Without them, incoming results and outgoing bytes keep the legacy Defjs envelope behavior.
98
+
99
+ ## Runtime Notes
100
+
101
+ Repository development, testing, building, packaging, and publishing use Bun `1.4.0`. The package uses standard Web APIs such as `fetch`, `Request`, `Response`, `Headers`, streams, and `WebSocket` according to the transports enabled by the application. The packed-consumer gate typechecks and executes the published artifact with Bun `1.4.0`.
102
+
103
+ The repository-validated and supported minimum compiler/lib contract uses `target: 'ES2022'` with `lib: ['ES2022', 'ESNext.Disposable', 'DOM', 'DOM.Iterable']`. The gate is pinned to TypeScript 7; older compiler versions are not promised. This supported set is verified together rather than claiming that every entry is independently forced by one declaration, and it supplies types only — not runtime Web API polyfills.
104
+
105
+ **SSE reconnect behavior change:** without `withSSEReconnect(...)`, SSE no longer retries network/stream-read failures. Pass `withSSEReconnect({ attempts: 3 })` (or another reviewed budget) to retry those failures.
106
+
107
+ When using a checkout through `file:` or a workspace link, run `bun run build` in the checkout before compiling or running the external consumer. The checkout manifest points to generated `dist/` files; published packages already contain those files.
108
+
109
+ After compiling an application to ESM, the repository-tested command is:
110
+
111
+ ```sh
112
+ bun run dist/index.js
113
+ ```
114
+
115
+ Browser applications use their bundler and the platform Fetch stream and WebSocket capabilities required by the transports they enable.
2
116
 
3
117
  ## Documentation
4
118
 
5
- Check out the [defjs.org](https://defjs.org) to get started.
119
+ Guides live on the documentation site, not in the published tarball.
120
+
121
+ - [Getting started](https://defjs.org/guide/getting-started)
122
+ - [Client](https://defjs.org/core/client)
123
+ - [HTTP](https://defjs.org/core/http)
124
+ - [SSE](https://defjs.org/core/sse)
125
+ - [WebSocket](https://defjs.org/core/web-socket)