@poe-platform/safe-fs 0.1.726 → 0.1.728

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.
Files changed (57) hide show
  1. package/README.md +8 -1
  2. package/dist/safe-fs/contracts/errors.js +8 -1
  3. package/dist/safe-fs/contracts/filesystem.d.ts +68 -2
  4. package/dist/safe-fs/contracts/io.js +183 -27
  5. package/dist/safe-fs/contracts/virtual-path.d.ts +3 -1
  6. package/dist/safe-fs/contracts/virtual-path.js +20 -1
  7. package/dist/safe-fs/core.d.ts +1 -0
  8. package/dist/safe-fs/core.js +1 -0
  9. package/dist/safe-fs/fs/capabilities.d.ts +1 -1
  10. package/dist/safe-fs/fs/capabilities.js +59 -21
  11. package/dist/safe-fs/fs/conditional-chmod.d.ts +7 -0
  12. package/dist/safe-fs/fs/conditional-chmod.js +19 -0
  13. package/dist/safe-fs/fs/descriptor.js +51 -25
  14. package/dist/safe-fs/fs/devices/index.d.ts +7 -2
  15. package/dist/safe-fs/fs/devices/index.js +210 -29
  16. package/dist/safe-fs/fs/devices/path.d.ts +3 -1
  17. package/dist/safe-fs/fs/devices/path.js +70 -12
  18. package/dist/safe-fs/fs/memory/index.d.ts +34 -48
  19. package/dist/safe-fs/fs/memory/index.js +1150 -235
  20. package/dist/safe-fs/fs/memory/ledger.d.ts +2 -0
  21. package/dist/safe-fs/fs/memory/ledger.js +15 -3
  22. package/dist/safe-fs/fs/memory/missing-target.js +21 -1
  23. package/dist/safe-fs/fs/mount/index.d.ts +11 -3
  24. package/dist/safe-fs/fs/mount/index.js +326 -15
  25. package/dist/safe-fs/fs/object-publication/index.js +79 -3
  26. package/dist/safe-fs/fs/overlay/index.d.ts +2 -2
  27. package/dist/safe-fs/fs/overlay/index.js +22 -2
  28. package/dist/safe-fs/fs/overlay/memory-publication.d.ts +2 -1
  29. package/dist/safe-fs/fs/overlay/memory-publication.js +39 -2
  30. package/dist/safe-fs/fs/quota/index.js +1 -1
  31. package/dist/safe-fs/fs/readonly/index.d.ts +1 -0
  32. package/dist/safe-fs/fs/readonly/index.js +13 -0
  33. package/dist/safe-fs/fs/real/index.d.ts +6 -2
  34. package/dist/safe-fs/fs/real/index.js +78 -8
  35. package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
  36. package/dist/safe-fs/fs/s3/filesystem.js +48 -16
  37. package/dist/safe-fs/fs/s3/http/transport.js +20 -3
  38. package/dist/safe-fs/fs/s3/mock.js +19 -2
  39. package/dist/safe-fs/fs/s3/namespace.js +8 -2
  40. package/dist/safe-fs/fs/s3/registry.js +2 -0
  41. package/dist/safe-fs/fs/scoped.d.ts +1 -0
  42. package/dist/safe-fs/fs/scoped.js +261 -74
  43. package/dist/safe-fs/fs/staging-ancestry.d.ts +8 -0
  44. package/dist/safe-fs/fs/staging-ancestry.js +115 -0
  45. package/dist/safe-fs/fs/staging-cleanup.d.ts +5 -0
  46. package/dist/safe-fs/fs/staging-cleanup.js +63 -0
  47. package/dist/safe-fs/platform/browser.d.ts +5 -0
  48. package/dist/safe-fs/platform/browser.js +17 -0
  49. package/dist/safe-fs/platform/node.d.ts +5 -0
  50. package/dist/safe-fs/platform/node.js +28 -0
  51. package/dist/safe-fs/platform/transport-budget.d.ts +7 -0
  52. package/dist/safe-fs/platform/transport-budget.js +4 -0
  53. package/dist/safe-fs/testing/object-io-metrics.d.ts +3 -0
  54. package/dist/safe-fs/testing/object-io-metrics.js +29 -0
  55. package/dist/safe-fs/testing/object-publication.d.ts +3 -1
  56. package/dist/safe-fs/testing/object-publication.js +2 -2
  57. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { FsOptions } from "../contracts/filesystem.js";
3
+ import { type ScopedTransportBudgetFrame } from "./transport-budget.js";
3
4
  export type PlatformErrno = number;
4
5
  export type PlatformComparisonCallback<Callback> = Callback;
