@effect-vfs/core 0.3.0 → 0.4.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.
Files changed (69) hide show
  1. package/README.md +36 -149
  2. package/dist/BytePath.d.ts +34 -2
  3. package/dist/BytePath.d.ts.map +1 -1
  4. package/dist/BytePath.js +1 -1
  5. package/dist/LiveVolume.d.ts +101 -0
  6. package/dist/LiveVolume.d.ts.map +1 -0
  7. package/dist/LiveVolume.js +72 -0
  8. package/dist/Snapshot.d.ts +60 -2
  9. package/dist/Snapshot.d.ts.map +1 -1
  10. package/dist/Snapshot.js +59 -1
  11. package/dist/SnapshotDelta.d.ts +113 -3
  12. package/dist/SnapshotDelta.d.ts.map +1 -1
  13. package/dist/SnapshotDelta.js +112 -2
  14. package/dist/VirtualFileSystem.d.ts +1224 -90
  15. package/dist/VirtualFileSystem.d.ts.map +1 -1
  16. package/dist/VirtualFileSystem.js +678 -20
  17. package/dist/VirtualFileSystemError.d.ts +54 -0
  18. package/dist/VirtualFileSystemError.d.ts.map +1 -0
  19. package/dist/VirtualFileSystemError.js +49 -0
  20. package/dist/index.d.ts +15 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -1
  23. package/dist/internal/bytePath.d.ts.map +1 -1
  24. package/dist/internal/bytePath.js +5 -10
  25. package/dist/internal/bytes.d.ts +6 -0
  26. package/dist/internal/bytes.d.ts.map +1 -0
  27. package/dist/internal/bytes.js +17 -0
  28. package/dist/internal/canonicalBase64.d.ts +13 -5
  29. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  30. package/dist/internal/canonicalBase64.js +28 -6
  31. package/dist/internal/image.d.ts +76 -90
  32. package/dist/internal/image.d.ts.map +1 -1
  33. package/dist/internal/image.js +98 -89
  34. package/dist/internal/liveImage.d.ts +286 -0
  35. package/dist/internal/liveImage.d.ts.map +1 -0
  36. package/dist/internal/liveImage.js +176 -0
  37. package/dist/internal/metadata.d.ts +28 -0
  38. package/dist/internal/metadata.d.ts.map +1 -0
  39. package/dist/internal/metadata.js +29 -0
  40. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  41. package/dist/internal/snapshotDelta.js +346 -300
  42. package/dist/internal/snapshotDeltaModel.d.ts +2 -2
  43. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  44. package/dist/internal/snapshotDeltaModel.js +3 -1
  45. package/dist/internal/virtualFileSystem/errors.d.ts +6 -20
  46. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
  47. package/dist/internal/virtualFileSystem/errors.js +22 -7
  48. package/dist/internal/virtualFileSystem/fixture.d.ts +4 -1
  49. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
  50. package/dist/internal/virtualFileSystem/fixture.js +80 -74
  51. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
  52. package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
  53. package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
  54. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
  55. package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
  56. package/dist/internal/virtualFileSystem/path.d.ts +15 -14
  57. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
  58. package/dist/internal/virtualFileSystem/path.js +57 -54
  59. package/dist/internal/virtualFileSystem/stagedState.d.ts +22 -0
  60. package/dist/internal/virtualFileSystem/stagedState.d.ts.map +1 -0
  61. package/dist/internal/virtualFileSystem/stagedState.js +62 -0
  62. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
  63. package/dist/internal/virtualFileSystem/watchHub.d.ts +3 -3
  64. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
  65. package/dist/internal/virtualFileSystem/watchHub.js +45 -24
  66. package/dist/internal/virtualFileSystem.d.ts +307 -23
  67. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  68. package/dist/internal/virtualFileSystem.js +1738 -525
  69. package/package.json +23 -5
@@ -1,12 +1,21 @@
1
1
  import * as ByteSize from "effect/ByteSize";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Result from "effect/Result";
