alchemy 0.1.2 → 0.1.3

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 (54) hide show
  1. package/README.md +8 -39
  2. package/package.json +12 -8
  3. package/src/$.ts +36 -0
  4. package/src/agent/ai.ts +215 -0
  5. package/src/{components/agent → agent}/anthropic.ts +6 -2
  6. package/src/agent/dependencies.ts +62 -0
  7. package/src/agent/file-context.ts +8 -0
  8. package/src/agent/index.ts +9 -0
  9. package/src/agent/model.ts +39 -0
  10. package/src/agent/module.ts +418 -0
  11. package/src/{components/agent → agent}/openai.ts +6 -0
  12. package/src/agent/program.md +38 -0
  13. package/src/agent/program.ts +37 -0
  14. package/src/agent/prompts.ts +20 -0
  15. package/src/alchemize.ts +45 -14
  16. package/src/apply.ts +25 -10
  17. package/src/{components/aws → aws}/bucket.ts +2 -2
  18. package/src/{components/aws → aws}/function.ts +2 -2
  19. package/src/{components/aws → aws}/policy.ts +2 -2
  20. package/src/{components/aws → aws}/queue.ts +1 -1
  21. package/src/{components/aws → aws}/role.ts +2 -2
  22. package/src/{components/aws → aws}/table.ts +2 -2
  23. package/src/destroy.ts +36 -12
  24. package/src/{components/esbuild.ts → esbuild.ts} +1 -1
  25. package/src/{components/fs.ts → fs.ts} +20 -18
  26. package/src/global.ts +7 -14
  27. package/src/index.ts +11 -2
  28. package/src/input.ts +1 -1
  29. package/src/markdown/design.ts +125 -0
  30. package/src/markdown/extract.ts +9 -0
  31. package/src/markdown/index.ts +3 -0
  32. package/src/{components/agent → markdown}/requirements.ts +53 -29
  33. package/src/output.ts +6 -0
  34. package/src/resource.ts +208 -75
  35. package/src/scope.ts +56 -0
  36. package/src/slug.ts +3 -0
  37. package/src/state.ts +66 -38
  38. package/src/typescript/check-omission.ts +47 -0
  39. package/src/typescript/debug-type-errors.ts +42 -0
  40. package/src/typescript/extract.ts +9 -0
  41. package/src/{components/agent → typescript}/index.ts +3 -1
  42. package/src/typescript/install-packages.ts +50 -0
  43. package/src/{components/agent → typescript}/package.ts +44 -27
  44. package/src/typescript/repair-omissions.ts +31 -0
  45. package/src/typescript/repair.ts +45 -0
  46. package/src/{components/agent → typescript}/tsconfig.ts +37 -17
  47. package/src/typescript/typescript.ts +217 -0
  48. package/src/typescript/validate.ts +73 -0
  49. package/test/scope.test.ts +67 -0
  50. package/src/components/agent/agent.ts +0 -45
  51. package/src/components/agent/model.ts +0 -22
  52. package/src/components/agent/typescript.ts +0 -177
  53. /package/src/{components/agent → agent}/scrape.ts +0 -0
  54. /package/src/{components/aws → aws}/index.ts +0 -0
package/src/output.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  export type Eval<U> = U extends Output<infer V> ? Eval<V> : U;
2
2
 
