@durable-streams/cli 0.1.9 → 0.2.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.
- package/dist/index.cjs +148 -32
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +148 -33
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,34 @@ function* flattenJsonForAppend(parsed) {
|
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/parseWriteArgs.ts
|
|
56
56
|
/**
|
|
57
|
+
* Extract a flag value from args, supporting both --flag=value and --flag value syntax.
|
|
58
|
+
* Returns { value, consumed } where consumed is the number of args used (0 if no match).
|
|
59
|
+
*/
|
|
60
|
+
function extractFlagValue$1(args, index, flagName) {
|
|
61
|
+
const arg = args[index];
|
|
62
|
+
const prefix = `${flagName}=`;
|
|
63
|
+
if (arg.startsWith(prefix)) {
|
|
64
|
+
const value = arg.slice(prefix.length);
|
|
65
|
+
if (!value) throw new Error(`${flagName} requires a value`);
|
|
66
|
+
return {
|
|
67
|
+
value,
|
|
68
|
+
consumed: 1
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (arg === flagName) {
|
|
72
|
+
const value = args[index + 1];
|
|
73
|
+
if (!value || value.startsWith(`--`)) throw new Error(`${flagName} requires a value`);
|
|
74
|
+
return {
|
|
75
|
+
value,
|
|
76
|
+
consumed: 2
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
value: null,
|
|
81
|
+
consumed: 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
57
85
|
* Parse write command arguments, extracting content-type flags and content.
|
|
58
86
|
* @param args - Arguments after the stream_id (starting from index 2)
|
|
59
87
|
* @returns Parsed content type and content string
|
|
@@ -74,11 +102,10 @@ function parseWriteArgs(args) {
|
|
|
74
102
|
contentType = `application/json`;
|
|
75
103
|
continue;
|
|
76
104
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
i++;
|
|
105
|
+
const contentTypeResult = extractFlagValue$1(args, i, `--content-type`);
|
|
106
|
+
if (contentTypeResult.value !== null) {
|
|
107
|
+
contentType = contentTypeResult.value;
|
|
108
|
+
i += contentTypeResult.consumed - 1;
|
|
82
109
|
continue;
|
|
83
110
|
}
|
|
84
111
|
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
@@ -129,6 +156,20 @@ function normalizeBaseUrl(url) {
|
|
|
129
156
|
return url.replace(/\/+$/, ``);
|
|
130
157
|
}
|
|
131
158
|
/**
|
|
159
|
+
* Build the full stream URL from a base URL and stream ID.
|
|
160
|
+
* Simply concatenates the stream ID to the base URL.
|
|
161
|
+
*
|
|
162
|
+
* Examples:
|
|
163
|
+
* buildStreamUrl("http://localhost:4437/v1/stream", "my-stream")
|
|
164
|
+
* => "http://localhost:4437/v1/stream/my-stream"
|
|
165
|
+
*
|
|
166
|
+
* buildStreamUrl("http://localhost:3002/v1/stream/my-group", "my-stream")
|
|
167
|
+
* => "http://localhost:3002/v1/stream/my-group/my-stream"
|
|
168
|
+
*/
|
|
169
|
+
function buildStreamUrl(baseUrl, streamId) {
|
|
170
|
+
return `${baseUrl}/${streamId}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
132
173
|
* Validate an authorization header value.
|
|
133
174
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
134
175
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -176,9 +217,37 @@ function validateStreamId(streamId) {
|
|
|
176
217
|
|
|
177
218
|
//#endregion
|
|
178
219
|
//#region src/index.ts
|
|
179
|
-
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437`;
|
|
220
|
+
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437/v1/stream`;
|
|
180
221
|
const STREAM_AUTH = process.env.STREAM_AUTH;
|
|
181
222
|
/**
|
|
223
|
+
* Extract a flag value from args, supporting both --flag=value and --flag value syntax.
|
|
224
|
+
* Returns { value, consumed } where consumed is the number of args used (0 if no match).
|
|
225
|
+
*/
|
|
226
|
+
function extractFlagValue(args, index, flagName, example) {
|
|
227
|
+
const arg = args[index];
|
|
228
|
+
const prefix = `${flagName}=`;
|
|
229
|
+
if (arg.startsWith(prefix)) {
|
|
230
|
+
const value = arg.slice(prefix.length);
|
|
231
|
+
if (!value) throw new Error(`${flagName} requires a value\n Example: ${flagName}="${example}"`);
|
|
232
|
+
return {
|
|
233
|
+
value,
|
|
234
|
+
consumed: 1
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (arg === flagName) {
|
|
238
|
+
const value = args[index + 1];
|
|
239
|
+
if (!value || value.startsWith(`--`)) throw new Error(`${flagName} requires a value\n Example: ${flagName} "${example}"`);
|
|
240
|
+
return {
|
|
241
|
+
value,
|
|
242
|
+
consumed: 2
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
value: null,
|
|
247
|
+
consumed: 0
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
182
251
|
* Parse global options (--url, --auth) from args.
|
|
183
252
|
* Falls back to STREAM_URL/STREAM_AUTH env vars when flags not provided.
|
|
184
253
|
* Returns the parsed options, remaining args, and any warnings.
|
|
@@ -189,22 +258,24 @@ function parseGlobalOptions(args) {
|
|
|
189
258
|
const warnings = [];
|
|
190
259
|
for (let i = 0; i < args.length; i++) {
|
|
191
260
|
const arg = args[i];
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const urlValidation = validateUrl(value);
|
|
261
|
+
const urlResult = extractFlagValue(args, i, `--url`, `http://localhost:4437`);
|
|
262
|
+
if (urlResult.value !== null) {
|
|
263
|
+
const urlValidation = validateUrl(urlResult.value);
|
|
196
264
|
if (!urlValidation.valid) throw new Error(urlValidation.error);
|
|
197
|
-
options.url = normalizeBaseUrl(value);
|
|
198
|
-
i
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
265
|
+
options.url = normalizeBaseUrl(urlResult.value);
|
|
266
|
+
i += urlResult.consumed - 1;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const authResult = extractFlagValue(args, i, `--auth`, `Bearer my-token`);
|
|
270
|
+
if (authResult.value !== null) {
|
|
271
|
+
const authValidation = validateAuth(authResult.value);
|
|
203
272
|
if (!authValidation.valid) throw new Error(authValidation.error);
|
|
204
273
|
if (authValidation.warning) warnings.push(authValidation.warning);
|
|
205
|
-
options.auth = value;
|
|
206
|
-
i
|
|
207
|
-
|
|
274
|
+
options.auth = authResult.value;
|
|
275
|
+
i += authResult.consumed - 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
remainingArgs.push(arg);
|
|
208
279
|
}
|
|
209
280
|
if (!options.url) {
|
|
210
281
|
const urlValidation = validateUrl(STREAM_URL);
|
|
@@ -243,13 +314,15 @@ Global Options:
|
|
|
243
314
|
--auth <value> Authorization header value (e.g., "Bearer my-token")
|
|
244
315
|
--help, -h Show this help message
|
|
245
316
|
|
|
246
|
-
Write Options:
|
|
247
|
-
--content-type <type> Content-Type for the
|
|
248
|
-
--json
|
|
317
|
+
Create/Write Options:
|
|
318
|
+
--content-type <type> Content-Type for the stream (default: application/octet-stream)
|
|
319
|
+
--json Shorthand for --content-type application/json
|
|
320
|
+
|
|
321
|
+
Write-only Options:
|
|
249
322
|
--batch-json Write as JSON array of messages (each array element stored separately)
|
|
250
323
|
|
|
251
324
|
Environment Variables:
|
|
252
|
-
STREAM_URL Base URL of the stream server (default: http://localhost:4437)
|
|
325
|
+
STREAM_URL Base URL of the stream server (default: http://localhost:4437/v1/stream)
|
|
253
326
|
STREAM_AUTH Authorization header value (overridden by --auth flag)
|
|
254
327
|
`;
|
|
255
328
|
}
|
|
@@ -257,13 +330,38 @@ function printUsage({ to = `stderr` } = {}) {
|
|
|
257
330
|
const out = to === `stderr` ? node_process.stderr : node_process.stdout;
|
|
258
331
|
out.write(getUsageText());
|
|
259
332
|
}
|
|
260
|
-
|
|
261
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Parse create command arguments, extracting content-type flags.
|
|
335
|
+
* @param args - Arguments after the stream_id
|
|
336
|
+
* @returns Parsed content type
|
|
337
|
+
* @throws Error if --content-type is missing its value or if unknown flags are provided
|
|
338
|
+
*/
|
|
339
|
+
function parseCreateArgs(args) {
|
|
340
|
+
let contentType = `application/octet-stream`;
|
|
341
|
+
for (let i = 0; i < args.length; i++) {
|
|
342
|
+
const arg = args[i];
|
|
343
|
+
if (arg === `--json`) {
|
|
344
|
+
contentType = `application/json`;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const contentTypeResult = extractFlagValue(args, i, `--content-type`, `application/json`);
|
|
348
|
+
if (contentTypeResult.value !== null) {
|
|
349
|
+
contentType = contentTypeResult.value;
|
|
350
|
+
i += contentTypeResult.consumed - 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
354
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
355
|
+
}
|
|
356
|
+
return { contentType };
|
|
357
|
+
}
|
|
358
|
+
async function createStream(baseUrl, streamId, headers, contentType) {
|
|
359
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
262
360
|
try {
|
|
263
361
|
await __durable_streams_client.DurableStream.create({
|
|
264
362
|
url,
|
|
265
363
|
headers,
|
|
266
|
-
contentType
|
|
364
|
+
contentType
|
|
267
365
|
});
|
|
268
366
|
console.log(`Stream created successfully: "${streamId}"`);
|
|
269
367
|
console.log(` URL: ${url}`);
|
|
@@ -323,7 +421,7 @@ function processEscapeSequences(content) {
|
|
|
323
421
|
return content.replace(/\\n/g, `\n`).replace(/\\t/g, `\t`).replace(/\\r/g, `\r`).replace(/\\\\/g, `\\`);
|
|
324
422
|
}
|
|
325
423
|
async function writeStream(baseUrl, streamId, contentType, batchJson, headers, content) {
|
|
326
|
-
const url =
|
|
424
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
327
425
|
const isJson = isJsonContentType(contentType);
|
|
328
426
|
let data;
|
|
329
427
|
let source;
|
|
@@ -388,7 +486,7 @@ function formatBytes(bytes) {
|
|
|
388
486
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
389
487
|
}
|
|
390
488
|
async function readStream(baseUrl, streamId, headers) {
|
|
391
|
-
const url =
|
|
489
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
392
490
|
try {
|
|
393
491
|
const stream = new __durable_streams_client.DurableStream({
|
|
394
492
|
url,
|
|
@@ -403,7 +501,7 @@ async function readStream(baseUrl, streamId, headers) {
|
|
|
403
501
|
}
|
|
404
502
|
}
|
|
405
503
|
async function deleteStream(baseUrl, streamId, headers) {
|
|
406
|
-
const url =
|
|
504
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
407
505
|
try {
|
|
408
506
|
const stream = new __durable_streams_client.DurableStream({
|
|
409
507
|
url,
|
|
@@ -460,7 +558,14 @@ async function main() {
|
|
|
460
558
|
switch (command) {
|
|
461
559
|
case `create`: {
|
|
462
560
|
const streamId = getStreamId();
|
|
463
|
-
|
|
561
|
+
let createArgs;
|
|
562
|
+
try {
|
|
563
|
+
createArgs = parseCreateArgs(args.slice(2));
|
|
564
|
+
} catch (error) {
|
|
565
|
+
node_process.stderr.write(`Error: ${getErrorMessage(error)}\n`);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
await createStream(options.url, streamId, headers, createArgs.contentType);
|
|
464
569
|
break;
|
|
465
570
|
}
|
|
466
571
|
case `write`: {
|
|
@@ -493,14 +598,24 @@ async function main() {
|
|
|
493
598
|
await deleteStream(options.url, streamId, headers);
|
|
494
599
|
break;
|
|
495
600
|
}
|
|
496
|
-
default:
|
|
497
|
-
|
|
601
|
+
default: {
|
|
602
|
+
const commandSpecificFlags = [
|
|
603
|
+
`--content-type`,
|
|
604
|
+
`--json`,
|
|
605
|
+
`--batch-json`
|
|
606
|
+
];
|
|
607
|
+
if (command && commandSpecificFlags.includes(command)) {
|
|
608
|
+
node_process.stderr.write(`Error: "${command}" must come after the command and stream_id\n`);
|
|
609
|
+
node_process.stderr.write(` Example: durable-stream create <stream_id> ${command} ...\n`);
|
|
610
|
+
node_process.stderr.write(` Example: durable-stream write <stream_id> ${command} ...\n`);
|
|
611
|
+
} else if (command?.startsWith(`-`)) node_process.stderr.write(`Error: Unknown option "${command}"\n`);
|
|
498
612
|
else {
|
|
499
613
|
node_process.stderr.write(`Error: Unknown command "${command}"\n`);
|
|
500
614
|
node_process.stderr.write(` Available commands: create, write, read, delete\n`);
|
|
501
615
|
}
|
|
502
616
|
node_process.stderr.write(` Run "durable-stream --help" for usage information\n`);
|
|
503
617
|
process.exit(1);
|
|
618
|
+
}
|
|
504
619
|
}
|
|
505
620
|
}
|
|
506
621
|
function isMainModule() {
|
|
@@ -522,6 +637,7 @@ if (isMainModule()) main().catch((error) => {
|
|
|
522
637
|
|
|
523
638
|
//#endregion
|
|
524
639
|
exports.buildHeaders = buildHeaders
|
|
640
|
+
exports.buildStreamUrl = buildStreamUrl
|
|
525
641
|
exports.flattenJsonForAppend = flattenJsonForAppend
|
|
526
642
|
exports.getUsageText = getUsageText
|
|
527
643
|
exports.isJsonContentType = isJsonContentType
|
package/dist/index.d.cts
CHANGED
|
@@ -50,6 +50,18 @@ declare function validateUrl(url: string): ValidationResult;
|
|
|
50
50
|
*/
|
|
51
51
|
declare function normalizeBaseUrl(url: string): string;
|
|
52
52
|
/**
|
|
53
|
+
* Build the full stream URL from a base URL and stream ID.
|
|
54
|
+
* Simply concatenates the stream ID to the base URL.
|
|
55
|
+
*
|
|
56
|
+
* Examples:
|
|
57
|
+
* buildStreamUrl("http://localhost:4437/v1/stream", "my-stream")
|
|
58
|
+
* => "http://localhost:4437/v1/stream/my-stream"
|
|
59
|
+
*
|
|
60
|
+
* buildStreamUrl("http://localhost:3002/v1/stream/my-group", "my-stream")
|
|
61
|
+
* => "http://localhost:3002/v1/stream/my-group/my-stream"
|
|
62
|
+
*/
|
|
63
|
+
declare function buildStreamUrl(baseUrl: string, streamId: string): string;
|
|
64
|
+
/**
|
|
53
65
|
* Validate an authorization header value.
|
|
54
66
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
55
67
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -82,4 +94,4 @@ declare function buildHeaders(options: GlobalOptions): Record<string, string>;
|
|
|
82
94
|
declare function getUsageText(): string;
|
|
83
95
|
|
|
84
96
|
//#endregion
|
|
85
|
-
export { GlobalOptions, ParsedWriteArgs, buildHeaders, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
|
97
|
+
export { GlobalOptions, ParsedWriteArgs, buildHeaders, buildStreamUrl, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -50,6 +50,18 @@ declare function validateUrl(url: string): ValidationResult;
|
|
|
50
50
|
*/
|
|
51
51
|
declare function normalizeBaseUrl(url: string): string;
|
|
52
52
|
/**
|
|
53
|
+
* Build the full stream URL from a base URL and stream ID.
|
|
54
|
+
* Simply concatenates the stream ID to the base URL.
|
|
55
|
+
*
|
|
56
|
+
* Examples:
|
|
57
|
+
* buildStreamUrl("http://localhost:4437/v1/stream", "my-stream")
|
|
58
|
+
* => "http://localhost:4437/v1/stream/my-stream"
|
|
59
|
+
*
|
|
60
|
+
* buildStreamUrl("http://localhost:3002/v1/stream/my-group", "my-stream")
|
|
61
|
+
* => "http://localhost:3002/v1/stream/my-group/my-stream"
|
|
62
|
+
*/
|
|
63
|
+
declare function buildStreamUrl(baseUrl: string, streamId: string): string;
|
|
64
|
+
/**
|
|
53
65
|
* Validate an authorization header value.
|
|
54
66
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
55
67
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -82,4 +94,4 @@ declare function buildHeaders(options: GlobalOptions): Record<string, string>;
|
|
|
82
94
|
declare function getUsageText(): string;
|
|
83
95
|
|
|
84
96
|
//#endregion
|
|
85
|
-
export { GlobalOptions, ParsedWriteArgs, buildHeaders, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
|
97
|
+
export { GlobalOptions, ParsedWriteArgs, buildHeaders, buildStreamUrl, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,34 @@ function* flattenJsonForAppend(parsed) {
|
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/parseWriteArgs.ts
|
|
32
32
|
/**
|
|
33
|
+
* Extract a flag value from args, supporting both --flag=value and --flag value syntax.
|
|
34
|
+
* Returns { value, consumed } where consumed is the number of args used (0 if no match).
|
|
35
|
+
*/
|
|
36
|
+
function extractFlagValue$1(args, index, flagName) {
|
|
37
|
+
const arg = args[index];
|
|
38
|
+
const prefix = `${flagName}=`;
|
|
39
|
+
if (arg.startsWith(prefix)) {
|
|
40
|
+
const value = arg.slice(prefix.length);
|
|
41
|
+
if (!value) throw new Error(`${flagName} requires a value`);
|
|
42
|
+
return {
|
|
43
|
+
value,
|
|
44
|
+
consumed: 1
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (arg === flagName) {
|
|
48
|
+
const value = args[index + 1];
|
|
49
|
+
if (!value || value.startsWith(`--`)) throw new Error(`${flagName} requires a value`);
|
|
50
|
+
return {
|
|
51
|
+
value,
|
|
52
|
+
consumed: 2
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
value: null,
|
|
57
|
+
consumed: 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
33
61
|
* Parse write command arguments, extracting content-type flags and content.
|
|
34
62
|
* @param args - Arguments after the stream_id (starting from index 2)
|
|
35
63
|
* @returns Parsed content type and content string
|
|
@@ -50,11 +78,10 @@ function parseWriteArgs(args) {
|
|
|
50
78
|
contentType = `application/json`;
|
|
51
79
|
continue;
|
|
52
80
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
i++;
|
|
81
|
+
const contentTypeResult = extractFlagValue$1(args, i, `--content-type`);
|
|
82
|
+
if (contentTypeResult.value !== null) {
|
|
83
|
+
contentType = contentTypeResult.value;
|
|
84
|
+
i += contentTypeResult.consumed - 1;
|
|
58
85
|
continue;
|
|
59
86
|
}
|
|
60
87
|
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
@@ -105,6 +132,20 @@ function normalizeBaseUrl(url) {
|
|
|
105
132
|
return url.replace(/\/+$/, ``);
|
|
106
133
|
}
|
|
107
134
|
/**
|
|
135
|
+
* Build the full stream URL from a base URL and stream ID.
|
|
136
|
+
* Simply concatenates the stream ID to the base URL.
|
|
137
|
+
*
|
|
138
|
+
* Examples:
|
|
139
|
+
* buildStreamUrl("http://localhost:4437/v1/stream", "my-stream")
|
|
140
|
+
* => "http://localhost:4437/v1/stream/my-stream"
|
|
141
|
+
*
|
|
142
|
+
* buildStreamUrl("http://localhost:3002/v1/stream/my-group", "my-stream")
|
|
143
|
+
* => "http://localhost:3002/v1/stream/my-group/my-stream"
|
|
144
|
+
*/
|
|
145
|
+
function buildStreamUrl(baseUrl, streamId) {
|
|
146
|
+
return `${baseUrl}/${streamId}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
108
149
|
* Validate an authorization header value.
|
|
109
150
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
110
151
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -152,9 +193,37 @@ function validateStreamId(streamId) {
|
|
|
152
193
|
|
|
153
194
|
//#endregion
|
|
154
195
|
//#region src/index.ts
|
|
155
|
-
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437`;
|
|
196
|
+
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437/v1/stream`;
|
|
156
197
|
const STREAM_AUTH = process.env.STREAM_AUTH;
|
|
157
198
|
/**
|
|
199
|
+
* Extract a flag value from args, supporting both --flag=value and --flag value syntax.
|
|
200
|
+
* Returns { value, consumed } where consumed is the number of args used (0 if no match).
|
|
201
|
+
*/
|
|
202
|
+
function extractFlagValue(args, index, flagName, example) {
|
|
203
|
+
const arg = args[index];
|
|
204
|
+
const prefix = `${flagName}=`;
|
|
205
|
+
if (arg.startsWith(prefix)) {
|
|
206
|
+
const value = arg.slice(prefix.length);
|
|
207
|
+
if (!value) throw new Error(`${flagName} requires a value\n Example: ${flagName}="${example}"`);
|
|
208
|
+
return {
|
|
209
|
+
value,
|
|
210
|
+
consumed: 1
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (arg === flagName) {
|
|
214
|
+
const value = args[index + 1];
|
|
215
|
+
if (!value || value.startsWith(`--`)) throw new Error(`${flagName} requires a value\n Example: ${flagName} "${example}"`);
|
|
216
|
+
return {
|
|
217
|
+
value,
|
|
218
|
+
consumed: 2
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
value: null,
|
|
223
|
+
consumed: 0
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
158
227
|
* Parse global options (--url, --auth) from args.
|
|
159
228
|
* Falls back to STREAM_URL/STREAM_AUTH env vars when flags not provided.
|
|
160
229
|
* Returns the parsed options, remaining args, and any warnings.
|
|
@@ -165,22 +234,24 @@ function parseGlobalOptions(args) {
|
|
|
165
234
|
const warnings = [];
|
|
166
235
|
for (let i = 0; i < args.length; i++) {
|
|
167
236
|
const arg = args[i];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const urlValidation = validateUrl(value);
|
|
237
|
+
const urlResult = extractFlagValue(args, i, `--url`, `http://localhost:4437`);
|
|
238
|
+
if (urlResult.value !== null) {
|
|
239
|
+
const urlValidation = validateUrl(urlResult.value);
|
|
172
240
|
if (!urlValidation.valid) throw new Error(urlValidation.error);
|
|
173
|
-
options.url = normalizeBaseUrl(value);
|
|
174
|
-
i
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
241
|
+
options.url = normalizeBaseUrl(urlResult.value);
|
|
242
|
+
i += urlResult.consumed - 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const authResult = extractFlagValue(args, i, `--auth`, `Bearer my-token`);
|
|
246
|
+
if (authResult.value !== null) {
|
|
247
|
+
const authValidation = validateAuth(authResult.value);
|
|
179
248
|
if (!authValidation.valid) throw new Error(authValidation.error);
|
|
180
249
|
if (authValidation.warning) warnings.push(authValidation.warning);
|
|
181
|
-
options.auth = value;
|
|
182
|
-
i
|
|
183
|
-
|
|
250
|
+
options.auth = authResult.value;
|
|
251
|
+
i += authResult.consumed - 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
remainingArgs.push(arg);
|
|
184
255
|
}
|
|
185
256
|
if (!options.url) {
|
|
186
257
|
const urlValidation = validateUrl(STREAM_URL);
|
|
@@ -219,13 +290,15 @@ Global Options:
|
|
|
219
290
|
--auth <value> Authorization header value (e.g., "Bearer my-token")
|
|
220
291
|
--help, -h Show this help message
|
|
221
292
|
|
|
222
|
-
Write Options:
|
|
223
|
-
--content-type <type> Content-Type for the
|
|
224
|
-
--json
|
|
293
|
+
Create/Write Options:
|
|
294
|
+
--content-type <type> Content-Type for the stream (default: application/octet-stream)
|
|
295
|
+
--json Shorthand for --content-type application/json
|
|
296
|
+
|
|
297
|
+
Write-only Options:
|
|
225
298
|
--batch-json Write as JSON array of messages (each array element stored separately)
|
|
226
299
|
|
|
227
300
|
Environment Variables:
|
|
228
|
-
STREAM_URL Base URL of the stream server (default: http://localhost:4437)
|
|
301
|
+
STREAM_URL Base URL of the stream server (default: http://localhost:4437/v1/stream)
|
|
229
302
|
STREAM_AUTH Authorization header value (overridden by --auth flag)
|
|
230
303
|
`;
|
|
231
304
|
}
|
|
@@ -233,13 +306,38 @@ function printUsage({ to = `stderr` } = {}) {
|
|
|
233
306
|
const out = to === `stderr` ? stderr : stdout;
|
|
234
307
|
out.write(getUsageText());
|
|
235
308
|
}
|
|
236
|
-
|
|
237
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Parse create command arguments, extracting content-type flags.
|
|
311
|
+
* @param args - Arguments after the stream_id
|
|
312
|
+
* @returns Parsed content type
|
|
313
|
+
* @throws Error if --content-type is missing its value or if unknown flags are provided
|
|
314
|
+
*/
|
|
315
|
+
function parseCreateArgs(args) {
|
|
316
|
+
let contentType = `application/octet-stream`;
|
|
317
|
+
for (let i = 0; i < args.length; i++) {
|
|
318
|
+
const arg = args[i];
|
|
319
|
+
if (arg === `--json`) {
|
|
320
|
+
contentType = `application/json`;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const contentTypeResult = extractFlagValue(args, i, `--content-type`, `application/json`);
|
|
324
|
+
if (contentTypeResult.value !== null) {
|
|
325
|
+
contentType = contentTypeResult.value;
|
|
326
|
+
i += contentTypeResult.consumed - 1;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
330
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
331
|
+
}
|
|
332
|
+
return { contentType };
|
|
333
|
+
}
|
|
334
|
+
async function createStream(baseUrl, streamId, headers, contentType) {
|
|
335
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
238
336
|
try {
|
|
239
337
|
await DurableStream.create({
|
|
240
338
|
url,
|
|
241
339
|
headers,
|
|
242
|
-
contentType
|
|
340
|
+
contentType
|
|
243
341
|
});
|
|
244
342
|
console.log(`Stream created successfully: "${streamId}"`);
|
|
245
343
|
console.log(` URL: ${url}`);
|
|
@@ -299,7 +397,7 @@ function processEscapeSequences(content) {
|
|
|
299
397
|
return content.replace(/\\n/g, `\n`).replace(/\\t/g, `\t`).replace(/\\r/g, `\r`).replace(/\\\\/g, `\\`);
|
|
300
398
|
}
|
|
301
399
|
async function writeStream(baseUrl, streamId, contentType, batchJson, headers, content) {
|
|
302
|
-
const url =
|
|
400
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
303
401
|
const isJson = isJsonContentType(contentType);
|
|
304
402
|
let data;
|
|
305
403
|
let source;
|
|
@@ -364,7 +462,7 @@ function formatBytes(bytes) {
|
|
|
364
462
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
365
463
|
}
|
|
366
464
|
async function readStream(baseUrl, streamId, headers) {
|
|
367
|
-
const url =
|
|
465
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
368
466
|
try {
|
|
369
467
|
const stream = new DurableStream({
|
|
370
468
|
url,
|
|
@@ -379,7 +477,7 @@ async function readStream(baseUrl, streamId, headers) {
|
|
|
379
477
|
}
|
|
380
478
|
}
|
|
381
479
|
async function deleteStream(baseUrl, streamId, headers) {
|
|
382
|
-
const url =
|
|
480
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
383
481
|
try {
|
|
384
482
|
const stream = new DurableStream({
|
|
385
483
|
url,
|
|
@@ -436,7 +534,14 @@ async function main() {
|
|
|
436
534
|
switch (command) {
|
|
437
535
|
case `create`: {
|
|
438
536
|
const streamId = getStreamId();
|
|
439
|
-
|
|
537
|
+
let createArgs;
|
|
538
|
+
try {
|
|
539
|
+
createArgs = parseCreateArgs(args.slice(2));
|
|
540
|
+
} catch (error) {
|
|
541
|
+
stderr.write(`Error: ${getErrorMessage(error)}\n`);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
await createStream(options.url, streamId, headers, createArgs.contentType);
|
|
440
545
|
break;
|
|
441
546
|
}
|
|
442
547
|
case `write`: {
|
|
@@ -469,14 +574,24 @@ async function main() {
|
|
|
469
574
|
await deleteStream(options.url, streamId, headers);
|
|
470
575
|
break;
|
|
471
576
|
}
|
|
472
|
-
default:
|
|
473
|
-
|
|
577
|
+
default: {
|
|
578
|
+
const commandSpecificFlags = [
|
|
579
|
+
`--content-type`,
|
|
580
|
+
`--json`,
|
|
581
|
+
`--batch-json`
|
|
582
|
+
];
|
|
583
|
+
if (command && commandSpecificFlags.includes(command)) {
|
|
584
|
+
stderr.write(`Error: "${command}" must come after the command and stream_id\n`);
|
|
585
|
+
stderr.write(` Example: durable-stream create <stream_id> ${command} ...\n`);
|
|
586
|
+
stderr.write(` Example: durable-stream write <stream_id> ${command} ...\n`);
|
|
587
|
+
} else if (command?.startsWith(`-`)) stderr.write(`Error: Unknown option "${command}"\n`);
|
|
474
588
|
else {
|
|
475
589
|
stderr.write(`Error: Unknown command "${command}"\n`);
|
|
476
590
|
stderr.write(` Available commands: create, write, read, delete\n`);
|
|
477
591
|
}
|
|
478
592
|
stderr.write(` Run "durable-stream --help" for usage information\n`);
|
|
479
593
|
process.exit(1);
|
|
594
|
+
}
|
|
480
595
|
}
|
|
481
596
|
}
|
|
482
597
|
function isMainModule() {
|
|
@@ -497,4 +612,4 @@ if (isMainModule()) main().catch((error) => {
|
|
|
497
612
|
});
|
|
498
613
|
|
|
499
614
|
//#endregion
|
|
500
|
-
export { buildHeaders, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
|
615
|
+
export { buildHeaders, buildStreamUrl, flattenJsonForAppend, getUsageText, isJsonContentType, normalizeBaseUrl, parseGlobalOptions, parseWriteArgs, validateAuth, validateStreamId, validateUrl };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@durable-streams/cli",
|
|
3
3
|
"description": "CLI tool for working with Durable Streams",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cli": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"tsx": "^4.19.2",
|
|
21
21
|
"typescript": "^5.5.2",
|
|
22
22
|
"vitest": "^3.1.3",
|
|
23
|
-
"@durable-streams/server": "0.
|
|
23
|
+
"@durable-streams/server": "0.2.0"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=18.0.0"
|