4
- import * as Schema from "effect/Schema";
5
- import { type BytePath } from "../../BytePath.js";
4
+ import type { BytePath } from "../../BytePath.js";
6
5
  import type { PathInput } from "../../VirtualFileSystem.js";
7
- import { ConfigurationError, type FsCode, FsError } from "./errors.js";
6
+ import { FsError } from "./errors.js";
8
7
  /** @internal */
9
- export declare const failure: (code: FsCode, operation: string, path?: PathInput) => FsError;
8
+ export declare const SLASH_HEX = "2f";
9
+ /** @internal */
10
+ export declare const DOT_HEX = "2e";
11
+ /** @internal */
12
+ export declare const DOT_DOT_HEX = "2e2e";
13
+ /** @internal */
14
+ export declare const SLASH_BYTE = 47;
15
+ /** @internal */
16
+ export declare const MAX_NAME_BYTES = 255;
17
+ /** @internal */
18
+ export declare const isDotComponent: (name: string | undefined) => name is undefined | typeof DOT_HEX | typeof DOT_DOT_HEX;
10
19
  /** @internal */
11
20
  export declare const ownedPath: (bytes: Uint8Array) => BytePath;
12
21
  /** @internal */
@@ -14,18 +23,12 @@ export declare const strictString: (bytes: Uint8Array, operation: string) => Eff
14
23
  /** @internal */
15
24
  export declare const nameBytes: (name: string) => Uint8Array;
16
25
  /** @internal */
17
- export declare const attachedBuffer: (bytes: Uint8Array) => boolean;
26
+ export declare const isAttachedBytes: (bytes: Uint8Array) => boolean;
18
27
  /** @internal */
19
28
  export declare const pathFromBytes: (bytes: Uint8Array<ArrayBufferLike>) => Effect.Effect<BytePath, FsError, never>;
20
29
  /** @internal */
21
30
  export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array<ArrayBuffer>, FsError, never>;
22
31
  /** @internal */