3
+ export function isOutput<T>(value: any): value is Output<T> {
4
+ return (
5
+ value && typeof value === "object" && typeof value.apply === "function"
6
+ );
7
+ }
8
+
3
9
  export type Outputs<P extends readonly any[]> = P extends [
4
10
  infer First,
5
11
  ...infer Rest,
package/src/resource.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { deletions, nodes, providers } from "./global";
2
- import type { Inputs } from "./input";
1
+ import { alchemize } from "./alchemize";
2
+ import { type ApplyOptions, apply } from "./apply";
3
+ import type { DestroyOptions } from "./destroy";
4
+ import { defaultStateStore, deletions, providers } from "./global";
5
+ import type { Inputs, Input as input } from "./input";
3
6
  import { Output } from "./output";
7
+ import { Scope as IScope, getScope, pushScope } from "./scope";
4
8
  import type { State, StateStore } from "./state";
5
9
 
6
10
  export type ResourceID = string;
@@ -12,6 +16,8 @@ export const Input = Symbol.for("Input");
12
16
  export const Value = Symbol.for("Value");
13
17
  export const Apply = Symbol.for("Apply");
14
18
  export const Provide = Symbol.for("Provide");
19
+ export const Scope = Symbol.for("Scope");
20
+ export const Options = Symbol.for("Options");
15
21
 
16
22
  // properties that pierce through the Proxy
17
23
  const OrthogonalProperties = [
@@ -21,8 +27,17 @@ const OrthogonalProperties = [
21
27
  Value,
22
28
  Apply,
23
29
  Provide,
30
+ Scope,
31
+ Options,
24
32
  ] as const;
25
33
 
34
+ export interface ProviderOptions {
35
+ /**
36
+ * If true, the resource will be updated even if the inputs have not changed.
37
+ */
38
+ alwaysUpdate: boolean;
39
+ }
40
+
26
41
  export type Resource<In extends any[] = any[], Out = any> = {
27
42
  [ResourceID]: string;
28
43
  [Provider]: Provider<any, any[], any>;
@@ -30,11 +45,14 @@ export type Resource<In extends any[] = any[], Out = any> = {
30
45
  [Value]?: Out;
31
46
  [Apply]: <O>(value: Out) => O;
32
47
  [Provide]: (value: Out) => void;
48
+ [Options]: ProviderOptions;
33
49
  } & Output<Out>;
34
50
 
35
- export type Context<Outputs> = {
51
+ export interface BaseContext {
52
+ quiet: boolean;
36
53
  stage: string;
37
54
  resourceID: ResourceID;
55
+ scope: IScope;
38
56
  get<T>(key: string): Promise<T | undefined>;
39
57
  set<T>(key: string, value: T): Promise<void>;
40
58
  delete<T>(key: string): Promise<T | undefined>;
@@ -43,15 +61,26 @@ export type Context<Outputs> = {
43
61
  * This will cause the resource to be deleted at the end of the stack's CREATE phase.
44
62
  */
45
63
  replace(): void;
46
- } & (
47
- | {
48
- event: "create";
49
- }
50
- | {
51
- event: "update" | "delete";
52
- output: Outputs;
53
- }
54
- );
64
+ }
65
+
66
+ export interface CreateContext extends BaseContext {
67
+ event: "create";
68
+ }
69
+
70
+ export interface UpdateContext<Outputs> extends BaseContext {
71
+ event: "update";
72
+ output: Outputs;
73
+ }
74
+
75
+ export interface DeleteContext<Outputs> extends BaseContext {
76
+ event: "delete";
77
+ output: Outputs;
78
+ }
79
+
80
+ export type Context<Outputs> =
81
+ | CreateContext
82
+ | UpdateContext<Outputs>
83
+ | DeleteContext<Outputs>;
55
84
 
56
85
  export type Provider<
57
86
  Type extends ResourceType = ResourceType,
@@ -65,16 +94,19 @@ export type Provider<
65
94
  deps: Set<ResourceID>,
66
95
  inputs: Inputs<In>,
67
96
  stateStore: StateStore,
97
+ options: ApplyOptions,
68
98
  ): Promise<Awaited<Out>>;
69
99
  delete(
70
100
  stage: string,
101
+ scope: IScope | undefined,
71
102
  resourceID: ResourceID,
72
103
  state: State,
73
104
  inputs: Inputs<In>,
105
+ options: DestroyOptions,
74
106
  ): Promise<void>;
75
107
  } & (new (
76
108
  id: string,
77
- ...inputs: Inputs<In>
109
+ ...inputs: [...Inputs<In>, ...any[]]
78
110
  ) => Resource<In, Out>);
79
111
 
80
112
  export function isResource(value: any): value is Resource {
@@ -87,41 +119,88 @@ export function Resource<
87
119
  Out,
88
120
  >(
89
121
  type: Type,
90
- func: (ctx: Context<Out>, ...args: Args) => Promise<Out | void> | Out | void,
122
+ options: Partial<ProviderOptions>,
123
+ func: (
124
+ ctx: Context<Out>,
125
+ ...args: Args
126
+ ) => Promise<input<Out> | void> | input<Out> | void,
127
+ ): Provider<Type, Args, Awaited<Out>>;
128
+
129
+ export function Resource<
130
+ const Type extends ResourceType,
131
+ Args extends any[],
132
+ Out,
133
+ >(
134
+ type: Type,
135
+ func: (
136
+ ctx: Context<Out>,
137
+ ...args: Args
138
+ ) => Promise<input<Out> | void> | input<Out> | void,
139
+ ): Provider<Type, Args, Awaited<Out>>;
140
+
141
+ export function Resource<
142
+ const Type extends ResourceType,
143
+ Args extends any[],
144
+ Out,
145
+ >(
146
+ type: Type,
147
+ ...args:
148
+ | [
149
+ Partial<ProviderOptions>,
150
+ (
151
+ ctx: Context<Out>,
152
+ ...args: Args
153
+ ) => Promise<input<Out> | void> | input<Out> | void,
154
+ ]
155
+ | [
156
+ (
157
+ ctx: Context<Out>,
158
+ ...args: Args
159
+ ) => Promise<input<Out> | void> | input<Out> | void,
160
+ ]
91
161
  ): Provider<Type, Args, Awaited<Out>> {
92
162
  if (providers.has(type)) {
93
163
  throw new Error(`Resource ${type} already exists`);
94
164
  }
165
+ const [options, func] = args.length === 2 ? args : [undefined, args[0]];
95
166
 
96
167
  interface Resource {
97
168
  [ResourceID]: ResourceID;
98
169
  [Provider]: Provider<Type, Args, Out>;
99
170
  [Input]: Inputs<Args>;
100
171
  [Value]?: Out;
172
+ [Scope]: IScope;
173
+ [Options]: ProviderOptions;
101
174
  }
102
175
 
103
176
  class Resource {
104
177
  static readonly type = type;
105
178
 
106
179
  constructor(id: ResourceID, ...input: Inputs<Args>) {
180
+ const scope = getScope();
107
181
  const node = {
108
182
  provider: Resource,
109
183
  resource: this,
110
184
  } as const;
111
- if (nodes.has(id)) {
185
+
186
+ if (scope.nodes.has(id)) {
112
187
  // TODO(sam): do we want to throw?
113
188
  // it's kind of awesome that you can re-create a resource and call apply
114
189
  // console.warn(`Resource ${id} already exists in the stack: ${stack.id}`);
115
190
  }
116
- nodes.set(id, node as any);
191
+ scope.nodes.set(id, node as any);
117
192
 
118
193
  this[ResourceID] = id;
119
194
  this[Provider] = Resource as any;
120
195
  this[Input] = input;
121
196
  this[Value] = undefined;
197
+ this[Scope] = scope;
122
198
  this[Provide] = (value: Out) => {
123
199
  this[Value] = value;
124
200
  };
201
+ this[Options] = {
202
+ alwaysUpdate: options?.alwaysUpdate ?? false,
203
+ };
125
204
 
126
205
  return new Proxy(this, {
127
206
  // TODO(sam): this won't work for the sub-class (class Table extends Resource)
@@ -175,14 +254,12 @@ export function Resource<
175
254
  deps: Set<ResourceID>,
176
255
  inputs: Args,
177
256
  stateStore: StateStore,
257
+ options: ApplyOptions,
178
258
  ): Promise<Awaited<Out | void>> {
179
259
  // const stack = resource[ResourceStack];
180
260
  const resourceID = resource[ResourceID];
181
261
 
182
- let resourceState: State | undefined = await stateStore.get(
183
- stage,
184
- resourceID,
185
- );
262
+ let resourceState: State | undefined = await stateStore.get(resourceID);
186
263
  if (resourceState === undefined) {
187
264
  resourceState = {
188
265
  provider: type,
@@ -192,20 +269,28 @@ export function Resource<
192
269
  deps: [...deps],
193
270
  inputs,
194
271
  };
195
- await stateStore.set(stage, resourceID, resourceState);
272
+ await stateStore.set(resourceID, resourceState);
196
273
  }
197
274
 
275
+ const resourceFQN = `${resource[Scope].getScopePath(stage)}/${resourceID}`;
276
+
198
277
  // Skip update if inputs haven't changed and resource is in a stable state
199
278
  if (
200
- (resourceState.status === "created" ||
201
- resourceState.status === "updated") &&
202
- JSON.stringify(resourceState.inputs) === JSON.stringify(inputs)
279
+ resourceState.status === "created" ||
280
+ resourceState.status === "updated"
203
281
  ) {
204
- console.log(`Skip: ${resourceID} (no changes)`);
205
- if (resourceState.output !== undefined) {
206
- resource[Provide](resourceState.output);
282
+ if (
283
+ JSON.stringify(resourceState.inputs) === JSON.stringify(inputs) &&
284
+ !resource[Options].alwaysUpdate
285
+ ) {
286
+ if (!options?.quiet) {
287
+ console.log(`Skip: ${resourceFQN} (no changes)`);
288
+ }
289
+ if (resourceState.output !== undefined) {
290
+ resource[Provide](resourceState.output);
291
+ }
292
+ return resourceState.output;
207
293
  }
208
- return resourceState.output;
209
294
  }
210
295
 
211
296
  const event = resourceState.status === "creating" ? "create" : "update";
@@ -213,78 +298,122 @@ export function Resource<
213
298
  resourceState.oldInputs = resourceState.inputs;
214
299
  resourceState.inputs = inputs;
215
300
 
216
- console.log(
217
- `${event === "create" ? "Create" : "Update"}: ${resourceID}`,
218
- );
301
+ if (!options?.quiet) {
302
+ console.log(
303
+ `${event === "create" ? "Create" : "Update"}: ${resourceFQN}`,
304
+ );
305
+ }
219
306
 
220
- await stateStore.set(stage, resourceID, resourceState);
307
+ await stateStore.set(resourceID, resourceState);
221
308
 
222
309
  let isReplaced = false;
223
310
 
224
- const result = await func(
225
- {
226
- stage,
227
- resourceID,
228
- event,
229
- output: resourceState.output,
230
- replace: () => {
231
- if (isReplaced) {
232
- console.warn(
233
- `Resource ${type} ${resourceID} is already marked as REPLACE`,
234
- );
235
- return;
236
- }
237
- isReplaced = true;
238
- deletions.push({
239
- id: resourceID,
240
- data: {
241
- ...resourceState!.data,
311
+ const quiet = options.quiet ?? false;
312
+
313
+ const evaluated = await pushScope(
314
+ resource[Scope],
315
+ resourceID,
316
+ async () => {
317
+ const result = await func(
318
+ {
319
+ stage,
320
+ resourceID,
321
+ event,
322
+ scope: getScope(),
323
+ output: resourceState.output,
324
+ replace: () => {
325
+ if (isReplaced) {
326
+ console.warn(
327
+ `Resource ${type} ${resourceFQN} is already marked as REPLACE`,
328
+ );
329
+ return;
330
+ }
331
+ isReplaced = true;
332
+ deletions.push({
333
+ id: resourceID,
334
+ data: {
335
+ ...resourceState!.data,
336
+ },
337
+ inputs: inputs,
338
+ });
242
339
  },
243
- inputs: inputs,
244
- });
245
- },
246
- get: (key) => resourceState!.data[key],
247
- set: async (key, value) => {
248
- resourceState!.data[key] = value;
249
- await stateStore.set(stage, resourceID, resourceState!);
250
- },
251
- delete: async (key) => {
252
- const value = resourceState!.data[key];
253
- delete resourceState!.data[key];
254
- await stateStore.set(stage, resourceID, resourceState!);
255
- return value;
256
- },
340
+ get: (key) => resourceState!.data[key],
341
+ set: async (key, value) => {
342
+ resourceState!.data[key] = value;
343
+ await stateStore.set(resourceID, resourceState!);
344
+ },
345
+ delete: async (key) => {
346
+ const value = resourceState!.data[key];
347
+ delete resourceState!.data[key];
348
+ await stateStore.set(resourceID, resourceState!);
349
+ return value;
350
+ },
351
+ quiet,
352
+ },
353
+ ...inputs,
354
+ );
355
+
356
+ if (result === undefined) {
357
+ return undefined;
358
+ }
359
+
360
+ const evaluated = await apply(result as Out, {
361
+ stage,
362
+ scope: getScope(),
363
+ quiet,
364
+ });
365
+
366
+ return evaluated;
257
367
  },
258
- ...inputs,
259
368
  );
260
369
 
261
- console.log(
262
- `${event === "create" ? "Created" : "Updated"}: ${resourceID}`,
263
- );
264
- await stateStore.set(stage, resourceID, {
370
+ if (!options?.quiet) {
371
+ console.log(
372
+ `${event === "create" ? "Created" : "Updated"}: ${resourceFQN}`,
373
+ );
374
+ }
375
+ await stateStore.set(resourceID, {
265
376
  provider: type,
266
377
  data: resourceState.data,
267
378
  status: event === "create" ? "created" : "updated",
268
- output: result,
379
+ output: evaluated,
269
380
  inputs,
270
381
  deps: [...deps],
271
382
  });
272
- if (result !== undefined) {
273
- resource[Provide](result);
383
+ if (evaluated !== undefined) {
384
+ resource[Provide](evaluated);
274
385
  }
275
- return result;
386
+ return evaluated;
276
387
  }
277
388
 
278
389
  static async delete(
279
390
  stage: string,
391
+ scope: IScope,
280
392
  resourceID: ResourceID,
281
393
  state: State,
282
394
  inputs: Args,
395
+ options: DestroyOptions,
283
396
  ) {
284
- console.log(`Delete: ${resourceID}`);
397
+ const resourceFQN = `${scope.getScopePath(stage)}/${resourceID}`;
398
+ const nestedScope = new IScope(resourceID, scope);
399
+
400
+ await alchemize({
401
+ mode: "destroy",
402
+ stage,
403
+ scope: nestedScope,
404
+ // TODO(sam): should use the appropriate state store
405
+ stateStore: defaultStateStore,
406
+ quiet: options.quiet,
407
+ });
408
+
409
+ if (!options?.quiet) {
410
+ console.log(`Delete: ${resourceFQN}`);
411
+ }
412
+
285
413
  await func(
286
414
  {
287
415
  stage,
416
+ scope,
288
417
  resourceID: resourceID,
289
418
  event: "delete",
290
419
  output: state.output,
@@ -302,10 +431,14 @@ export function Resource<
302
431
  delete state.data[key];
303
432
  return value;
304
433
  },
434
+ quiet: options.quiet ?? false,
305
435
  },
306
436
  ...inputs,
307
437
  );
308
- console.log(`Deleted: ${resourceID}`);
438
+
439
+ if (!options?.quiet) {
440
+ console.log(`Deleted: ${resourceFQN}`);
441
+ }
309
442
  }
310
443
  }
311
444
  providers.set(type, Resource as any);
package/src/scope.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import path from "node:path";
3
+ import { rootScope } from "./global";
4
+ import type { Provider, Resource, ResourceID } from "./resource";
5
+
6
+ const scopeGlobal = new Map<string | null, Scope>();
7
+
8
+ const scopeStorage = new AsyncLocalStorage<Scope>();
9
+
10
+ export class Scope {
11
+ public readonly nodes = new Map<
12
+ ResourceID,
13
+ {
14
+ provider: Provider<any, any[], any>;
15
+ resource: Resource<any, any>;
16
+ }
17
+ >();
18
+
19
+ constructor(
20
+ public readonly scopeName: string | null = null,
21
+ public readonly parent: Scope | undefined = undefined,
22
+ ) {}
23
+
24
+ public getScopePath(root: string): string {
25
+ // First, compute the parent's scope path
26
+ const parentPath = this.parent ? this.parent.getScopePath(root) : root;
27
+ // Then join the current scope name (if any) onto the parent's path
28
+ return this.scopeName ? path.join(parentPath, this.scopeName) : parentPath;
29
+ }
30
+ }
31
+
32
+ export function getScope(): Scope {
33
+ return scopeStorage.getStore() ?? rootScope;
34
+ }
35
+
36
+ export function withScope<T>(
37
+ scope: Scope,
38
+ fn: () => T | Promise<T>,
39
+ ): Promise<T> {
40
+ return scopeStorage.run(scope, async () => {
41
+ try {
42
+ return await fn();
43
+ } catch (error) {
44
+ throw error;
45
+ }
46
+ });
47
+ }
48
+
49
+ export function pushScope<T>(
50
+ parent: Scope,
51
+ scopeName: string,
52
+ fn: () => T | Promise<T>,
53
+ ): Promise<T> {
54
+ const newScope = new Scope(scopeName, parent);
55
+ return withScope(newScope, fn);
56
+ }
package/src/slug.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function slugify(str: string) {
2
+ return str.replaceAll("/", "_");
3
+ }
package/src/state.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { ignore } from "./error";
3
4
 
4
5
  export interface State {
5
6
  status:
@@ -18,41 +19,64 @@ export interface State {
18
19
  }
19
20
 
20
21
  export interface StateStore {
22
+ /** Initialize the state container if one is required */
21
23
  init?(): Promise<void>;
24
+ /** Delete the state container if one exists */
25
+ deinit?(): Promise<void>;
22
26
  /** List all resources in the given stage. */
23
- list(stage: string): Promise<string[]>;
24
- get(stage: string, key: string): Promise<State | undefined>;
25
- getBatch(stage: string, ids: string[]): Promise<Record<string, State>>;
26
- all(stage: string): Promise<Record<string, State>>;
27
- set(stage: string, key: string, value: State): Promise<void>;
28
- delete(stage: string, key: string): Promise<void>;
27
+ list(): Promise<string[]>;
28
+ /** Return the number of items let in this store */
29
+ count(): Promise<number>;
30
+ get(key: string): Promise<State | undefined>;
31
+ getBatch(ids: string[]): Promise<Record<string, State>>;
32
+ all(): Promise<Record<string, State>>;
33
+ set(key: string, value: State): Promise<void>;
34
+ delete(key: string): Promise<void>;
29
35
  }
30
36
 
31
37
  const stateFile = path.join(process.cwd(), ".alchemy");
32
38
 
33
- /**
34
- * @internal
35
- */
36
- export const defaultStore: StateStore = {
37
- async init() {
39
+ export type StateStoreType = new (dir: string) => StateStore;
40
+
41
+ export class FileSystemStateStore implements StateStore {
42
+ public readonly path: string;
43
+ constructor(dir: string) {
44
+ this.path = path.join(stateFile, dir);
45
+ }
46
+
47
+ async init(): Promise<void> {
38
48
  await fs.promises.mkdir(stateFile, { recursive: true });
39
- },
40
- async list(stage) {
49
+ await fs.promises.mkdir(this.path, { recursive: true });
50
+ }
51
+
52
+ async deinit(): Promise<void> {
53
+ await ignore("ENOENT", () => fs.promises.rmdir(this.path));
54
+ }
55
+
56
+ async count(): Promise<number> {
57
+ return Object.keys(await this.list()).length;
58
+ }
59
+
60
+ async list(): Promise<string[]> {
41
61
  try {
42
- return (await fs.promises.readdir(path.join(stateFile, stage))).map(
43
- (file) => file.replace(/\.json$/, ""),
44
- );
62
+ const files = await fs.promises.readdir(this.path, {
63
+ withFileTypes: true,
64
+ });
65
+ return files
66
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
67
+ .map((dirent) => dirent.name.replace(/\.json$/, ""));
45
68
  } catch (error: any) {
46
69
  if (error.code === "ENOENT") {
47
70
  return [];
48
71
  }
49
72
  throw error;
50
73
  }
51
- },
52
- async get(stage, key) {
74
+ }
75
+
76
+ async get(key: string): Promise<State | undefined> {
53
77
  try {
54
78
  const content = await fs.promises.readFile(
55
- await getPath(stage, key),
79
+ await this.getPath(key),
56
80
  "utf8",
57
81
  );
58
82
  return JSON.parse(content) as State;
@@ -62,25 +86,29 @@ export const defaultStore: StateStore = {
62
86
  }
63
87
  throw error;
64
88
  }
65
- },
66
- async set(stage, key, value) {
89
+ }
90
+
91
+ async set(key: string, value: State): Promise<void> {
67
92
  return fs.promises.writeFile(
68
- await getPath(stage, key),
93
+ await this.getPath(key),
69
94
  JSON.stringify(value, null, 2),
70
95
  );
71
- },
72
- async delete(stage, key) {
73
- return fs.promises.unlink(await getPath(stage, key));
74
- },
75
- async all(stage: string): Promise<Record<string, State>> {
76
- return this.getBatch(stage, await this.list(stage));
77
- },
78
- async getBatch(stage: string, ids: string[]): Promise<Record<string, State>> {
96
+ }
97
+
98
+ async delete(key: string): Promise<void> {
99
+ return fs.promises.unlink(await this.getPath(key));
100
+ }
101
+
102
+ async all(): Promise<Record<string, State>> {
103
+ return this.getBatch(await this.list());
104
+ }
105
+
106
+ async getBatch(ids: string[]): Promise<Record<string, State>> {
79
107
  return Object.fromEntries(
80
108
  (
81
109
  await Promise.all(
82
110
  Array.from(ids).flatMap(async (id) => {
83
- const s = await this.get(stage, id);
111
+ const s = await this.get(id);
84
112
  if (s === undefined) {
85
113
  return [] as const;
86
114
  }
@@ -89,12 +117,12 @@ export const defaultStore: StateStore = {
89
117
  )
90
118
  ).flat(),
91
119
  );
92
- },
93
- };
120
+ }
94
121
 
95
- async function getPath(stage: string, key: string) {
96
- const file = path.join(stateFile, stage, `${key}.json`);
97
- const dir = path.dirname(file);
98
- await fs.promises.mkdir(dir, { recursive: true });
99
- return file;
122
+ private async getPath(key: string): Promise<string> {
123
+ const file = path.join(this.path, `${key}.json`);
124
+ const dir = path.dirname(file);
125
+ await fs.promises.mkdir(dir, { recursive: true });
126
+ return file;
127
+ }
100
128
  }