@durable-streams/cli 0.1.9 → 0.2.1

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 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
- if (arg === `--content-type`) {
78
- const nextArg = args[i + 1];
79
- if (!nextArg || nextArg.startsWith(`--`)) throw new Error(`--content-type requires a value`);
80
- contentType = nextArg;
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
- if (arg === `--url`) {
193
- const value = args[i + 1];
194
- if (!value || value.startsWith(`--`)) throw new Error(`--url requires a value\n Example: --url "http://localhost:4437"`);
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
- } else if (arg === `--auth`) {
200
- const value = args[i + 1];
201
- if (!value || value.startsWith(`--`)) throw new Error(`--auth requires a value\n Example: --auth "Bearer my-token"`);
202
- const authValidation = validateAuth(value);
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
- } else remainingArgs.push(arg);
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 message (default: application/octet-stream)
248
- --json Write as JSON (input stored as single message)
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
- async function createStream(baseUrl, streamId, headers) {
261
- const url = `${baseUrl}/v1/stream/${streamId}`;
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: `application/octet-stream`
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 = `${baseUrl}/v1/stream/${streamId}`;
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 = `${baseUrl}/v1/stream/${streamId}`;
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 = `${baseUrl}/v1/stream/${streamId}`;
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
- await createStream(options.url, streamId, headers);
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
- if (command?.startsWith(`-`)) node_process.stderr.write(`Error: Unknown option "${command}"\n`);
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
- if (arg === `--content-type`) {
54
- const nextArg = args[i + 1];
55
- if (!nextArg || nextArg.startsWith(`--`)) throw new Error(`--content-type requires a value`);
56
- contentType = nextArg;
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
- if (arg === `--url`) {
169
- const value = args[i + 1];
170
- if (!value || value.startsWith(`--`)) throw new Error(`--url requires a value\n Example: --url "http://localhost:4437"`);
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
- } else if (arg === `--auth`) {
176
- const value = args[i + 1];
177
- if (!value || value.startsWith(`--`)) throw new Error(`--auth requires a value\n Example: --auth "Bearer my-token"`);
178
- const authValidation = validateAuth(value);
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
- } else remainingArgs.push(arg);
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 message (default: application/octet-stream)
224
- --json Write as JSON (input stored as single message)
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
- async function createStream(baseUrl, streamId, headers) {
237
- const url = `${baseUrl}/v1/stream/${streamId}`;
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: `application/octet-stream`
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 = `${baseUrl}/v1/stream/${streamId}`;
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 = `${baseUrl}/v1/stream/${streamId}`;
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 = `${baseUrl}/v1/stream/${streamId}`;
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
- await createStream(options.url, streamId, headers);
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
- if (command?.startsWith(`-`)) stderr.write(`Error: Unknown option "${command}"\n`);
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.1.9",
4
+ "version": "0.2.1",
5
5
  "author": "Durable Stream contributors",
6
6
  "bin": {
7
7
  "cli": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "url": "https://github.com/durable-streams/durable-streams/issues"
13
13
  },
14
14
  "dependencies": {
15
- "@durable-streams/client": "0.2.0"
15
+ "@durable-streams/client": "0.2.1"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^22.15.21",
@@ -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.1.7"
23
+ "@durable-streams/server": "0.2.1"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=18.0.0"