23
- export interface LookupOptions {
24
- readonly followFinalSymlink?: boolean;
25
- readonly allowMissing?: boolean;
26
- readonly parentOnly?: boolean;
27
- }
28
- /** @internal */
29
32
  export interface PreparedPath {
30
33
  readonly input: PathInput;
31
34
  readonly absolute: boolean;
@@ -35,9 +38,7 @@ export interface PreparedPath {
35
38
  readonly components: ReadonlyArray<string>;
36
39
  }
37
40
  /** @internal */
38
- export declare const wellFormed: (value: string) => boolean;
41
+ export declare const inputBytes: (input: PathInput) => Result.Result<Uint8Array, "InvalidPathEncoding" | "InvalidArgument">;
39
42
  /** @internal */
40
43
  export declare const preparePath: (input: PathInput, operation: string, maxPathBytes: ByteSize.ByteSize | undefined) => Result.Result<PreparedPath, FsError>;
41
- /** @internal */
42
- export declare const decodeConfiguration: <A>(schema: Schema.Codec<A>, value: unknown) => Result.Result<A, ConfigurationError>;
43
44
  //# sourceMappingURL=path.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/path.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,OAAO,EAAE,kBAAkB,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEtE,gBAAgB;AAChB,eAAO,MAAM,OAAO,SAAU,MAAM,aAAa,MAAM,SAAS,SAAS,YACE,CAAA;AAE3E,gBAAgB;AAChB,eAAO,MAAM,SAAS,UAAW,UAAU,KAAG,QAE7C,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,YAAY,UAAW,UAAU,aAAa,MAAM,0CAI7D,CAAA;AAEJ,gBAAgB;AAChB,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,UAExC,CAAA;AAGD,gBAAgB;AAChB,eAAO,MAAM,cAAc,UAAW,UAAU,KAAG,OAQlD,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,aAAa,iFAQxB,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,WAAW,4EAItB,CAAA;AAEF,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,gBAAgB;AAChB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;AAED,gBAAgB;AAChB,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,OAS1C,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,WAAW,UACf,SAAS,aACL,MAAM,gBACH,QAAQ,CAAC,QAAQ,GAAG,SAAS,KAC1C,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAmCrC,CAAA;AAQD,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,GAAI,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,OAAO,yCAG3E,CAAA"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/path.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAGrC,gBAAgB;AAChB,eAAO,MAAM,SAAS,OAAO,CAAA;AAE7B,gBAAgB;AAChB,eAAO,MAAM,OAAO,OAAO,CAAA;AAE3B,gBAAgB;AAChB,eAAO,MAAM,WAAW,SAAS,CAAA;AAEjC,gBAAgB;AAChB,eAAO,MAAM,UAAU,KAAK,CAAA;AAG5B,gBAAgB;AAChB,eAAO,MAAM,cAAc,MAAM,CAAA;AAKjC,gBAAgB;AAChB,eAAO,MAAM,cAAc,SAAU,MAAM,GAAG,SAAS,KAAG,IAAI,IAAI,SAAS,GAAG,OAAO,OAAO,GAAG,OAAO,WACtC,CAAA;AAEhE,gBAAgB;AAChB,eAAO,MAAM,SAAS,UAAW,UAAU,KAAG,QAA+B,CAAA;AAE7E,gBAAgB;AAChB,eAAO,MAAM,YAAY,UAAW,UAAU,aAAa,MAAM,0CAI7D,CAAA;AAEJ,gBAAgB;AAChB,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,UAAyD,CAAA;AAgBlG,gBAAgB;AAChB,eAAO,MAAM,eAAe,UAAW,UAAU,KAAG,OAC2C,CAAA;AAE/F,gBAAgB;AAChB,eAAO,MAAM,aAAa,iFASxB,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,WAAW,4EAMtB,CAAA;AAEF,gBAAgB;AAChB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;AAoBD,gBAAgB;AAChB,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,qBAAqB,GAAG,iBAAiB,CAG5B,CAAA;AAErF,gBAAgB;AAChB,eAAO,MAAM,WAAW,UACf,SAAS,aACL,MAAM,gBACH,QAAQ,CAAC,QAAQ,GAAG,SAAS,KAC1C,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAgDrC,CAAA"}
@@ -2,33 +2,41 @@
2
2
  import * as ByteSize from "effect/ByteSize";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Encoding from "effect/Encoding";
5
+ import * as Predicate from "effect/Predicate";
5
6
  import * as Result from "effect/Result";
6
- import * as Schema from "effect/Schema";
7
- import * as SchemaIssue from "effect/SchemaIssue";
8
- import {} from "../../BytePath.js";
9
7
  import { getBytes as getBytePathBytes, make as makeBytePath } from "../bytePath.js";
10
- import { ConfigurationError, FsError } from "./errors.js";
8
+ import { FsError } from "./errors.js";
9
+ // Components are hex-encoded bytes so names compare as bytes, not text: 2f is "/", 2e is ".", 2e2e is "..".
11
10
  /** @internal */
12
- export const failure = (code, operation, path) => new FsError({ code, operation, ...(path === undefined ? {} : { path }) });
11
+ export const SLASH_HEX = "2f";
13
12
  /** @internal */
14
- export const ownedPath = (bytes) => {
15
- return makeBytePath(bytes);
16
- };
13
+ export const DOT_HEX = "2e";
14
+ /** @internal */
15
+ export const DOT_DOT_HEX = "2e2e";
16
+ /** @internal */
17
+ export const SLASH_BYTE = 47;
18
+ // POSIX NAME_MAX: the longest single path component.
19
+ /** @internal */
20
+ export const MAX_NAME_BYTES = 255;
21
+ // A missing final component is rejected wherever "." and ".." are, so it counts as a dot component.
22
+ // Callers pass the code POSIX gives their operation, so the codes differ on purpose: EEXIST for
23
+ // create (link, symlink, mkdir), EISDIR for open and unlink, EINVAL for rename and rmdir.
24
+ /** @internal */
25
+ export const isDotComponent = (name) => name === undefined || name === DOT_HEX || name === DOT_DOT_HEX;
26
+ /** @internal */
27
+ export const ownedPath = (bytes) => makeBytePath(bytes);
17
28
  /** @internal */
18
29
  export const strictString = (bytes, operation) => Effect.try({
19
30
  try: () => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes),
20
- catch: () => failure("UnrepresentableName", operation)
31
+ catch: () => new FsError({ code: "UnrepresentableName", operation })
21
32
  });
