@zuplo/cli 6.70.27 → 6.70.28
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/dist/cmds/project/index.d.ts.map +1 -1
- package/dist/cmds/project/index.js +2 -1
- package/dist/cmds/project/index.js.map +1 -1
- package/dist/cmds/project/list.d.ts +9 -0
- package/dist/cmds/project/list.d.ts.map +1 -0
- package/dist/cmds/project/list.js +52 -0
- package/dist/cmds/project/list.js.map +1 -0
- package/dist/cmds/test.d.ts.map +1 -1
- package/dist/cmds/test.js +4 -0
- package/dist/cmds/test.js.map +1 -1
- package/dist/project/list/handler.d.ts +7 -0
- package/dist/project/list/handler.d.ts.map +1 -0
- package/dist/project/list/handler.js +49 -0
- package/dist/project/list/handler.js.map +1 -0
- package/dist/test/handler.d.ts +1 -0
- package/dist/test/handler.d.ts.map +1 -1
- package/dist/test/handler.js.map +1 -1
- package/dist/test/invoke-test.d.ts.map +1 -1
- package/dist/test/invoke-test.js +46 -19
- package/dist/test/invoke-test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +1 -1
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-H7UGARU6.js → chunk-F7DJMBWH.js} +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-H7UGARU6.js.map → chunk-F7DJMBWH.js.map} +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-P4G7GA42.js → chunk-TEXCCTJT.js} +63 -63
- package/node_modules/@zuplo/runtime/out/esm/chunk-TEXCCTJT.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +19 -21
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/types/index.d.ts +36 -0
- package/node_modules/@zuplo/runtime/out/types/mcp-gateway/index.d.ts +4 -4
- package/node_modules/@zuplo/runtime/package.json +1 -1
- package/node_modules/pino-http/node_modules/thread-stream/.claude/settings.local.json +15 -0
- package/node_modules/pino-http/node_modules/thread-stream/index.js +92 -87
- package/node_modules/pino-http/node_modules/thread-stream/lib/indexes.js +3 -1
- package/node_modules/pino-http/node_modules/thread-stream/lib/wait.js +11 -2
- package/node_modules/pino-http/node_modules/thread-stream/lib/worker.js +27 -9
- package/node_modules/pino-http/node_modules/thread-stream/package.json +2 -2
- package/node_modules/pino-http/node_modules/thread-stream/test/multibyte-overrun.test.mjs +33 -0
- package/package.json +6 -6
- package/node_modules/@zuplo/runtime/out/esm/chunk-P4G7GA42.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-P4G7GA42.js.LEGAL.txt → chunk-TEXCCTJT.js.LEGAL.txt} +0 -0
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
* DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*--------------------------------------------------------------------------------------------*/
|
|
24
24
|
|
|
25
|
-
import{b as d}from"../chunk-
|
|
25
|
+
import{b as d}from"../chunk-F7DJMBWH.js";import{_ as n,a as t}from"../chunk-JAEQKE5H.js";function g(u={request:new Request("https://api.example.com")}){let e=[];function o(i){e.push(Promise.resolve(i))}return t(o,"waitUntil"),{context:new s({event:{waitUntil:o},route:u.route}),invokeResponse:t(async()=>{await Promise.all(e)},"invokeResponse")}}t(g,"createMockContext");var p={path:"/",methods:["GET"],handler:{module:{},export:"default"},raw:t(()=>({}),"raw")},s=class extends EventTarget{static{t(this,"MockZuploContext")}#e;contextId;requestId;log;route;custom;incomingRequestProperties;parentContext;analyticsContext;constructor({event:e,route:o=p,parentContext:r}){super(),this.requestId=crypto.randomUUID(),this.contextId=crypto.randomUUID(),this.log={info:n.console.info,log:n.console.log,debug:n.console.debug,warn:n.console.warn,error:n.console.error,setLogProperties:t(()=>{},"setLogProperties")},this.custom={},this.route=o,this.incomingRequestProperties={asn:1234,asOrganization:"ORGANIZATION",city:"Seattle",region:"Washington",regionCode:"WA",colo:"SEA",continent:"NA",country:"US",postalCode:"98004",metroCode:"SEA",latitude:void 0,longitude:void 0,timezone:void 0,httpProtocol:void 0,clientCert:void 0,clientMtlsVerificationStatus:void 0,clientMtlsVerificationReason:void 0},this.parentContext=r,this.#e=e,this.analyticsContext=new d(this.requestId)}waitUntil(e){this.#e.waitUntil(e)}invokeInboundPolicy(e,o){throw new Error("Not implemented")}invokeOutboundPolicy(e,o,r){throw new Error("Not implemented")}invokeRoute(e,o){throw new Error("Not implemented")}addResponseSendingHook(e){throw new Error("Not implemented")}addResponseSendingFinalHook(e){throw new Error("Not implemented")}addEventListener(e,o,r){let l=t(i=>{try{typeof o=="function"?o(i):o.handleEvent(i)}catch(a){throw this.log.error(`Error invoking event ${e}. See following logs for details.`),a}},"wrapped");super.addEventListener(e,l,r)}};export{s as MockZuploContext,g as createMockContext};
|
|
26
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -8957,6 +8957,42 @@ export declare interface SetStatusOutboundPolicyOptions {
|
|
|
8957
8957
|
statusText?: string;
|
|
8958
8958
|
}
|
|
8959
8959
|
|
|
8960
|
+
/**
|
|
8961
|
+
* Sets a single header on the incoming request, typically used to attach an
|
|
8962
|
+
* API key for the upstream service. A more directed version of the
|
|
8963
|
+
* `SetHeadersInboundPolicy` that defaults the header name to `Authorization`
|
|
8964
|
+
* and is intended to be used with an `$env()` reference for the value.
|
|
8965
|
+
*
|
|
8966
|
+
* @title Set Upstream API Key
|
|
8967
|
+
* @product api-gateway
|
|
8968
|
+
* @public
|
|
8969
|
+
* @param request - The ZuploRequest
|
|
8970
|
+
* @param context - The ZuploContext
|
|
8971
|
+
* @param options - The policy options set in policies.json
|
|
8972
|
+
* @param policyName - The name of the policy as set in policies.json
|
|
8973
|
+
* @returns A Request or a Response
|
|
8974
|
+
*/
|
|
8975
|
+
export declare const SetUpstreamApiKeyInboundPolicy: InboundPolicyHandler<SetUpstreamApiKeyInboundPolicyOptions>;
|
|
8976
|
+
|
|
8977
|
+
/**
|
|
8978
|
+
* The options for this policy.
|
|
8979
|
+
* @public
|
|
8980
|
+
*/
|
|
8981
|
+
export declare interface SetUpstreamApiKeyInboundPolicyOptions {
|
|
8982
|
+
/**
|
|
8983
|
+
* The name of the header to set on the request. Defaults to `Authorization`.
|
|
8984
|
+
*/
|
|
8985
|
+
header?: string;
|
|
8986
|
+
/**
|
|
8987
|
+
* The value of the header. Most commonly an environment variable reference such as `Bearer $env(UPSTREAM_API_KEY)` so the secret is sourced from your environment.
|
|
8988
|
+
*/
|
|
8989
|
+
value: string;
|
|
8990
|
+
/**
|
|
8991
|
+
* Overwrite the value if the header is already present in the request.
|
|
8992
|
+
*/
|
|
8993
|
+
overwrite?: boolean;
|
|
8994
|
+
}
|
|
8995
|
+
|
|
8960
8996
|
/**
|
|
8961
8997
|
* Function type for determining if a request should be logged
|
|
8962
8998
|
* @public
|
|
@@ -973,7 +973,7 @@ export declare interface McpAuth0OAuthInboundPolicyOptions {
|
|
|
973
973
|
/**
|
|
974
974
|
* The Auth0 client_secret. Use $env(...) to source from a secret environment variable.
|
|
975
975
|
*/
|
|
976
|
-
clientSecret
|
|
976
|
+
clientSecret: string;
|
|
977
977
|
/**
|
|
978
978
|
* OIDC scopes requested during browser login.
|
|
979
979
|
*/
|
|
@@ -1076,9 +1076,9 @@ export declare interface McpOAuthInboundPolicyOptions {
|
|
|
1076
1076
|
*/
|
|
1077
1077
|
jwksUrl: string;
|
|
1078
1078
|
/**
|
|
1079
|
-
*
|
|
1079
|
+
* Optional IdP audience value. Leave unset when browser login ID tokens use the OIDC client_id as their audience.
|
|
1080
1080
|
*/
|
|
1081
|
-
audience
|
|
1081
|
+
audience?: string;
|
|
1082
1082
|
};
|
|
1083
1083
|
/**
|
|
1084
1084
|
* Browser-side OAuth/OIDC settings used when the gateway redirects the user to the identity provider for login.
|
|
@@ -1097,7 +1097,7 @@ export declare interface McpOAuthInboundPolicyOptions {
|
|
|
1097
1097
|
*/
|
|
1098
1098
|
clientId?: string;
|
|
1099
1099
|
/**
|
|
1100
|
-
* The OIDC client_secret. Use $env(...) to source from a secret environment variable.
|
|
1100
|
+
* The OIDC client_secret. Required for federated browser login. Use $env(...) to source from a secret environment variable.
|
|
1101
1101
|
*/
|
|
1102
1102
|
clientSecret?: string;
|
|
1103
1103
|
/**
|
|
@@ -8,18 +8,33 @@ const { pathToFileURL } = require('url')
|
|
|
8
8
|
const { wait } = require('./lib/wait')
|
|
9
9
|
const {
|
|
10
10
|
WRITE_INDEX,
|
|
11
|
-
READ_INDEX
|
|
11
|
+
READ_INDEX,
|
|
12
|
+
SEQ_INDEX
|
|
12
13
|
} = require('./lib/indexes')
|
|
13
14
|
const buffer = require('buffer')
|
|
14
15
|
const assert = require('assert')
|
|
15
16
|
|
|
16
17
|
const kImpl = Symbol('kImpl')
|
|
17
18
|
|
|
18
|
-
//
|
|
19
|
+
// Maximum pending buffered data before forcing a synchronous drain
|
|
19
20
|
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
|
|
20
21
|
|
|
21
22
|
function noop () {}
|
|
22
23
|
|
|
24
|
+
function updateState (stream, fn) {
|
|
25
|
+
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
|
|
26
|
+
fn()
|
|
27
|
+
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
|
|
28
|
+
Atomics.notify(stream[kImpl].state, SEQ_INDEX)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resetIndexes (stream) {
|
|
32
|
+
updateState(stream, () => {
|
|
33
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
34
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
class FakeWeakRef {
|
|
24
39
|
constructor (value) {
|
|
25
40
|
this._value = value
|
|
@@ -93,66 +108,46 @@ function drain (stream) {
|
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
function nextFlush (stream) {
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
while (true) {
|
|
112
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
113
|
+
const leftover = stream[kImpl].data.length - writeIndex
|
|
114
|
+
|
|
115
|
+
if (leftover > 0) {
|
|
116
|
+
if (stream[kImpl].bufLen === 0) {
|
|
117
|
+
stream[kImpl].flushing = false
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
if (stream[kImpl].ending) {
|
|
120
|
+
end(stream)
|
|
121
|
+
} else if (stream[kImpl].needDrain) {
|
|
122
|
+
process.nextTick(drain, stream)
|
|
123
|
+
}
|
|
102
124
|
|
|
103
|
-
|
|
104
|
-
end(stream)
|
|
105
|
-
} else if (stream[kImpl].needDrain) {
|
|
106
|
-
process.nextTick(drain, stream)
|
|
125
|
+
return
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
write(stream, leftover, noop)
|
|
129
|
+
continue
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
118
|
-
} else {
|
|
119
|
-
// multi-byte utf-8
|
|
132
|
+
if (leftover === 0) {
|
|
133
|
+
if (writeIndex === 0 && stream[kImpl].bufLen === 0) {
|
|
134
|
+
// we had a flushSync in the meanwhile
|
|
135
|
+
return
|
|
136
|
+
}
|
|
120
137
|
waitForRead(stream, () => {
|
|
121
|
-
// err is already handled in waitForRead()
|
|
122
138
|
if (stream.destroyed) {
|
|
123
139
|
return
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
129
|
-
|
|
130
|
-
// Find a toWrite length that fits the buffer
|
|
131
|
-
// it must exists as the buffer is at least 4 bytes length
|
|
132
|
-
// and the max utf-8 length for a char is 4 bytes.
|
|
133
|
-
while (toWriteBytes > stream[kImpl].data.length) {
|
|
134
|
-
leftover = leftover / 2
|
|
135
|
-
toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
136
|
-
toWriteBytes = Buffer.byteLength(toWrite)
|
|
137
|
-
}
|
|
138
|
-
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
139
|
-
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
142
|
+
resetIndexes(stream)
|
|
143
|
+
nextFlush(stream)
|
|
140
144
|
})
|
|
141
|
-
}
|
|
142
|
-
} else if (leftover === 0) {
|
|
143
|
-
if (writeIndex === 0 && stream[kImpl].buf.length === 0) {
|
|
144
|
-
// we had a flushSync in the meanwhile
|
|
145
145
|
return
|
|
146
146
|
}
|
|
147
|
-
|
|
148
|
-
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
149
|
-
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
150
|
-
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
151
|
-
nextFlush(stream)
|
|
152
|
-
})
|
|
153
|
-
} else {
|
|
147
|
+
|
|
154
148
|
// This should never happen
|
|
155
149
|
destroy(stream, new Error('overwritten'))
|
|
150
|
+
return
|
|
156
151
|
}
|
|
157
152
|
}
|
|
158
153
|
|
|
@@ -248,7 +243,9 @@ class ThreadStream extends EventEmitter {
|
|
|
248
243
|
this[kImpl].finished = false
|
|
249
244
|
this[kImpl].errored = null
|
|
250
245
|
this[kImpl].closed = false
|
|
251
|
-
this[kImpl].buf =
|
|
246
|
+
this[kImpl].buf = []
|
|
247
|
+
this[kImpl].bufHead = 0
|
|
248
|
+
this[kImpl].bufLen = 0
|
|
252
249
|
this[kImpl].flushCallbacks = new Map()
|
|
253
250
|
this[kImpl].nextFlushId = 0
|
|
254
251
|
|
|
@@ -260,6 +257,7 @@ class ThreadStream extends EventEmitter {
|
|
|
260
257
|
}
|
|
261
258
|
|
|
262
259
|
write (data) {
|
|
260
|
+
const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data)
|
|
263
261
|
if (this[kImpl].destroyed) {
|
|
264
262
|
error(this, new Error('the worker has exited'))
|
|
265
263
|
return false
|
|
@@ -270,7 +268,7 @@ class ThreadStream extends EventEmitter {
|
|
|
270
268
|
return false
|
|
271
269
|
}
|
|
272
270
|
|
|
273
|
-
if (this[kImpl].flushing && this[kImpl].
|
|
271
|
+
if (this[kImpl].flushing && this[kImpl].bufLen + dataBuf.length >= MAX_STRING) {
|
|
274
272
|
try {
|
|
275
273
|
writeSync(this)
|
|
276
274
|
this[kImpl].flushing = true
|
|
@@ -280,7 +278,8 @@ class ThreadStream extends EventEmitter {
|
|
|
280
278
|
}
|
|
281
279
|
}
|
|
282
280
|
|
|
283
|
-
this[kImpl].buf
|
|
281
|
+
this[kImpl].buf.push(dataBuf)
|
|
282
|
+
this[kImpl].bufLen += dataBuf.length
|
|
284
283
|
|
|
285
284
|
if (this[kImpl].sync) {
|
|
286
285
|
try {
|
|
@@ -297,7 +296,7 @@ class ThreadStream extends EventEmitter {
|
|
|
297
296
|
setImmediate(nextFlush, this)
|
|
298
297
|
}
|
|
299
298
|
|
|
300
|
-
this[kImpl].needDrain = this[kImpl].data.length - this[kImpl].
|
|
299
|
+
this[kImpl].needDrain = this[kImpl].data.length - this[kImpl].bufLen - Atomics.load(this[kImpl].state, WRITE_INDEX) <= 0
|
|
301
300
|
return !this[kImpl].needDrain
|
|
302
301
|
}
|
|
303
302
|
|
|
@@ -383,7 +382,7 @@ function flushBuffer (stream, cb) {
|
|
|
383
382
|
return
|
|
384
383
|
}
|
|
385
384
|
|
|
386
|
-
if (!stream[kImpl].sync && (stream[kImpl].flushing || stream[kImpl].
|
|
385
|
+
if (!stream[kImpl].sync && (stream[kImpl].flushing || stream[kImpl].bufLen > 0)) {
|
|
387
386
|
setImmediate(flushBuffer, stream, cb)
|
|
388
387
|
return
|
|
389
388
|
}
|
|
@@ -497,13 +496,43 @@ function destroy (stream, err) {
|
|
|
497
496
|
}
|
|
498
497
|
}
|
|
499
498
|
|
|
500
|
-
function write (stream,
|
|
499
|
+
function write (stream, maxBytes, cb) {
|
|
501
500
|
// data is smaller than the shared buffer length
|
|
502
501
|
const current = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
502
|
+
let offset = current
|
|
503
|
+
let remaining = maxBytes
|
|
504
|
+
|
|
505
|
+
while (remaining > 0 && stream[kImpl].bufLen !== 0) {
|
|
506
|
+
const head = stream[kImpl].bufHead
|
|
507
|
+
const buf = stream[kImpl].buf[head]
|
|
508
|
+
|
|
509
|
+
if (buf.length <= remaining) {
|
|
510
|
+
buf.copy(stream[kImpl].data, offset)
|
|
511
|
+
offset += buf.length
|
|
512
|
+
remaining -= buf.length
|
|
513
|
+
stream[kImpl].bufLen -= buf.length
|
|
514
|
+
stream[kImpl].bufHead = head + 1
|
|
515
|
+
|
|
516
|
+
if (stream[kImpl].bufHead === stream[kImpl].buf.length) {
|
|
517
|
+
stream[kImpl].buf.length = 0
|
|
518
|
+
stream[kImpl].bufHead = 0
|
|
519
|
+
} else if (stream[kImpl].bufHead >= 1024 && stream[kImpl].bufHead * 2 >= stream[kImpl].buf.length) {
|
|
520
|
+
stream[kImpl].buf.splice(0, stream[kImpl].bufHead)
|
|
521
|
+
stream[kImpl].bufHead = 0
|
|
522
|
+
}
|
|
523
|
+
continue
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
buf.copy(stream[kImpl].data, offset, 0, remaining)
|
|
527
|
+
stream[kImpl].buf[head] = buf.subarray(remaining)
|
|
528
|
+
stream[kImpl].bufLen -= remaining
|
|
529
|
+
offset += remaining
|
|
530
|
+
remaining = 0
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
updateState(stream, () => {
|
|
534
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, offset)
|
|
535
|
+
})
|
|
507
536
|
cb()
|
|
508
537
|
return true
|
|
509
538
|
}
|
|
@@ -520,9 +549,10 @@ function end (stream) {
|
|
|
520
549
|
let readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
|
|
521
550
|
|
|
522
551
|
// process._rawDebug('writing index')
|
|
523
|
-
|
|
552
|
+
updateState(stream, () => {
|
|
553
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, -1)
|
|
554
|
+
})
|
|
524
555
|
// process._rawDebug(`(end) readIndex (${Atomics.load(stream.state, READ_INDEX)}) writeIndex (${Atomics.load(stream.state, WRITE_INDEX)})`)
|
|
525
|
-
Atomics.notify(stream[kImpl].state, WRITE_INDEX)
|
|
526
556
|
|
|
527
557
|
// Wait for the process to complete
|
|
528
558
|
let spins = 0
|
|
@@ -562,44 +592,19 @@ function writeSync (stream) {
|
|
|
562
592
|
}
|
|
563
593
|
stream[kImpl].flushing = false
|
|
564
594
|
|
|
565
|
-
while (stream[kImpl].
|
|
595
|
+
while (stream[kImpl].bufLen !== 0) {
|
|
566
596
|
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
567
|
-
|
|
597
|
+
const leftover = stream[kImpl].data.length - writeIndex
|
|
568
598
|
if (leftover === 0) {
|
|
569
599
|
flushSync(stream)
|
|
570
|
-
|
|
571
|
-
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
572
|
-
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
600
|
+
resetIndexes(stream)
|
|
573
601
|
continue
|
|
574
602
|
} else if (leftover < 0) {
|
|
575
603
|
// stream should never happen
|
|
576
604
|
throw new Error('overwritten')
|
|
577
605
|
}
|
|
578
606
|
|
|
579
|
-
|
|
580
|
-
let toWriteBytes = Buffer.byteLength(toWrite)
|
|
581
|
-
if (toWriteBytes <= leftover) {
|
|
582
|
-
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
583
|
-
// process._rawDebug('writing ' + toWrite.length)
|
|
584
|
-
write(stream, toWrite, cb)
|
|
585
|
-
} else {
|
|
586
|
-
// multi-byte utf-8
|
|
587
|
-
flushSync(stream)
|
|
588
|
-
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
589
|
-
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
590
|
-
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
591
|
-
|
|
592
|
-
// Find a toWrite length that fits the buffer
|
|
593
|
-
// it must exists as the buffer is at least 4 bytes length
|
|
594
|
-
// and the max utf-8 length for a char is 4 bytes.
|
|
595
|
-
while (toWriteBytes > stream[kImpl].buf.length) {
|
|
596
|
-
leftover = leftover / 2
|
|
597
|
-
toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
598
|
-
toWriteBytes = Buffer.byteLength(toWrite)
|
|
599
|
-
}
|
|
600
|
-
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
601
|
-
write(stream, toWrite, cb)
|
|
602
|
-
}
|
|
607
|
+
write(stream, leftover, cb)
|
|
603
608
|
}
|
|
604
609
|
}
|
|
605
610
|
|
|
@@ -50,12 +50,21 @@ function waitDiff (state, index, expected, timeout, done) {
|
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Wait for value to change from expected
|
|
53
|
+
// Wait for value to change from expected.
|
|
54
|
+
// If we are notified, resume immediately even if the value cycled back
|
|
55
|
+
// to the same number before we could re-read it.
|
|
54
56
|
const remaining = max === Infinity ? WAIT_MS : Math.min(WAIT_MS, Math.max(1, max - Date.now()))
|
|
55
57
|
const result = Atomics.waitAsync(state, index, expected, remaining)
|
|
56
58
|
|
|
57
59
|
if (result.async) {
|
|
58
|
-
result.value.then(
|
|
60
|
+
result.value.then((res) => {
|
|
61
|
+
if (res === 'ok') {
|
|
62
|
+
done(null, 'ok')
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
check()
|
|
67
|
+
})
|
|
59
68
|
} else {
|
|
60
69
|
// Value already changed (not-equal) - recheck on next tick
|
|
61
70
|
setImmediate(check)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { realImport, realRequire } = require('real-require')
|
|
4
4
|
const { workerData, parentPort } = require('worker_threads')
|
|
5
|
-
const {
|
|
5
|
+
const { StringDecoder } = require('string_decoder')
|
|
6
|
+
const { WRITE_INDEX, READ_INDEX, SEQ_INDEX } = require('./indexes')
|
|
6
7
|
const { waitDiff } = require('./wait')
|
|
7
8
|
|
|
8
9
|
const {
|
|
@@ -17,6 +18,7 @@ let flushing = false
|
|
|
17
18
|
|
|
18
19
|
const state = new Int32Array(stateBuf)
|
|
19
20
|
const data = Buffer.from(dataBuf)
|
|
21
|
+
const decoder = new StringDecoder('utf8')
|
|
20
22
|
|
|
21
23
|
// Keep the event loop alive - Atomics.waitAsync promises don't prevent worker exit
|
|
22
24
|
const keepAlive = setInterval(() => {}, 60 * 60 * 1000)
|
|
@@ -207,18 +209,30 @@ start().then(function () {
|
|
|
207
209
|
process.nextTick(run)
|
|
208
210
|
})
|
|
209
211
|
|
|
212
|
+
function readState () {
|
|
213
|
+
while (true) {
|
|
214
|
+
const seq = Atomics.load(state, SEQ_INDEX)
|
|
215
|
+
|
|
216
|
+
if ((seq & 1) !== 0) {
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const current = Atomics.load(state, READ_INDEX)
|
|
221
|
+
const end = Atomics.load(state, WRITE_INDEX)
|
|
222
|
+
|
|
223
|
+
if (seq === Atomics.load(state, SEQ_INDEX)) {
|
|
224
|
+
return { current, end, seq }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
210
229
|
function run () {
|
|
211
|
-
const current =
|
|
212
|
-
const end = Atomics.load(state, WRITE_INDEX)
|
|
230
|
+
const { current, end, seq } = readState()
|
|
213
231
|
|
|
214
232
|
// process._rawDebug(`pre state ${current} ${end}`)
|
|
215
233
|
|
|
216
234
|
if (end === current) {
|
|
217
|
-
|
|
218
|
-
waitDiff(state, READ_INDEX, end, Infinity, run)
|
|
219
|
-
} else {
|
|
220
|
-
waitDiff(state, WRITE_INDEX, end, Infinity, run)
|
|
221
|
-
}
|
|
235
|
+
waitDiff(state, SEQ_INDEX, seq, Infinity, run)
|
|
222
236
|
return
|
|
223
237
|
}
|
|
224
238
|
|
|
@@ -226,11 +240,15 @@ function run () {
|
|
|
226
240
|
|
|
227
241
|
if (end === -1) {
|
|
228
242
|
// process._rawDebug('end')
|
|
243
|
+
const remaining = decoder.end()
|
|
244
|
+
if (remaining.length > 0) {
|
|
245
|
+
destination.write(remaining)
|
|
246
|
+
}
|
|
229
247
|
destination.end()
|
|
230
248
|
return
|
|
231
249
|
}
|
|
232
250
|
|
|
233
|
-
const toWrite = data.
|
|
251
|
+
const toWrite = decoder.write(data.subarray(current, end))
|
|
234
252
|
// process._rawDebug('worker writing: ' + toWrite)
|
|
235
253
|
|
|
236
254
|
const res = destination.write(toWrite)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thread-stream",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "A streaming way to send data to a Node.js Worker Thread",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"eslint": "^9.39.1",
|
|
19
19
|
"fastbench": "^1.0.1",
|
|
20
20
|
"neostandard": "^0.13.0",
|
|
21
|
-
"pino-elasticsearch": "^
|
|
21
|
+
"pino-elasticsearch": "^9.0.0",
|
|
22
22
|
"sonic-boom": "^5.0.0",
|
|
23
23
|
"ts-node": "^10.8.0",
|
|
24
24
|
"typescript": "~5.7.3"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import ThreadStream from '../index.js'
|
|
5
|
+
import { join } from 'desm'
|
|
6
|
+
import { file } from './helper.js'
|
|
7
|
+
|
|
8
|
+
test('preserves multibyte records that cross the buffer boundary', async () => {
|
|
9
|
+
const dest = file()
|
|
10
|
+
const stream = new ThreadStream({
|
|
11
|
+
bufferSize: 128,
|
|
12
|
+
filename: join(import.meta.url, 'to-file.js'),
|
|
13
|
+
workerData: { dest },
|
|
14
|
+
sync: false
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
let expected = ''
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < 1000; i++) {
|
|
20
|
+
const line = `{"idx":${i},"alert":"🚨"}\n`
|
|
21
|
+
expected += line
|
|
22
|
+
stream.write(line)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await new Promise((resolve, reject) => {
|
|
26
|
+
stream.once('error', reject)
|
|
27
|
+
stream.once('close', resolve)
|
|
28
|
+
stream.end()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const data = await readFile(dest, 'utf8')
|
|
32
|
+
assert.strictEqual(data, expected)
|
|
33
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuplo/cli",
|
|
3
|
-
"version": "6.70.
|
|
3
|
+
"version": "6.70.28",
|
|
4
4
|
"repository": "https://github.com/zuplo/zuplo",
|
|
5
5
|
"author": "Zuplo, Inc.",
|
|
6
6
|
"type": "module",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"@opentelemetry/api": "1.9.0",
|
|
30
30
|
"@sentry/node": "9.22.0",
|
|
31
31
|
"@swc/core": "1.10.18",
|
|
32
|
-
"@zuplo/core": "6.70.
|
|
32
|
+
"@zuplo/core": "6.70.28",
|
|
33
33
|
"@zuplo/editor": "1.0.20821740935",
|
|
34
|
-
"@zuplo/openapi-tools": "6.70.
|
|
35
|
-
"@zuplo/runtime": "6.70.
|
|
34
|
+
"@zuplo/openapi-tools": "6.70.28",
|
|
35
|
+
"@zuplo/runtime": "6.70.28",
|
|
36
36
|
"chalk": "5.4.1",
|
|
37
37
|
"chokidar": "3.5.3",
|
|
38
38
|
"cookie": "1.0.2",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"workerd": "1.20241230.0",
|
|
67
67
|
"yargs": "17.7.2",
|
|
68
68
|
"zod": "3.25.76",
|
|
69
|
-
"@zuplo/graphql": "6.70.
|
|
70
|
-
"@zuplo/otel": "6.70.
|
|
69
|
+
"@zuplo/graphql": "6.70.28",
|
|
70
|
+
"@zuplo/otel": "6.70.28"
|
|
71
71
|
},
|
|
72
72
|
"bundleDependencies": [
|
|
73
73
|
"@fastify/cors",
|