@durable-streams/cli 0.1.8 → 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 +158 -35
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +158 -36
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
}) : target, mod));
|
|
24
24
|
|
|
25
25
|
//#endregion
|
|
26
|
+
const node_fs = __toESM(require("node:fs"));
|
|
26
27
|
const node_http = __toESM(require("node:http"));
|
|
27
28
|
const node_path = __toESM(require("node:path"));
|
|
28
29
|
const node_process = __toESM(require("node:process"));
|
|
@@ -53,6 +54,34 @@ function* flattenJsonForAppend(parsed) {
|
|
|
53
54
|
//#endregion
|
|
54
55
|
//#region src/parseWriteArgs.ts
|
|
55
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
|
+
/**
|
|
56
85
|
* Parse write command arguments, extracting content-type flags and content.
|
|
57
86
|
* @param args - Arguments after the stream_id (starting from index 2)
|
|
58
87
|
* @returns Parsed content type and content string
|
|
@@ -73,11 +102,10 @@ function parseWriteArgs(args) {
|
|
|
73
102
|
contentType = `application/json`;
|
|
74
103
|
continue;
|
|
75
104
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
i++;
|
|
105
|
+
const contentTypeResult = extractFlagValue$1(args, i, `--content-type`);
|
|
106
|
+
if (contentTypeResult.value !== null) {
|
|
107
|
+
contentType = contentTypeResult.value;
|
|
108
|
+
i += contentTypeResult.consumed - 1;
|
|
81
109
|
continue;
|
|
82
110
|
}
|
|
83
111
|
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
@@ -128,6 +156,20 @@ function normalizeBaseUrl(url) {
|
|
|
128
156
|
return url.replace(/\/+$/, ``);
|
|
129
157
|
}
|
|
130
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
|
+
/**
|
|
131
173
|
* Validate an authorization header value.
|
|
132
174
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
133
175
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -175,9 +217,37 @@ function validateStreamId(streamId) {
|
|
|
175
217
|
|
|
176
218
|
//#endregion
|
|
177
219
|
//#region src/index.ts
|
|
178
|
-
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437`;
|
|
220
|
+
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437/v1/stream`;
|
|
179
221
|
const STREAM_AUTH = process.env.STREAM_AUTH;
|
|
180
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
|
+
/**
|
|
181
251
|
* Parse global options (--url, --auth) from args.
|
|
182
252
|
* Falls back to STREAM_URL/STREAM_AUTH env vars when flags not provided.
|
|
183
253
|
* Returns the parsed options, remaining args, and any warnings.
|
|
@@ -188,22 +258,24 @@ function parseGlobalOptions(args) {
|
|
|
188
258
|
const warnings = [];
|
|
189
259
|
for (let i = 0; i < args.length; i++) {
|
|
190
260
|
const arg = args[i];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
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);
|
|
195
264
|
if (!urlValidation.valid) throw new Error(urlValidation.error);
|
|
196
|
-
options.url = normalizeBaseUrl(value);
|
|
197
|
-
i
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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);
|
|
202
272
|
if (!authValidation.valid) throw new Error(authValidation.error);
|
|
203
273
|
if (authValidation.warning) warnings.push(authValidation.warning);
|
|
204
|
-
options.auth = value;
|
|
205
|
-
i
|
|
206
|
-
|
|
274
|
+
options.auth = authResult.value;
|
|
275
|
+
i += authResult.consumed - 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
remainingArgs.push(arg);
|
|
207
279
|
}
|
|
208
280
|
if (!options.url) {
|
|
209
281
|
const urlValidation = validateUrl(STREAM_URL);
|
|
@@ -242,13 +314,15 @@ Global Options:
|
|
|
242
314
|
--auth <value> Authorization header value (e.g., "Bearer my-token")
|
|
243
315
|
--help, -h Show this help message
|
|
244
316
|
|
|
245
|
-
Write Options:
|
|
246
|
-
--content-type <type> Content-Type for the
|
|
247
|
-
--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:
|
|
248
322
|
--batch-json Write as JSON array of messages (each array element stored separately)
|
|
249
323
|
|
|
250
324
|
Environment Variables:
|
|
251
|
-
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)
|
|
252
326
|
STREAM_AUTH Authorization header value (overridden by --auth flag)
|
|
253
327
|
`;
|
|
254
328
|
}
|
|
@@ -256,13 +330,38 @@ function printUsage({ to = `stderr` } = {}) {
|
|
|
256
330
|
const out = to === `stderr` ? node_process.stderr : node_process.stdout;
|
|
257
331
|
out.write(getUsageText());
|
|
258
332
|
}
|
|
259
|
-
|
|
260
|
-
|
|
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);
|
|
261
360
|
try {
|
|
262
361
|
await __durable_streams_client.DurableStream.create({
|
|
263
362
|
url,
|
|
264
363
|
headers,
|
|
265
|
-
contentType
|
|
364
|
+
contentType
|
|
266
365
|
});
|
|
267
366
|
console.log(`Stream created successfully: "${streamId}"`);
|
|
268
367
|
console.log(` URL: ${url}`);
|
|
@@ -322,7 +421,7 @@ function processEscapeSequences(content) {
|
|
|
322
421
|
return content.replace(/\\n/g, `\n`).replace(/\\t/g, `\t`).replace(/\\r/g, `\r`).replace(/\\\\/g, `\\`);
|
|
323
422
|
}
|
|
324
423
|
async function writeStream(baseUrl, streamId, contentType, batchJson, headers, content) {
|
|
325
|
-
const url =
|
|
424
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
326
425
|
const isJson = isJsonContentType(contentType);
|
|
327
426
|
let data;
|
|
328
427
|
let source;
|
|
@@ -387,7 +486,7 @@ function formatBytes(bytes) {
|
|
|
387
486
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
388
487
|
}
|
|
389
488
|
async function readStream(baseUrl, streamId, headers) {
|
|
390
|
-
const url =
|
|
489
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
391
490
|
try {
|
|
392
491
|
const stream = new __durable_streams_client.DurableStream({
|
|
393
492
|
url,
|
|
@@ -402,7 +501,7 @@ async function readStream(baseUrl, streamId, headers) {
|
|
|
402
501
|
}
|
|
403
502
|
}
|
|
404
503
|
async function deleteStream(baseUrl, streamId, headers) {
|
|
405
|
-
const url =
|
|
504
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
406
505
|
try {
|
|
407
506
|
const stream = new __durable_streams_client.DurableStream({
|
|
408
507
|
url,
|
|
@@ -459,7 +558,14 @@ async function main() {
|
|
|
459
558
|
switch (command) {
|
|
460
559
|
case `create`: {
|
|
461
560
|
const streamId = getStreamId();
|
|
462
|
-
|
|
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);
|
|
463
569
|
break;
|
|
464
570
|
}
|
|
465
571
|
case `write`: {
|
|
@@ -492,21 +598,37 @@ async function main() {
|
|
|
492
598
|
await deleteStream(options.url, streamId, headers);
|
|
493
599
|
break;
|
|
494
600
|
}
|
|
495
|
-
default:
|
|
496
|
-
|
|
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`);
|
|
497
612
|
else {
|
|
498
613
|
node_process.stderr.write(`Error: Unknown command "${command}"\n`);
|
|
499
614
|
node_process.stderr.write(` Available commands: create, write, read, delete\n`);
|
|
500
615
|
}
|
|
501
616
|
node_process.stderr.write(` Run "durable-stream --help" for usage information\n`);
|
|
502
617
|
process.exit(1);
|
|
618
|
+
}
|
|
503
619
|
}
|
|
504
620
|
}
|
|
505
621
|
function isMainModule() {
|
|
506
622
|
if (!process.argv[1]) return false;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
623
|
+
try {
|
|
624
|
+
const scriptPath = (0, node_fs.realpathSync)((0, node_path.resolve)(process.argv[1]));
|
|
625
|
+
const modulePath = (0, node_fs.realpathSync)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
626
|
+
return scriptPath === modulePath;
|
|
627
|
+
} catch {
|
|
628
|
+
const scriptPath = (0, node_path.resolve)(process.argv[1]);
|
|
629
|
+
const modulePath = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
630
|
+
return scriptPath === modulePath;
|
|
631
|
+
}
|
|
510
632
|
}
|
|
511
633
|
if (isMainModule()) main().catch((error) => {
|
|
512
634
|
node_process.stderr.write(`Fatal error: ${getErrorMessage(error)}\n`);
|
|
@@ -515,6 +637,7 @@ if (isMainModule()) main().catch((error) => {
|
|
|
515
637
|
|
|
516
638
|
//#endregion
|
|
517
639
|
exports.buildHeaders = buildHeaders
|
|
640
|
+
exports.buildStreamUrl = buildStreamUrl
|
|
518
641
|
exports.flattenJsonForAppend = flattenJsonForAppend
|
|
519
642
|
exports.getUsageText = getUsageText
|
|
520
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import { STATUS_CODES } from "node:http";
|
|
3
4
|
import { resolve } from "node:path";
|
|
4
5
|
import { stderr, stdin, stdout } from "node:process";
|
|
@@ -29,6 +30,34 @@ function* flattenJsonForAppend(parsed) {
|
|
|
29
30
|
//#endregion
|
|
30
31
|
//#region src/parseWriteArgs.ts
|
|
31
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
|
+
/**
|
|
32
61
|
* Parse write command arguments, extracting content-type flags and content.
|
|
33
62
|
* @param args - Arguments after the stream_id (starting from index 2)
|
|
34
63
|
* @returns Parsed content type and content string
|
|
@@ -49,11 +78,10 @@ function parseWriteArgs(args) {
|
|
|
49
78
|
contentType = `application/json`;
|
|
50
79
|
continue;
|
|
51
80
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
i++;
|
|
81
|
+
const contentTypeResult = extractFlagValue$1(args, i, `--content-type`);
|
|
82
|
+
if (contentTypeResult.value !== null) {
|
|
83
|
+
contentType = contentTypeResult.value;
|
|
84
|
+
i += contentTypeResult.consumed - 1;
|
|
57
85
|
continue;
|
|
58
86
|
}
|
|
59
87
|
if (arg.startsWith(`--`)) throw new Error(`unknown flag: ${arg}`);
|
|
@@ -104,6 +132,20 @@ function normalizeBaseUrl(url) {
|
|
|
104
132
|
return url.replace(/\/+$/, ``);
|
|
105
133
|
}
|
|
106
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
|
+
/**
|
|
107
149
|
* Validate an authorization header value.
|
|
108
150
|
* Returns valid=true for any non-empty value, but includes a warning
|
|
109
151
|
* if the value doesn't match common auth schemes (Bearer, Basic, ApiKey, Token).
|
|
@@ -151,9 +193,37 @@ function validateStreamId(streamId) {
|
|
|
151
193
|
|
|
152
194
|
//#endregion
|
|
153
195
|
//#region src/index.ts
|
|
154
|
-
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437`;
|
|
196
|
+
const STREAM_URL = process.env.STREAM_URL || `http://localhost:4437/v1/stream`;
|
|
155
197
|
const STREAM_AUTH = process.env.STREAM_AUTH;
|
|
156
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
|
+
/**
|
|
157
227
|
* Parse global options (--url, --auth) from args.
|
|
158
228
|
* Falls back to STREAM_URL/STREAM_AUTH env vars when flags not provided.
|
|
159
229
|
* Returns the parsed options, remaining args, and any warnings.
|
|
@@ -164,22 +234,24 @@ function parseGlobalOptions(args) {
|
|
|
164
234
|
const warnings = [];
|
|
165
235
|
for (let i = 0; i < args.length; i++) {
|
|
166
236
|
const arg = args[i];
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
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);
|
|
171
240
|
if (!urlValidation.valid) throw new Error(urlValidation.error);
|
|
172
|
-
options.url = normalizeBaseUrl(value);
|
|
173
|
-
i
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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);
|
|
178
248
|
if (!authValidation.valid) throw new Error(authValidation.error);
|
|
179
249
|
if (authValidation.warning) warnings.push(authValidation.warning);
|
|
180
|
-
options.auth = value;
|
|
181
|
-
i
|
|
182
|
-
|
|
250
|
+
options.auth = authResult.value;
|
|
251
|
+
i += authResult.consumed - 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
remainingArgs.push(arg);
|
|
183
255
|
}
|
|
184
256
|
if (!options.url) {
|
|
185
257
|
const urlValidation = validateUrl(STREAM_URL);
|
|
@@ -218,13 +290,15 @@ Global Options:
|
|
|
218
290
|
--auth <value> Authorization header value (e.g., "Bearer my-token")
|
|
219
291
|
--help, -h Show this help message
|
|
220
292
|
|
|
221
|
-
Write Options:
|
|
222
|
-
--content-type <type> Content-Type for the
|
|
223
|
-
--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:
|
|
224
298
|
--batch-json Write as JSON array of messages (each array element stored separately)
|
|
225
299
|
|
|
226
300
|
Environment Variables:
|
|
227
|
-
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)
|
|
228
302
|
STREAM_AUTH Authorization header value (overridden by --auth flag)
|
|
229
303
|
`;
|
|
230
304
|
}
|
|
@@ -232,13 +306,38 @@ function printUsage({ to = `stderr` } = {}) {
|
|
|
232
306
|
const out = to === `stderr` ? stderr : stdout;
|
|
233
307
|
out.write(getUsageText());
|
|
234
308
|
}
|
|
235
|
-
|
|
236
|
-
|
|
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);
|
|
237
336
|
try {
|
|
238
337
|
await DurableStream.create({
|
|
239
338
|
url,
|
|
240
339
|
headers,
|
|
241
|
-
contentType
|
|
340
|
+
contentType
|
|
242
341
|
});
|
|
243
342
|
console.log(`Stream created successfully: "${streamId}"`);
|
|
244
343
|
console.log(` URL: ${url}`);
|
|
@@ -298,7 +397,7 @@ function processEscapeSequences(content) {
|
|
|
298
397
|
return content.replace(/\\n/g, `\n`).replace(/\\t/g, `\t`).replace(/\\r/g, `\r`).replace(/\\\\/g, `\\`);
|
|
299
398
|
}
|
|
300
399
|
async function writeStream(baseUrl, streamId, contentType, batchJson, headers, content) {
|
|
301
|
-
const url =
|
|
400
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
302
401
|
const isJson = isJsonContentType(contentType);
|
|
303
402
|
let data;
|
|
304
403
|
let source;
|
|
@@ -363,7 +462,7 @@ function formatBytes(bytes) {
|
|
|
363
462
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
364
463
|
}
|
|
365
464
|
async function readStream(baseUrl, streamId, headers) {
|
|
366
|
-
const url =
|
|
465
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
367
466
|
try {
|
|
368
467
|
const stream = new DurableStream({
|
|
369
468
|
url,
|
|
@@ -378,7 +477,7 @@ async function readStream(baseUrl, streamId, headers) {
|
|
|
378
477
|
}
|
|
379
478
|
}
|
|
380
479
|
async function deleteStream(baseUrl, streamId, headers) {
|
|
381
|
-
const url =
|
|
480
|
+
const url = buildStreamUrl(baseUrl, streamId);
|
|
382
481
|
try {
|
|
383
482
|
const stream = new DurableStream({
|
|
384
483
|
url,
|
|
@@ -435,7 +534,14 @@ async function main() {
|
|
|
435
534
|
switch (command) {
|
|
436
535
|
case `create`: {
|
|
437
536
|
const streamId = getStreamId();
|
|
438
|
-
|
|
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);
|
|
439
545
|
break;
|
|
440
546
|
}
|
|
441
547
|
case `write`: {
|
|
@@ -468,21 +574,37 @@ async function main() {
|
|
|
468
574
|
await deleteStream(options.url, streamId, headers);
|
|
469
575
|
break;
|
|
470
576
|
}
|
|
471
|
-
default:
|
|
472
|
-
|
|
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`);
|
|
473
588
|
else {
|
|
474
589
|
stderr.write(`Error: Unknown command "${command}"\n`);
|
|
475
590
|
stderr.write(` Available commands: create, write, read, delete\n`);
|
|
476
591
|
}
|
|
477
592
|
stderr.write(` Run "durable-stream --help" for usage information\n`);
|
|
478
593
|
process.exit(1);
|
|
594
|
+
}
|
|
479
595
|
}
|
|
480
596
|
}
|
|
481
597
|
function isMainModule() {
|
|
482
598
|
if (!process.argv[1]) return false;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
599
|
+
try {
|
|
600
|
+
const scriptPath = realpathSync(resolve(process.argv[1]));
|
|
601
|
+
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
|
602
|
+
return scriptPath === modulePath;
|
|
603
|
+
} catch {
|
|
604
|
+
const scriptPath = resolve(process.argv[1]);
|
|
605
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
606
|
+
return scriptPath === modulePath;
|
|
607
|
+
}
|
|
486
608
|
}
|
|
487
609
|
if (isMainModule()) main().catch((error) => {
|
|
488
610
|
stderr.write(`Fatal error: ${getErrorMessage(error)}\n`);
|
|
@@ -490,4 +612,4 @@ if (isMainModule()) main().catch((error) => {
|
|
|
490
612
|
});
|
|
491
613
|
|
|
492
614
|
//#endregion
|
|
493
|
-
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"
|