@gaganref/confect-cli 0.0.0-stage → 10.3.1-fork.10

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 (99) hide show
  1. package/CHANGELOG.md +1824 -0
  2. package/LICENSE +7 -0
  3. package/README.md +16 -2
  4. package/bin/confect.mjs +26 -0
  5. package/dist/Ansi.mjs +18 -0
  6. package/dist/Ansi.mjs.map +1 -0
  7. package/dist/BuildError.mjs +30 -0
  8. package/dist/BuildError.mjs.map +1 -0
  9. package/dist/Bundler.mjs +426 -0
  10. package/dist/Bundler.mjs.map +1 -0
  11. package/dist/CodeBlockWriter.mjs +55 -0
  12. package/dist/CodeBlockWriter.mjs.map +1 -0
  13. package/dist/CodegenError.mjs +194 -0
  14. package/dist/CodegenError.mjs.map +1 -0
  15. package/dist/ConfectDirectory.mjs +35 -0
  16. package/dist/ConfectDirectory.mjs.map +1 -0
  17. package/dist/ConvexConfig.mjs +198 -0
  18. package/dist/ConvexConfig.mjs.map +1 -0
  19. package/dist/ConvexDirectory.mjs +54 -0
  20. package/dist/ConvexDirectory.mjs.map +1 -0
  21. package/dist/Declaration.mjs +258 -0
  22. package/dist/Declaration.mjs.map +1 -0
  23. package/dist/DirectoryOptions.mjs +42 -0
  24. package/dist/DirectoryOptions.mjs.map +1 -0
  25. package/dist/DocName.mjs +25 -0
  26. package/dist/DocName.mjs.map +1 -0
  27. package/dist/EntryPoint.mjs +111 -0
  28. package/dist/EntryPoint.mjs.map +1 -0
  29. package/dist/Evaluation.mjs +117 -0
  30. package/dist/Evaluation.mjs.map +1 -0
  31. package/dist/EvaluationWorker.mjs +46 -0
  32. package/dist/EvaluationWorker.mjs.map +1 -0
  33. package/dist/FunctionModule.mjs +137 -0
  34. package/dist/FunctionModule.mjs.map +1 -0
  35. package/dist/FunctionPath.mjs +18 -0
  36. package/dist/FunctionPath.mjs.map +1 -0
  37. package/dist/FunctionPaths.mjs +45 -0
  38. package/dist/FunctionPaths.mjs.map +1 -0
  39. package/dist/GenerationSnapshot.mjs +183 -0
  40. package/dist/GenerationSnapshot.mjs.map +1 -0
  41. package/dist/GroupPath.mjs +51 -0
  42. package/dist/GroupPath.mjs.map +1 -0
  43. package/dist/GroupPaths.mjs +8 -0
  44. package/dist/GroupPaths.mjs.map +1 -0
  45. package/dist/ImplModulePath.mjs +8 -0
  46. package/dist/ImplModulePath.mjs.map +1 -0
  47. package/dist/LeafModule.mjs +285 -0
  48. package/dist/LeafModule.mjs.map +1 -0
  49. package/dist/ProjectRoot.mjs +38 -0
  50. package/dist/ProjectRoot.mjs.map +1 -0
  51. package/dist/RelativeModulePath.mjs +13 -0
  52. package/dist/RelativeModulePath.mjs.map +1 -0
  53. package/dist/Remove.mjs +24 -0
  54. package/dist/Remove.mjs.map +1 -0
  55. package/dist/RestoreWatcher.mjs +31 -0
  56. package/dist/RestoreWatcher.mjs.map +1 -0
  57. package/dist/Rollback.mjs +50 -0
  58. package/dist/Rollback.mjs.map +1 -0
  59. package/dist/SnapshotError.mjs +21 -0
  60. package/dist/SnapshotError.mjs.map +1 -0
  61. package/dist/SpecAssemblyNode.mjs +91 -0
  62. package/dist/SpecAssemblyNode.mjs.map +1 -0
  63. package/dist/SpecModulePath.mjs +10 -0
  64. package/dist/SpecModulePath.mjs.map +1 -0
  65. package/dist/SyncState.mjs +122 -0
  66. package/dist/SyncState.mjs.map +1 -0
  67. package/dist/TableModule.mjs +97 -0
  68. package/dist/TableModule.mjs.map +1 -0
  69. package/dist/TableModulePath.mjs +8 -0
  70. package/dist/TableModulePath.mjs.map +1 -0
  71. package/dist/WatchBuild.mjs +190 -0
  72. package/dist/WatchBuild.mjs.map +1 -0
  73. package/dist/WatchGraph.mjs +35 -0
  74. package/dist/WatchGraph.mjs.map +1 -0
  75. package/dist/WatchTopology.mjs +55 -0
  76. package/dist/WatchTopology.mjs.map +1 -0
  77. package/dist/Watcher.mjs +377 -0
  78. package/dist/Watcher.mjs.map +1 -0
  79. package/dist/cliApp.mjs +9 -0
  80. package/dist/cliApp.mjs.map +1 -0
  81. package/dist/confect/codegen.mjs +570 -0
  82. package/dist/confect/codegen.mjs.map +1 -0
  83. package/dist/confect/declaration.mjs +219 -0
  84. package/dist/confect/declaration.mjs.map +1 -0
  85. package/dist/confect/dev.mjs +182 -0
  86. package/dist/confect/dev.mjs.map +1 -0
  87. package/dist/confect.mjs +9 -0
  88. package/dist/confect.mjs.map +1 -0
  89. package/dist/index.mjs +19 -0
  90. package/dist/index.mjs.map +1 -0
  91. package/dist/log.mjs +107 -0
  92. package/dist/log.mjs.map +1 -0
  93. package/dist/package.mjs +6 -0
  94. package/dist/package.mjs.map +1 -0
  95. package/dist/templates.mjs +448 -0
  96. package/dist/templates.mjs.map +1 -0
  97. package/dist/utils.mjs +105 -0
  98. package/dist/utils.mjs.map +1 -0
  99. package/package.json +51 -3
