@weft-js/start 0.1.0 → 0.1.2

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 (213) hide show
  1. package/README.md +36 -3
  2. package/crate/Cargo.lock +1347 -0
  3. package/crate/Cargo.toml +72 -0
  4. package/crate/rust-toolchain.toml +3 -0
  5. package/crate/src/build/emit.rs +103 -0
  6. package/crate/src/build.rs +367 -0
  7. package/crate/src/ctx.rs +186 -0
  8. package/crate/src/envelope.rs +163 -0
  9. package/crate/src/functions.rs +173 -0
  10. package/crate/src/lib.rs +118 -0
  11. package/crate/src/native.rs +212 -0
  12. package/crate/src/otel.rs +62 -0
  13. package/crate/src/server.rs +203 -0
  14. package/package.json +46 -37
  15. package/src/app.mjs +51 -10
  16. package/src/asset-receipt.d.mts +13 -0
  17. package/src/asset-receipt.mjs +61 -0
  18. package/src/auth/better-auth.ts +215 -17
  19. package/src/auth/drizzle.ts +1 -1
  20. package/src/auth.ts +21 -6
  21. package/src/boot.ts +19 -8
  22. package/src/bus/postgres.ts +32 -19
  23. package/src/bus.ts +7 -4
  24. package/src/{capture.mjs → capture.ts} +34 -1
  25. package/src/changes-sql.d.mts +8 -2
  26. package/src/changes-sql.mjs +26 -10
  27. package/src/changes.ts +165 -66
  28. package/src/channel/connections.ts +216 -0
  29. package/src/channel/socket.ts +396 -0
  30. package/src/channel-fake.ts +19 -4
  31. package/src/channel.ts +124 -490
  32. package/src/client.tsx +9 -2
  33. package/src/contract/describe.mjs +231 -0
  34. package/src/contract.mjs +146 -155
  35. package/src/data/cache.ts +354 -0
  36. package/src/data/definitions.ts +94 -0
  37. package/src/data/draft.ts +104 -0
  38. package/src/data/foundation.ts +95 -0
  39. package/src/data/live-api.ts +22 -0
  40. package/src/data/mutation.ts +138 -0
  41. package/src/data/primitives.ts +135 -0
  42. package/src/data/query.ts +106 -0
  43. package/src/data/transport.ts +121 -0
  44. package/src/data/types.ts +303 -0
  45. package/src/data.ts +69 -894
  46. package/src/db/bun-sqlite.d.ts +4 -1
  47. package/src/db/changes-mysql.ts +61 -21
  48. package/src/db/changes-postgres.ts +197 -118
  49. package/src/db/changes-sqlite.ts +131 -64
  50. package/src/db/core.ts +38 -13
  51. package/src/db/index.ts +20 -4
  52. package/src/db/mysql.ts +22 -6
  53. package/src/db/postgres.ts +49 -13
  54. package/src/db/sqlite.ts +53 -27
  55. package/src/derive.d.mts +4 -1
  56. package/src/derive.mjs +136 -39
  57. package/src/dev-runtime.mjs +8 -4
  58. package/src/exec/errors.ts +57 -0
  59. package/src/exec/landing.ts +66 -0
  60. package/src/exec/scope.ts +174 -0
  61. package/src/exec/touch.ts +80 -0
  62. package/src/exec/validation.ts +176 -0
  63. package/src/exec.ts +191 -341
  64. package/src/extension.ts +8 -2
  65. package/src/first-paint.ts +26 -9
  66. package/src/form/value.ts +15 -0
  67. package/src/form.tsx +132 -43
  68. package/src/hub/channels.ts +102 -0
  69. package/src/hub/connections.ts +147 -0
  70. package/src/hub/delivery.ts +62 -0
  71. package/src/hub/feed.ts +168 -0
  72. package/src/hub/presence.ts +64 -0
  73. package/src/hub/route.ts +158 -0
  74. package/src/hub/subscriptions.ts +212 -0
  75. package/src/hub/topic.ts +98 -0
  76. package/src/hub/tracked.ts +12 -0
  77. package/src/hub/types.ts +190 -0
  78. package/src/hub/view.ts +147 -0
  79. package/src/hub.ts +132 -758
  80. package/src/index.ts +84 -10
  81. package/src/inline.mjs +356 -0
  82. package/src/inspect/compare.mjs +162 -0
  83. package/src/inspect/routes.mjs +154 -0
  84. package/src/inspect/samples.mjs +59 -0
  85. package/src/inspect/url.mjs +2 -0
  86. package/src/inspect.mjs +146 -187
  87. package/src/link/election.ts +178 -0
  88. package/src/link/lines.ts +27 -0
  89. package/src/link.ts +97 -168
  90. package/src/list-drizzle.ts +104 -18
  91. package/src/list.ts +127 -36
  92. package/src/listen.ts +32 -6
  93. package/src/live/api.ts +18 -0
  94. package/src/live/backoff.ts +6 -0
  95. package/src/live/frames.ts +42 -0
  96. package/src/live/read.ts +77 -0
  97. package/src/live/sub-types.ts +24 -0
  98. package/src/live/transport.ts +86 -0
  99. package/src/live/types.ts +52 -0
  100. package/src/live-fake.ts +20 -5
  101. package/src/live-target.mjs +113 -35
  102. package/src/live.ts +116 -225
  103. package/src/media.ts +8 -1
  104. package/src/migrations.mjs +14 -6
  105. package/src/mux.ts +161 -64
  106. package/src/og/DefaultCard.tsx +40 -9
  107. package/src/og/fonts.mjs +16 -8
  108. package/src/og/images.mjs +43 -22
  109. package/src/og/index.mjs +119 -43
  110. package/src/og/native.mjs +11 -4
  111. package/src/range.mjs +15 -3
  112. package/src/router.ts +56 -6
  113. package/src/routes.mjs +73 -22
  114. package/src/run/admission.ts +80 -0
  115. package/src/run/boot.tsx +115 -0
  116. package/src/run/cache-presentation.ts +67 -0
  117. package/src/run/cache.ts +254 -0
  118. package/src/run/helpers.ts +197 -0
  119. package/src/run/listeners.ts +155 -0
  120. package/src/run/navigation.ts +382 -0
  121. package/src/run/page-skeleton.tsx +35 -0
  122. package/src/run/prefetch.ts +128 -0
  123. package/src/run/retain-view.ts +18 -0
  124. package/src/run/shape-preparation.ts +97 -0
  125. package/src/run/shape-styles.ts +72 -0
  126. package/src/run/source-context/pin.ts +124 -0
  127. package/src/run/source-context.ts +311 -0
  128. package/src/run.tsx +50 -174
  129. package/src/rust/common.ts +163 -0
  130. package/src/rust/native.ts +282 -0
  131. package/src/rust.ts +103 -229
  132. package/src/server/card.tsx +65 -0
  133. package/src/server/compression.ts +115 -0
  134. package/src/server/core.ts +70 -0
  135. package/src/server/dispatch.ts +70 -0
  136. package/src/server/form.ts +85 -0
  137. package/src/server/functions.ts +129 -0
  138. package/src/server/handler.ts +39 -0
  139. package/src/server/head.ts +96 -0
  140. package/src/server/media.ts +60 -0
  141. package/src/server/navigation-cache.ts +80 -0
  142. package/src/server/navigation.tsx +85 -0
  143. package/src/server/page-cold.ts +95 -0
  144. package/src/server/page-stream.tsx +181 -0
  145. package/src/server/page-types.ts +51 -0
  146. package/src/server/page-warm.ts +130 -0
  147. package/src/server/page.ts +69 -0
  148. package/src/server/payload.ts +89 -0
  149. package/src/server/prerender.ts +60 -0
  150. package/src/server/preview.tsx +67 -0
  151. package/src/server/render.ts +167 -0
  152. package/src/server/request.ts +98 -0
  153. package/src/server/resolver.ts +228 -0
  154. package/src/server/routes.ts +150 -0
  155. package/src/server/services.ts +69 -0
  156. package/src/server/text.ts +29 -0
  157. package/src/server/types.ts +160 -0
  158. package/src/server.tsx +82 -964
  159. package/src/shape.mjs +30 -6
  160. package/src/share/display.ts +50 -0
  161. package/src/share/network.ts +119 -0
  162. package/src/share/receive.ts +70 -0
  163. package/src/share/state.ts +30 -0
  164. package/src/share/tab-queue.ts +28 -0
  165. package/src/share-server.ts +128 -43
  166. package/src/share.ts +103 -209
  167. package/src/sheets.mjs +30 -18
  168. package/src/sidecar/build.mjs +170 -0
  169. package/src/sidecar/cargo.mjs +164 -0
  170. package/src/sidecar/common.mjs +17 -0
  171. package/src/sidecar/dev-build.mjs +91 -0
  172. package/src/sidecar/dev-watch.mjs +88 -0
  173. package/src/sidecar/manifest.mjs +19 -0
  174. package/src/sidecar/process.mjs +58 -0
  175. package/src/sidecar/refresh.mjs +19 -0
  176. package/src/sidecar.mjs +75 -416
  177. package/src/socket/check.ts +50 -0
  178. package/src/socket/deliver.ts +16 -0
  179. package/src/socket/publish.ts +56 -0
  180. package/src/socket/rooms.ts +41 -0
  181. package/src/socket/route.ts +79 -0
  182. package/src/socket/shared.ts +26 -0
  183. package/src/socket/types.ts +48 -0
  184. package/src/socket.ts +205 -194
  185. package/src/stream.ts +10 -1
  186. package/src/verify/app.ts +39 -0
  187. package/src/verify/cleanup.ts +27 -0
  188. package/src/verify/database.ts +11 -0
  189. package/src/verify/live.ts +63 -0
  190. package/src/verify/notify.ts +35 -0
  191. package/src/verify/skip.ts +19 -0
  192. package/src/verify/support.ts +149 -0
  193. package/src/verify/timing.ts +15 -0
  194. package/src/verify/trigger.ts +14 -0
  195. package/src/verify.ts +77 -176
  196. package/src/view.tsx +96 -11
  197. package/src/vite/asset-bytes.mjs +95 -0
  198. package/src/vite/assets.mjs +117 -0
  199. package/src/vite/config-hooks.mjs +166 -0
  200. package/src/vite/dev-hooks.mjs +103 -0
  201. package/src/vite/dev.mjs +102 -0
  202. package/src/vite/entries.mjs +145 -0
  203. package/src/vite/module-hooks.mjs +288 -0
  204. package/src/vite/route-modules.mjs +289 -0
  205. package/src/vite/server-functions.mjs +222 -0
  206. package/src/vite/shape-entry-css.mjs +187 -0
  207. package/src/vite/shape-override.mjs +226 -0
  208. package/src/vite/shared.mjs +51 -0
  209. package/src/vite/sheets.mjs +92 -0
  210. package/src/vite/state.mjs +89 -0
  211. package/src/vite/syntax.mjs +139 -0
  212. package/src/vite.mjs +152 -853
  213. package/src/capture.d.mts +0 -1
