@abide/abide 0.41.1 → 0.43.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 (167) hide show
  1. package/AGENTS.md +234 -294
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +51 -95
  4. package/package.json +3 -2
  5. package/src/abideLsp.ts +46 -8
  6. package/src/abideResolverPlugin.ts +107 -19
  7. package/src/buildCli.ts +1 -1
  8. package/src/discoveryEntry.ts +3 -3
  9. package/src/lib/bundle/BundleMenuItem.ts +1 -1
  10. package/src/lib/cli/createClient.ts +12 -12
  11. package/src/lib/cli/dispatchCommand.ts +1 -1
  12. package/src/lib/cli/printSessionStatus.ts +1 -1
  13. package/src/lib/cli/resolveCliTarget.ts +1 -1
  14. package/src/lib/cli/runCli.ts +1 -1
  15. package/src/lib/cli/types/CliManifest.ts +1 -1
  16. package/src/lib/cli/types/CliManifestEntry.ts +2 -2
  17. package/src/lib/mcp/annotationsForMethod.ts +5 -5
  18. package/src/lib/mcp/createMcpServer.ts +3 -3
  19. package/src/lib/mcp/mcpSurface.ts +14 -14
  20. package/src/lib/mcp/types/McpServerOptions.ts +1 -1
  21. package/src/lib/server/DELETE.ts +4 -4
  22. package/src/lib/server/GET.ts +4 -4
  23. package/src/lib/server/HEAD.ts +4 -4
  24. package/src/lib/server/PATCH.ts +4 -4
  25. package/src/lib/server/POST.ts +4 -4
  26. package/src/lib/server/PUT.ts +4 -4
  27. package/src/lib/server/agent.ts +4 -4
  28. package/src/lib/server/env.ts +1 -1
  29. package/src/lib/server/error.ts +49 -7
  30. package/src/lib/server/json.ts +1 -1
  31. package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
  32. package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
  33. package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
  34. package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
  35. package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
  36. package/src/lib/server/rpc/parseArgs.ts +6 -6
  37. package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
  38. package/src/lib/server/rpc/registerRpc.ts +6 -0
  39. package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
  40. package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
  41. package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
  42. package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
  43. package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
  44. package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
  45. package/src/lib/server/rpc/unprocessed.ts +6 -7
  46. package/src/lib/server/rpc/validationError.ts +17 -0
  47. package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
  48. package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
  49. package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
  50. package/src/lib/server/runtime/createServer.ts +4 -4
  51. package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
  52. package/src/lib/server/runtime/crossOriginGate.ts +4 -4
  53. package/src/lib/server/runtime/devClientFingerprint.ts +2 -1
  54. package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
  55. package/src/lib/server/runtime/registryManifests.ts +2 -2
  56. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  57. package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
  58. package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
  59. package/src/lib/server/runtime/types/RequestStore.ts +1 -1
  60. package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
  61. package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
  62. package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
  63. package/src/lib/shared/HttpError.ts +15 -1
  64. package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
  65. package/src/lib/shared/buildRpcRequest.ts +5 -5
  66. package/src/lib/shared/cache.ts +50 -168
  67. package/src/lib/shared/carriesBodyArgs.ts +4 -4
  68. package/src/lib/shared/createCacheStore.ts +12 -0
  69. package/src/lib/shared/createRemoteFunction.ts +11 -6
  70. package/src/lib/shared/decodeResponse.ts +2 -2
  71. package/src/lib/shared/detectRpcMethod.ts +17 -0
  72. package/src/lib/shared/emitLogRecord.ts +3 -3
  73. package/src/lib/shared/escapeRegex.ts +9 -0
  74. package/src/lib/shared/extraForwardHeaders.ts +1 -1
  75. package/src/lib/shared/fileName.ts +9 -0
  76. package/src/lib/shared/fileStem.ts +3 -1
  77. package/src/lib/shared/findExportCallSite.ts +30 -273
  78. package/src/lib/shared/forwardHeaders.ts +2 -2
  79. package/src/lib/shared/httpErrorFor.ts +26 -0
  80. package/src/lib/shared/isAsciiWhitespace.ts +5 -0
  81. package/src/lib/shared/isIdentPart.ts +9 -0
  82. package/src/lib/shared/isIdentStart.ts +8 -0
  83. package/src/lib/shared/isReadOnlyMethod.ts +4 -4
  84. package/src/lib/shared/keyForRemoteCall.ts +4 -4
  85. package/src/lib/shared/log.ts +1 -1
  86. package/src/lib/shared/outboxProbeSlot.ts +20 -0
  87. package/src/lib/shared/pending.ts +4 -1
  88. package/src/lib/shared/prepareRpcModule.ts +96 -14
  89. package/src/lib/shared/probeRegistries.ts +17 -4
  90. package/src/lib/shared/programNameForPackage.ts +3 -1
  91. package/src/lib/shared/queryStringFromArgs.ts +1 -1
  92. package/src/lib/shared/remoteMetaStore.ts +2 -2
  93. package/src/lib/shared/resolveClientFlags.ts +1 -1
  94. package/src/lib/shared/skipNonCode.ts +230 -0
  95. package/src/lib/shared/streamResponse.ts +9 -6
  96. package/src/lib/shared/stripImport.ts +6 -4
  97. package/src/lib/shared/types/CacheEntry.ts +2 -4
  98. package/src/lib/shared/types/CacheOnContext.ts +1 -11
  99. package/src/lib/shared/types/CacheStore.ts +13 -7
  100. package/src/lib/shared/types/ClientFlags.ts +1 -1
  101. package/src/lib/shared/types/ErrorConstructors.ts +17 -0
  102. package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
  103. package/src/lib/shared/types/ErrorSpec.ts +11 -0
  104. package/src/lib/shared/types/HttpMethod.ts +1 -0
  105. package/src/lib/shared/types/Outbox.ts +9 -0
  106. package/src/lib/shared/types/OutboxEntry.ts +27 -0
  107. package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
  108. package/src/lib/shared/types/RemoteCallable.ts +2 -2
  109. package/src/lib/shared/types/RemoteFunction.ts +21 -6
  110. package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
  111. package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
  112. package/src/lib/shared/types/RpcInvoker.ts +1 -1
  113. package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
  114. package/src/lib/shared/types/ValidationErrorData.ts +20 -0
  115. package/src/lib/shared/url.ts +3 -3
  116. package/src/lib/shared/writeRpcDts.ts +7 -7
  117. package/src/lib/shared/writeTestRpcDts.ts +3 -3
  118. package/src/lib/test/createTestApp.ts +10 -10
  119. package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +45 -0
  120. package/src/lib/ui/compile/abideUiPlugin.ts +2 -1
  121. package/src/lib/ui/compile/awaitPlan.ts +48 -0
  122. package/src/lib/ui/compile/compileShadow.ts +117 -31
  123. package/src/lib/ui/compile/createShadowLanguageService.ts +48 -0
  124. package/src/lib/ui/compile/desugarSignals.ts +42 -6
  125. package/src/lib/ui/compile/encodeSemanticTokens.ts +37 -0
  126. package/src/lib/ui/compile/generateBuild.ts +45 -62
  127. package/src/lib/ui/compile/generateSSR.ts +90 -60
  128. package/src/lib/ui/compile/ifPlan.ts +30 -0
  129. package/src/lib/ui/compile/makeVarNamer.ts +10 -0
  130. package/src/lib/ui/compile/offsetToPosition.ts +9 -0
  131. package/src/lib/ui/compile/parseTemplate.ts +565 -115
  132. package/src/lib/ui/compile/stripEffects.ts +17 -9
  133. package/src/lib/ui/compile/structuralBlockTokens.ts +101 -0
  134. package/src/lib/ui/compile/switchPlan.ts +22 -0
  135. package/src/lib/ui/compile/tryPlan.ts +29 -0
  136. package/src/lib/ui/compile/types/SemanticToken.ts +11 -0
  137. package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
  138. package/src/lib/ui/compile/types/TemplateNode.ts +15 -2
  139. package/src/lib/ui/createScope.ts +20 -2
  140. package/src/lib/ui/dom/appendText.ts +1 -5
  141. package/src/lib/ui/dom/applyResolved.ts +1 -5
  142. package/src/lib/ui/dom/disposeRange.ts +6 -10
  143. package/src/lib/ui/dom/hydrate.ts +2 -5
  144. package/src/lib/ui/dom/isComment.ts +6 -0
  145. package/src/lib/ui/dom/mount.ts +1 -2
  146. package/src/lib/ui/dom/withScope.ts +6 -9
  147. package/src/lib/ui/effect.ts +4 -2
  148. package/src/lib/ui/navigate.ts +40 -13
  149. package/src/lib/ui/outbox.ts +30 -110
  150. package/src/lib/ui/remoteProxy.ts +160 -10
  151. package/src/lib/ui/router.ts +3 -3
  152. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
  153. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
  154. package/src/lib/ui/runtime/createDoc.ts +3 -3
  155. package/src/lib/ui/runtime/pathSegments.ts +10 -0
  156. package/src/lib/ui/socketChannel.ts +7 -1
  157. package/src/lib/ui/types/Scope.ts +20 -5
  158. package/template/.zed/settings.json +4 -0
  159. package/template/CLAUDE.md +1 -1
  160. package/template/src/server/rpc/getHello.ts +3 -3
  161. package/template/src/ui/pages/page.abide +4 -4
  162. package/template/test/app.test.ts +2 -2
  163. package/src/lib/server/rpc/registerVerb.ts +0 -6
  164. package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
  165. package/src/lib/shared/detectVerbMethod.ts +0 -17
  166. package/src/lib/shared/types/HttpVerb.ts +0 -1
  167. package/src/lib/ui/types/Outbox.ts +0 -14