5
6
  export declare const platform: Readonly<{
@@ -12,3 +13,7 @@ export declare const comparisonContext: Readonly<{
12
13
  active(_options: FsOptions): boolean;
13
14
  run<Result>(options: FsOptions, action: (nested: FsOptions) => Promise<Result>): Promise<Result>;
14
15
  }>;
16
+ export declare function getScopedTransportBudget(): readonly ScopedTransportBudgetFrame[] | undefined;
17
+ export declare function withScopedTransportBudget<Result>(frames: readonly ScopedTransportBudgetFrame[] | undefined, action: () => Result): Result;
18
+ export declare function runScopedTransportBudget<Result>(admit: (options?: FsOptions) => void, action: () => Result, credit?: number): Result;
19
+ export declare function chargeScopedTransportCall(options?: FsOptions): void;
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as util from "node:util";
4
4
  import { constants } from "node:os";
5
+ import { hasRegisteredS3FileSystem } from "./transport-budget.js";
5
6
  const systemErrnos = (() => {
6
7
  const fallback = new Map(Object.entries(constants.errno).map(([name, errno]) => [name, -Math.abs(errno)]));
7
8
  if (typeof util.getSystemErrorMap !== "function")
@@ -33,3 +34,30 @@ export const comparisonContext = Object.freeze({
33
34
  return negotiating.run(true, () => action(options));
34
35
  }
35
36
  });
37
+ const transportBudgets = new AsyncLocalStorage();
38
+ export function getScopedTransportBudget() {
39
+ return hasRegisteredS3FileSystem ? transportBudgets.getStore() : undefined;
40
+ }
41
+ export function withScopedTransportBudget(frames, action) {
42
+ return frames ? transportBudgets.run(frames, action) : action();
43
+ }
44
+ export function runScopedTransportBudget(admit, action, credit = 1) {
45
+ if (!hasRegisteredS3FileSystem)
46
+ return action();
47
+ const parent = transportBudgets.getStore() ?? [];
48
+ const frame = { credit, admit };
49
+ return transportBudgets.run([...parent, frame], action);
50
+ }
51
+ export function chargeScopedTransportCall(options) {
52
+ if (!hasRegisteredS3FileSystem)
53
+ return;
54
+ const frames = transportBudgets.getStore();
55
+ if (!frames)
56
+ return;
57
+ for (const frame of frames) {
58
+ if (frame.credit > 0)
59
+ frame.credit--;
60
+ else
61
+ frame.admit(options);
62
+ }
63
+ }
@@ -0,0 +1,7 @@
1
+ import type { FsOptions } from "../contracts/filesystem.js";
2
+ export interface ScopedTransportBudgetFrame {
3
+ credit: number;
4
+ readonly admit: (options?: FsOptions) => void;
5
+ }
6
+ export declare let hasRegisteredS3FileSystem: boolean;
7
+ export declare function enableS3TransportBudget(): void;
@@ -0,0 +1,4 @@
1
+ export let hasRegisteredS3FileSystem = false;
2
+ export function enableS3TransportBudget() {
3
+ hasRegisteredS3FileSystem = true;
4
+ }
@@ -1,3 +1,4 @@
1
+ import type { ObjectFilePublicationStore } from "../fs/object-publication/index.js";
1
2
  export interface ObjectIoOperationMetrics {
2
3
  count: number;
3
4
  failed: number;
@@ -17,6 +18,8 @@ export declare class ObjectIoMetrics {
17
18
  measure<Value>(category: string, name: string, operation: () => Value | Promise<Value>): Promise<Value>;
18
19
  snapshot(): Record<string, ObjectIoPhaseMetrics>;
19
20
  }
21
+ /** Timings are inclusive: store publication can contain staging reads and backend I/O. */
22
+ export declare function measureObjectIoStore(store: ObjectFilePublicationStore, metrics: ObjectIoMetrics): ObjectFilePublicationStore;
20
23
  export declare function delayObjectIoBackend<Backend extends object>(backend: Backend, metrics: ObjectIoMetrics, delayMs: number, sleep?: (duration: number) => Promise<void>): Backend;
21
24
  export interface ObjectIoReadbackOptions {
22
25
  readonly body: ReadableStream<Uint8Array>;
@@ -37,6 +37,35 @@ export class ObjectIoMetrics {
37
37
  return result;
38
38
  }
39
39
  }
40
+ /** Timings are inclusive: store publication can contain staging reads and backend I/O. */
41
+ export function measureObjectIoStore(store, metrics) {
42
+ const version = (head) => ({
43
+ revision: head.revision, stat: head.stat,
44
+ ...(head.publicationToken === undefined ? {} : { publicationToken: head.publicationToken }),
45
+ read: (...args) => metrics.measure("version", "read", () => head.read(...args)),
46
+ close: () => metrics.measure("version", "close", () => head.close()),
47
+ });
48
+ const publish = store.publish;
49
+ const createStaging = store.createStaging;
50
+ return {
51
+ async acquire(...args) {
52
+ const head = await metrics.measure("store", "acquire", () => store.acquire(...args));
53
+ return head && version(head);
54
+ },
55
+ ...(publish ? { async publish(...args) {
56
+ return version(await metrics.measure("store", "publish", () => publish.apply(store, args)));
57
+ } } : {}),
58
+ ...(createStaging ? { async createStaging(...args) {
59
+ const staging = await metrics.measure("store", "createStaging", () => createStaging.apply(store, args));
60
+ return {
61
+ readPage: (...values) => metrics.measure("staging", "readPage", () => staging.readPage(...values)),
62
+ writePage: (...values) => metrics.measure("staging", "writePage", () => staging.writePage(...values)),
63
+ truncate: (...values) => metrics.measure("staging", "truncate", () => staging.truncate(...values)),
64
+ close: () => metrics.measure("staging", "close", () => staging.close()),
65
+ };
66
+ } } : {}),
67
+ };
68
+ }
40
69
  export function delayObjectIoBackend(backend, metrics, delayMs, sleep = duration => new Promise(resolve => setTimeout(resolve, duration))) {
41
70
  if (!Number.isSafeInteger(delayMs) || delayMs < 0)
42
71
  throw new RangeError("Backend delay must be a nonnegative integer");
@@ -1,14 +1,16 @@
1
1
  import type { FileSystem } from "../contracts/filesystem.js";
2
- import type { ObjectFilePublicationStore } from "../fs/object-publication/index.js";
2
+ import type { ObjectFileDescriptorOptions, ObjectFilePublicationStore } from "../fs/object-publication/index.js";
3
3
  export interface ObjectFilePublicationConformanceFixture {
4
4
  readonly fs: FileSystem;
5
5
  readonly store: ObjectFilePublicationStore;
6
6
  readonly root: string;
7
+ readonly descriptorOptions?: ObjectFileDescriptorOptions;
7
8
  dispose(): void | Promise<void>;
8
9
  }
9
10
  export interface ObjectFilePublicationConformanceOptions {
10
11
  readonly createFixture: () => ObjectFilePublicationConformanceFixture | Promise<ObjectFilePublicationConformanceFixture>;
11
12
  readonly requireStaging?: boolean;
13
+ readonly descriptorOptions?: ObjectFileDescriptorOptions;
12
14
  }
13
15
  export interface ObjectFilePublicationConformanceCase {
14
16
  readonly name: string;
@@ -92,7 +92,7 @@ export function createObjectFilePublicationConformanceCases(options) {
92
92
  async run({ fixture, path, track, publish }) {
93
93
  const name = path("descriptor");
94
94
  await publish(name, null, new Uint8Array([1, 2, 3, 4]));
95
- const fs = withObjectFileDescriptors(fixture.fs, fixture.store, { chunkBytes: 4, maxStagedBytes: 16 });
95
+ const fs = withObjectFileDescriptors(fixture.fs, fixture.store, { chunkBytes: 4, maxStagedBytes: 16, maxFileBytes: 16, ...options.descriptorOptions, ...fixture.descriptorOptions });
96
96
  const descriptor = track(await fs.open(name, { access: "readwrite" }));
97
97
  check(descriptor.capabilities.publication === "conditional", "descriptor publication profile was lost");
98
98
  await descriptor.write(new Uint8Array([7]), 1);
@@ -156,7 +156,7 @@ export function createObjectFilePublicationConformanceCases(options) {
156
156
  async run({ fixture, path, track, publish }) {
157
157
  const name = path("stage-descriptor");
158
158
  await publish(name, null, new Uint8Array([1, 2, 3, 4]));
159
- const fs = withObjectFileDescriptors(fixture.fs, fixture.store, { chunkBytes: 4, maxStagedBytes: 4, maxStagedPages: 1, maxFileBytes: 16 });
159
+ const fs = withObjectFileDescriptors(fixture.fs, fixture.store, { chunkBytes: 4, maxStagedBytes: 4, maxStagedPages: 1, maxFileBytes: 16, ...options.descriptorOptions, ...fixture.descriptorOptions });
160
160
  const descriptor = track(await fs.open(name, { access: "readwrite" }));
161
161
  const content = new Uint8Array([5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
162
162
  await descriptor.write(content, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.726",
3
+ "version": "0.1.728",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",