@vimhead.dev/norn 0.1.0-tip.35390859630.1 → 0.1.0-tip.35436871363.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +23 -46
- package/dist/api.js +2 -7
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -6
- package/package.json +1 -5
- package/dist/agent-resource-adapter.d.ts +0 -12
- package/dist/agent-resource-adapter.js +0 -0
- package/dist/files.d.ts +0 -17
- package/dist/files.js +0 -162
- package/dist/resources.d.ts +0 -17
- package/dist/resources.js +0 -0
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateAgentSessionOptions, EventBus, PromptOptions } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { CreateAgentSessionOptions, EventBus, PromptOptions, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type, type StaticDecode, type StaticEncode, type TCodec, type TSchema } from "typebox";
|
|
3
3
|
import type { TLocalizedValidationError } from "typebox/error";
|
|
4
4
|
declare const WORKFLOW_DECLARATION_KIND = "norn.workflow";
|
|
@@ -8,11 +8,13 @@ export type NornWorkflowAnyGate = {
|
|
|
8
8
|
readonly enabled: true;
|
|
9
9
|
readonly fields?: readonly string[];
|
|
10
10
|
};
|
|
11
|
-
export type
|
|
12
|
-
|
|
13
|
-
readonly
|
|
11
|
+
export type NornWorkflowPaths = {
|
|
12
|
+
/** Absolute project root. Excluded from run checkpoints and rollback. */
|
|
13
|
+
readonly project: string;
|
|
14
|
+
/** Absolute, initially empty workspace. Saved in run checkpoints and restored on rollback. */
|
|
15
|
+
readonly workspace: string;
|
|
14
16
|
};
|
|
15
|
-
export type NornWorkflowDeclaration<Id extends string = string, ArgsSchema extends TSchema = TSchema,
|
|
17
|
+
export type NornWorkflowDeclaration<Id extends string = string, ArgsSchema extends TSchema = TSchema, ConfigSchema extends TSchema | undefined = TSchema | undefined, Scope extends NornWorkflowScopeInfo | undefined = NornWorkflowScopeInfo | undefined> = {
|
|
16
18
|
(args: StaticEncode<ArgsSchema>): NornRunNext;
|
|
17
19
|
readonly kind: typeof WORKFLOW_DECLARATION_KIND;
|
|
18
20
|
readonly id: Id;
|
|
@@ -20,12 +22,11 @@ export type NornWorkflowDeclaration<Id extends string = string, ArgsSchema exten
|
|
|
20
22
|
readonly instructions?: string;
|
|
21
23
|
readonly args: ArgsSchema;
|
|
22
24
|
readonly gate?: NornWorkflowGate<ArgsSchema> & {
|
|
23
|
-
describe?(context: NornWorkflowContext<ArgsSchema,
|
|
25
|
+
describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
|
|
24
26
|
};
|
|
25
|
-
readonly isolation: NornWorkflowIsolation<IsolationMode>;
|
|
26
27
|
readonly config?: ConfigSchema;
|
|
27
28
|
readonly scope: Scope;
|
|
28
|
-
execute(context: NornWorkflowContext<ArgsSchema,
|
|
29
|
+
execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
|
|
29
30
|
};
|
|
30
31
|
export type NornAnyWorkflowDeclaration = Pick<NornWorkflowDeclaration, keyof NornWorkflowDeclaration> & ((args: never) => NornRunNext);
|
|
31
32
|
export type NornWorkflowRefSchemaOptions<ArgsSchema extends TSchema = TSchema> = {
|
|
@@ -49,27 +50,26 @@ type NornWorkflowInstructions = {
|
|
|
49
50
|
readonly isEntrypoint: false;
|
|
50
51
|
readonly instructions?: string;
|
|
51
52
|
};
|
|
52
|
-
type RunForIsolation<Mode extends NornWorkflowIsolationMode> = Mode extends "project" ? NornProjectRun : NornRun;
|
|
53
53
|
type WorkflowConfig<Schema extends TSchema | undefined> = Schema extends TSchema ? StaticDecode<Schema> : undefined;
|
|
54
|
-
export type NornWorkflowContext<ArgsSchema extends TSchema = TSchema,
|
|
54
|
+
export type NornWorkflowContext<ArgsSchema extends TSchema = TSchema, ConfigSchema extends TSchema | undefined = TSchema | undefined, Scope extends NornWorkflowScopeInfo | undefined = NornWorkflowScopeInfo | undefined> = {
|
|
55
55
|
readonly args: StaticDecode<ArgsSchema>;
|
|
56
56
|
readonly config: WorkflowConfig<ConfigSchema>;
|
|
57
|
-
readonly
|
|
57
|
+
readonly paths: NornWorkflowPaths;
|
|
58
|
+
readonly run: NornRun;
|
|
58
59
|
} & (Scope extends NornWorkflowScopeInfo ? {
|
|
59
60
|
readonly scope: {
|
|
60
61
|
readonly id: Scope["id"];
|
|
61
62
|
readonly config: WorkflowConfig<Scope["config"]>;
|
|
62
63
|
};
|
|
63
64
|
} : {});
|
|
64
|
-
export type NornWorkflowDefinition<Id extends string = string, ArgsSchema extends TSchema = TSchema,
|
|
65
|
+
export type NornWorkflowDefinition<Id extends string = string, ArgsSchema extends TSchema = TSchema, ConfigSchema extends TSchema | undefined = undefined, Scope extends NornWorkflowScopeInfo | undefined = undefined> = {
|
|
65
66
|
readonly id: Id;
|
|
66
67
|
readonly args: ArgsSchema;
|
|
67
68
|
readonly config?: ConfigSchema;
|
|
68
|
-
readonly isolation?: NornWorkflowIsolation<IsolationMode>;
|
|
69
69
|
readonly gate?: NornWorkflowGate<ArgsSchema> & {
|
|
70
|
-
describe?(context: NornWorkflowContext<ArgsSchema,
|
|
70
|
+
describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
|
|
71
71
|
};
|
|
72
|
-
execute(context: NornWorkflowContext<ArgsSchema,
|
|
72
|
+
execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
|
|
73
73
|
} & NornWorkflowInstructions;
|
|
74
74
|
export type NornWorkflowScopeInfo = {
|
|
75
75
|
readonly id: string;
|
|
@@ -78,15 +78,15 @@ export type NornWorkflowScopeInfo = {
|
|
|
78
78
|
export type NornWorkflowScope<Id extends string, ConfigSchema extends TSchema | undefined> = {
|
|
79
79
|
readonly id: Id;
|
|
80
80
|
readonly config: ConfigSchema;
|
|
81
|
-
workflow<const LocalId extends string, ArgsSchema extends TSchema,
|
|
81
|
+
workflow<const LocalId extends string, ArgsSchema extends TSchema, LocalConfig extends TSchema | undefined = undefined>(definition: NornWorkflowDefinition<LocalId, ArgsSchema, LocalConfig, {
|
|
82
82
|
readonly id: Id;
|
|
83
83
|
readonly config: ConfigSchema;
|
|
84
|
-
}>): NornWorkflowDeclaration<`${Id}.${LocalId}`, ArgsSchema,
|
|
84
|
+
}>): NornWorkflowDeclaration<`${Id}.${LocalId}`, ArgsSchema, LocalConfig, {
|
|
85
85
|
readonly id: Id;
|
|
86
86
|
readonly config: ConfigSchema;
|
|
87
87
|
}>;
|
|
88
88
|
};
|
|
89
|
-
export declare function workflow<const Id extends string, ArgsSchema extends TSchema,
|
|
89
|
+
export declare function workflow<const Id extends string, ArgsSchema extends TSchema, ConfigSchema extends TSchema | undefined = undefined>(definition: NornWorkflowDefinition<Id, ArgsSchema, ConfigSchema>): NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, undefined>;
|
|
90
90
|
export declare function workflowScope<const Id extends string, ConfigSchema extends TSchema | undefined = undefined>(input: {
|
|
91
91
|
readonly id: Id;
|
|
92
92
|
readonly config?: ConfigSchema;
|
|
@@ -98,7 +98,6 @@ export type NornRunNext = {
|
|
|
98
98
|
};
|
|
99
99
|
export type NornRunOutcomeMetadata = {
|
|
100
100
|
readonly summary?: string;
|
|
101
|
-
readonly artifacts?: Record<string, NornArtifactRef>;
|
|
102
101
|
readonly logs?: Record<string, NornLogRef>;
|
|
103
102
|
readonly data?: Record<string, unknown>;
|
|
104
103
|
};
|
|
@@ -113,11 +112,6 @@ export type NornRunFail = {
|
|
|
113
112
|
};
|
|
114
113
|
};
|
|
115
114
|
export type WorkflowResult = NornRunNext | NornRunComplete | NornRunFail;
|
|
116
|
-
export type NornRunFor<TWorkflow extends NornAnyWorkflowDeclaration> = TWorkflow extends {
|
|
117
|
-
readonly isolation: {
|
|
118
|
-
readonly mode: "project";
|
|
119
|
-
};
|
|
120
|
-
} ? NornProjectRun : NornRun;
|
|
121
115
|
export type NornRunStartOptions = {
|
|
122
116
|
readonly id?: string;
|
|
123
117
|
readonly name?: string;
|
|
@@ -195,6 +189,7 @@ export type NornRunInfo = {
|
|
|
195
189
|
readonly id: string;
|
|
196
190
|
readonly name: string;
|
|
197
191
|
readonly path: string;
|
|
192
|
+
readonly paths: NornWorkflowPaths;
|
|
198
193
|
readonly entrypointWorkflowId: string;
|
|
199
194
|
readonly currentWorkflowId?: string;
|
|
200
195
|
readonly status: NornRunStatus;
|
|
@@ -220,14 +215,10 @@ export type NornRunCheckpoint = {
|
|
|
220
215
|
export type NornCommandRunInput = {
|
|
221
216
|
readonly label: string;
|
|
222
217
|
readonly command: string | readonly [string, ...string[]];
|
|
223
|
-
readonly cwd
|
|
218
|
+
readonly cwd: string;
|
|
224
219
|
readonly env?: Record<string, string>;
|
|
225
220
|
readonly timeoutMs?: number;
|
|
226
221
|
};
|
|
227
|
-
export declare const artifactRefSchema: Type.TObject<{
|
|
228
|
-
path: Type.TString;
|
|
229
|
-
}>;
|
|
230
|
-
export type NornArtifactRef = StaticDecode<typeof artifactRefSchema>;
|
|
231
222
|
declare const emptyWorkflowRefArgsSchema: Type.TObject<{}>;
|
|
232
223
|
declare const workflowReferenceInputSchema: Type.TUnion<[Type.TString, Type.TObject<{
|
|
233
224
|
workflow: Type.TString;
|
|
@@ -252,9 +243,9 @@ export type NornAgentBeforeSessionStartContext = {
|
|
|
252
243
|
readonly events: EventBus;
|
|
253
244
|
};
|
|
254
245
|
export type NornAgentCreateSessionInput = {
|
|
255
|
-
readonly
|
|
246
|
+
readonly customTools?: readonly ToolDefinition[];
|
|
256
247
|
readonly label: string;
|
|
257
|
-
readonly cwd
|
|
248
|
+
readonly cwd: string;
|
|
258
249
|
readonly tools?: string[];
|
|
259
250
|
readonly beforeSessionStart?: (context: NornAgentBeforeSessionStartContext) => MaybePromise<void>;
|
|
260
251
|
readonly model?: CreateAgentSessionOptions["model"];
|
|
@@ -366,26 +357,13 @@ export type NornAgentSession = {
|
|
|
366
357
|
prompt<ResponseSchema extends TSchema>(input: NornAgentPromptInput<ResponseSchema>): Promise<StaticDecode<ResponseSchema>>;
|
|
367
358
|
dispose(): Promise<void>;
|
|
368
359
|
};
|
|
369
|
-
export type NornRun =
|
|
370
|
-
export type NornProjectRun = NornRunBase & {
|
|
371
|
-
projectRoot: string;
|
|
372
|
-
projectPath(relativePath: string): string;
|
|
373
|
-
};
|
|
374
|
-
type NornRunBase = {
|
|
360
|
+
export type NornRun = {
|
|
375
361
|
id: string;
|
|
376
|
-
workspace: string;
|
|
377
|
-
cwd: string;
|
|
378
|
-
path(relativePath: string): string;
|
|
379
362
|
next(workflowId: string, args: unknown): NornRunNext;
|
|
380
363
|
complete(metadata?: NornRunOutcomeMetadata): NornRunComplete;
|
|
381
364
|
fail(metadata: NornRunOutcomeMetadata & {
|
|
382
365
|
readonly summary: string;
|
|
383
366
|
}): NornRunFail;
|
|
384
|
-
resources: import("./resources.ts").NornResources;
|
|
385
|
-
artifacts: {
|
|
386
|
-
write(path: string, content: string): Promise<NornArtifactRef>;
|
|
387
|
-
read(ref: NornArtifactRef): Promise<string>;
|
|
388
|
-
};
|
|
389
367
|
logs: {
|
|
390
368
|
read(log: NornLogRef): Promise<string>;
|
|
391
369
|
};
|
|
@@ -410,7 +388,6 @@ export type NornRegisteredWorkflowInfo = {
|
|
|
410
388
|
readonly id: string;
|
|
411
389
|
readonly instructions?: string;
|
|
412
390
|
readonly isEntrypoint: boolean;
|
|
413
|
-
readonly isolation: NornWorkflowIsolation;
|
|
414
391
|
readonly scope?: {
|
|
415
392
|
readonly id: string;
|
|
416
393
|
};
|
package/dist/api.js
CHANGED
|
@@ -20,8 +20,7 @@ function createWorkflow(input) {
|
|
|
20
20
|
const callable = (args) => createWorkflowTransition({ workflowId: definition.id, args });
|
|
21
21
|
const result = Object.assign(callable, definition, {
|
|
22
22
|
kind: WORKFLOW_DECLARATION_KIND,
|
|
23
|
-
scope
|
|
24
|
-
isolation: definition.isolation ?? { mode: "runWorkspace" }
|
|
23
|
+
scope
|
|
25
24
|
});
|
|
26
25
|
if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${definition.id}`);
|
|
27
26
|
assertWorkflowMetadata(result);
|
|
@@ -39,9 +38,6 @@ function workflowScope(input) {
|
|
|
39
38
|
}
|
|
40
39
|
};
|
|
41
40
|
}
|
|
42
|
-
var artifactRefSchema = Type.Object({
|
|
43
|
-
path: Type.String()
|
|
44
|
-
});
|
|
45
41
|
var emptyWorkflowRefArgsSchema = Type.Object({});
|
|
46
42
|
var workflowReferenceInputSchema = Type.Union([
|
|
47
43
|
Type.String({ minLength: 1 }),
|
|
@@ -66,7 +62,7 @@ function assertDeclarationId(id) {
|
|
|
66
62
|
function isWorkflowDeclaration(value) {
|
|
67
63
|
if (typeof value !== "function") return false;
|
|
68
64
|
const candidate = value;
|
|
69
|
-
return candidate.kind === WORKFLOW_DECLARATION_KIND && typeof candidate.id === "string" && candidate.id.length > 0 && (candidate.instructions === void 0 || typeof candidate.instructions === "string") && typeof candidate.isEntrypoint === "boolean" && Boolean(candidate.args) && typeof candidate.execute === "function"
|
|
65
|
+
return candidate.kind === WORKFLOW_DECLARATION_KIND && typeof candidate.id === "string" && candidate.id.length > 0 && (candidate.instructions === void 0 || typeof candidate.instructions === "string") && typeof candidate.isEntrypoint === "boolean" && Boolean(candidate.args) && typeof candidate.execute === "function";
|
|
70
66
|
}
|
|
71
67
|
function isWorkflowNext(value) {
|
|
72
68
|
if (!value || typeof value !== "object") return false;
|
|
@@ -84,7 +80,6 @@ function isWorkflowFail(value) {
|
|
|
84
80
|
return candidate.type === "fail" && typeof candidate.metadata?.summary === "string" && candidate.metadata.summary.length > 0;
|
|
85
81
|
}
|
|
86
82
|
export {
|
|
87
|
-
artifactRefSchema,
|
|
88
83
|
isWorkflowComplete,
|
|
89
84
|
isWorkflowDeclaration,
|
|
90
85
|
isWorkflowFail,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
1
|
export type { CreateAgentSessionOptions, EventBus, PromptOptions, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export * from "./api.ts";
|
|
3
|
-
export { NornFileCoordinator } from "./files.ts";
|
|
4
|
-
export * from "./resources.ts";
|
|
5
|
-
export * from "./agent-resource-adapter.ts";
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vimhead.dev/norn",
|
|
3
|
-
"version": "0.1.0-tip.
|
|
3
|
+
"version": "0.1.0-tip.35436871363.1",
|
|
4
4
|
"description": "TypeScript SDK for building agent-driven and code-driven Norn workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,10 +26,6 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"import": "./dist/index.js"
|
|
28
28
|
},
|
|
29
|
-
"./files": {
|
|
30
|
-
"types": "./dist/files.d.ts",
|
|
31
|
-
"import": "./dist/files.js"
|
|
32
|
-
},
|
|
33
29
|
"./schema": {
|
|
34
30
|
"types": "./dist/schema.d.ts",
|
|
35
31
|
"import": "./dist/schema.js"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
export type NornAgentResourceBinding = {
|
|
3
|
-
readonly tools: readonly ToolDefinition[];
|
|
4
|
-
dispose(): Promise<void>;
|
|
5
|
-
};
|
|
6
|
-
export type NornAgentResourceAdapter = {
|
|
7
|
-
readonly name: string;
|
|
8
|
-
bind(context: {
|
|
9
|
-
readonly runId: string;
|
|
10
|
-
readonly label: string;
|
|
11
|
-
}): Promise<NornAgentResourceBinding>;
|
|
12
|
-
};
|
|
File without changes
|
package/dist/files.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export declare class NornFileCoordinator {
|
|
2
|
-
private readonly input;
|
|
3
|
-
private readonly lockRoot;
|
|
4
|
-
constructor(input: {
|
|
5
|
-
readonly lockRoot: string;
|
|
6
|
-
readonly waitTimeoutMs: number;
|
|
7
|
-
});
|
|
8
|
-
withExclusiveLock<T>(path: string, operation: (lockedPath: string) => Promise<T>): Promise<T>;
|
|
9
|
-
readText(path: string): Promise<string>;
|
|
10
|
-
writeText(path: string, content: string): Promise<void>;
|
|
11
|
-
private withCleanup;
|
|
12
|
-
private resolveTarget;
|
|
13
|
-
private acquire;
|
|
14
|
-
private reclaimDeadOwner;
|
|
15
|
-
private removeEmptyLock;
|
|
16
|
-
}
|
|
17
|
-
export declare function createRunFileCoordinator(runRoot: string): NornFileCoordinator;
|
package/dist/files.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// ../core/src/atomic-files.ts
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import { dirname } from "node:path";
|
|
5
|
-
|
|
6
|
-
// ../core/src/errors.ts
|
|
7
|
-
function isNodeError(error) {
|
|
8
|
-
return error instanceof Error && "code" in error;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// ../core/src/atomic-files.ts
|
|
12
|
-
async function writeTextAtomically(path, content) {
|
|
13
|
-
await mkdir(dirname(path), { recursive: true });
|
|
14
|
-
let existingMode;
|
|
15
|
-
try {
|
|
16
|
-
existingMode = (await stat(path)).mode & 511;
|
|
17
|
-
} catch (error) {
|
|
18
|
-
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
19
|
-
}
|
|
20
|
-
const tmpPath = `${path}.${randomUUID()}.tmp`;
|
|
21
|
-
try {
|
|
22
|
-
await writeFile(tmpPath, content, { encoding: "utf8", mode: existingMode });
|
|
23
|
-
if (existingMode !== void 0) await chmod(tmpPath, existingMode);
|
|
24
|
-
await rename(tmpPath, path);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
await rm(tmpPath, { force: true }).catch(() => void 0);
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/files.ts
|
|
32
|
-
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
33
|
-
import { lstat, mkdir as mkdir2, mkdtemp, readFile, readdir, realpath, rename as rename2, rm as rm2, rmdir, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
34
|
-
import { hostname } from "node:os";
|
|
35
|
-
import { basename, dirname as dirname2, join, resolve } from "node:path";
|
|
36
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
37
|
-
import { Type } from "typebox";
|
|
38
|
-
import { Value } from "typebox/value";
|
|
39
|
-
var ownerSchema = Type.Object({
|
|
40
|
-
token: Type.String({ format: "uuid" }),
|
|
41
|
-
pid: Type.Integer({ exclusiveMinimum: 0 }),
|
|
42
|
-
host: Type.String(),
|
|
43
|
-
target: Type.String()
|
|
44
|
-
}, { additionalProperties: false });
|
|
45
|
-
var NornFileCoordinator = class {
|
|
46
|
-
constructor(input) {
|
|
47
|
-
this.input = input;
|
|
48
|
-
if (!Number.isSafeInteger(input.waitTimeoutMs) || input.waitTimeoutMs <= 0) throw new Error("Lock wait timeout must be a positive integer");
|
|
49
|
-
this.lockRoot = resolve(input.lockRoot);
|
|
50
|
-
}
|
|
51
|
-
input;
|
|
52
|
-
lockRoot;
|
|
53
|
-
async withExclusiveLock(path, operation) {
|
|
54
|
-
const target = await this.resolveTarget(path);
|
|
55
|
-
await mkdir2(this.lockRoot, { recursive: true, mode: 448 });
|
|
56
|
-
const key = createHash("sha256").update(target).digest("hex");
|
|
57
|
-
const lockPath = join(this.lockRoot, `${key}.lock`);
|
|
58
|
-
const stagingPath = await mkdtemp(join(this.lockRoot, `${key}.pending-`));
|
|
59
|
-
const token = randomUUID2();
|
|
60
|
-
const marker = `${token}.json`;
|
|
61
|
-
return this.withCleanup({
|
|
62
|
-
operation: async () => {
|
|
63
|
-
await writeFile2(join(stagingPath, marker), JSON.stringify({ token, pid: process.pid, host: hostname(), target }), { mode: 384 });
|
|
64
|
-
await this.acquire({ stagingPath, lockPath, target });
|
|
65
|
-
return this.withCleanup({
|
|
66
|
-
operation: () => operation(target),
|
|
67
|
-
cleanup: async () => {
|
|
68
|
-
await unlink(join(lockPath, marker));
|
|
69
|
-
await this.removeEmptyLock(lockPath);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
},
|
|
73
|
-
cleanup: () => rm2(stagingPath, { recursive: true, force: true })
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
async readText(path) {
|
|
77
|
-
return this.withExclusiveLock(path, (lockedPath) => readFile(lockedPath, "utf8"));
|
|
78
|
-
}
|
|
79
|
-
async writeText(path, content) {
|
|
80
|
-
await mkdir2(dirname2(path), { recursive: true });
|
|
81
|
-
await this.withExclusiveLock(path, (lockedPath) => writeTextAtomically(lockedPath, content));
|
|
82
|
-
}
|
|
83
|
-
async withCleanup(input) {
|
|
84
|
-
let result;
|
|
85
|
-
try {
|
|
86
|
-
result = await input.operation();
|
|
87
|
-
} catch (error) {
|
|
88
|
-
try {
|
|
89
|
-
await input.cleanup();
|
|
90
|
-
} catch (cleanupError) {
|
|
91
|
-
throw new AggregateError([error, cleanupError], "File operation and lock cleanup failed");
|
|
92
|
-
}
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
await input.cleanup();
|
|
96
|
-
return result;
|
|
97
|
-
}
|
|
98
|
-
async resolveTarget(path) {
|
|
99
|
-
try {
|
|
100
|
-
return await realpath(path);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
103
|
-
const entry = await lstat(path).catch((error2) => {
|
|
104
|
-
if (isNodeError(error2) && error2.code === "ENOENT") return void 0;
|
|
105
|
-
throw error2;
|
|
106
|
-
});
|
|
107
|
-
if (entry?.isSymbolicLink()) throw new Error(`Cannot lock a dangling symbolic link: ${path}`);
|
|
108
|
-
return join(await realpath(dirname2(path)), basename(path));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async acquire(input) {
|
|
112
|
-
const deadline = Date.now() + this.input.waitTimeoutMs;
|
|
113
|
-
while (true) {
|
|
114
|
-
try {
|
|
115
|
-
await rename2(input.stagingPath, input.lockPath);
|
|
116
|
-
return;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
if (!isNodeError(error) || !["ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
|
|
119
|
-
}
|
|
120
|
-
await this.reclaimDeadOwner(input);
|
|
121
|
-
if (Date.now() >= deadline) throw new Error(`Timed out waiting for file lock: ${input.target}`);
|
|
122
|
-
await delay(10);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
async reclaimDeadOwner(input) {
|
|
126
|
-
try {
|
|
127
|
-
const entries = await readdir(input.lockPath);
|
|
128
|
-
if (entries.length === 0) return;
|
|
129
|
-
if (entries.length !== 1) throw new Error(`Invalid file lock ownership: ${input.lockPath}`);
|
|
130
|
-
const marker = entries[0];
|
|
131
|
-
const owner = Value.Parse(ownerSchema, JSON.parse(await readFile(join(input.lockPath, marker), "utf8")));
|
|
132
|
-
if (marker !== `${owner.token}.json` || owner.target !== input.target || owner.host !== hostname()) {
|
|
133
|
-
throw new Error(`Incompatible file lock ownership: ${input.lockPath}`);
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
process.kill(owner.pid, 0);
|
|
137
|
-
return;
|
|
138
|
-
} catch (error) {
|
|
139
|
-
if (isNodeError(error) && error.code === "EPERM") return;
|
|
140
|
-
if (!isNodeError(error) || error.code !== "ESRCH") throw error;
|
|
141
|
-
}
|
|
142
|
-
await unlink(join(input.lockPath, marker));
|
|
143
|
-
await this.removeEmptyLock(input.lockPath);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
async removeEmptyLock(path) {
|
|
149
|
-
try {
|
|
150
|
-
await rmdir(path);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (!isNodeError(error) || !["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
function createRunFileCoordinator(runRoot) {
|
|
157
|
-
return new NornFileCoordinator({ lockRoot: join(runRoot, "locks"), waitTimeoutMs: 3e4 });
|
|
158
|
-
}
|
|
159
|
-
export {
|
|
160
|
-
NornFileCoordinator,
|
|
161
|
-
createRunFileCoordinator
|
|
162
|
-
};
|
package/dist/resources.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { NornFileCoordinator } from "./files.ts";
|
|
2
|
-
import type { NornJsonValue } from "./schema.ts";
|
|
3
|
-
export type NornResourceContext = {
|
|
4
|
-
readonly mode: "create" | "open";
|
|
5
|
-
readonly directory: string;
|
|
6
|
-
readonly files: NornFileCoordinator;
|
|
7
|
-
};
|
|
8
|
-
export type NornResourceDefinition<T> = {
|
|
9
|
-
readonly name: string;
|
|
10
|
-
readonly kind: string;
|
|
11
|
-
readonly configuration: NornJsonValue;
|
|
12
|
-
initialize(context: NornResourceContext): Promise<T>;
|
|
13
|
-
};
|
|
14
|
-
export type NornResources = {
|
|
15
|
-
readonly files: NornFileCoordinator;
|
|
16
|
-
ensure<T>(definition: NornResourceDefinition<T>): Promise<T>;
|
|
17
|
-
};
|
package/dist/resources.js
DELETED
|
File without changes
|