@@ -0,0 +1,190 @@
1
+ import { affected, make } from "./WatchGraph.mjs";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Array from "effect/Array";
4
+ import * as Result from "effect/Result";
5
+ import * as HashSet from "effect/HashSet";
6
+ import * as Match from "effect/Match";
7
+ import * as Option from "effect/Option";
8
+ import * as HashMap from "effect/HashMap";
9
+ import * as Brand from "effect/Brand";
10
+ import * as Data from "effect/Data";
11
+ import * as Equal from "effect/Equal";
12
+ import * as Hex from "effect/encoding/Hex";
13
+ //#region src/WatchBuild.ts
14
+ const Failure = Data.taggedEnum();
15
+ const emptyCapture = {
16
+ fingerprints: HashMap.empty(),
17
+ failures: HashMap.empty()
18
+ };
19
+ const Outcome = Data.taggedEnum();
20
+ const Generation = Brand.nominal();
21
+ const Barrier = Brand.nominal();
22
+ const RequestId = Brand.nominal();
23
+ const makeRequestId = () => RequestId({});
24
+ const Request = Data.taggedEnum();
25
+ const initialProgress = {
26
+ generation: Generation(0),
27
+ request: Request.Idle(),
28
+ lastRequest: Option.none()
29
+ };
30
+ const start = (progress) => ({
31
+ ...progress,
32
+ generation: Generation(progress.generation + 1)
33
+ });
34
+ const captureBarrier = (progress) => Barrier(progress.generation);
35
+ const request = (progress, id) => ({
36
+ ...progress,
37
+ request: Option.isSome(progress.lastRequest) && progress.lastRequest.value === id ? Request.Joined() : Request.Pending(),
38
+ lastRequest: Option.some(id)
39
+ });
40
+ const complete = (progress, completion) => ({
41
+ ...progress,
42
+ request: Request.$match(progress.request, {
43
+ Idle: (idle) => idle,
44
+ Pending: () => Request.Completed({ completion }),
45
+ Joined: () => Request.Completed({ completion }),
46
+ Completed: (completed) => completed
47
+ })
48
+ });
49
+ const getCompletion = (progress) => Request.$match(progress.request, {
50
+ Idle: () => Option.none(),
51
+ Pending: () => Option.none(),
52
+ Joined: () => Option.none(),
53
+ Completed: ({ completion }) => Option.some(completion)
54
+ });
55
+ const joinedWithoutCompletion = (progress) => Request.$is("Joined")(progress.request);
56
+ const endRequest = (progress) => ({
57
+ ...progress,
58
+ request: Request.Idle()
59
+ });
60
+ const isFresh = (barrier, completion) => completion.generation > barrier;
61
+ const verifyInputs = Effect.fn("WatchBuild.verifyInputs")(function* (snapshot, fs, crypto) {
62
+ const matches = yield* Effect.forEach(snapshot.fingerprints, ([file, expected]) => fs.readFile(file).pipe(Effect.flatMap((contents) => crypto.digest("SHA-256", contents)), Effect.map((digest) => Hex.encode(digest) === expected), Effect.mapError((cause) => Failure.InputCaptureFailed({
63
+ file,
64
+ cause
65
+ }))), { concurrency: 8 });
66
+ return Array.every(matches, (matchesInput) => matchesInput);
67
+ });
68
+ const State = Data.taggedEnum();
69
+ const lastSuccessful = (state) => State.$match(state, {
70
+ Initial: () => Option.none(),
71
+ Succeeded: ({ snapshot }) => Option.some(snapshot),
72
+ Failed: ({ lastSuccessful: snapshot }) => snapshot
73
+ });
74
+ const observe = (previous, outcome) => Outcome.$match(outcome, {
75
+ Succeeded: ({ snapshot, warnings }) => State.Succeeded({
76
+ snapshot,
77
+ warnings
78
+ }),
79
+ Failed: (observation) => State.Failed({
80
+ observation,
81
+ lastSuccessful: lastSuccessful(previous),
82
+ discovered: State.$is("Initial")(previous) ? HashSet.empty() : HashSet.union(State.$is("Failed")(previous) ? previous.discovered : HashSet.empty(), HashSet.difference(HashSet.fromIterable(HashMap.keys(observation.fingerprints)), HashSet.fromIterable(HashMap.keys(fingerprints(previous)))))
83
+ })
84
+ });
85
+ const fingerprints = (state) => State.$match(state, {
86
+ Initial: () => HashMap.empty(),
87
+ Succeeded: ({ snapshot }) => snapshot.fingerprints,
88
+ Failed: ({ observation }) => observation.fingerprints
89
+ });
90
+ const outputHash = (state) => State.$match(state, {
91
+ Initial: () => Option.none(),
92
+ Succeeded: ({ snapshot }) => Option.some(snapshot.outputHash),
93
+ Failed: () => Option.none()
94
+ });
95
+ const Change = Data.taggedEnum();
96
+ const diff = (previous, current, generatedDirectoryPrefix) => {
97
+ const previousFingerprints = fingerprints(previous);
98
+ const currentFingerprints = fingerprints(current);
99
+ const files = HashSet.union(HashSet.fromIterable(HashMap.keys(previousFingerprints)), HashSet.fromIterable(HashMap.keys(currentFingerprints)));
100
+ const changed = Array.fromIterable(files).filter((file) => !Equal.equals(HashMap.get(previousFingerprints, file), HashMap.get(currentFingerprints, file)));
101
+ const sources = changed.filter((file) => !file.startsWith(generatedDirectoryPrefix));
102
+ const recovered = State.$is("Failed")(previous) && State.$is("Succeeded")(current);
103
+ const untrackedOutput = recovered ? Option.match(lastSuccessful(previous), {
104
+ onNone: () => false,
105
+ onSome: (baseline) => Equal.equals(baseline.fingerprints, currentFingerprints) && !Equal.equals(Option.some(baseline.outputHash), outputHash(current))
106
+ }) : changed.length === 0 && !Equal.equals(outputHash(previous), outputHash(current));
107
+ if (State.$is("Initial")(previous) || sources.some((file) => HashMap.has(previousFingerprints, file)) && State.$is("Failed")(current) || untrackedOutput) return Change.Full();
108
+ const graph = Option.match(lastSuccessful(current), {
109
+ onNone: () => HashMap.empty(),
110
+ onSome: (snapshot) => snapshot.graph
111
+ });
112
+ const previousGraph = Option.match(lastSuccessful(previous), {
113
+ onNone: () => HashMap.empty(),
114
+ onSome: (snapshot) => snapshot.graph
115
+ });
116
+ const affectedSources = recovered && State.$is("Failed")(previous) ? Array.fromIterable(HashSet.union(HashSet.fromIterable(sources), HashSet.filter(previous.discovered, (file) => !file.startsWith(generatedDirectoryPrefix)))) : sources;
117
+ const paths = HashSet.union(affected(graph, affectedSources), affected(previousGraph, affectedSources));
118
+ return recovered ? Change.Recovered({ paths }) : Change.Affected({ paths });
119
+ };
120
+ const parse = Effect.fn("WatchBuild.parse")(function* (result, capture, resolve, crypto) {
121
+ const failures = Array.fromIterable(HashMap.values(capture.failures));
122
+ if (Array.isReadonlyArrayNonEmpty(result.errors)) failures.push(Failure.BuildFailed({ errors: result.errors }));
123
+ if (Array.isReadonlyArrayNonEmpty(failures)) return Outcome.Failed({
124
+ fingerprints: capture.fingerprints,
125
+ failures,
126
+ warnings: result.warnings
127
+ });
128
+ if (!result.metafile) return Outcome.Failed({
129
+ fingerprints: capture.fingerprints,
130
+ failures: [Failure.InvalidBuildResult({ missing: "metafile" })],
131
+ warnings: result.warnings
132
+ });
133
+ if (!result.outputFiles || !Array.isReadonlyArrayNonEmpty(result.outputFiles)) return Outcome.Failed({
134
+ fingerprints: capture.fingerprints,
135
+ failures: [Failure.InvalidBuildResult({ missing: "outputFiles" })],
136
+ warnings: result.warnings
137
+ });
138
+ const hashes = yield* Effect.forEach(result.outputFiles, (file) => crypto.digest("SHA-256", file.contents).pipe(Effect.map(Hex.encode), Effect.mapError((cause) => Failure.OutputCaptureFailed({
139
+ file: file.path,
140
+ cause
141
+ })))).pipe(Effect.result);
142
+ if (Result.isFailure(hashes)) return Outcome.Failed({
143
+ fingerprints: capture.fingerprints,
144
+ failures: [hashes.failure],
145
+ warnings: result.warnings
146
+ });
147
+ return Outcome.Succeeded({
148
+ snapshot: {
149
+ fingerprints: capture.fingerprints,
150
+ graph: make(result.metafile, resolve),
151
+ outputHash: hashes.success.join(":")
152
+ },
153
+ warnings: result.warnings
154
+ });
155
+ });
156
+ const failureMessage = (failure) => Failure.$match(failure, {
157
+ BuildFailed: ({ errors }) => errors.map((error) => error.text).join("\n"),
158
+ InputCaptureFailed: ({ file, cause }) => `Could not fingerprint input ${file}: ${cause.message}`,
159
+ OutputCaptureFailed: ({ file, cause }) => `Could not fingerprint output ${file}: ${cause.message}`,
160
+ InvalidBuildResult: ({ missing }) => `esbuild completed without ${missing}`
161
+ });
162
+ const messages = (outcome) => Outcome.$match(outcome, {
163
+ Succeeded: () => [],
164
+ Failed: ({ failures }) => failures.flatMap((failure) => Match.value(failure).pipe(Match.tag("BuildFailed", ({ errors }) => errors), Match.tag("InputCaptureFailed", "OutputCaptureFailed", "InvalidBuildResult", (captureFailure) => [{
165
+ id: "confect-watch",
166
+ pluginName: "confect",
167
+ text: failureMessage(captureFailure),
168
+ location: null,
169
+ notes: [],
170
+ detail: captureFailure
171
+ }]), Match.exhaustive))
172
+ });
173
+ const diagnostics = (state) => State.$match(state, {
174
+ Initial: () => ({
175
+ errors: [],
176
+ warnings: []
177
+ }),
178
+ Succeeded: ({ warnings }) => ({
179
+ errors: [],
180
+ warnings
181
+ }),
182
+ Failed: ({ observation }) => ({
183
+ errors: messages(observation),
184
+ warnings: observation.warnings
185
+ })
186
+ });
187
+ //#endregion
188
+ export { Change, Failure, Outcome, State, captureBarrier, complete, diagnostics, diff, emptyCapture, endRequest, failureMessage, fingerprints, getCompletion, initialProgress, isFresh, joinedWithoutCompletion, lastSuccessful, makeRequestId, messages, observe, outputHash, parse, request, start, verifyInputs };
189
+
190
+ //# sourceMappingURL=WatchBuild.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchBuild.mjs","names":["WatchGraph.affected","WatchGraph.make"],"sources":["../src/WatchBuild.ts"],"sourcesContent":["import * as Array from \"effect/Array\";\nimport * as Brand from \"effect/Brand\";\nimport type * as Crypto from \"effect/Crypto\";\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport type * as FileSystem from \"effect/FileSystem\";\nimport * as Equal from \"effect/Equal\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Match from \"effect/Match\";\nimport * as Hex from \"effect/encoding/Hex\";\nimport * as Option from \"effect/Option\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as Result from \"effect/Result\";\nimport type * as esbuild from \"esbuild\";\nimport * as WatchGraph from \"./WatchGraph.js\";\n\nexport type Failure = Data.TaggedEnum<{\n BuildFailed: {\n readonly errors: Array.NonEmptyReadonlyArray<esbuild.Message>;\n };\n InputCaptureFailed: { readonly file: string; readonly cause: PlatformError };\n OutputCaptureFailed: { readonly file: string; readonly cause: PlatformError };\n InvalidBuildResult: { readonly missing: \"metafile\" | \"outputFiles\" };\n}>;\nexport const Failure = Data.taggedEnum<Failure>();\nexport type Fingerprints = HashMap.HashMap<string, string>;\n\nexport interface Capture {\n readonly fingerprints: Fingerprints;\n readonly failures: HashMap.HashMap<string, Failure>;\n}\nexport const emptyCapture: Capture = {\n fingerprints: HashMap.empty(),\n failures: HashMap.empty(),\n};\n\nexport interface Snapshot {\n readonly fingerprints: Fingerprints;\n readonly graph: WatchGraph.WatchGraph;\n readonly outputHash: string;\n}\n\nexport type Outcome = Data.TaggedEnum<{\n Succeeded: {\n readonly snapshot: Snapshot;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n Failed: {\n readonly fingerprints: Fingerprints;\n readonly failures: Array.NonEmptyReadonlyArray<Failure>;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n}>;\nexport const Outcome = Data.taggedEnum<Outcome>();\n\nexport type Generation = number & Brand.Brand<\"WatchBuild.Generation\">;\nconst Generation = Brand.nominal<Generation>();\n\nexport type Barrier = number & Brand.Brand<\"WatchBuild.Barrier\">;\nconst Barrier = Brand.nominal<Barrier>();\n\nexport type RequestId = object & Brand.Brand<\"WatchBuild.RequestId\">;\nconst RequestId = Brand.nominal<RequestId>();\n\nexport const makeRequestId = (): RequestId => RequestId({});\n\nexport interface Completion {\n readonly generation: Generation;\n readonly outcome: Outcome;\n}\n\ntype Request = Data.TaggedEnum<{\n Idle: {};\n Pending: {};\n Joined: {};\n Completed: { readonly completion: Completion };\n}>;\nconst Request = Data.taggedEnum<Request>();\n\nexport interface Progress {\n readonly generation: Generation;\n readonly request: Request;\n readonly lastRequest: Option.Option<RequestId>;\n}\n\nexport const initialProgress: Progress = {\n generation: Generation(0),\n request: Request.Idle(),\n lastRequest: Option.none(),\n};\n\nexport const start = (progress: Progress): Progress => ({\n ...progress,\n generation: Generation(progress.generation + 1),\n});\n\nexport const captureBarrier = (progress: Progress): Barrier =>\n Barrier(progress.generation);\n\nexport const request = (progress: Progress, id: RequestId): Progress => ({\n ...progress,\n request:\n Option.isSome(progress.lastRequest) && progress.lastRequest.value === id\n ? Request.Joined()\n : Request.Pending(),\n lastRequest: Option.some(id),\n});\n\nexport const complete = (\n progress: Progress,\n completion: Completion,\n): Progress => ({\n ...progress,\n request: Request.$match(progress.request, {\n Idle: (idle) => idle,\n Pending: () => Request.Completed({ completion }),\n Joined: () => Request.Completed({ completion }),\n Completed: (completed) => completed,\n }),\n});\n\nexport const getCompletion = (progress: Progress): Option.Option<Completion> =>\n Request.$match(progress.request, {\n Idle: () => Option.none(),\n Pending: () => Option.none(),\n Joined: () => Option.none(),\n Completed: ({ completion }) => Option.some(completion),\n });\n\nexport const joinedWithoutCompletion = (progress: Progress): boolean =>\n Request.$is(\"Joined\")(progress.request);\n\nexport const endRequest = (progress: Progress): Progress => ({\n ...progress,\n request: Request.Idle(),\n});\n\nexport const isFresh = (barrier: Barrier, completion: Completion): boolean =>\n completion.generation > barrier;\n\nexport const verifyInputs = Effect.fn(\"WatchBuild.verifyInputs\")(function* (\n snapshot: Snapshot,\n fs: FileSystem.FileSystem,\n crypto: Crypto.Crypto,\n) {\n const matches = yield* Effect.forEach(\n snapshot.fingerprints,\n ([file, expected]) =>\n fs.readFile(file).pipe(\n Effect.flatMap((contents) => crypto.digest(\"SHA-256\", contents)),\n Effect.map((digest) => Hex.encode(digest) === expected),\n Effect.mapError((cause) => Failure.InputCaptureFailed({ file, cause })),\n ),\n { concurrency: 8 },\n );\n return Array.every(matches, (matchesInput) => matchesInput);\n});\n\nexport type State = Data.TaggedEnum<{\n Initial: {};\n Succeeded: {\n readonly snapshot: Snapshot;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n };\n Failed: {\n readonly observation: Extract<Outcome, { readonly _tag: \"Failed\" }>;\n readonly lastSuccessful: Option.Option<Snapshot>;\n readonly discovered: HashSet.HashSet<string>;\n };\n}>;\nexport const State = Data.taggedEnum<State>();\n\nexport const lastSuccessful = (state: State): Option.Option<Snapshot> =>\n State.$match(state, {\n Initial: () => Option.none(),\n Succeeded: ({ snapshot }) => Option.some(snapshot),\n Failed: ({ lastSuccessful: snapshot }) => snapshot,\n });\n\nexport const observe = (previous: State, outcome: Outcome): State =>\n Outcome.$match(outcome, {\n Succeeded: ({ snapshot, warnings }) =>\n State.Succeeded({ snapshot, warnings }),\n Failed: (observation) =>\n State.Failed({\n observation,\n lastSuccessful: lastSuccessful(previous),\n discovered: State.$is(\"Initial\")(previous)\n ? HashSet.empty()\n : HashSet.union(\n State.$is(\"Failed\")(previous)\n ? previous.discovered\n : HashSet.empty(),\n HashSet.difference(\n HashSet.fromIterable(HashMap.keys(observation.fingerprints)),\n HashSet.fromIterable(HashMap.keys(fingerprints(previous))),\n ),\n ),\n }),\n });\n\nexport const fingerprints = (state: State): Fingerprints =>\n State.$match(state, {\n Initial: () => HashMap.empty(),\n Succeeded: ({ snapshot }) => snapshot.fingerprints,\n Failed: ({ observation }) => observation.fingerprints,\n });\n\nexport const outputHash = (state: State): Option.Option<string> =>\n State.$match(state, {\n Initial: () => Option.none(),\n Succeeded: ({ snapshot }) => Option.some(snapshot.outputHash),\n Failed: () => Option.none(),\n });\n\nexport type Change = Data.TaggedEnum<{\n Full: {};\n Affected: { readonly paths: HashSet.HashSet<string> };\n Recovered: { readonly paths: HashSet.HashSet<string> };\n}>;\nexport const Change = Data.taggedEnum<Change>();\n\nexport const diff = (\n previous: State,\n current: State,\n generatedDirectoryPrefix: string,\n): Change => {\n const previousFingerprints = fingerprints(previous);\n const currentFingerprints = fingerprints(current);\n const files = HashSet.union(\n HashSet.fromIterable(HashMap.keys(previousFingerprints)),\n HashSet.fromIterable(HashMap.keys(currentFingerprints)),\n );\n const changed = Array.fromIterable(files).filter(\n (file) =>\n !Equal.equals(\n HashMap.get(previousFingerprints, file),\n HashMap.get(currentFingerprints, file),\n ),\n );\n const sources = changed.filter(\n (file) => !file.startsWith(generatedDirectoryPrefix),\n );\n const recovered =\n State.$is(\"Failed\")(previous) && State.$is(\"Succeeded\")(current);\n const untrackedOutput = recovered\n ? Option.match(lastSuccessful(previous), {\n onNone: () => false,\n onSome: (baseline) =>\n Equal.equals(baseline.fingerprints, currentFingerprints) &&\n !Equal.equals(Option.some(baseline.outputHash), outputHash(current)),\n })\n : changed.length === 0 &&\n !Equal.equals(outputHash(previous), outputHash(current));\n if (\n State.$is(\"Initial\")(previous) ||\n (sources.some((file) => HashMap.has(previousFingerprints, file)) &&\n State.$is(\"Failed\")(current)) ||\n untrackedOutput\n )\n return Change.Full();\n\n const graph = Option.match(lastSuccessful(current), {\n onNone: () => HashMap.empty<string, HashSet.HashSet<string>>(),\n onSome: (snapshot) => snapshot.graph,\n });\n const previousGraph = Option.match(lastSuccessful(previous), {\n onNone: () => HashMap.empty<string, HashSet.HashSet<string>>(),\n onSome: (snapshot) => snapshot.graph,\n });\n const affectedSources =\n recovered && State.$is(\"Failed\")(previous)\n ? Array.fromIterable(\n HashSet.union(\n HashSet.fromIterable(sources),\n HashSet.filter(\n previous.discovered,\n (file) => !file.startsWith(generatedDirectoryPrefix),\n ),\n ),\n )\n : sources;\n const paths = HashSet.union(\n WatchGraph.affected(graph, affectedSources),\n WatchGraph.affected(previousGraph, affectedSources),\n );\n return recovered ? Change.Recovered({ paths }) : Change.Affected({ paths });\n};\n\nexport const parse = Effect.fn(\"WatchBuild.parse\")(function* (\n result: esbuild.BuildResult,\n capture: Capture,\n resolve: (file: string) => string,\n crypto: Crypto.Crypto,\n) {\n const failures = Array.fromIterable(HashMap.values(capture.failures));\n if (Array.isReadonlyArrayNonEmpty(result.errors))\n failures.push(Failure.BuildFailed({ errors: result.errors }));\n if (Array.isReadonlyArrayNonEmpty(failures))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures,\n warnings: result.warnings,\n });\n if (!result.metafile)\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [Failure.InvalidBuildResult({ missing: \"metafile\" })],\n warnings: result.warnings,\n });\n if (!result.outputFiles || !Array.isReadonlyArrayNonEmpty(result.outputFiles))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [Failure.InvalidBuildResult({ missing: \"outputFiles\" })],\n warnings: result.warnings,\n });\n const hashes = yield* Effect.forEach(result.outputFiles, (file) =>\n crypto.digest(\"SHA-256\", file.contents).pipe(\n Effect.map(Hex.encode),\n Effect.mapError((cause) =>\n Failure.OutputCaptureFailed({ file: file.path, cause }),\n ),\n ),\n ).pipe(Effect.result);\n if (Result.isFailure(hashes))\n return Outcome.Failed({\n fingerprints: capture.fingerprints,\n failures: [hashes.failure],\n warnings: result.warnings,\n });\n return Outcome.Succeeded({\n snapshot: {\n fingerprints: capture.fingerprints,\n graph: WatchGraph.make(result.metafile, resolve),\n outputHash: hashes.success.join(\":\"),\n },\n warnings: result.warnings,\n });\n});\n\nexport const failureMessage = (failure: Failure): string =>\n Failure.$match(failure, {\n BuildFailed: ({ errors }) => errors.map((error) => error.text).join(\"\\n\"),\n InputCaptureFailed: ({ file, cause }) =>\n `Could not fingerprint input ${file}: ${cause.message}`,\n OutputCaptureFailed: ({ file, cause }) =>\n `Could not fingerprint output ${file}: ${cause.message}`,\n InvalidBuildResult: ({ missing }) => `esbuild completed without ${missing}`,\n });\n\nexport const messages = (outcome: Outcome): ReadonlyArray<esbuild.Message> =>\n Outcome.$match(outcome, {\n Succeeded: () => [],\n Failed: ({ failures }) =>\n failures.flatMap((failure) =>\n Match.value(failure).pipe(\n Match.tag(\"BuildFailed\", ({ errors }) => errors),\n Match.tag(\n \"InputCaptureFailed\",\n \"OutputCaptureFailed\",\n \"InvalidBuildResult\",\n (captureFailure) => [\n {\n id: \"confect-watch\",\n pluginName: \"confect\",\n text: failureMessage(captureFailure),\n location: null,\n notes: [],\n detail: captureFailure,\n },\n ],\n ),\n Match.exhaustive,\n ),\n ),\n });\n\nexport const diagnostics = (\n state: State,\n): {\n readonly errors: ReadonlyArray<esbuild.Message>;\n readonly warnings: ReadonlyArray<esbuild.Message>;\n} =>\n State.$match(state, {\n Initial: () => ({ errors: [], warnings: [] }),\n Succeeded: ({ warnings }) => ({ errors: [], warnings }),\n Failed: ({ observation }) => ({\n errors: messages(observation),\n warnings: observation.warnings,\n }),\n });\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import * as HashSet from "effect/HashSet";
2
+ import * as Option from "effect/Option";
3
+ import * as HashMap from "effect/HashMap";
4
+ //#region src/WatchGraph.ts
5
+ const make = (metafile, resolve) => {
6
+ let importers = HashMap.empty();
7
+ for (const [file, input] of Object.entries(metafile.inputs)) {
8
+ const importer = resolve(file);
9
+ for (const dependency of input.imports) {
10
+ if (dependency.external) continue;
11
+ const imported = resolve(dependency.path);
12
+ const parents = Option.getOrElse(HashMap.get(importers, imported), () => HashSet.empty());
13
+ importers = HashMap.set(importers, imported, HashSet.add(parents, importer));
14
+ }
15
+ }
16
+ return importers;
17
+ };
18
+ const affected = (graph, changed) => {
19
+ let visited = HashSet.fromIterable(changed);
20
+ const pending = [...changed];
21
+ while (pending.length > 0) {
22
+ const file = pending.pop();
23
+ if (file === void 0) break;
24
+ for (const importer of Option.getOrElse(HashMap.get(graph, file), () => HashSet.empty())) {
25
+ if (HashSet.has(visited, importer)) continue;
26
+ visited = HashSet.add(visited, importer);
27
+ pending.push(importer);
28
+ }
29
+ }
30
+ return visited;
31
+ };
32
+ //#endregion
33
+ export { affected, make };
34
+
35
+ //# sourceMappingURL=WatchGraph.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchGraph.mjs","names":[],"sources":["../src/WatchGraph.ts"],"sourcesContent":["import type * as esbuild from \"esbuild\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\n\nexport type WatchGraph = HashMap.HashMap<string, HashSet.HashSet<string>>;\n\nexport const make = (\n metafile: esbuild.Metafile,\n resolve: (file: string) => string,\n): WatchGraph => {\n let importers = HashMap.empty<string, HashSet.HashSet<string>>();\n for (const [file, input] of Object.entries(metafile.inputs)) {\n const importer = resolve(file);\n for (const dependency of input.imports) {\n if (dependency.external) continue;\n const imported = resolve(dependency.path);\n const parents = Option.getOrElse(HashMap.get(importers, imported), () =>\n HashSet.empty<string>(),\n );\n importers = HashMap.set(\n importers,\n imported,\n HashSet.add(parents, importer),\n );\n }\n }\n return importers;\n};\n\nexport const affected = (\n graph: WatchGraph,\n changed: ReadonlyArray<string>,\n): HashSet.HashSet<string> => {\n let visited = HashSet.fromIterable(changed);\n const pending = [...changed];\n while (pending.length > 0) {\n const file = pending.pop();\n if (file === undefined) break;\n for (const importer of Option.getOrElse(HashMap.get(graph, file), () =>\n HashSet.empty<string>(),\n )) {\n if (HashSet.has(visited, importer)) continue;\n visited = HashSet.add(visited, importer);\n pending.push(importer);\n }\n }\n return visited;\n};\n"],"mappings":";;;;AAOA,MAAa,QACX,UACA,YACe;CACf,IAAI,YAAY,QAAQ,MAAuC;CAC/D,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,MAAM,GAAG;EAC3D,MAAM,WAAW,QAAQ,IAAI;EAC7B,KAAK,MAAM,cAAc,MAAM,SAAS;GACtC,IAAI,WAAW,UAAU;GACzB,MAAM,WAAW,QAAQ,WAAW,IAAI;GACxC,MAAM,UAAU,OAAO,UAAU,QAAQ,IAAI,WAAW,QAAQ,SAC9D,QAAQ,MAAc,CACxB;GACA,YAAY,QAAQ,IAClB,WACA,UACA,QAAQ,IAAI,SAAS,QAAQ,CAC/B;EACF;CACF;CACA,OAAO;AACT;AAEA,MAAa,YACX,OACA,YAC4B;CAC5B,IAAI,UAAU,QAAQ,aAAa,OAAO;CAC1C,MAAM,UAAU,CAAC,GAAG,OAAO;CAC3B,OAAO,QAAQ,SAAS,GAAG;EACzB,MAAM,OAAO,QAAQ,IAAI;EACzB,IAAI,SAAS,KAAA,GAAW;EACxB,KAAK,MAAM,YAAY,OAAO,UAAU,QAAQ,IAAI,OAAO,IAAI,SAC7D,QAAQ,MAAc,CACxB,GAAG;GACD,IAAI,QAAQ,IAAI,SAAS,QAAQ,GAAG;GACpC,UAAU,QAAQ,IAAI,SAAS,QAAQ;GACvC,QAAQ,KAAK,QAAQ;EACvB;CACF;CACA,OAAO;AACT"}
@@ -0,0 +1,55 @@
1
+ import { add, empty, full } from "./SyncState.mjs";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import * as Ref from "effect/Ref";
5
+ import * as Data from "effect/Data";
6
+ import * as Deferred from "effect/Deferred";
7
+ import * as Semaphore from "effect/Semaphore";
8
+ //#region src/WatchTopology.ts
9
+ const State = Data.taggedEnum();
10
+ const Decision = Data.taggedEnum();
11
+ const make = Effect.fn("WatchTopology.make")(function* (pending) {
12
+ const state = yield* Ref.make(State.Reconciling({ revision: 0 }));
13
+ const changed = yield* Ref.make(yield* Deferred.make());
14
+ const lock = yield* Semaphore.make(1);
15
+ const notify = Effect.gen(function* () {
16
+ const previous = yield* Ref.getAndSet(changed, yield* Deferred.make());
17
+ yield* Deferred.succeed(previous, void 0);
18
+ });
19
+ const invalidate = Effect.fn("WatchTopology.invalidate")(function* (update) {
20
+ yield* Ref.update(state, ({ revision }) => State.Reconciling({ revision: revision + 1 }));
21
+ yield* Ref.update(pending, update);
22
+ yield* notify;
23
+ }, (effect) => lock.withPermit(effect));
24
+ return {
25
+ revision: Ref.get(state).pipe(Effect.map((current) => current.revision)),
26
+ invalidate: invalidate(full),
27
+ invalidateEntry: (entry) => invalidate((current) => add(current, [entry])),
28
+ reconcile: Effect.fn("WatchTopology.reconcile")(function* (revision, initial) {
29
+ if ((yield* Ref.get(state)).revision !== revision) return;
30
+ yield* Ref.set(state, State.Observing({
31
+ revision,
32
+ initial
33
+ }));
34
+ yield* notify;
35
+ }, (effect) => lock.withPermit(effect)),
36
+ takePending: lock.withPermit(Effect.gen(function* () {
37
+ const current = yield* Ref.get(state);
38
+ const notification = yield* Ref.get(changed);
39
+ return yield* State.$match(current, {
40
+ Reconciling: () => Effect.succeed(Decision.Wait({ ready: Deferred.await(notification) })),
41
+ Observing: ({ initial }) => Effect.gen(function* () {
42
+ if (yield* Deferred.isDone(initial)) return Decision.Take({ pending: yield* Ref.getAndSet(pending, empty) });
43
+ return Decision.Wait({ ready: Effect.raceFirst(Deferred.await(notification), Deferred.await(initial)) });
44
+ })
45
+ });
46
+ })).pipe(Effect.flatMap(Decision.$match({
47
+ Take: ({ pending: taken }) => Effect.succeedSome(taken),
48
+ Wait: ({ ready }) => ready.pipe(Effect.as(Option.none()))
49
+ })), Effect.repeat({ until: Option.isSome }), Effect.map((result) => result.value))
50
+ };
51
+ });
52
+ //#endregion
53
+ export { make };
54
+
55
+ //# sourceMappingURL=WatchTopology.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchTopology.mjs","names":["SyncState.full","SyncState.add","SyncState.empty"],"sources":["../src/WatchTopology.ts"],"sourcesContent":["import * as Data from \"effect/Data\";\nimport * as Deferred from \"effect/Deferred\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\nimport * as Ref from \"effect/Ref\";\nimport * as Semaphore from \"effect/Semaphore\";\nimport type { Kind } from \"./EntryPoint.js\";\nimport * as SyncState from \"./SyncState.js\";\n\ntype State = Data.TaggedEnum<{\n Reconciling: { readonly revision: number };\n Observing: {\n readonly revision: number;\n readonly initial: Deferred.Deferred<void>;\n };\n}>;\nconst State = Data.taggedEnum<State>();\n\ntype Decision = Data.TaggedEnum<{\n Take: { readonly pending: SyncState.Pending };\n Wait: { readonly ready: Effect.Effect<void> };\n}>;\nconst Decision = Data.taggedEnum<Decision>();\n\nexport const make = Effect.fn(\"WatchTopology.make\")(function* (\n pending: Ref.Ref<SyncState.Pending>,\n) {\n const state = yield* Ref.make<State>(State.Reconciling({ revision: 0 }));\n const changed = yield* Ref.make(yield* Deferred.make<void>());\n const lock = yield* Semaphore.make(1);\n const notify = Effect.gen(function* () {\n const previous = yield* Ref.getAndSet(\n changed,\n yield* Deferred.make<void>(),\n );\n yield* Deferred.succeed(previous, undefined);\n });\n\n const invalidate = Effect.fn(\"WatchTopology.invalidate\")(\n function* (update: (pending: SyncState.Pending) => SyncState.Pending) {\n yield* Ref.update(state, ({ revision }) =>\n State.Reconciling({ revision: revision + 1 }),\n );\n yield* Ref.update(pending, update);\n yield* notify;\n },\n (effect) => lock.withPermit(effect),\n );\n\n return {\n revision: Ref.get(state).pipe(Effect.map((current) => current.revision)),\n invalidate: invalidate(SyncState.full),\n invalidateEntry: (entry: Kind) =>\n invalidate((current) => SyncState.add(current, [entry])),\n reconcile: Effect.fn(\"WatchTopology.reconcile\")(\n function* (revision: number, initial: Deferred.Deferred<void>) {\n const current = yield* Ref.get(state);\n if (current.revision !== revision) return;\n yield* Ref.set(state, State.Observing({ revision, initial }));\n yield* notify;\n },\n (effect) => lock.withPermit(effect),\n ),\n takePending: lock\n .withPermit(\n Effect.gen(function* () {\n const current = yield* Ref.get(state);\n const notification = yield* Ref.get(changed);\n return yield* State.$match(current, {\n Reconciling: () =>\n Effect.succeed(\n Decision.Wait({ ready: Deferred.await(notification) }),\n ),\n Observing: ({ initial }) =>\n Effect.gen(function* () {\n if (yield* Deferred.isDone(initial))\n return Decision.Take({\n pending: yield* Ref.getAndSet(pending, SyncState.empty),\n });\n return Decision.Wait({\n ready: Effect.raceFirst(\n Deferred.await(notification),\n Deferred.await(initial),\n ),\n });\n }),\n });\n }),\n )\n .pipe(\n Effect.flatMap(\n Decision.$match({\n Take: ({ pending: taken }) => Effect.succeedSome(taken),\n Wait: ({ ready }) =>\n ready.pipe(Effect.as(Option.none<SyncState.Pending>())),\n }),\n ),\n Effect.repeat({ until: Option.isSome<SyncState.Pending> }),\n Effect.map((result) => result.value),\n ),\n };\n});\n\nexport type WatchTopology = Effect.Success<ReturnType<typeof make>>;\n"],"mappings":";;;;;;;;AAgBA,MAAM,QAAQ,KAAK,WAAkB;AAMrC,MAAM,WAAW,KAAK,WAAqB;AAE3C,MAAa,OAAO,OAAO,GAAG,oBAAoB,CAAC,CAAC,WAClD,SACA;CACA,MAAM,QAAQ,OAAO,IAAI,KAAY,MAAM,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;CACvE,MAAM,UAAU,OAAO,IAAI,KAAK,OAAO,SAAS,KAAW,CAAC;CAC5D,MAAM,OAAO,OAAO,UAAU,KAAK,CAAC;CACpC,MAAM,SAAS,OAAO,IAAI,aAAa;EACrC,MAAM,WAAW,OAAO,IAAI,UAC1B,SACA,OAAO,SAAS,KAAW,CAC7B;EACA,OAAO,SAAS,QAAQ,UAAU,KAAA,CAAS;CAC7C,CAAC;CAED,MAAM,aAAa,OAAO,GAAG,0BAA0B,CAAC,CACtD,WAAW,QAA2D;EACpE,OAAO,IAAI,OAAO,QAAQ,EAAE,eAC1B,MAAM,YAAY,EAAE,UAAU,WAAW,EAAE,CAAC,CAC9C;EACA,OAAO,IAAI,OAAO,SAAS,MAAM;EACjC,OAAO;CACT,IACC,WAAW,KAAK,WAAW,MAAM,CACpC;CAEA,OAAO;EACL,UAAU,IAAI,IAAI,KAAK,CAAC,CAAC,KAAK,OAAO,KAAK,YAAY,QAAQ,QAAQ,CAAC;EACvE,YAAY,WAAWA,IAAc;EACrC,kBAAkB,UAChB,YAAY,YAAYC,IAAc,SAAS,CAAC,KAAK,CAAC,CAAC;EACzD,WAAW,OAAO,GAAG,yBAAyB,CAAC,CAC7C,WAAW,UAAkB,SAAkC;GAE7D,KAAI,OADmB,IAAI,IAAI,KAAK,EAAA,CACxB,aAAa,UAAU;GACnC,OAAO,IAAI,IAAI,OAAO,MAAM,UAAU;IAAE;IAAU;GAAQ,CAAC,CAAC;GAC5D,OAAO;EACT,IACC,WAAW,KAAK,WAAW,MAAM,CACpC;EACA,aAAa,KACV,WACC,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,KAAK;GACpC,MAAM,eAAe,OAAO,IAAI,IAAI,OAAO;GAC3C,OAAO,OAAO,MAAM,OAAO,SAAS;IAClC,mBACE,OAAO,QACL,SAAS,KAAK,EAAE,OAAO,SAAS,MAAM,YAAY,EAAE,CAAC,CACvD;IACF,YAAY,EAAE,cACZ,OAAO,IAAI,aAAa;KACtB,IAAI,OAAO,SAAS,OAAO,OAAO,GAChC,OAAO,SAAS,KAAK,EACnB,SAAS,OAAO,IAAI,UAAU,SAASC,KAAe,EACxD,CAAC;KACH,OAAO,SAAS,KAAK,EACnB,OAAO,OAAO,UACZ,SAAS,MAAM,YAAY,GAC3B,SAAS,MAAM,OAAO,CACxB,EACF,CAAC;IACH,CAAC;GACL,CAAC;EACH,CAAC,CACH,CAAC,CACA,KACC,OAAO,QACL,SAAS,OAAO;GACd,OAAO,EAAE,SAAS,YAAY,OAAO,YAAY,KAAK;GACtD,OAAO,EAAE,YACP,MAAM,KAAK,OAAO,GAAG,OAAO,KAAwB,CAAC,CAAC;EAC1D,CAAC,CACH,GACA,OAAO,OAAO,EAAE,OAAO,OAAO,OAA0B,CAAC,GACzD,OAAO,KAAK,WAAW,OAAO,KAAK,CACrC;CACJ;AACF,CAAC"}