@durable-streams/server 0.3.5 → 0.3.6

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
@@ -187,6 +187,7 @@ var StreamStore = class {
187
187
  if (sourceStream.softDeleted) throw new Error(`Source stream is soft-deleted: ${options.forkedFrom}`);
188
188
  if (this.isExpired(sourceStream)) throw new Error(`Source stream not found: ${options.forkedFrom}`);
189
189
  sourceContentType = sourceStream.contentType;
190
+ if (options.contentType && options.contentType.trim() !== `` && normalizeContentType(options.contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
190
191
  if (options.forkOffset) forkOffset = options.forkOffset;
191
192
  else forkOffset = sourceStream.currentOffset;
192
193
  const zeroOffset = `0000000000000000_0000000000000000`;
@@ -197,7 +198,7 @@ var StreamStore = class {
197
198
  let contentType = options.contentType;
198
199
  if (!contentType || contentType.trim() === ``) {
199
200
  if (isFork) contentType = sourceContentType;
200
- } else if (isFork && normalizeContentType(contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
201
+ }
201
202
  let effectiveExpiresAt = options.expiresAt;
202
203
  let effectiveTtlSeconds = options.ttlSeconds;
203
204
  if (isFork) {
@@ -1385,6 +1386,7 @@ var FileBackedStreamStore = class {
1385
1386
  if (sourceMeta.softDeleted) throw new Error(`Source stream is soft-deleted: ${options.forkedFrom}`);
1386
1387
  if (this.isExpired(sourceMeta)) throw new Error(`Source stream not found: ${options.forkedFrom}`);
1387
1388
  sourceContentType = sourceMeta.contentType;
1389
+ if (options.contentType && options.contentType.trim() !== `` && normalizeContentType(options.contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
1388
1390
  if (options.forkOffset) forkOffset = options.forkOffset;
1389
1391
  else forkOffset = sourceMeta.currentOffset;
1390
1392
  const zeroOffset = `0000000000000000_0000000000000000`;
@@ -1400,7 +1402,7 @@ var FileBackedStreamStore = class {
1400
1402
  let contentType = options.contentType;
1401
1403
  if (!contentType || contentType.trim() === ``) {
1402
1404
  if (isFork) contentType = sourceContentType;
1403
- } else if (isFork && normalizeContentType(contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
1405
+ }
1404
1406
  let effectiveExpiresAt = options.expiresAt;
1405
1407
  let effectiveTtlSeconds = options.ttlSeconds;
1406
1408
  if (isFork) {
package/dist/index.js CHANGED
@@ -163,6 +163,7 @@ var StreamStore = class {
163
163
  if (sourceStream.softDeleted) throw new Error(`Source stream is soft-deleted: ${options.forkedFrom}`);
164
164
  if (this.isExpired(sourceStream)) throw new Error(`Source stream not found: ${options.forkedFrom}`);
165
165
  sourceContentType = sourceStream.contentType;
166
+ if (options.contentType && options.contentType.trim() !== `` && normalizeContentType(options.contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
166
167
  if (options.forkOffset) forkOffset = options.forkOffset;
167
168
  else forkOffset = sourceStream.currentOffset;
168
169
  const zeroOffset = `0000000000000000_0000000000000000`;
@@ -173,7 +174,7 @@ var StreamStore = class {
173
174
  let contentType = options.contentType;
174
175
  if (!contentType || contentType.trim() === ``) {
175
176
  if (isFork) contentType = sourceContentType;
176
- } else if (isFork && normalizeContentType(contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
177
+ }
177
178
  let effectiveExpiresAt = options.expiresAt;
178
179
  let effectiveTtlSeconds = options.ttlSeconds;
179
180
  if (isFork) {
@@ -1361,6 +1362,7 @@ var FileBackedStreamStore = class {
1361
1362
  if (sourceMeta.softDeleted) throw new Error(`Source stream is soft-deleted: ${options.forkedFrom}`);
1362
1363
  if (this.isExpired(sourceMeta)) throw new Error(`Source stream not found: ${options.forkedFrom}`);
1363
1364
  sourceContentType = sourceMeta.contentType;
1365
+ if (options.contentType && options.contentType.trim() !== `` && normalizeContentType(options.contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
1364
1366
  if (options.forkOffset) forkOffset = options.forkOffset;
1365
1367
  else forkOffset = sourceMeta.currentOffset;
1366
1368
  const zeroOffset = `0000000000000000_0000000000000000`;
@@ -1376,7 +1378,7 @@ var FileBackedStreamStore = class {
1376
1378
  let contentType = options.contentType;
1377
1379
  if (!contentType || contentType.trim() === ``) {
1378
1380
  if (isFork) contentType = sourceContentType;
1379
- } else if (isFork && normalizeContentType(contentType) !== normalizeContentType(sourceContentType)) throw new Error(`Content type mismatch with source stream`);
1381
+ }
1380
1382
  let effectiveExpiresAt = options.expiresAt;
1381
1383
  let effectiveTtlSeconds = options.ttlSeconds;
1382
1384
  if (isFork) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@durable-streams/server",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Node.js reference server implementation for Durable Streams",
5
5
  "author": "Durable Stream contributors",
6
6
  "license": "Apache-2.0",
@@ -40,14 +40,14 @@
40
40
  "@neophi/sieve-cache": "^1.0.0",
41
41
  "lmdb": "^3.3.0",
42
42
  "@durable-streams/client": "0.2.6",
43
- "@durable-streams/state": "0.2.9"
43
+ "@durable-streams/state": "0.3.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.0.0",
47
47
  "tsdown": "^0.9.0",
48
48
  "typescript": "^5.0.0",
49
49
  "vitest": "^4.0.0",
50
- "@durable-streams/server-conformance-tests": "0.3.4"
50
+ "@durable-streams/server-conformance-tests": "0.3.5"
51
51
  },
52
52
  "files": [
53
53
  "dist",
package/src/file-store.ts CHANGED
@@ -818,6 +818,18 @@ export class FileBackedStreamStore {
818
818
 
819
819
  sourceContentType = sourceMeta.contentType
820
820
 
821
+ // Reject a content-type mismatch up front, before taking a reference on
822
+ // the source. Doing this after the refCount increment below would leak a
823
+ // reference on the failed fork and pin the source in a soft-deleted state.
824
+ if (
825
+ options.contentType &&
826
+ options.contentType.trim() !== `` &&
827
+ normalizeContentType(options.contentType) !==
828
+ normalizeContentType(sourceContentType)
829
+ ) {
830
+ throw new Error(`Content type mismatch with source stream`)
831
+ }
832
+
821
833
  // Resolve fork offset: use provided or source's currentOffset
822
834
  if (options.forkOffset) {
823
835
  forkOffset = options.forkOffset
@@ -851,18 +863,14 @@ export class FileBackedStreamStore {
851
863
  this.db.putSync(sourceKey, updatedSource)
852
864
  }
853
865
 
854
- // Determine content type: use options, or inherit from source if fork
866
+ // Determine content type: use options, or inherit from source if fork. A
867
+ // fork content-type mismatch is already rejected above, before the source
868
+ // refCount is taken.
855
869
  let contentType = options.contentType
856
870
  if (!contentType || contentType.trim() === ``) {
857
871
  if (isFork) {
858
872
  contentType = sourceContentType
859
873
  }
860
- } else if (
861
- isFork &&
862
- normalizeContentType(contentType) !==
863
- normalizeContentType(sourceContentType)
864
- ) {
865
- throw new Error(`Content type mismatch with source stream`)
866
874
  }
867
875
 
868
876
  // Compute effective expiry for forks
package/src/store.ts CHANGED
@@ -329,6 +329,18 @@ export class StreamStore {
329
329
 
330
330
  sourceContentType = sourceStream.contentType
331
331
 
332
+ // Reject a content-type mismatch up front, before taking a reference on
333
+ // the source. Doing this after the refCount increment below would leak a
334
+ // reference on the failed fork and pin the source in a soft-deleted state.
335
+ if (
336
+ options.contentType &&
337
+ options.contentType.trim() !== `` &&
338
+ normalizeContentType(options.contentType) !==
339
+ normalizeContentType(sourceContentType)
340
+ ) {
341
+ throw new Error(`Content type mismatch with source stream`)
342
+ }
343
+
332
344
  // Resolve fork offset: use provided or source's currentOffset
333
345
  if (options.forkOffset) {
334
346
  forkOffset = options.forkOffset
@@ -358,18 +370,14 @@ export class StreamStore {
358
370
  sourceStream.refCount++
359
371
  }
360
372
 
361
- // Determine content type: use options, or inherit from source if fork
373
+ // Determine content type: use options, or inherit from source if fork. A
374
+ // fork content-type mismatch is already rejected above, before the source
375
+ // refCount is taken.
362
376
  let contentType = options.contentType
363
377
  if (!contentType || contentType.trim() === ``) {
364
378
  if (isFork) {
365
379
  contentType = sourceContentType
366
380
  }
367
- } else if (
368
- isFork &&
369
- normalizeContentType(contentType) !==
370
- normalizeContentType(sourceContentType)
371
- ) {
372
- throw new Error(`Content type mismatch with source stream`)
373
381
  }
374
382
 
375
383
  // Compute effective expiry for forks