@pikku/core 0.12.31 → 0.12.32

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 (38) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/middleware-runner.js +4 -1
  3. package/dist/pikku-state.js +1 -0
  4. package/dist/services/gopass-secrets.d.ts +1 -1
  5. package/dist/services/local-secrets.d.ts +1 -1
  6. package/dist/services/local-variables.d.ts +1 -0
  7. package/dist/services/local-variables.js +9 -0
  8. package/dist/services/scoped-secret-service.d.ts +1 -1
  9. package/dist/services/scoped-secret-service.js +2 -2
  10. package/dist/services/secret-service.d.ts +6 -2
  11. package/dist/services/typed-secret-service.d.ts +1 -1
  12. package/dist/services/typed-variables-service.d.ts +1 -0
  13. package/dist/services/typed-variables-service.js +3 -0
  14. package/dist/services/variables-service.d.ts +9 -0
  15. package/dist/types/state.types.d.ts +3 -0
  16. package/dist/wirings/http/http-runner.js +4 -1
  17. package/dist/wirings/http/pikku-fetch-http-request.js +34 -20
  18. package/dist/wirings/http/web-request.js +6 -1
  19. package/package.json +1 -1
  20. package/src/middleware-runner.test.ts +16 -1
  21. package/src/middleware-runner.ts +4 -1
  22. package/src/pikku-state.ts +1 -0
  23. package/src/services/gopass-secrets.ts +4 -2
  24. package/src/services/local-secrets.ts +4 -2
  25. package/src/services/local-variables.ts +11 -0
  26. package/src/services/scoped-secret-service.test.ts +20 -0
  27. package/src/services/scoped-secret-service.ts +5 -3
  28. package/src/services/secret-service.ts +8 -2
  29. package/src/services/typed-secret-service.ts +4 -2
  30. package/src/services/typed-variables-service.ts +6 -0
  31. package/src/services/variables-service.ts +11 -0
  32. package/src/types/state.types.ts +5 -0
  33. package/src/wirings/http/http-runner.test.ts +13 -0
  34. package/src/wirings/http/http-runner.ts +4 -1
  35. package/src/wirings/http/pikku-fetch-http-request.ts +37 -20
  36. package/src/wirings/http/web-request.test.ts +2 -2
  37. package/src/wirings/http/web-request.ts +6 -1
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -16,7 +16,7 @@ export class PikkuFetchHTTPRequest<
16
16
  #url: URL
17
17
  #rawBodyText: string | undefined
18
18
  #rawBodyBuffer: ArrayBuffer | undefined
19
- #bodyRead = false
19
+ #rawBufferPromise: Promise<ArrayBuffer> | undefined
20
20
 
21
21
  constructor(private request: Request) {
22
22
  this.#url = new URL(request.url)
@@ -44,36 +44,53 @@ export class PikkuFetchHTTPRequest<
44
44
  * @returns A promise that resolves to the raw request body.
45
45
  */
46
46
  public async arrayBuffer(): Promise<ArrayBuffer> {
47
+ return this.#readRawBuffer()
48
+ }
49
+
50
+ /**
51
+ * Reads the underlying single-use request body exactly once and memoises both
52
+ * the in-flight promise and the result. `json()`, `arrayBuffer()`, `body()`
53
+ * and `toWebRequest()` all funnel through here, so a request whose body is
54
+ * consumed in more than one place can no longer race on the underlying stream
55
+ * and fail with "Body has already been used".
56
+ *
57
+ * If a second consumer asks for the body while the first read is still in
58
+ * flight, that is the concurrent double-read that used to crash — now safe,
59
+ * but warned about so the redundant consumer is found and removed at the
60
+ * source rather than silently leaning on this cache.
61
+ */
62
+ async #readRawBuffer(): Promise<ArrayBuffer> {
47
63
  if (this.#rawBodyBuffer !== undefined) {
48
64
  return this.#rawBodyBuffer
49
65
  }
50
- if (this.#bodyRead) {
51
- if (this.#rawBodyText !== undefined) {
52
- const buf = new TextEncoder().encode(this.#rawBodyText)
53
- .buffer as ArrayBuffer
54
- this.#rawBodyBuffer = buf
55
- return buf
56
- }
57
- return new ArrayBuffer(0)
66
+ if (this.#rawBodyText !== undefined) {
67
+ const buf = new TextEncoder().encode(this.#rawBodyText)
68
+ .buffer as ArrayBuffer
69
+ this.#rawBodyBuffer = buf
70
+ return buf
71
+ }
72
+ if (this.#rawBufferPromise !== undefined) {
73
+ console.warn(
74
+ `[pikku] request body for ${this.method().toUpperCase()} ${this.path()} ` +
75
+ `was requested again before the first read resolved — the read is now ` +
76
+ `shared, but a duplicate body consumer (e.g. middleware calling ` +
77
+ `toWebRequest just for headers) should be removed.`
78
+ )
79
+ return this.#rawBufferPromise
58
80
  }
59
- const buf = await this.request.arrayBuffer()
60
- this.#rawBodyBuffer = buf
61
- this.#bodyRead = true
62
- return buf
81
+ this.#rawBufferPromise = this.request.arrayBuffer().then((buf) => {
82
+ this.#rawBodyBuffer = buf
83
+ return buf
84
+ })
85
+ return this.#rawBufferPromise
63
86
  }
64
87
 
65
88
  async #readRawText(): Promise<string> {
66
89
  if (this.#rawBodyText !== undefined) {
67
90
  return this.#rawBodyText
68
91
  }
69
- if (this.#rawBodyBuffer !== undefined) {
70
- const text = new TextDecoder().decode(this.#rawBodyBuffer)
71
- this.#rawBodyText = text
72
- return text
73
- }
74
- const text = await this.request.text()
92
+ const text = new TextDecoder().decode(await this.#readRawBuffer())
75
93
  this.#rawBodyText = text
76
- this.#bodyRead = true
77
94
  return text
78
95
  }
79
96
 
@@ -274,8 +274,8 @@ describe('applyWebResponse', () => {
274
274
  const { res, state } = createMockResponse()
275
275
  const webRes = new Response('{"csrfToken":"abc"}', { status: 200 })
276
276
  const cookies = [
277
- 'authjs.csrf-token=abc; Path=/; HttpOnly',
278
- 'authjs.callback-url=http%3A%2F%2Flocalhost; Path=/; HttpOnly',
277
+ 'better-auth.session_token=abc; Path=/; HttpOnly',
278
+ 'better-auth.callback-url=http%3A%2F%2Flocalhost; Path=/; HttpOnly',
279
279
  ]
280
280
  ;(webRes.headers as any).getSetCookie = () => cookies
281
281
 
@@ -30,8 +30,13 @@ export function toWebRequest(req: PikkuHTTPRequest, baseUrl?: string): Request {
30
30
  return new Request(url, {
31
31
  method,
32
32
  headers,
33
+ // `pull` (lazy) rather than `start` (eager): the body is only read from the
34
+ // underlying request when this stream is actually consumed. A consumer that
35
+ // builds a web Request just to read its headers (e.g. a session middleware
36
+ // calling getSession({ headers })) never touches the body, so it does zero
37
+ // body I/O and cannot race the route handler's read of the same request.
33
38
  body: new ReadableStream({
34
- async start(controller) {
39
+ async pull(controller) {
35
40
  try {
36
41
  const buffer = await req.arrayBuffer()
37
42
  if (buffer.byteLength > 0) {