@exaudeus/workrail 3.12.0 → 3.13.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.
- package/dist/console/assets/{index-CRgjJiMS.js → index-EsSXrC_a.js} +11 -11
- package/dist/console/index.html +1 -1
- package/dist/di/container.js +8 -0
- package/dist/di/tokens.d.ts +1 -0
- package/dist/di/tokens.js +1 -0
- package/dist/infrastructure/session/HttpServer.js +2 -14
- package/dist/manifest.json +83 -43
- package/dist/mcp/boundary-coercion.d.ts +2 -0
- package/dist/mcp/boundary-coercion.js +73 -0
- package/dist/mcp/handler-factory.d.ts +1 -1
- package/dist/mcp/handler-factory.js +13 -6
- package/dist/mcp/handlers/v2-manage-workflow-source.d.ts +7 -0
- package/dist/mcp/handlers/v2-manage-workflow-source.js +50 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tool-descriptions.js +20 -0
- package/dist/mcp/tools.js +6 -0
- package/dist/mcp/types/tool-description-types.d.ts +1 -1
- package/dist/mcp/types/tool-description-types.js +1 -0
- package/dist/mcp/types/workflow-tool-edition.d.ts +1 -1
- package/dist/mcp/types.d.ts +2 -0
- package/dist/mcp/v2/tool-registry.js +8 -0
- package/dist/mcp/v2/tools.d.ts +12 -0
- package/dist/mcp/v2/tools.js +7 -1
- package/dist/v2/infra/in-memory/managed-source-store/index.d.ts +8 -0
- package/dist/v2/infra/in-memory/managed-source-store/index.js +33 -0
- package/dist/v2/infra/local/data-dir/index.d.ts +2 -0
- package/dist/v2/infra/local/data-dir/index.js +6 -0
- package/dist/v2/infra/local/managed-source-store/index.d.ts +15 -0
- package/dist/v2/infra/local/managed-source-store/index.js +164 -0
- package/dist/v2/ports/data-dir.port.d.ts +2 -0
- package/dist/v2/ports/managed-source-store.port.d.ts +25 -0
- package/dist/v2/ports/managed-source-store.port.js +2 -0
- package/package.json +1 -1
- package/workflows/adaptive-ticket-creation.json +276 -282
- package/workflows/document-creation-workflow.json +70 -191
- package/workflows/documentation-update-workflow.json +59 -309
- package/workflows/intelligent-test-case-generation.json +37 -212
- package/workflows/personal-learning-materials-creation-branched.json +1 -21
- package/workflows/presentation-creation.json +143 -308
- package/workflows/relocation-workflow-us.json +161 -535
- package/workflows/scoped-documentation-workflow.json +110 -181
- package/workflows/workflow-for-workflows.v2.json +21 -5
- package/workflows/CHANGELOG-bug-investigation.md +0 -298
- package/workflows/bug-investigation.agentic.json +0 -212
- package/workflows/bug-investigation.json +0 -112
- package/workflows/mr-review-workflow.agentic.json +0 -538
- package/workflows/mr-review-workflow.json +0 -277
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildV2ToolRegistry = buildV2ToolRegistry;
|
|
4
4
|
const handler_factory_js_1 = require("../handler-factory.js");
|
|
5
5
|
const tools_js_1 = require("./tools.js");
|
|
6
|
+
const v2_manage_workflow_source_js_1 = require("../handlers/v2-manage-workflow-source.js");
|
|
6
7
|
const v2_execution_js_1 = require("../handlers/v2-execution.js");
|
|
7
8
|
const v2_workflow_js_1 = require("../handlers/v2-workflow.js");
|
|
8
9
|
const v2_checkpoint_js_1 = require("../handlers/v2-checkpoint.js");
|
|
@@ -46,6 +47,12 @@ function buildV2ToolRegistry(buildTool) {
|
|
|
46
47
|
inputSchema: tools_js_1.V2ResumeSessionInput,
|
|
47
48
|
annotations: tools_js_1.V2_TOOL_ANNOTATIONS.resume_session,
|
|
48
49
|
}),
|
|
50
|
+
buildTool({
|
|
51
|
+
name: 'manage_workflow_source',
|
|
52
|
+
title: tools_js_1.V2_TOOL_TITLES.manage_workflow_source,
|
|
53
|
+
inputSchema: tools_js_1.V2ManageWorkflowSourceInput,
|
|
54
|
+
annotations: tools_js_1.V2_TOOL_ANNOTATIONS.manage_workflow_source,
|
|
55
|
+
}),
|
|
49
56
|
];
|
|
50
57
|
const handlers = {
|
|
51
58
|
list_workflows: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ListWorkflowsInput, v2_workflow_js_1.handleV2ListWorkflows),
|
|
@@ -54,6 +61,7 @@ function buildV2ToolRegistry(buildTool) {
|
|
|
54
61
|
continue_workflow: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ContinueWorkflowInput, v2_execution_js_1.handleV2ContinueWorkflow, tools_js_1.V2ContinueWorkflowInputShape, workflow_protocol_contracts_js_1.CONTINUE_WORKFLOW_PROTOCOL.aliasMap),
|
|
55
62
|
checkpoint_workflow: (0, handler_factory_js_1.createHandler)(tools_js_1.V2CheckpointWorkflowInput, v2_checkpoint_js_1.handleV2CheckpointWorkflow),
|
|
56
63
|
resume_session: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ResumeSessionInput, v2_resume_js_1.handleV2ResumeSession),
|
|
64
|
+
manage_workflow_source: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ManageWorkflowSourceInput, v2_manage_workflow_source_js_1.handleV2ManageWorkflowSource),
|
|
57
65
|
};
|
|
58
66
|
return { tools, handlers };
|
|
59
67
|
}
|
package/dist/mcp/v2/tools.d.ts
CHANGED
|
@@ -174,6 +174,17 @@ export declare const V2ResumeSessionInput: z.ZodObject<{
|
|
|
174
174
|
sameWorkspaceOnly?: boolean | undefined;
|
|
175
175
|
}>;
|
|
176
176
|
export type V2ResumeSessionInput = z.infer<typeof V2ResumeSessionInput>;
|
|
177
|
+
export declare const V2ManageWorkflowSourceInput: z.ZodObject<{
|
|
178
|
+
action: z.ZodEnum<["attach", "detach"]>;
|
|
179
|
+
path: z.ZodEffects<z.ZodString, string, string>;
|
|
180
|
+
}, "strict", z.ZodTypeAny, {
|
|
181
|
+
path: string;
|
|
182
|
+
action: "attach" | "detach";
|
|
183
|
+
}, {
|
|
184
|
+
path: string;
|
|
185
|
+
action: "attach" | "detach";
|
|
186
|
+
}>;
|
|
187
|
+
export type V2ManageWorkflowSourceInput = z.infer<typeof V2ManageWorkflowSourceInput>;
|
|
177
188
|
export declare const V2CheckpointWorkflowInput: z.ZodObject<{
|
|
178
189
|
checkpointToken: z.ZodString;
|
|
179
190
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -189,5 +200,6 @@ export declare const V2_TOOL_TITLES: {
|
|
|
189
200
|
readonly continue_workflow: "Continue Workflow (v2)";
|
|
190
201
|
readonly checkpoint_workflow: "Checkpoint Workflow (v2)";
|
|
191
202
|
readonly resume_session: "Resume Session (v2)";
|
|
203
|
+
readonly manage_workflow_source: "Manage Workflow Source (v2)";
|
|
192
204
|
};
|
|
193
205
|
export declare const V2_TOOL_ANNOTATIONS: Readonly<Record<keyof typeof V2_TOOL_TITLES, ToolAnnotations>>;
|
package/dist/mcp/v2/tools.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.V2_TOOL_ANNOTATIONS = exports.V2_TOOL_TITLES = exports.V2CheckpointWorkflowInput = exports.V2ResumeSessionInput = exports.V2ContinueWorkflowInput = exports.V2ContinueWorkflowInputShape = exports.V2StartWorkflowInput = exports.V2InspectWorkflowInput = exports.V2ListWorkflowsInput = void 0;
|
|
6
|
+
exports.V2_TOOL_ANNOTATIONS = exports.V2_TOOL_TITLES = exports.V2CheckpointWorkflowInput = exports.V2ManageWorkflowSourceInput = exports.V2ResumeSessionInput = exports.V2ContinueWorkflowInput = exports.V2ContinueWorkflowInputShape = exports.V2StartWorkflowInput = exports.V2InspectWorkflowInput = exports.V2ListWorkflowsInput = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
const workflow_protocol_contracts_js_1 = require("../workflow-protocol-contracts.js");
|
|
@@ -112,6 +112,10 @@ exports.V2ResumeSessionInput = zod_1.z.object({
|
|
|
112
112
|
sameWorkspaceOnly: zod_1.z.boolean().optional().describe('If true, only sessions from the same repo/workspace are considered when repo_root_hash is available. ' +
|
|
113
113
|
'Use this when the user clearly means "resume work from this repo only".'),
|
|
114
114
|
}).strict();
|
|
115
|
+
exports.V2ManageWorkflowSourceInput = zod_1.z.object({
|
|
116
|
+
action: zod_1.z.enum(['attach', 'detach']).describe('The operation to perform. "attach" registers the directory as a managed workflow source; "detach" removes it. Both operations are idempotent.'),
|
|
117
|
+
path: zod_1.z.string().min(1).refine((p) => path_1.default.isAbsolute(p), 'path must be an absolute path').describe('Absolute filesystem path to the workflow directory to attach or detach. Must be an absolute path. The path is normalized (resolved) before storage.'),
|
|
118
|
+
}).strict();
|
|
115
119
|
exports.V2CheckpointWorkflowInput = zod_1.z.object({
|
|
116
120
|
checkpointToken: zod_1.z.string().min(1).describe('The checkpoint token from the most recent start_workflow or continue_workflow response. ' +
|
|
117
121
|
'Creates a checkpoint on the current step without advancing. Idempotent — calling with the same token is safe.'),
|
|
@@ -123,6 +127,7 @@ exports.V2_TOOL_TITLES = {
|
|
|
123
127
|
continue_workflow: 'Continue Workflow (v2)',
|
|
124
128
|
checkpoint_workflow: 'Checkpoint Workflow (v2)',
|
|
125
129
|
resume_session: 'Resume Session (v2)',
|
|
130
|
+
manage_workflow_source: 'Manage Workflow Source (v2)',
|
|
126
131
|
};
|
|
127
132
|
exports.V2_TOOL_ANNOTATIONS = {
|
|
128
133
|
list_workflows: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
@@ -131,4 +136,5 @@ exports.V2_TOOL_ANNOTATIONS = {
|
|
|
131
136
|
continue_workflow: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
132
137
|
checkpoint_workflow: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
133
138
|
resume_session: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
139
|
+
manage_workflow_source: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
134
140
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { ManagedSourceRecordV2, ManagedSourceStoreError, ManagedSourceStorePortV2 } from '../../../ports/managed-source-store.port.js';
|
|
3
|
+
export declare class InMemoryManagedSourceStoreV2 implements ManagedSourceStorePortV2 {
|
|
4
|
+
private readonly sources;
|
|
5
|
+
list(): ResultAsync<readonly ManagedSourceRecordV2[], ManagedSourceStoreError>;
|
|
6
|
+
attach(sourcePath: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
7
|
+
detach(sourcePath: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InMemoryManagedSourceStoreV2 = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const neverthrow_1 = require("neverthrow");
|
|
9
|
+
class InMemoryManagedSourceStoreV2 {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sources = [];
|
|
12
|
+
}
|
|
13
|
+
list() {
|
|
14
|
+
return (0, neverthrow_1.okAsync)([...this.sources]);
|
|
15
|
+
}
|
|
16
|
+
attach(sourcePath) {
|
|
17
|
+
const normalizedPath = path_1.default.resolve(sourcePath);
|
|
18
|
+
const alreadyPresent = this.sources.some((s) => s.path === normalizedPath);
|
|
19
|
+
if (!alreadyPresent) {
|
|
20
|
+
this.sources.push({ path: normalizedPath, addedAtMs: Date.now() });
|
|
21
|
+
}
|
|
22
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
23
|
+
}
|
|
24
|
+
detach(sourcePath) {
|
|
25
|
+
const normalizedPath = path_1.default.resolve(sourcePath);
|
|
26
|
+
const index = this.sources.findIndex((s) => s.path === normalizedPath);
|
|
27
|
+
if (index !== -1) {
|
|
28
|
+
this.sources.splice(index, 1);
|
|
29
|
+
}
|
|
30
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.InMemoryManagedSourceStoreV2 = InMemoryManagedSourceStoreV2;
|
|
@@ -19,4 +19,6 @@ export declare class LocalDataDirV2 implements DataDirPortV2 {
|
|
|
19
19
|
sessionManifestPath(sessionId: SessionId): string;
|
|
20
20
|
sessionLockPath(sessionId: SessionId): string;
|
|
21
21
|
tokenIndexPath(): string;
|
|
22
|
+
managedSourcesPath(): string;
|
|
23
|
+
managedSourcesLockPath(): string;
|
|
22
24
|
}
|
|
@@ -89,5 +89,11 @@ class LocalDataDirV2 {
|
|
|
89
89
|
tokenIndexPath() {
|
|
90
90
|
return path.join(this.keysDir(), 'token-index.jsonl');
|
|
91
91
|
}
|
|
92
|
+
managedSourcesPath() {
|
|
93
|
+
return path.join(this.root(), 'managed-sources', 'managed-sources.json');
|
|
94
|
+
}
|
|
95
|
+
managedSourcesLockPath() {
|
|
96
|
+
return path.join(this.root(), 'managed-sources', 'managed-sources.lock');
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
exports.LocalDataDirV2 = LocalDataDirV2;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
|
|
3
|
+
import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
|
|
4
|
+
import type { ManagedSourceRecordV2, ManagedSourceStoreError, ManagedSourceStorePortV2 } from '../../../ports/managed-source-store.port.js';
|
|
5
|
+
export declare class LocalManagedSourceStoreV2 implements ManagedSourceStorePortV2 {
|
|
6
|
+
private readonly dataDir;
|
|
7
|
+
private readonly fs;
|
|
8
|
+
constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2);
|
|
9
|
+
list(): ResultAsync<readonly ManagedSourceRecordV2[], ManagedSourceStoreError>;
|
|
10
|
+
attach(sourcePath: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
11
|
+
detach(sourcePath: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
12
|
+
private readSources;
|
|
13
|
+
private persist;
|
|
14
|
+
private withLock;
|
|
15
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LocalManagedSourceStoreV2 = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const neverthrow_1 = require("neverthrow");
|
|
10
|
+
const MANAGED_SOURCE_LOCK_RETRY_MS = 250;
|
|
11
|
+
const jcs_js_1 = require("../../../durable-core/canonical/jcs.js");
|
|
12
|
+
const ManagedSourceRecordSchema = zod_1.z.object({
|
|
13
|
+
path: zod_1.z.string(),
|
|
14
|
+
addedAtMs: zod_1.z.number().int().nonnegative(),
|
|
15
|
+
});
|
|
16
|
+
const ManagedSourcesFileSchema = zod_1.z.object({
|
|
17
|
+
v: zod_1.z.literal(1),
|
|
18
|
+
sources: zod_1.z.array(ManagedSourceRecordSchema),
|
|
19
|
+
});
|
|
20
|
+
function mapFsToManagedSourceError(e) {
|
|
21
|
+
if (e.code === 'FS_ALREADY_EXISTS') {
|
|
22
|
+
return {
|
|
23
|
+
code: 'MANAGED_SOURCE_BUSY',
|
|
24
|
+
message: 'Managed sources are being updated by another WorkRail process.',
|
|
25
|
+
retry: { kind: 'retryable_after_ms', afterMs: MANAGED_SOURCE_LOCK_RETRY_MS },
|
|
26
|
+
lockPath: 'managed-sources.lock',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { code: 'MANAGED_SOURCE_IO_ERROR', message: e.message };
|
|
30
|
+
}
|
|
31
|
+
function normalizeRecords(sources) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const normalized = [];
|
|
34
|
+
for (const source of sources) {
|
|
35
|
+
const normalizedPath = path_1.default.resolve(source.path);
|
|
36
|
+
if (seen.has(normalizedPath))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(normalizedPath);
|
|
39
|
+
normalized.push({ path: normalizedPath, addedAtMs: source.addedAtMs });
|
|
40
|
+
}
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
class LocalManagedSourceStoreV2 {
|
|
44
|
+
constructor(dataDir, fs) {
|
|
45
|
+
this.dataDir = dataDir;
|
|
46
|
+
this.fs = fs;
|
|
47
|
+
}
|
|
48
|
+
list() {
|
|
49
|
+
return this.readSources();
|
|
50
|
+
}
|
|
51
|
+
attach(sourcePath) {
|
|
52
|
+
const normalizedPath = path_1.default.resolve(sourcePath);
|
|
53
|
+
const nowMs = Date.now();
|
|
54
|
+
return this.withLock(() => this.readSources().andThen((sources) => {
|
|
55
|
+
const alreadyPresent = sources.some((s) => s.path === normalizedPath);
|
|
56
|
+
if (alreadyPresent)
|
|
57
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
58
|
+
const next = [...sources, { path: normalizedPath, addedAtMs: nowMs }];
|
|
59
|
+
return this.persist(next);
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
detach(sourcePath) {
|
|
63
|
+
const normalizedPath = path_1.default.resolve(sourcePath);
|
|
64
|
+
return this.withLock(() => this.readSources().andThen((sources) => {
|
|
65
|
+
const next = sources.filter((s) => s.path !== normalizedPath);
|
|
66
|
+
if (next.length === sources.length)
|
|
67
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
68
|
+
return this.persist(next);
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
readSources() {
|
|
72
|
+
const filePath = this.dataDir.managedSourcesPath();
|
|
73
|
+
return this.fs.readFileUtf8(filePath)
|
|
74
|
+
.orElse((e) => {
|
|
75
|
+
if (e.code === 'FS_NOT_FOUND')
|
|
76
|
+
return (0, neverthrow_1.okAsync)('');
|
|
77
|
+
return (0, neverthrow_1.errAsync)(mapFsToManagedSourceError(e));
|
|
78
|
+
})
|
|
79
|
+
.andThen((raw) => {
|
|
80
|
+
if (raw === '')
|
|
81
|
+
return (0, neverthrow_1.okAsync)([]);
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return (0, neverthrow_1.errAsync)({
|
|
88
|
+
code: 'MANAGED_SOURCE_CORRUPTION',
|
|
89
|
+
message: `Invalid JSON in managed sources file: ${filePath}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const validated = ManagedSourcesFileSchema.safeParse(parsed);
|
|
93
|
+
if (!validated.success) {
|
|
94
|
+
return (0, neverthrow_1.errAsync)({
|
|
95
|
+
code: 'MANAGED_SOURCE_CORRUPTION',
|
|
96
|
+
message: `Managed sources file has invalid shape: ${filePath}`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return (0, neverthrow_1.okAsync)(normalizeRecords(validated.data.sources));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
persist(sources) {
|
|
103
|
+
const filePath = this.dataDir.managedSourcesPath();
|
|
104
|
+
const dir = path_1.default.dirname(filePath);
|
|
105
|
+
const tmpPath = `${filePath}.tmp`;
|
|
106
|
+
const fileValue = {
|
|
107
|
+
v: 1,
|
|
108
|
+
sources: [...normalizeRecords(sources)],
|
|
109
|
+
};
|
|
110
|
+
const canonical = (0, jcs_js_1.toCanonicalBytes)(fileValue).mapErr((e) => ({
|
|
111
|
+
code: 'MANAGED_SOURCE_IO_ERROR',
|
|
112
|
+
message: `Failed to canonicalize managed sources state: ${e.message}`,
|
|
113
|
+
}));
|
|
114
|
+
if (canonical.isErr())
|
|
115
|
+
return (0, neverthrow_1.errAsync)(canonical.error);
|
|
116
|
+
const bytes = canonical.value;
|
|
117
|
+
return this.fs.mkdirp(dir)
|
|
118
|
+
.mapErr(mapFsToManagedSourceError)
|
|
119
|
+
.andThen(() => this.fs.openWriteTruncate(tmpPath).mapErr(mapFsToManagedSourceError))
|
|
120
|
+
.andThen(({ fd }) => this.fs.writeAll(fd, bytes)
|
|
121
|
+
.mapErr(mapFsToManagedSourceError)
|
|
122
|
+
.andThen(() => this.fs.fsyncFile(fd).mapErr(mapFsToManagedSourceError))
|
|
123
|
+
.andThen(() => this.fs.closeFile(fd).mapErr(mapFsToManagedSourceError))
|
|
124
|
+
.orElse((e) => this.fs.closeFile(fd)
|
|
125
|
+
.mapErr(() => e)
|
|
126
|
+
.andThen(() => (0, neverthrow_1.errAsync)(e))))
|
|
127
|
+
.andThen(() => this.fs.rename(tmpPath, filePath).mapErr(mapFsToManagedSourceError))
|
|
128
|
+
.andThen(() => this.fs.fsyncDir(dir).mapErr(mapFsToManagedSourceError));
|
|
129
|
+
}
|
|
130
|
+
withLock(run) {
|
|
131
|
+
const lockPath = this.dataDir.managedSourcesLockPath();
|
|
132
|
+
const dir = path_1.default.dirname(lockPath);
|
|
133
|
+
const lockBytes = new TextEncoder().encode(JSON.stringify({ v: 1, pid: process.pid }));
|
|
134
|
+
return this.fs.mkdirp(dir)
|
|
135
|
+
.mapErr(mapFsToManagedSourceError)
|
|
136
|
+
.andThen(() => this.fs.openExclusive(lockPath, lockBytes)
|
|
137
|
+
.mapErr((e) => {
|
|
138
|
+
const mapped = mapFsToManagedSourceError(e);
|
|
139
|
+
if (mapped.code === 'MANAGED_SOURCE_BUSY') {
|
|
140
|
+
return { ...mapped, lockPath };
|
|
141
|
+
}
|
|
142
|
+
return mapped;
|
|
143
|
+
}))
|
|
144
|
+
.andThen(({ fd }) => this.fs.fsyncFile(fd)
|
|
145
|
+
.mapErr(mapFsToManagedSourceError)
|
|
146
|
+
.andThen(() => this.fs.closeFile(fd).mapErr(mapFsToManagedSourceError))
|
|
147
|
+
.andThen(() => run())
|
|
148
|
+
.andThen((value) => this.fs.unlink(lockPath)
|
|
149
|
+
.orElse((e) => {
|
|
150
|
+
if (e.code === 'FS_NOT_FOUND')
|
|
151
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
152
|
+
return (0, neverthrow_1.errAsync)(mapFsToManagedSourceError(e));
|
|
153
|
+
})
|
|
154
|
+
.map(() => value))
|
|
155
|
+
.orElse((error) => this.fs.unlink(lockPath)
|
|
156
|
+
.orElse((e) => {
|
|
157
|
+
if (e.code === 'FS_NOT_FOUND')
|
|
158
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
159
|
+
return (0, neverthrow_1.errAsync)(mapFsToManagedSourceError(e));
|
|
160
|
+
})
|
|
161
|
+
.andThen(() => (0, neverthrow_1.errAsync)(error))));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.LocalManagedSourceStoreV2 = LocalManagedSourceStoreV2;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
2
|
+
export type ManagedSourceStoreError = {
|
|
3
|
+
readonly code: 'MANAGED_SOURCE_BUSY';
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly retry: {
|
|
6
|
+
readonly kind: 'retryable_after_ms';
|
|
7
|
+
readonly afterMs: number;
|
|
8
|
+
};
|
|
9
|
+
readonly lockPath: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly code: 'MANAGED_SOURCE_IO_ERROR';
|
|
12
|
+
readonly message: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly code: 'MANAGED_SOURCE_CORRUPTION';
|
|
15
|
+
readonly message: string;
|
|
16
|
+
};
|
|
17
|
+
export interface ManagedSourceRecordV2 {
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly addedAtMs: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ManagedSourceStorePortV2 {
|
|
22
|
+
list(): ResultAsync<readonly ManagedSourceRecordV2[], ManagedSourceStoreError>;
|
|
23
|
+
attach(path: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
24
|
+
detach(path: string): ResultAsync<void, ManagedSourceStoreError>;
|
|
25
|
+
}
|