22
33
  /** @internal */
23
- export const nameBytes = (name) => {
24
- return Result.getOrThrow(Encoding.decodeHex(name));
25
- };
34
+ export const nameBytes = (name) => Result.getOrThrow(Encoding.decodeHex(name));
26
35
  // A zero-length view distinguishes a detached buffer from a valid empty buffer.
27
- /** @internal */
28
- export const attachedBuffer = (bytes) => {
36
+ const attachedBuffer = (bytes) => {
29
37
  try {
30
- new Uint8Array(bytes.buffer, bytes.byteOffset, 0);
31
- return true;
38
+ const probe = new Uint8Array(bytes.buffer, bytes.byteOffset, 0);
39
+ return probe.byteLength === 0;
32
40
  }
33
41
  catch (error) {
34
42
  if (error instanceof TypeError)
@@ -36,27 +44,29 @@ export const attachedBuffer = (bytes) => {
36
44
  throw error;
37
45
  }
38
46
  };
47
+ // Accepts any Uint8Array, subclasses such as Node's Buffer included; rejects views over a
48
+ // SharedArrayBuffer or a detached buffer before the bytes are copied or trusted.
49
+ /** @internal */
50
+ export const isAttachedBytes = (bytes) => Predicate.isUint8Array(bytes) && bytes.buffer instanceof ArrayBuffer && attachedBuffer(bytes);
39
51
  /** @internal */
40
52
  export const pathFromBytes = Effect.fn("VirtualFileSystem.pathFromBytes")(function* (bytes) {
41
- if (!(bytes instanceof Uint8Array) || !(bytes.buffer instanceof ArrayBuffer)) {
42
- return yield* failure("InvalidArgument", "pathFromBytes");
43
- }
44
- if (!attachedBuffer(bytes))
45
- return yield* failure("InvalidArgument", "pathFromBytes");
53
+ if (!isAttachedBytes(bytes))
54
+ return yield* new FsError({ code: "InvalidArgument", operation: "pathFromBytes" });
46
55
  const owned = new Uint8Array(bytes);
47
- if (owned.length === 0 || owned.includes(0))
48
- return yield* failure("InvalidArgument", "pathFromBytes");
56
+ if (owned.length === 0 || owned.includes(0)) {
57
+ return yield* new FsError({ code: "InvalidArgument", operation: "pathFromBytes" });
58
+ }
49
59
  return makeBytePath(owned);
50
60
  });
51
61
  /** @internal */
52
62
  export const pathToBytes = Effect.fn("VirtualFileSystem.pathToBytes")(function* (path) {
53
63
  const bytes = getBytePathBytes(path);
54
64
  if (bytes === undefined)
55
- return yield* failure("InvalidArgument", "pathToBytes");
65
+ return yield* new FsError({ code: "InvalidArgument", operation: "pathToBytes" });
56
66
  return new Uint8Array(bytes);
57
67
  });
58
- /** @internal */
59
- export const wellFormed = (value) => {
68
+ // Hand-rolled because String.prototype.isWellFormed is ES2024 and the package targets ES2023.
69
+ const wellFormed = (value) => {
60
70
  for (let index = 0; index < value.length; index++) {
61
71
  const code = value.charCodeAt(index);
62
72
  if (code >= 0xd800 && code <= 0xdbff) {
@@ -69,36 +79,38 @@ export const wellFormed = (value) => {
69
79
  }
70
80
  return true;
71
81
  };
82
+ const UTF8_ENCODER = new TextEncoder();
83
+ // Strings must be well-formed UTF-16 before encoding; BytePaths must belong to this package.
84
+ /** @internal */
85
+ export const inputBytes = (input) => Predicate.isString(input)
86
+ ? wellFormed(input) ? Result.succeed(UTF8_ENCODER.encode(input)) : Result.fail("InvalidPathEncoding")
87
+ : Result.fromNullishOr(getBytePathBytes(input), () => "InvalidArgument");
72
88
  /** @internal */
73
89
  export const preparePath = (input, operation, maxPathBytes) => {
74
- let bytes;
75
- if (typeof input === "string") {
76
- if (!wellFormed(input))
77
- return Result.fail(failure("InvalidPathEncoding", operation, input));
78
- bytes = new TextEncoder().encode(input);
90
+ const encoded = inputBytes(input);
91
+ if (Result.isFailure(encoded)) {
92
+ return Result.fail(encoded.failure === "InvalidPathEncoding"
93
+ ? new FsError({ code: "InvalidPathEncoding", operation, path: input })
94
+ : new FsError({ code: "InvalidArgument", operation }));
79
95
  }
80
- else if (typeof input === "object" && input !== null) {
81
- bytes = getBytePathBytes(input);
82
- }
83
- if (bytes === undefined)
84
- return Result.fail(failure("InvalidArgument", operation));
96
+ const bytes = encoded.success;
85
97
  if (bytes.length === 0)
86
- return Result.fail(failure("NotFound", operation, input));
98
+ return Result.fail(new FsError({ code: "NotFound", operation, path: input }));
87
99
  if (bytes.includes(0))
88
- return Result.fail(failure("InvalidArgument", operation, input));
100
+ return Result.fail(new FsError({ code: "InvalidArgument", operation, path: input }));
89
101
  if (maxPathBytes !== undefined && ByteSize.isGreaterThan(ByteSize.bytes(bytes.length), maxPathBytes)) {
90
- return Result.fail(failure("PathTooLong", operation, input));
102
+ return Result.fail(new FsError({ code: "PathTooLong", operation, path: input }));
91
103
  }
92
104
  const components = [];
93
105
  const suffixes = [];
94
106
  let start = 0;
95
107
  for (let index = 0; index <= bytes.length; index++) {
96
- if (index !== bytes.length && bytes[index] !== 47)
108
+ if (index !== bytes.length && bytes[index] !== SLASH_BYTE)
97
109
  continue;
98
110
  if (index > start) {
99
- // Provisional component bound from decision 0019; names are compared as bytes.
100
- if (index - start > 255)
101
- return Result.fail(failure("PathTooLong", operation, input));
111
+ if (index - start > MAX_NAME_BYTES) {
112
+ return Result.fail(new FsError({ code: "PathTooLong", operation, path: input }));
113
+ }
102
114
  components.push(Encoding.encodeHex(bytes.subarray(start, index)));
103
115
  suffixes.push(bytes.subarray(index));
104
116
  }
@@ -106,19 +118,10 @@ export const preparePath = (input, operation, maxPathBytes) => {
106
118
  }
107
119
  return Result.succeed({
108
120
  input,
109
- absolute: bytes[0] === 47,
110
- trailingSlash: bytes.at(-1) === 47,
121
+ absolute: bytes[0] === SLASH_BYTE,
122
+ trailingSlash: bytes.at(-1) === SLASH_BYTE,
111
123
  bytes,
112
124
  suffixes,
113
125
  components
114
126
  });
115
127
  };
116
- const configurationField = (issue) => {
117
- if (issue._tag === "Pointer")
118
- return issue.path.map(String).join(".");
119
- if (issue._tag === "Composite")
120
- return configurationField(issue.issues[0]);
121
- return "options";
122
- };
123
- /** @internal */
124
- export const decodeConfiguration = (schema, value) => Schema.decodeUnknownResult(schema, { onExcessProperty: "error" })(value).pipe(Result.mapError((error) => new ConfigurationError({ field: configurationField(error.issue) })));
@@ -0,0 +1,22 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { FsError } from "./errors.js";
3
+ /** @internal */
4
+ export type CommitOutcome = "committed" | "rejected" | "unknown";
5
+ /** @internal */
6
+ export interface CommitProvider<State> {
7
+ /** Provides a gate-coordinated shutdown effect once staging is ready. */
8
+ readonly onReady?: (shutdown: Effect.Effect<void>) => void;
9
+ /** Checks and encodes a candidate before the commit boundary. Failure leaves the volume available. */
10
+ readonly prepare?: (candidate: State) => Effect.Effect<void, FsError>;
11
+ /** Classifies a candidate as committed, definitely rejected, or uncertain. */
12
+ readonly commit: (candidate: State) => Effect.Effect<CommitOutcome>;
13
+ }
14
+ /** @internal */
15
+ export declare const makeStagedState: <State, Event = never>(initial: State, copy: (current: State) => Effect.Effect<State>, rawProvider: CommitProvider<State>, publish?: (candidate: State, events: ReadonlyArray<Event>) => void) => {
16
+ read: <A, E, R>(operation: string, inspect: (state: Readonly<State>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E | FsError, R>;
17
+ mutate: <A, E, R>(operation: string, change: (candidate: State, emit: (event: Event) => void) => Effect.Effect<A, E, R>, onStorageFailure?: () => void) => Effect.Effect<A, E | FsError, R>;
18
+ coordinate: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
19
+ checkAvailable: (operation: string) => Effect.Effect<void, FsError, never>;
20
+ shutdown: Effect.Effect<void, never, never>;
21
+ };
22
+ //# sourceMappingURL=stagedState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stagedState.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/stagedState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC,gBAAgB;AAChB,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhE,gBAAgB;AAChB,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;IAC1D,sGAAsG;IACtG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACrE,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;CACpE;AAED,gBAAgB;AAChB,eAAO,MAAM,eAAe,GAAI,KAAK,EAAE,KAAK,mBACjC,KAAK,QAER,CAAC,OAAO,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,eACjC,cAAc,CAAC,KAAK,CAAC,YACxB,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,IAAI;WAyBpD,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,MAAM,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;aAOrF,CAAC,EAAE,CAAC,EAAE,CAAC,aACV,MAAM,UACT,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,qBAC/D,MAAM,IAAI;iBAhBX,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gCAPxB,MAAM;;CA0E1C,CAAA"}
@@ -0,0 +1,62 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Exit from "effect/Exit";
3
+ import * as Semaphore from "effect/Semaphore";
4
+ import { FsError } from "./errors.js";
5
+ /** @internal */
6
+ export const makeStagedState = (initial,
7
+ // The copy must detach every mutable value that change can reach.
8
+ copy, rawProvider, publish) => {
9
+ const gate = Semaphore.makeUnsafe(1);
10
+ const provider = {
11
+ ...rawProvider,
12
+ commit: (candidate) => Effect.suspend(() => rawProvider.commit(candidate))
13
+ };
14
+ let current = initial;
15
+ let available = true;
16
+ const checkAvailable = (operation) => Effect.suspend(() => available
17
+ ? Effect.void
18
+ : Effect.fail(new FsError({ code: "VolumeUnavailable", operation })));
19
+ const coordinate = (effect) => gate.withPermit(effect);
20
+ const shutdown = coordinate(Effect.sync(() => {
21
+ available = false;
22
+ }));
23
+ const read = (operation, inspect) => coordinate(Effect.gen(function* () {
24
+ yield* checkAvailable(operation);
25
+ return yield* inspect(current);
26
+ }));
27
+ const mutate = (operation, change, onStorageFailure) => coordinate(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
28
+ yield* checkAvailable(operation);
29
+ const candidate = yield* restore(copy(current));
30
+ const events = [];
31
+ const value = yield* restore(change(candidate, (event) => events.push(event)));
32
+ if (provider.prepare !== undefined)
33
+ yield* restore(provider.prepare(candidate));
34
+ const committed = yield* Effect.exit(Effect.suspend(() => provider.commit(candidate)));
35
+ if (Exit.isFailure(committed)) {
36
+ available = false;
37
+ onStorageFailure?.();
38
+ return yield* new FsError({ code: "OutcomeUnknown", operation });
39
+ }
40
+ const outcome = committed.value;
41
+ if (outcome === "rejected") {
42
+ if (onStorageFailure !== undefined) {
43
+ available = false;
44
+ onStorageFailure();
45
+ }
46
+ return yield* new FsError({ code: "StorageRejected", operation });
47
+ }
48
+ if (outcome === "unknown") {
49
+ available = false;
50
+ onStorageFailure?.();
51
+ return yield* new FsError({ code: "OutcomeUnknown", operation });
52
+ }
53
+ current = candidate;
54
+ const published = yield* Effect.exit(Effect.sync(() => publish?.(candidate, events)));
55
+ if (Exit.isFailure(published)) {
56
+ available = false;
57
+ return yield* new FsError({ code: "OutcomeUnknown", operation });
58
+ }
59
+ return value;
60
+ })));
61
+ return { read, mutate, coordinate, checkAvailable, shutdown };
62
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"testHooks.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/testHooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAExD,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACxD;AAID,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,KAAG,eAAe,GAAG,SAA2C,CAAA;AAErH,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,QAAQ,eAAe,KAAG,MAAM,IAKpF,CAAA;AAED,gBAAgB;AAChB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC7C;AAID,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,KAAG,gBAAgB,GAAG,SAA0C,CAAA;AAElH,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,QAAQ,gBAAgB,KAAG,MAAM,IAKlF,CAAA"}
1
+ {"version":3,"file":"testHooks.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/testHooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAExD,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACxD;AAID,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,KAAG,eAAe,GAAG,SAA2C,CAAA;AAErH,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,QAAQ,eAAe,KAAG,MAAM,IAMpF,CAAA;AAED,gBAAgB;AAChB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC7C;AAID,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,KAAG,gBAAgB,GAAG,SAA0C,CAAA;AAElH,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,QAAQ,gBAAgB,KAAG,MAAM,IAMlF,CAAA"}
@@ -6,11 +6,11 @@ export interface Coordinator {
6
6
  <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
7
7
  }
8
8
  /** @internal */
9
- export interface WatchHub<A> {
9
+ export interface WatchHub<A, E = never> {
10
10
  readonly publishUnsafe: (event: () => A) => void;
11
11
  readonly publishManyUnsafe: (events: () => Iterable<A>) => void;
12
- readonly subscribe: (afterSubscribe?: Effect.Effect<void>) => Effect.Effect<Stream.Stream<A>, never, Scope.Scope>;
12
+ readonly subscribe: (afterSubscribe?: Effect.Effect<void>) => Effect.Effect<Stream.Stream<A>, E, Scope.Scope>;
13
13
  }
14
14
  /** @internal */
15
- export declare const make: <A>(coordinate: Coordinator) => Effect.Effect<WatchHub<A>, never, never>;
15
+ export declare const make: <A, E = never>(coordinate: Coordinator, capacity: number, rescan: () => A, checkAvailable?: Effect.Effect<void, E, never> | undefined) => Effect.Effect<WatchHub<A, E>, never, never>;
16
16
  //# sourceMappingURL=watchHub.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"watchHub.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/watchHub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,gBAAgB;AAChB,MAAM,WAAW,WAAW;IAC1B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAClE;AAED,gBAAgB;AAChB,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CAClH;AAED,gBAAgB;AAChB,eAAO,MAAM,IAAI,GAA+B,CAAC,sEA+B/C,CAAA"}
1
+ {"version":3,"file":"watchHub.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/watchHub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,gBAAgB;AAChB,MAAM,WAAW,WAAW;IAC1B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAClE;AAED,gBAAgB;AAChB,MAAM,WAAW,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK;IACpC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CAC9G;AAQD,gBAAgB;AAChB,eAAO,MAAM,IAAI,GAA+B,CAAC,EAAE,CAAC,mEAGpC,CAAC,4GAiEf,CAAA"}
@@ -1,33 +1,54 @@
1
1
  import * as Effect from "effect/Effect";
2
- import * as PubSub from "effect/PubSub";
2
+ import * as Queue from "effect/Queue";
3
3
  import * as Stream from "effect/Stream";
4
4
  /** @internal */
5
- export const make = Effect.fnUntraced(function* (coordinate) {
6
- const pubsub = yield* PubSub.unbounded();
7
- let activeSubscribers = 0;
5
+ export const make = Effect.fnUntraced(function* (coordinate, capacity, rescan, checkAvailable) {
6
+ const subscribers = new Set();
7
+ const publish = (event) => {
8
+ for (const subscriber of subscribers) {
9
+ if (subscriber.overflowed)
10
+ continue;
11
+ if (Queue.sizeUnsafe(subscriber.queue) < capacity - 1) {
12
+ Queue.offerUnsafe(subscriber.queue, event);
13
+ }
14
+ else {
15
+ subscriber.overflowed = true;
16
+ subscriber.marker = rescan();
17
+ Queue.offerUnsafe(subscriber.queue, subscriber.marker);
18
+ }
19
+ }
20
+ };
8
21
  const publishUnsafe = (event) => {
9
- if (activeSubscribers === 0)
10
- return;
11
- PubSub.publishUnsafe(pubsub, event());
22
+ if (subscribers.size > 0)
23
+ publish(event());
12
24
  };
13
25
  const publishManyUnsafe = (events) => {
14
- if (activeSubscribers === 0)
15
- return;
16
- for (const event of events())
17
- PubSub.publishUnsafe(pubsub, event);
26
+ if (subscribers.size > 0) {
27
+ for (const event of events())
28
+ publish(event);
29
+ }
18
30
  };
19
- const subscribe = (afterSubscribe) => Effect.uninterruptible(Effect.gen(function* () {
20
- const subscription = yield* coordinate(Effect.gen(function* () {
21
- const subscription = yield* PubSub.subscribe(pubsub);
22
- if (afterSubscribe !== undefined)
23
- yield* afterSubscribe;
24
- activeSubscribers += 1;
25
- return subscription;
26
- }));
27
- yield* Effect.addFinalizer(() => coordinate(Effect.sync(() => {
28
- activeSubscribers -= 1;
29
- })));
30
- return Stream.fromSubscription(subscription);
31
- }));
31
+ const subscribe = (afterSubscribe) => Effect.acquireRelease(coordinate(Effect.gen(function* () {
32
+ if (checkAvailable !== undefined)
33
+ yield* checkAvailable;
34
+ const queue = yield* Queue.bounded(capacity);
35
+ const subscriber = {
36
+ queue,
37
+ overflowed: false,
38
+ marker: undefined
39
+ };
40
+ if (afterSubscribe !== undefined)
41
+ yield* afterSubscribe;
42
+ subscribers.add(subscriber);
43
+ return subscriber;
44
+ })), (subscriber) => coordinate(Effect.sync(() => {
45
+ subscribers.delete(subscriber);
46
+ })).pipe(Effect.andThen(Queue.shutdown(subscriber.queue))), { interruptible: true }).pipe(Effect.map((subscriber) => Stream.fromEffectRepeat(Queue.take(subscriber.queue)).pipe(Stream.map((event) => {
47
+ if (subscriber.overflowed && event === subscriber.marker) {
48
+ subscriber.overflowed = false;
49
+ subscriber.marker = undefined;
50
+ }
51
+ return event;
52
+ }))));
32
53
  return { publishUnsafe, publishManyUnsafe, subscribe };
33
54
  });