@@ -0,0 +1,303 @@
1
+ import type { Derived, Resource } from '@weft-js/core'
2
+ import type { Context, FnOptions, Skip } from '../data'
3
+ import type { ServerExtension } from '../extension'
4
+
5
+ /** Here and not in verify.ts: that file needs `postgres`, and an app without it still reads this type. */
6
+ export interface VerifyOptions {
7
+ /** `true` or a target's name, as `verify: true` and `verify: 'staging'` mean, for when the rest of this is needed too. */
8
+ target?: true | string
9
+ /** A running staging app. Fine in code. */
10
+ url?: string
11
+ /** A database, from the environment: `process.env.STAGING_DATABASE_URL`. */
12
+ db?: string
13
+ /** The query's input. */
14
+ input?: unknown
15
+ /** Columns of the test row: whose it is, so the signed-in test user can see it. The rest are made up. */
16
+ row?: Record<string, unknown>
17
+ /** Name of the env var with a cookie for a user who can see the row. Default `WEFT_STAGING_COOKIE`. */
18
+ cookie?: string
19
+ /** Name of the env var with a cookie for a user who can't. Default `WEFT_STAGING_OTHER_COOKIE`; without it, a signed-out client. */
20
+ otherCookie?: string
21
+ /** Ms each link may take. Default 5000. */
22
+ timeout?: number
23
+ }
24
+
25
+ /** The Standard Schema interface (standardschema.dev): Zod, Valibot, ArkType and Drizzle's derived schemas. */
26
+ export interface StandardSchemaV1<I = unknown, O = I> {
27
+ readonly '~standard': {
28
+ readonly version: 1
29
+ readonly vendor: string
30
+ readonly validate: (value: unknown) => SchemaResult<O> | Promise<SchemaResult<O>>
31
+ readonly types?: { readonly input: I; readonly output: O } | undefined
32
+ }
33
+ }
34
+ /** What a Standard Schema validator answers. */
35
+ export type SchemaResult<O> =
36
+ | { readonly value: O; readonly issues?: undefined }
37
+ | { readonly issues: readonly SchemaIssue[] }
38
+ /** One failure from a schema, with the path to the field when there is one. */
39
+ export interface SchemaIssue {
40
+ readonly message: string
41
+ readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[] | undefined
42
+ }
43
+
44
+ /** `'minutes'` etc. or seconds: how long the shared server cache keeps a value (research 27 §4). */
45
+ export type Life = 'seconds' | 'minutes' | 'hours' | 'days' | 'max' | number
46
+
47
+ /** `auth: 'user'` narrows `ctx.user` to non-null in `run` (A002 answers a call with no session). */
48
+ export type AuthScope = 'user'
49
+ type Ctx<A> = A extends 'user'
50
+ ? Context extends { user: infer U }
51
+ ? Omit<Context, 'user'> & { user: NonNullable<U> }
52
+ : Context
53
+ : Context
54
+
55
+ export type In<S> = S extends StandardSchemaV1<infer I, unknown> ? I : void
56
+ export type Out<S> = S extends StandardSchemaV1<unknown, infer O> ? O : undefined
57
+ type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T
58
+ type Tags<A extends unknown[]> = readonly string[] | ((...args: A) => readonly string[])
59
+ /** Read before `run`, for one user's tag: `tags: (_, ctx) => [`entitlements:${ctx.user.id}`]`. */
60
+ type QueryTags<I, A> = Tags<[input: I, ctx: Ctx<A>]>
61
+
62
+ /** What an `Impl` is called with. */
63
+ export interface ImplCall {
64
+ fn: Fn
65
+ input: unknown
66
+ ctx: Context
67
+ request: Request
68
+ }
69
+
70
+ /** A server function's body in Rust: `remote('rust://search')` or `native('media')`, from `@weft-js/start/rust` (research 36, 37). */
71
+ export interface Impl {
72
+ readonly kind: string
73
+ readonly target: string
74
+ readonly service: string
75
+ call(c: ImplCall): Promise<{ value: unknown; tags: string[]; touched: string[] }>
76
+ }
77
+
78
+ /** Errors a function raises by name, with the data each carries: a Rust function's become an enum. */
79
+ export type Errors = Record<string, StandardSchemaV1>
80
+
81
+ /** `query()` with a `run` on this server. */
82
+ export interface QueryDef<S, O, A = undefined> extends FnOptions {
83
+ input?: S
84
+ /** The result's shape: checked in dev and tests (C002), and the contract's `output`. */
85
+ output?: StandardSchemaV1
86
+ /** `'user'`: refuse a call with no session (A002). The contract's auth scope. */
87
+ auth?: A
88
+ /** An example result, for what `output` can't say: sizes a pending view (research 31 §2). */
89
+ shape?: O extends (infer R)[] ? DeepPartial<R>[] : DeepPartial<O>
90
+ /** What invalidates it: the client cache, the request cache and the shared cache all go by these. */
91
+ tags?: QueryTags<Out<S>, A>
92
+ /** `required: true`: `weft check` and `weft build` fail on a target with no real change feed, and the server won't start without one (R013, R015). */
93
+ live?: {
94
+ required?: boolean
95
+ /** A real round trip against a target that isn't production, in `weft check` and `weft build`. `true` finds `WEFT_STAGING_URL` or `STAGING_DATABASE_URL`; a string names a target in `live.targets`. */
96
+ verify?: true | string | VerifyOptions
97
+ }
98
+ /** Turn on the shared server cache (across requests). Omit it and the query runs once per request. */
99
+ cache?: {
100
+ life?: Life
101
+ /** One cached value per signed-in user: `ctx.user` is readable. */ scope?: 'user'
102
+ }
103
+ /** Named failures, for the client as `FnError.code` and `.data`. */
104
+ errors?: Errors
105
+ run: (args: { input: Out<S>; ctx: Ctx<A> }) => O | Promise<O>
106
+ impl?: undefined
107
+ /** Set by the `weft:start` plugin: `<export>.<file hash>`. */
108
+ $id?: string
109
+ }
110
+
111
+ /** The same declaration with its body elsewhere: `output` is required, because it's the whole contract for what comes back. */
112
+ export type ImplQueryDef<S, OS extends StandardSchemaV1, A = undefined> = Omit<
113
+ QueryDef<S, Out<OS>, A>,
114
+ 'run' | 'impl' | 'output'
115
+ > & { output: OS; impl: Impl; run?: undefined }
116
+ /** `mutation()` with `impl` and an `output` schema: no `run` here. */
117
+ export type ImplMutationDef<S, OS extends StandardSchemaV1, A = undefined> = Omit<
118
+ MutationDef<S, Out<OS>, A>,
119
+ 'run' | 'impl' | 'output'
120
+ > & { output: OS; impl: Impl; run?: undefined }
121
+
122
+ /** `mutation()` with a `run` on this server. */
123
+ export interface MutationDef<S, O, A = undefined> extends FnOptions {
124
+ input?: S
125
+ output?: StandardSchemaV1
126
+ auth?: A
127
+ /** Tags whose queries the response refreshes (single-flight) and whose cached values it drops. */
128
+ invalidates?: Tags<[input: Out<S>, result: Awaited<O>]>
129
+ /** Client only: predict the result. Each call's layers are re-applied on new values until it settles. */
130
+ optimistic?: (args: { input: In<S> }, cache: OptimisticCache) => void
131
+ errors?: Errors
132
+ run: (args: { input: Out<S>; ctx: Ctx<A> }) => O | Promise<O>
133
+ impl?: undefined
134
+ $id?: string
135
+ }
136
+
137
+ type Arg<I> = I | (() => I | Skip)
138
+
139
+ /** What `query()` returns: call it for a resource, or `.live()` for one that stays current. */
140
+ export interface Query<I, O> {
141
+ /** The live value, read in a component or a defineState setup (Q001 elsewhere: use `fetch`). */
142
+ (
143
+ ...args: [I] extends [void] ? [input?: undefined | (() => undefined | Skip)] : [input: Arg<I>]
144
+ ): Resource<O>
145
+ /** The value as a promise: in `load`, handlers, server code and tests. Through the request or client cache. */
146
+ fetch(...args: [I] extends [void] ? [] : [input: I]): Promise<O>
147
+ /** A placeholder result to size a pending view by (last seen, else declared), or undefined when nothing is known. */
148
+ shape(...args: [I] extends [void] ? [] : [input: I]): O | undefined
149
+ /**
150
+ * The same value, refetched whenever a mutation anywhere invalidates one of its tags. `.live` is the connection's
151
+ * status. `keep`: how long the subscription outlives its last reader (`'30s'`, `'session'`).
152
+ */
153
+ live(
154
+ ...args: [I] extends [void]
155
+ ? [input?: undefined | (() => undefined | Skip), options?: LiveOptions]
156
+ : [input: Arg<I>, options?: LiveOptions]
157
+ ): LiveQuery<O>
158
+ readonly kind: 'query'
159
+ readonly $id: string
160
+ }
161
+
162
+ /** What `mutation()` returns: call it, or read its `pending` and `error`. */
163
+ export interface Mutation<I, O> {
164
+ (...args: [I] extends [void] ? [] : [input: I]): Promise<O>
165
+ /** A call is in flight. */
166
+ readonly pending: Derived<boolean>
167
+ /** The last call's failure, until the next call succeeds. */
168
+ readonly error: Derived<unknown>
169
+ readonly kind: 'mutation'
170
+ readonly $id: string
171
+ }
172
+
173
+ /** `degraded`: connected, and writes from outside the app arrive late or not at all. `.liveMode` says which. */
174
+ export type LiveStatus = 'connecting' | 'live' | 'degraded' | 'reconnecting' | 'offline'
175
+ /**
176
+ * How writes reach this page. `app`: mutations only, no `changes` configured. `feed`: the database reports every change.
177
+ * `external`: a separate listener does, through the bus. `poll`: an estimate, every few seconds. `off`: mutations only, though `changes` asks for more.
178
+ */
179
+ export type LiveMode = 'app' | 'feed' | 'external' | 'poll' | 'off'
180
+ /** `.live()`: how long it's kept, and whether it refuses to run without a real feed. */
181
+ export interface LiveOptions {
182
+ keep?: string | number
183
+ /** Fail `weft check`, `weft build` and server startup rather than run without a real change feed (R013, R015). */
184
+ required?: boolean
185
+ }
186
+ /** A resource that also says how live it is. */
187
+ export type LiveQuery<O> = Resource<O> & {
188
+ readonly live: Derived<LiveStatus>
189
+ readonly liveMode: Derived<LiveMode>
190
+ }
191
+
192
+ /** Any definition, as the executor and transports see it. */
193
+ export interface Fn {
194
+ kind: 'query' | 'mutation' | 'stream' | 'channel' | 'shared'
195
+ $id: string
196
+ def: {
197
+ input?: StandardSchemaV1
198
+ output?: StandardSchemaV1
199
+ message?: StandardSchemaV1
200
+ shape?: unknown
201
+ auth?: AuthScope
202
+ run?: (args: {
203
+ input: unknown
204
+ ctx: Context
205
+ signal?: AbortSignal
206
+ lastEventId?: unknown
207
+ }) => unknown
208
+ impl?: Impl
209
+ errors?: Errors
210
+ tags?: Tags<[input: unknown, ctx?: Context]>
211
+ cache?: { life?: Life; scope?: 'user' }
212
+ invalidates?: Tags<[unknown, unknown]>
213
+ optimistic?: (args: { input: unknown }, cache: OptimisticCache) => void
214
+ /** `channel` and `sharedState` (research 32). */
215
+ params?: StandardSchemaV1
216
+ authorize?: unknown
217
+ left?: (args: { id: string; ctx: Context; params: unknown; room: unknown }) => unknown
218
+ recheck?: (args: { id: string; ctx: Context; reply: unknown }) => unknown
219
+ client?: Record<string, StandardSchemaV1>
220
+ server?: Record<string, StandardSchemaV1>
221
+ on?: Record<string, (args: never) => unknown>
222
+ limits?: { perSecond?: number }
223
+ key?: StandardSchemaV1
224
+ value?: StandardSchemaV1
225
+ initial?: unknown
226
+ }
227
+ }
228
+
229
+ /** One cached entry as the server sends it: `[id, input, value, tags]` (page payload, navigation, refreshes). */
230
+ export type Wire = [id: string, input: unknown, value: unknown, tags: string[]]
231
+
232
+ /** What the transports answer (research 27 §3). */
233
+ export interface Answer {
234
+ v?: unknown
235
+ t?: string[]
236
+ c?: Wire[]
237
+ r?: string
238
+ /** `r` is a full load: the session changed, so nothing cached may survive. */ x?: 1
239
+ }
240
+
241
+ /** How a client call reaches the server: `fetch` by default, a fake under test. */
242
+ export interface Transport {
243
+ query(id: string, input: unknown): Promise<Answer>
244
+ /** `form`: the input came from a form, so strings may be coerced; a FormData is sent as is (it holds files). */
245
+ mutate(
246
+ id: string,
247
+ input: unknown,
248
+ refresh: [string, unknown][],
249
+ form?: true | FormData,
250
+ ): Promise<Answer>
251
+ }
252
+
253
+ /** @weft-js/core/test's fake-transport controls (research 27 §7). */
254
+ export interface NetControl {
255
+ latency: number
256
+ ctx?: object
257
+ /** Server extensions for this test (`net.extension`). */
258
+ extensions: ServerExtension[]
259
+ online(): boolean
260
+ fails: Map<string, unknown[]>
261
+ handlers: Map<string, (input: unknown, ctx: Context) => unknown>
262
+ log: { id: string; kind: string; input: unknown }[]
263
+ inflight: Set<Promise<unknown>>
264
+ als: { run<T>(store: unknown, fn: () => T): T; getStore(): unknown }
265
+ resets: (() => unknown)[]
266
+ /** Subscriptions an owner holds: open at the end of a test, they're T012. */
267
+ open: Map<object, [what: string, close: () => void]>
268
+ /** The realtime fake, while one is connected: `net.push`, `net.poke`, `net.drop`. */
269
+ rt?:
270
+ | {
271
+ push(id: string, msg: unknown): number
272
+ poke(tags: string[]): Promise<void>
273
+ drop(): void
274
+ subscriptions(): { kind: string; id: string; input: unknown }[]
275
+ }
276
+ | undefined
277
+ /** The channel fake, while a socket is open. */
278
+ ws?:
279
+ | {
280
+ publish(id: string, params: unknown, name: string, msg: unknown): Promise<void>
281
+ drop(): void
282
+ recheck(): Promise<void>
283
+ }
284
+ | undefined
285
+ /** What this client's sockets sent, across reconnects. */
286
+ sent: { id: string; name: string; msg: unknown }[]
287
+ reconnects: number
288
+ }
289
+
290
+ /** A query handle, whatever its input: what the cache needs of one. */
291
+ export type QueryRef<O> = Pick<Query<void, O>, 'kind' | '$id'> & {
292
+ fetch(...args: never[]): Promise<O>
293
+ }
294
+
295
+ /** The cache as an `optimistic` sees it. */
296
+ export interface OptimisticCache {
297
+ /** Change a query's cached value with a draft (copy-on-write: untouched rows keep their identity). */
298
+ update<O>(q: QueryRef<O>, input: unknown, recipe: (draft: O) => O | void): void
299
+ set<O>(q: QueryRef<O>, input: unknown, value: O): void
300
+ get<O>(q: QueryRef<O>, input?: unknown): O | undefined
301
+ /** The value (a row) was made by a pending optimistic layer. */
302
+ isOptimistic(value: unknown): boolean
303
+ }