@@ -1,3 +1,5 @@
1
+ import { fileName } from './fileName.ts'
2
+
1
3
  /*
2
4
  The bare filename of a path, with directory and trailing extension stripped —
3
5
  e.g. `users/list.ts` → `list`, `/_virtual/mcp-resources.ts` → `mcp-resources`.
@@ -5,5 +7,5 @@ Used to derive a virtual-module name from its path and to check an $rpc /
5
7
  $sockets module's single export name against its file stem.
6
8
  */
7
9
  export function fileStem(path: string): string {
8
- return (path.split('/').pop() ?? '').replace(/\.[^.]+$/, '')
10
+ return fileName(path).replace(/\.[^.]+$/, '')
9
11
  }
@@ -1,9 +1,14 @@
1
+ import { isAsciiWhitespace } from './isAsciiWhitespace.ts'
2
+ import { isIdentPart } from './isIdentPart.ts'
3
+ import { isIdentStart } from './isIdentStart.ts'
4
+ import { skipNonCode } from './skipNonCode.ts'
5
+
1
6
  /*
2
7
  Scans a module source character-by-character — skipping strings,
3
- templates, comments, and TypeScript generics for an
4
- `export const <name> = <IDENT>(...)` binding the caller cares about.
5
- On a match returns the identifier text, the export name, and the byte
6
- ranges of the call's open and close parens; the $rpc and $sockets
8
+ templates, comments, regex literals (via skipNonCode), and TypeScript
9
+ generics — for an `export const <name> = <IDENT>(...)` binding the caller
10
+ cares about. On a match returns the identifier text, the export name, and
11
+ the byte ranges of the call's open and close parens; the $rpc and $sockets
7
12
  rewriters splice their runtime bindings into those ranges.
8
13
 
9
14
  The scanner enforces a single matching export per module: a second match
@@ -32,20 +37,12 @@ export function findExportCallSite(
32
37
  const len = source.length
33
38
  let i = 0
34
39
  while (i < len) {
35
- const c = source[i]
36
- const afterSlash = skipSlashConstruct(source, i)
37
- if (afterSlash !== undefined) {
38
- i = afterSlash
39
- continue
40
- }
41
- if (c === '"' || c === "'") {
42
- i = skipString(source, i + 1, c)
43
- continue
44
- }
45
- if (c === '`') {
46
- i = skipTemplate(source, i + 1)
40
+ const skipped = skipNonCode(source, i)
41
+ if (skipped !== undefined) {
42
+ i = skipped
47
43
  continue
48
44
  }
45
+ const c = source[i]
49
46
  if (isIdentStart(c) && !isIdentPart(source[i - 1])) {
50
47
  let j = i + 1
51
48
  while (j < len && isIdentPart(source[j])) {
@@ -84,106 +81,9 @@ export function findExportCallSite(
84
81
  return found
85
82
  }
86
83
 
87
- function skipString(source: string, start: number, quote: string): number {
88
- let i = start
89
- while (i < source.length) {
90
- const c = source[i]
91
- if (c === '\\') {
92
- i += 2
93
- continue
94
- }
95
- if (c === quote) {
96
- return i + 1
97
- }
98
- if (c === '\n') {
99
- return i
100
- }
101
- i++
102
- }
103
- return source.length
104
- }
105
-
106
- function skipTemplate(source: string, start: number): number {
107
- let i = start
108
- while (i < source.length) {
109
- const c = source[i]
110
- if (c === '\\') {
111
- i += 2
112
- continue
113
- }
114
- if (c === '`') {
115
- return i + 1
116
- }
117
- if (c === '$' && source[i + 1] === '{') {
118
- i = skipTemplateExpression(source, i + 2)
119
- continue
120
- }
121
- i++
122
- }
123
- return source.length
124
- }
125
-
126
- function skipTemplateExpression(source: string, start: number): number {
127
- let depth = 1
128
- let i = start
129
- while (i < source.length && depth > 0) {
130
- const c = source[i]
131
- if (c === '{') {
132
- depth++
133
- i++
134
- continue
135
- }
136
- if (c === '}') {
137
- depth--
138
- i++
139
- continue
140
- }
141
- if (c === '"' || c === "'") {
142
- i = skipString(source, i + 1, c)
143
- continue
144
- }
145
- if (c === '`') {
146
- i = skipTemplate(source, i + 1)
147
- continue
148
- }
149
- const afterSlash = skipSlashConstruct(source, i)
150
- if (afterSlash !== undefined) {
151
- i = afterSlash
152
- continue
153
- }
154
- i++
155
- }
156
- return i
157
- }
158
-
159
- /*
160
- When `i` sits on a `/` introducing a line comment, block comment, or regex
161
- literal, returns the index immediately after the construct; undefined when
162
- the `/` is division (or `i` isn't a `/`). One statement of the skip rules
163
- shared by every scanner loop so they can't drift.
164
- */
165
- function skipSlashConstruct(source: string, i: number): number | undefined {
166
- if (source[i] !== '/') {
167
- return undefined
168
- }
169
- const next = source[i + 1]
170
- if (next === '/') {
171
- const newline = source.indexOf('\n', i + 2)
172
- return newline === -1 ? source.length : newline + 1
173
- }
174
- if (next === '*') {
175
- const end = source.indexOf('*/', i + 2)
176
- return end === -1 ? source.length : end + 2
177
- }
178
- if (isRegexContext(source, i)) {
179
- return skipRegex(source, i + 1)
180
- }
181
- return undefined
182
- }
183
-
184
84
  function matchCallTail(source: string, after: number): number | undefined {
185
85
  let j = after
186
- while (j < source.length && isWhitespace(source[j])) {
86
+ while (j < source.length && isAsciiWhitespace(source[j])) {
187
87
  j++
188
88
  }
189
89
  if (source[j] === '<') {
@@ -192,7 +92,7 @@ function matchCallTail(source: string, after: number): number | undefined {
192
92
  return undefined
193
93
  }
194
94
  j = closed
195
- while (j < source.length && isWhitespace(source[j])) {
95
+ while (j < source.length && isAsciiWhitespace(source[j])) {
196
96
  j++
197
97
  }
198
98
  }
@@ -216,15 +116,12 @@ function skipGenerics(source: string, start: number): number | undefined {
216
116
  let bracketDepth = 0
217
117
  let i = start
218
118
  while (i < source.length) {
219
- const c = source[i]
220
- if (c === '"' || c === "'") {
221
- i = skipString(source, i + 1, c)
222
- continue
223
- }
224
- if (c === '`') {
225
- i = skipTemplate(source, i + 1)
119
+ const skipped = skipNonCode(source, i)
120
+ if (skipped !== undefined) {
121
+ i = skipped
226
122
  continue
227
123
  }
124
+ const c = source[i]
228
125
  if (c === '<') {
229
126
  angleDepth++
230
127
  } else if (c === '>') {
@@ -254,28 +151,20 @@ function skipGenerics(source: string, start: number): number | undefined {
254
151
  }
255
152
 
256
153
  /*
257
- Walks the call body, skipping strings/templates/comments and respecting
258
- nested `()` so brackets inside object literals or nested calls don't
259
- throw the depth count.
154
+ Walks the call body, skipping strings/templates/comments/regex and
155
+ respecting nested `()` so brackets inside object literals or nested calls
156
+ don't throw the depth count.
260
157
  */
261
158
  function findCallEnd(source: string, parenStart: number): number | undefined {
262
159
  let depth = 1
263
160
  let i = parenStart + 1
264
161
  while (i < source.length) {
265
- const c = source[i]
266
- if (c === '"' || c === "'") {
267
- i = skipString(source, i + 1, c)
268
- continue
269
- }
270
- if (c === '`') {
271
- i = skipTemplate(source, i + 1)
272
- continue
273
- }
274
- const afterSlash = skipSlashConstruct(source, i)
275
- if (afterSlash !== undefined) {
276
- i = afterSlash
162
+ const skipped = skipNonCode(source, i)
163
+ if (skipped !== undefined) {
164
+ i = skipped
277
165
  continue
278
166
  }
167
+ const c = source[i]
279
168
  if (c === '(') {
280
169
  depth++
281
170
  } else if (c === ')') {
@@ -297,14 +186,14 @@ isn't the module's declared export.
297
186
  */
298
187
  function detectExportName(source: string, callStart: number): string | undefined {
299
188
  let i = callStart - 1
300
- while (i >= 0 && isWhitespace(source[i])) {
189
+ while (i >= 0 && isAsciiWhitespace(source[i])) {
301
190
  i--
302
191
  }
303
192
  if (source[i] !== '=') {
304
193
  return undefined
305
194
  }
306
195
  i--
307
- while (i >= 0 && isWhitespace(source[i])) {
196
+ while (i >= 0 && isAsciiWhitespace(source[i])) {
308
197
  i--
309
198
  }
310
199
  const nameEnd = i + 1
@@ -316,14 +205,14 @@ function detectExportName(source: string, callStart: number): string | undefined
316
205
  return undefined
317
206
  }
318
207
  const name = source.slice(nameStart, nameEnd)
319
- while (i >= 0 && isWhitespace(source[i])) {
208
+ while (i >= 0 && isAsciiWhitespace(source[i])) {
320
209
  i--
321
210
  }
322
211
  if (!matchesBackwards(source, i, 'const')) {
323
212
  return undefined
324
213
  }
325
214
  i -= 'const'.length
326
- while (i >= 0 && isWhitespace(source[i])) {
215
+ while (i >= 0 && isAsciiWhitespace(source[i])) {
327
216
  i--
328
217
  }
329
218
  if (!matchesBackwards(source, i, 'export')) {
@@ -342,135 +231,3 @@ function matchesBackwards(source: string, end: number, keyword: string): boolean
342
231
  }
343
232
  return start === 0 || !isIdentPart(source[start - 1])
344
233
  }
345
-
346
- function isIdentStart(c: string | undefined): boolean {
347
- if (c === undefined) {
348
- return false
349
- }
350
- return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$'
351
- }
352
-
353
- function isIdentPart(c: string | undefined): boolean {
354
- if (c === undefined) {
355
- return false
356
- }
357
- return isIdentStart(c) || (c >= '0' && c <= '9')
358
- }
359
-
360
- function isWhitespace(c: string | undefined): boolean {
361
- return c === ' ' || c === '\t' || c === '\n' || c === '\r'
362
- }
363
-
364
- /*
365
- A `/` starts a regex literal when the prior expression context expects an
366
- expression rather than a value — after an open delimiter, operator, or
367
- expression-prefix keyword (return, typeof, instanceof, in, of, delete,
368
- void, await, yield, new, throw, case, do). Otherwise `/` is division.
369
- Without this disambiguation a regex like `/^\//` reads as `/` (division),
370
- then `^`, `\`, `/`, `/` — and the final `//` pair fakes a line comment
371
- that swallows the rest of the line, eating any `)` that closes the
372
- enclosing call.
373
- */
374
- const REGEX_PREFIX_KEYWORDS = new Set([
375
- 'return',
376
- 'typeof',
377
- 'instanceof',
378
- 'in',
379
- 'of',
380
- 'delete',
381
- 'void',
382
- 'await',
383
- 'yield',
384
- 'new',
385
- 'throw',
386
- 'case',
387
- 'do',
388
- ])
389
-
390
- const REGEX_PUNCTUATION = new Set([
391
- '(',
392
- '[',
393
- '{',
394
- ',',
395
- ';',
396
- ':',
397
- '?',
398
- '!',
399
- '&',
400
- '|',
401
- '^',
402
- '~',
403
- '+',
404
- '-',
405
- '*',
406
- '%',
407
- '<',
408
- '>',
409
- '=',
410
- '/',
411
- ])
412
-
413
- function isRegexContext(source: string, slashIndex: number): boolean {
414
- let i = slashIndex - 1
415
- while (i >= 0 && isWhitespace(source[i])) {
416
- i--
417
- }
418
- if (i < 0) {
419
- return true
420
- }
421
- const prev = source[i] as string
422
- if (REGEX_PUNCTUATION.has(prev)) {
423
- return true
424
- }
425
- if (isIdentPart(prev)) {
426
- let start = i
427
- while (start > 0 && isIdentPart(source[start - 1])) {
428
- start--
429
- }
430
- return REGEX_PREFIX_KEYWORDS.has(source.slice(start, i + 1))
431
- }
432
- return false
433
- }
434
-
435
- /*
436
- Walks past a regex literal body, respecting character classes (`[...]`
437
- where `/` is literal) and backslash escapes, then consumes trailing
438
- flag identifiers. Returns the index immediately after the regex. An
439
- unterminated regex (newline before closing `/`) returns the newline
440
- position so the outer scanner can resume normally on the next line.
441
- */
442
- function skipRegex(source: string, start: number): number {
443
- let i = start
444
- let inClass = false
445
- while (i < source.length) {
446
- const c = source[i]
447
- if (c === '\\') {
448
- i += 2
449
- continue
450
- }
451
- if (c === '\n') {
452
- return i
453
- }
454
- if (inClass) {
455
- if (c === ']') {
456
- inClass = false
457
- }
458
- i++
459
- continue
460
- }
461
- if (c === '[') {
462
- inClass = true
463
- i++
464
- continue
465
- }
466
- if (c === '/') {
467
- let j = i + 1
468
- while (j < source.length && isIdentPart(source[j])) {
469
- j++
470
- }
471
- return j
472
- }
473
- i++
474
- }
475
- return source.length
476
- }
@@ -4,8 +4,8 @@ import { extraForwardHeaders } from './extraForwardHeaders.ts'
4
4
  Headers abide forwards from an inbound HTTP/MCP request onto every
5
5
  synthesized in-process rpc Request — cookies + bearer auth + the four
6
6
  forwarding hints proxies set when terminating TLS in front of the app.
7
- defineVerb uses this when an SSR pass calls a verb in-process; the MCP
8
- dispatcher uses it when piping a tool invocation through verb.fetch.
7
+ defineRpc uses this when an SSR pass calls a rpc in-process; the MCP
8
+ dispatcher uses it when piping a tool invocation through rpc.fetch.
9
9
 
10
10
  WARNING — this is an allowlist: every inbound header NOT named here (and
11
11
  not added via app.forwardHeaders) is DROPPED on the in-process path. A
@@ -0,0 +1,26 @@
1
+ import { contentBodyKind } from './contentBodyKind.ts'
2
+ import { contentTypeOf } from './contentTypeOf.ts'
3
+ import { HttpError } from './HttpError.ts'
4
+
5
+ /*
6
+ Builds the HttpError for a non-2xx response, parsing a typed-error body
7
+ (`{ $abideError, data }`, emitted by `error(errors.x(...))` and validation 422)
8
+ onto `.kind` / `.data`. Reads a clone so the original `response.body` stays
9
+ unread for callers that inspect it. A non-JSON or malformed body leaves
10
+ `.kind` / `.data` undefined (a plain `error(status, text)`). Shared by the
11
+ plain decode path (decodeResponse) and the streaming path (streamResponse) so
12
+ both surface the same typed error on a non-2xx.
13
+ */
14
+ export async function httpErrorFor(response: Response): Promise<HttpError> {
15
+ if (contentBodyKind(contentTypeOf(response.headers)) === 'json') {
16
+ try {
17
+ const body = await response.clone().json()
18
+ if (body !== null && typeof body === 'object' && '$abideError' in body) {
19
+ return new HttpError(response, body.$abideError, body.data)
20
+ }
21
+ } catch {
22
+ /* malformed JSON error body — fall through to a plain HttpError */
23
+ }
24
+ }
25
+ return new HttpError(response)
26
+ }
@@ -0,0 +1,5 @@
1
+ /* True for the source-scanner whitespace set (space, tab, CR, LF). Distinct from
2
+ isWhitespaceText, which classifies template text nodes. */
3
+ export function isAsciiWhitespace(c: string | undefined): boolean {
4
+ return c === ' ' || c === '\t' || c === '\n' || c === '\r'
5
+ }
@@ -0,0 +1,9 @@
1
+ import { isIdentStart } from './isIdentStart.ts'
2
+
3
+ /* True when `c` can continue a JavaScript identifier — an identifier-start char or a digit. */
4
+ export function isIdentPart(c: string | undefined): boolean {
5
+ if (c === undefined) {
6
+ return false
7
+ }
8
+ return isIdentStart(c) || (c >= '0' && c <= '9')
9
+ }
@@ -0,0 +1,8 @@
1
+ /* True when `c` can begin a JavaScript identifier (ASCII letters, `_`, `$`). Shared by the
2
+ source scanners (findExportCallSite, skipNonCode) to find identifiers and regex flags. */
3
+ export function isIdentStart(c: string | undefined): boolean {
4
+ if (c === undefined) {
5
+ return false
6
+ }
7
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$'
8
+ }
@@ -1,14 +1,14 @@
1
- import type { HttpVerb } from './types/HttpVerb.ts'
1
+ import type { HttpMethod } from './types/HttpMethod.ts'
2
2
 
3
3
  /*
4
4
  Read-only (safe) HTTP methods — they don't mutate server state. Abide
5
- uses this to decide which verbs auto-expose to MCP: reads flip MCP on
5
+ uses this to decide which rpcs auto-expose to MCP: reads flip MCP on
6
6
  when a schema is present, mutations require an explicit `clients.mcp`
7
7
  opt-in so a model can't delete/overwrite data just because the handler
8
8
  carries a schema. Also feeds the MCP tool `readOnlyHint` annotation.
9
9
  */
10
- const READ_ONLY_METHODS = new Set<HttpVerb>(['GET', 'HEAD'])
10
+ const READ_ONLY_METHODS = new Set<HttpMethod>(['GET', 'HEAD'])
11
11
 
12
- export function isReadOnlyMethod(method: HttpVerb): boolean {
12
+ export function isReadOnlyMethod(method: HttpMethod): boolean {
13
13
  return READ_ONLY_METHODS.has(method)
14
14
  }
@@ -1,17 +1,17 @@
1
1
  import { canonicalJson } from './canonicalJson.ts'
2
2
  import { carriesBodyArgs } from './carriesBodyArgs.ts'
3
3
  import { queryStringFromArgs } from './queryStringFromArgs.ts'
4
- import type { HttpVerb } from './types/HttpVerb.ts'
4
+ import type { HttpMethod } from './types/HttpMethod.ts'
5
5
 
6
6
  /*
7
- Derives a cache key from a verb-defined remote function and its args. The
7
+ Derives a cache key from a rpc-defined remote function and its args. The
8
8
  prefix is `${method} ${url}` where `url` is the route template. GET/DELETE/HEAD
9
9
  serialise args onto the URL as `?key=value` (sorted, via queryStringFromArgs —
10
10
  the same encoder buildRpcRequest builds its query with, so the key and the
11
11
  synthesized Request can't disagree); POST/PUT/PATCH join args after a space as
12
- canonical JSON. The verb split mirrors buildRpcRequest exactly.
12
+ canonical JSON. The rpc split mirrors buildRpcRequest exactly.
13
13
  */
14
- export function keyForRemoteCall(method: HttpVerb, url: string, args: unknown): string {
14
+ export function keyForRemoteCall(method: HttpMethod, url: string, args: unknown): string {
15
15
  const prefix = `${method} ${url}`
16
16
  if (!carriesBodyArgs(method)) {
17
17
  if (args && typeof args === 'object' && !Array.isArray(args)) {
@@ -5,7 +5,7 @@ import type { Log } from './types/Log.ts'
5
5
 
6
6
  /*
7
7
  The unified logger: every record carries the request-scope context (short
8
- trace id, +elapsed, verb+path) when one is active, plus a channel — the
8
+ trace id, +elapsed, method+path) when one is active, plus a channel — the
9
9
  line's speaker. `log(...)`/`warn`/`error`/`trace` speak on the app's own
10
10
  always-on channel (the app name, resolved per emission so boot order doesn't
11
11
  matter); `log.channel(name)` returns the same shape on a DEBUG-gated
@@ -0,0 +1,20 @@
1
+ /*
2
+ Internal slot the browser outbox registry registers its prober into, so the
3
+ shared pending() probe can count parked durable-rpc writes without shared/
4
+ importing browser/. Mirrors tailProbeSlot. The prober reads the doc-backed
5
+ queue entries (reactive inside $derived / $effect) and reports whether any
6
+ undelivered entry matches the selector: a durable rpc selector (carries a
7
+ `url`) narrows to its own queue, the bare form spans every registered queue,
8
+ optional args narrow to one parked call by structural compare. Outbox state is
9
+ pending-only — a parked write has no value, so it never contributes to
10
+ refreshing(); probeRegistries reads this slot only on its `field === 'pending'`
11
+ branch, which is why the slot mirrors tailProbeSlot but reports a bare boolean
12
+ rather than a { pending, refreshing } pair. When no prober is registered (server
13
+ render, or no durable rpc was ever used) pending() sees no parked writes,
14
+ exactly as on the server where there are no client queues.
15
+ */
16
+ export const outboxProbeSlot: {
17
+ probe: ((selector: unknown, args: unknown) => boolean) | undefined
18
+ } = {
19
+ probe: undefined,
20
+ }
@@ -15,9 +15,12 @@ tail registry (streams). Pending means "no value yet":
15
15
  pending(subscribable) → that stream awaiting its first frame
16
16
  (tail.status === 'pending'; true when nothing
17
17
  is reading yet — there is no value either way)
18
+ pending(durableRpc) → that durable rpc's parked writes (a parked write
19
+ has no value yet); the bare form counts them too
18
20
  Probes report, never act: reading one opens no fetch and no stream. SSR
19
21
  loading state is driven by {#await}, not this. Scan semantics (tap order,
20
- selector grammar, registry spans) live in probeRegistries.
22
+ selector grammar, registry spans, the parked-write term) live in
23
+ probeRegistries.
21
24
  */
22
25
  // @documentation probes
23
26
  export function pending<Args, Return>(