@pipelab/shared 0.0.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.
Files changed (55) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.turbo/turbo-build.log +41 -0
  3. package/.turbo/turbo-format.log +6 -0
  4. package/.turbo/turbo-lint.log +156 -0
  5. package/CHANGELOG.md +44 -0
  6. package/LICENSE +110 -0
  7. package/README.md +3 -0
  8. package/dist/index.d.mts +3649 -0
  9. package/dist/index.d.mts.map +1 -0
  10. package/dist/index.mjs +43748 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/package.json +53 -0
  13. package/src/apis.ts +193 -0
  14. package/src/build-history.ts +131 -0
  15. package/src/config/migrators.ts +291 -0
  16. package/src/config/projects-definition.ts +25 -0
  17. package/src/config/projects-types.ts +25 -0
  18. package/src/config/projects.ts +1 -0
  19. package/src/config/settings-definition.ts +21 -0
  20. package/src/config/settings.ts +21 -0
  21. package/src/config.schema.ts +147 -0
  22. package/src/database.types.ts +95 -0
  23. package/src/errors.ts +11 -0
  24. package/src/evaluator.ts +58 -0
  25. package/src/fmt.ts +5 -0
  26. package/src/graph.ts +207 -0
  27. package/src/i18n/de_DE.json +86 -0
  28. package/src/i18n/en_US.json +158 -0
  29. package/src/i18n/es_ES.json +86 -0
  30. package/src/i18n/fr_FR.json +89 -0
  31. package/src/i18n/pt_BR.json +86 -0
  32. package/src/i18n/zh_CN.json +86 -0
  33. package/src/i18n-utils.ts +10 -0
  34. package/src/index.ts +51 -0
  35. package/src/ipc.types.ts +73 -0
  36. package/src/logger.ts +20 -0
  37. package/src/migrations/model.ts +1 -0
  38. package/src/migrations/projects.ts +1 -0
  39. package/src/migrations/settings.ts +1 -0
  40. package/src/model.test.ts +214 -0
  41. package/src/model.ts +233 -0
  42. package/src/plugins/definitions.ts +472 -0
  43. package/src/plugins.ts +20 -0
  44. package/src/quickjs.ts +98 -0
  45. package/src/save-location.ts +42 -0
  46. package/src/subscription-errors.ts +87 -0
  47. package/src/supabase.ts +28 -0
  48. package/src/tests/helpers.ts +5 -0
  49. package/src/types.ts +3 -0
  50. package/src/utils.ts +3 -0
  51. package/src/validation.ts +16 -0
  52. package/src/variables.ts +27 -0
  53. package/src/wasm.d.ts +9 -0
  54. package/src/websocket.types.ts +186 -0
  55. package/tsconfig.json +13 -0
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@pipelab/shared",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "description": "Shared logic and types for the Pipelab ecosystem",
6
+ "license": "FSL-1.1-MIT",
7
+ "author": "CynToolkit",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/CynToolkit/pipelab.git",
11
+ "directory": "packages/shared"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.mts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.mts",
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.cjs",
22
+ "default": "./dist/index.mjs"
23
+ }
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@jitl/quickjs-singlefile-mjs-release-sync": "0.31.0",
30
+ "@jitl/quickjs-wasmfile-release-sync": "0.31.0",
31
+ "@supabase/supabase-js": "2.99.3",
32
+ "@types/node": "^24.12.2",
33
+ "klona": "2.0.6",
34
+ "nanoid": "5.0.7",
35
+ "quickjs-emscripten": "0.31.0",
36
+ "quickjs-emscripten-sync": "1.6.0",
37
+ "tslog": "4.9.3",
38
+ "type-fest": "4.26.1",
39
+ "valibot": "0.42.1",
40
+ "@pipelab/migration": "0.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "esbuild-plugin-wasm": "1.1.0",
44
+ "tsdown": "0.21.2",
45
+ "typescript": "5.9.3",
46
+ "@pipelab/tsconfig": "0.0.1"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "format": "oxfmt .",
51
+ "lint": "oxlint ."
52
+ }
53
+ }
package/src/apis.ts ADDED
@@ -0,0 +1,193 @@
1
+ import { RendererPluginDefinition } from "./plugins/definitions";
2
+ import { User, UserResponse } from "@supabase/supabase-js";
3
+ import type { Tagged } from "type-fest";
4
+ import { PresetResult, Steps } from "./model";
5
+ import { AppConfig } from "./config.schema";
6
+ import { Agent } from "./websocket.types";
7
+ import {
8
+ BuildHistoryEntry,
9
+ BuildHistoryQuery,
10
+ BuildHistoryResponse,
11
+ BuildHistoryConfig,
12
+ RetentionPolicy,
13
+ } from "./build-history";
14
+
15
+ type Event<TYPE extends string, DATA> =
16
+ | { type: TYPE; data: DATA }
17
+ | { type: "log"; data: { decorator: string; message: unknown[]; time: number } };
18
+ type EndEvent<DATA> = {
19
+ type: "end";
20
+ data:
21
+ | {
22
+ type: "success";
23
+ result: DATA;
24
+ }
25
+ | {
26
+ type: "error";
27
+ ipcError: string;
28
+ code?: string;
29
+ };
30
+ };
31
+
32
+ export type Presets = Record<string, PresetResult>;
33
+
34
+ export type IpcDefinition = {
35
+ "fs:read": [
36
+ // input
37
+ { path: string },
38
+ EndEvent<{ content: string }>,
39
+ ];
40
+ "fs:remove": [
41
+ // input
42
+ { path: string },
43
+ EndEvent<boolean>,
44
+ ];
45
+ "fs:write": [
46
+ // input
47
+ {
48
+ path: string;
49
+ content: string;
50
+ },
51
+ EndEvent<{ ok: boolean }>,
52
+ ];
53
+ "fs:rm": [
54
+ // input
55
+ {
56
+ path: string;
57
+ recursive: boolean;
58
+ force: boolean;
59
+ },
60
+ EndEvent<{ ok: boolean }>,
61
+ ];
62
+ "fs:listDirectory": [
63
+ // input
64
+ { path: string },
65
+ EndEvent<{
66
+ files: {
67
+ name: string;
68
+ isDirectory: boolean;
69
+ isSymbolicLink: boolean;
70
+ size: number;
71
+ mtime: number;
72
+ }[];
73
+ }>,
74
+ ];
75
+ "fs:getHomeDirectory": [void, EndEvent<{ path: string }>];
76
+ "dialog:showOpenDialog": [
77
+ // input
78
+ Electron.OpenDialogOptions,
79
+ EndEvent<{ canceled: boolean; filePaths: string[] }>,
80
+ ];
81
+ "dialog:showSaveDialog": [
82
+ // input
83
+ Electron.SaveDialogOptions,
84
+ EndEvent<{ canceled: boolean; filePath: string | undefined }>,
85
+ ];
86
+ "nodes:get": [void, EndEvent<{ nodes: RendererPluginDefinition[] }>];
87
+ "presets:get": [void, EndEvent<Presets>];
88
+ "action:execute": [
89
+ {
90
+ pluginId: string;
91
+ nodeId: string;
92
+ params: any;
93
+ steps: Steps;
94
+ },
95
+ (
96
+ | Event<"progress", unknown>
97
+ | Event<"progress", unknown>
98
+ | EndEvent<{ outputs: Record<string, unknown>; tmp: string }>
99
+ ),
100
+ ];
101
+ "condition:execute": [
102
+ {
103
+ pluginId: string;
104
+ nodeId: string;
105
+ params: any;
106
+ steps: Steps;
107
+ },
108
+ (
109
+ | Event<"progress", unknown>
110
+ | Event<"progress", unknown>
111
+ | EndEvent<{ outputs: Record<string, unknown>; value: boolean }>
112
+ ),
113
+ ];
114
+ "constants:get": [void, EndEvent<{ result: { userData: string } }>];
115
+
116
+ "config:load": [{ config: string }, EndEvent<{ result: any }>];
117
+ "config:save": [{ data: any; config: string }, EndEvent<{ result: "ok" }>];
118
+ "config:reset": [{ config: string; key: string }, EndEvent<{ result: "ok" }>];
119
+ "action:cancel": [void, EndEvent<{ result: "ok" | "ko" }>];
120
+
121
+ // Build History APIs
122
+ "build-history:save": [{ entry: BuildHistoryEntry }, EndEvent<{ result: "ok" | "ko" }>];
123
+ "build-history:get": [
124
+ { id: string; pipelineId?: string },
125
+ EndEvent<{ entry?: BuildHistoryEntry }>,
126
+ ];
127
+ "build-history:get-all": [{ query?: BuildHistoryQuery }, EndEvent<BuildHistoryResponse>];
128
+ "build-history:update": [
129
+ { id: string; updates: Partial<BuildHistoryEntry>; pipelineId?: string },
130
+ EndEvent<{ result: "ok" | "ko" }>,
131
+ ];
132
+ "build-history:delete": [{ id: string; pipelineId?: string }, EndEvent<{ result: "ok" | "ko" }>];
133
+ "build-history:clear": [void, EndEvent<{ result: "ok" | "ko" }>];
134
+ "build-history:clear-by-pipeline": [{ pipelineId: string }, EndEvent<{ result: "ok" | "ko" }>];
135
+ "build-history:get-storage-info": [
136
+ void,
137
+ EndEvent<{
138
+ totalEntries: number;
139
+ totalSize: number;
140
+ oldestEntry?: number;
141
+ newestEntry?: number;
142
+ }>,
143
+ ];
144
+ "build-history:configure": [
145
+ { config: Partial<BuildHistoryConfig> },
146
+ EndEvent<{ result: "ok" | "ko" }>,
147
+ ];
148
+ "agents:get": [void, EndEvent<{ agents: Agent[] }>];
149
+ "graph:execute": [
150
+ {
151
+ graph: any[];
152
+ variables: any[];
153
+ pipelineId?: string;
154
+ projectId?: string;
155
+ projectName?: string;
156
+ projectPath?: string;
157
+ },
158
+ (
159
+ | { type: "node-enter"; data: { nodeUid: string; nodeName: string } }
160
+ | { type: "node-exit"; data: { nodeUid: string; nodeName: string } }
161
+ | { type: "node-log"; data: { nodeUid: string; logData: any } }
162
+ | EndEvent<{ result: any; buildId: string }>
163
+ ),
164
+ ];
165
+ "auth:getUser": [void, EndEvent<{ user: User | null }>];
166
+ "auth:signInWithPassword": [{ email: string; password: string }, EndEvent<UserResponse>];
167
+ "auth:signUp": [{ email: string; password: string }, EndEvent<UserResponse>];
168
+ "auth:signOut": [void, EndEvent<void>];
169
+ "auth:resetPasswordForEmail": [{ email: string }, EndEvent<{ error: any | null }>];
170
+ "auth:invoke": [
171
+ { name: string; options?: any },
172
+ EndEvent<{ data: any | null; error: any | null }>,
173
+ ];
174
+ "agent:version:get": [void, EndEvent<{ version: string }>];
175
+ "startup:progress": [void, { type: "progress"; data: { message: string } } | { type: "ready" }];
176
+ };
177
+
178
+ export type Channels = keyof IpcDefinition;
179
+
180
+ export const ShellChannels: Channels[] = ["dialog:showOpenDialog", "dialog:showSaveDialog"];
181
+
182
+ export type Data<KEY extends Channels> = IpcDefinition[KEY][0];
183
+ export type Events<KEY extends Channels> = IpcDefinition[KEY][1];
184
+ export type End<KEY extends Channels> = Extract<IpcDefinition[KEY][1], { type: "end" }>["data"];
185
+
186
+ export type IpcMessage = {
187
+ // the channel to communicate
188
+ requestId: RequestId;
189
+ data: any;
190
+ };
191
+ export type RequestId = Tagged<string, "request-id">;
192
+
193
+ // type Output = End<'fs:openFolder'>
@@ -0,0 +1,131 @@
1
+ // Build History Storage Types and Interfaces
2
+
3
+ export interface ExecutionStep {
4
+ id: string;
5
+ name: string;
6
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
7
+ startTime: number;
8
+ endTime?: number;
9
+ duration?: number;
10
+ logs: LogEntry[];
11
+ error?: ExecutionError;
12
+ output?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface ExecutionError {
16
+ message: string;
17
+ stack?: string;
18
+ code?: string;
19
+ timestamp: number;
20
+ }
21
+
22
+ export interface LogEntry {
23
+ id: string;
24
+ timestamp: number;
25
+ level: "debug" | "info" | "warn" | "error";
26
+ message: string;
27
+ source?: string;
28
+ data?: Record<string, unknown>;
29
+ }
30
+
31
+ export interface BuildHistoryEntry {
32
+ id: string;
33
+ pipelineId: string;
34
+ projectName: string;
35
+ projectPath: string;
36
+ status: "running" | "completed" | "failed" | "cancelled";
37
+ startTime: number;
38
+ endTime?: number;
39
+ duration?: number;
40
+ steps: ExecutionStep[];
41
+ totalSteps: number;
42
+ completedSteps: number;
43
+ failedSteps: number;
44
+ cancelledSteps: number;
45
+ logs: LogEntry[];
46
+ error?: ExecutionError;
47
+ output?: Record<string, unknown>;
48
+ metadata?: Record<string, unknown>;
49
+ userId?: string;
50
+ createdAt: number;
51
+ updatedAt: number;
52
+ }
53
+
54
+ // Query interface supporting both pipeline and scenario filtering
55
+ export interface BuildHistoryQuery {
56
+ pipelineId?: string;
57
+ }
58
+
59
+ export interface BuildHistoryResponse {
60
+ entries: BuildHistoryEntry[];
61
+ total: number;
62
+ }
63
+
64
+ // Storage interfaces - Simplified for pipeline-specific storage
65
+ export interface IBuildHistoryStorage {
66
+ save(entry: BuildHistoryEntry): Promise<void>;
67
+ get(id: string, pipelineId?: string): Promise<BuildHistoryEntry | undefined>;
68
+ getAll(): Promise<BuildHistoryEntry[]>;
69
+ getByPipeline(pipelineId: string): Promise<BuildHistoryEntry[]>;
70
+ update(id: string, updates: Partial<BuildHistoryEntry>, pipelineId?: string): Promise<void>;
71
+ delete(id: string, pipelineId?: string): Promise<void>;
72
+ clear(): Promise<void>;
73
+ getStorageInfo(): Promise<{
74
+ totalEntries: number;
75
+ totalSize: number;
76
+ oldestEntry?: number;
77
+ newestEntry?: number;
78
+ numberOfPipelines: number;
79
+ userDataPath: string;
80
+ retentionPolicy: {
81
+ enabled: boolean;
82
+ maxEntries: number;
83
+ maxAge: number;
84
+ };
85
+ disk: {
86
+ total: number;
87
+ free: number;
88
+ pipelab: number;
89
+ };
90
+ }>;
91
+ }
92
+
93
+ // Retention policy configuration
94
+ export interface RetentionPolicy {
95
+ enabled: boolean;
96
+ maxEntries: number;
97
+ maxAge: number; // in milliseconds
98
+ maxSize: number; // in bytes
99
+ keepFailedBuilds: boolean;
100
+ keepSuccessfulBuilds: boolean;
101
+ }
102
+
103
+ // Storage configuration
104
+ export interface BuildHistoryConfig {
105
+ storagePath: string;
106
+ indexFileName: string;
107
+ entryFilePrefix: string;
108
+ retentionPolicy: RetentionPolicy;
109
+ }
110
+
111
+ // Authorization and subscription types
112
+ export interface SubscriptionBenefit {
113
+ id: string;
114
+ name: string;
115
+ description?: string;
116
+ }
117
+
118
+ export interface SubscriptionError extends Error {
119
+ code: "SUBSCRIPTION_REQUIRED" | "SUBSCRIPTION_EXPIRED" | "BENEFIT_NOT_FOUND" | "UNAUTHORIZED";
120
+ benefit?: string;
121
+ userMessage: string;
122
+ }
123
+
124
+ export interface AuthorizationContext {
125
+ userId?: string;
126
+ hasBenefit: (benefitId: string) => boolean;
127
+ isPaidUser: boolean;
128
+ }
129
+
130
+ // Authorization check function type
131
+ export type AuthorizationCheck = (context: AuthorizationContext) => void;
@@ -0,0 +1,291 @@
1
+ import {
2
+ createMigration as createMigrationBase,
3
+ createMigrator,
4
+ finalVersion,
5
+ OmitVersion,
6
+ SemVer,
7
+ Awaitable,
8
+ MigrationSchema,
9
+ } from "@pipelab/migration";
10
+ import {
11
+ AppConfig,
12
+ AppConfigV1,
13
+ AppConfigV2,
14
+ AppConfigV3,
15
+ AppConfigV4,
16
+ AppConfigV5,
17
+ AppConfigV6,
18
+ AppConfigV7,
19
+ } from "../config.schema";
20
+ import { FileRepoV1, FileRepoV2, FileRepo } from "./projects-types";
21
+ import { SavedFileV1, SavedFileV2, SavedFileV3, SavedFileV4, SavedFile } from "../model";
22
+
23
+ // --- Types ---
24
+
25
+ export type Additive<T, P> = OmitVersion<T> & OmitVersion<P>;
26
+
27
+ const createMigration = <From extends MigrationSchema, To extends MigrationSchema>(config: {
28
+ version: SemVer;
29
+ up: (state: OmitVersion<From>, targetVersion: string) => Awaitable<Additive<To, From>>;
30
+ }) => createMigrationBase<From, To>(config);
31
+
32
+ export interface Migrator<T> {
33
+ migrate: (data: any, options?: any) => Promise<T>;
34
+ defaultValue: T;
35
+ }
36
+
37
+ // --- Settings Migrator ---
38
+
39
+ const settingsMigratorInternal = createMigrator<AppConfigV1, AppConfig>();
40
+
41
+ export const defaultAppSettings = settingsMigratorInternal.createDefault({
42
+ locale: "en-US",
43
+ theme: "light",
44
+ version: "7.0.0",
45
+ autosave: true,
46
+ agents: [],
47
+ tours: {
48
+ dashboard: {
49
+ step: 0,
50
+ completed: false,
51
+ },
52
+ editor: {
53
+ step: 0,
54
+ completed: false,
55
+ },
56
+ },
57
+ buildHistory: {
58
+ retentionPolicy: {
59
+ enabled: false,
60
+ maxEntries: 50,
61
+ maxAge: 30,
62
+ },
63
+ },
64
+ });
65
+
66
+ export const appSettingsMigrator = settingsMigratorInternal.createMigrations({
67
+ defaultValue: defaultAppSettings,
68
+ migrations: [
69
+ createMigration<AppConfigV1, AppConfigV2>({
70
+ version: "1.0.0" as SemVer,
71
+ up: (state) => state,
72
+ }),
73
+ createMigration<AppConfigV2, AppConfigV3>({
74
+ version: "2.0.0" as SemVer,
75
+ up: (state) => {
76
+ return {
77
+ ...state,
78
+ clearTemporaryFoldersOnPipelineEnd: false,
79
+ };
80
+ },
81
+ }),
82
+ createMigration<AppConfigV3, AppConfigV4>({
83
+ version: "3.0.0" as SemVer,
84
+ up: (state) => ({
85
+ ...state,
86
+ locale: "en-US" as const,
87
+ }),
88
+ }),
89
+ createMigration<AppConfigV4, AppConfigV5>({
90
+ version: "4.0.0" as SemVer,
91
+ up: (state) => ({
92
+ ...state,
93
+ tours: {
94
+ dashboard: {
95
+ step: 0,
96
+ completed: false,
97
+ },
98
+ editor: {
99
+ step: 0,
100
+ completed: false,
101
+ },
102
+ },
103
+ }),
104
+ }),
105
+ createMigration<AppConfigV5, AppConfigV6>({
106
+ version: "5.0.0" as SemVer,
107
+ up: (state) => ({
108
+ ...state,
109
+ autosave: true,
110
+ }),
111
+ }),
112
+ createMigration<AppConfigV6, AppConfigV7>({
113
+ version: "6.0.0" as SemVer,
114
+ up: (state) => {
115
+ // Upgrades V6 to V7: Add agents, add buildHistory.
116
+ // (Additive only - keeping cacheFolder and clearTemporaryFoldersOnPipelineEnd)
117
+ return {
118
+ ...state,
119
+ agents: [],
120
+ buildHistory: {
121
+ retentionPolicy: {
122
+ enabled: false,
123
+ maxEntries: 50,
124
+ maxAge: 30,
125
+ },
126
+ },
127
+ };
128
+ },
129
+ }),
130
+ createMigration<AppConfigV7, never>({
131
+ version: "7.0.0" as SemVer,
132
+ up: finalVersion,
133
+ }),
134
+ ],
135
+ });
136
+
137
+ // --- Projects Migrator ---
138
+
139
+ const fileRepoMigratorInternal = createMigrator<FileRepoV1, FileRepo>();
140
+
141
+ export const defaultFileRepo = fileRepoMigratorInternal.createDefault({
142
+ version: "2.0.0",
143
+ projects: [
144
+ {
145
+ id: "main",
146
+ name: "Default project",
147
+ description: "The initial default project",
148
+ },
149
+ ],
150
+ pipelines: [],
151
+ });
152
+
153
+ export const fileRepoMigrations = fileRepoMigratorInternal.createMigrations({
154
+ defaultValue: defaultFileRepo,
155
+ migrations: [
156
+ createMigration<FileRepoV1, FileRepoV2>({
157
+ version: "1.0.0",
158
+ up: (state) => {
159
+ const pipelines: FileRepoV2["pipelines"] = Object.entries(state.data || {}).map(
160
+ ([id, file]) => {
161
+ return {
162
+ ...file,
163
+ id,
164
+ project: "main",
165
+ };
166
+ },
167
+ );
168
+ return {
169
+ ...state,
170
+ projects: [
171
+ {
172
+ id: "main",
173
+ name: "Default project",
174
+ description: "The initial default project",
175
+ },
176
+ ],
177
+ pipelines: pipelines,
178
+ };
179
+ },
180
+ }),
181
+ createMigration<FileRepoV2, never>({
182
+ version: "2.0.0",
183
+ up: finalVersion,
184
+ }),
185
+ ],
186
+ });
187
+
188
+ // --- Saved File Migrator ---
189
+
190
+ const savedFileMigratorInternal = createMigrator<SavedFileV1, SavedFile>();
191
+ const savedFileDefaultValue = savedFileMigratorInternal.createDefault({
192
+ canvas: {
193
+ triggers: [],
194
+ blocks: [],
195
+ },
196
+ description: "",
197
+ name: "",
198
+ variables: [],
199
+ type: "default",
200
+ version: "4.0.0",
201
+ });
202
+
203
+ export const savedFileMigrator = savedFileMigratorInternal.createMigrations({
204
+ defaultValue: savedFileDefaultValue,
205
+ migrations: [
206
+ createMigration<SavedFileV1, SavedFileV2>({
207
+ version: "1.0.0" as SemVer,
208
+ up: (state) => {
209
+ const blocks = state.canvas.blocks;
210
+
211
+ const triggers: SavedFileV2["canvas"]["triggers"] = [];
212
+ const newBlocks: SavedFileV2["canvas"]["blocks"] = [];
213
+
214
+ for (const block of blocks) {
215
+ if (block.type === "event") {
216
+ triggers.push(block);
217
+ } else {
218
+ newBlocks.push(block);
219
+ }
220
+ }
221
+
222
+ return {
223
+ ...state,
224
+ canvas: {
225
+ ...state.canvas,
226
+ blocks: newBlocks,
227
+ triggers: triggers,
228
+ },
229
+ };
230
+ },
231
+ }),
232
+ createMigration<SavedFileV2, SavedFileV3>({
233
+ version: "2.0.0" as SemVer,
234
+ up: (state) => {
235
+ const { canvas } = state;
236
+ const { blocks, triggers } = canvas;
237
+
238
+ const newBlocks: SavedFileV3["canvas"]["blocks"] = [];
239
+
240
+ for (const block of blocks) {
241
+ const newParams: SavedFileV3["canvas"]["blocks"][number]["params"] = {};
242
+
243
+ for (const data of Object.entries(block.params)) {
244
+ if (data === undefined) {
245
+ throw new Error("Can't migrate block with undefined params");
246
+ } else {
247
+ const [key, value] = data;
248
+ newParams[key] = {
249
+ editor: "editor",
250
+ value,
251
+ };
252
+ }
253
+ }
254
+
255
+ newBlocks.push({
256
+ ...block,
257
+ params: newParams,
258
+ });
259
+ }
260
+
261
+ return {
262
+ ...state,
263
+ canvas: {
264
+ ...canvas,
265
+ triggers,
266
+ blocks: newBlocks,
267
+ },
268
+ };
269
+ },
270
+ }),
271
+ createMigration<SavedFileV3, SavedFileV4>({
272
+ version: "3.0.0" as SemVer,
273
+ up: (state) => ({
274
+ ...state,
275
+ type: "default",
276
+ }),
277
+ }),
278
+ createMigration<SavedFileV4, never>({
279
+ version: "4.0.0" as SemVer,
280
+ up: finalVersion,
281
+ }),
282
+ ],
283
+ });
284
+
285
+ // --- Registry ---
286
+
287
+ export const configRegistry: Record<string, Migrator<any>> = {
288
+ settings: appSettingsMigrator,
289
+ projects: fileRepoMigrations,
290
+ pipeline: savedFileMigrator,
291
+ };
@@ -0,0 +1,25 @@
1
+ import { SaveLocationValidator } from "../save-location";
2
+ import { object, string, optional, record, InferInput, literal, array } from "valibot";
3
+
4
+ export const FileRepoValidatorV1 = object({
5
+ version: literal("1.0.0"),
6
+ data: optional(record(string(), SaveLocationValidator), {}),
7
+ });
8
+
9
+ export const FileRepoProjectValidatorV2 = object({
10
+ id: string(),
11
+ name: string(),
12
+ description: string(),
13
+ });
14
+
15
+ export const FileRepoValidatorV2 = object({
16
+ version: literal("2.0.0"),
17
+ projects: array(FileRepoProjectValidatorV2),
18
+ pipelines: optional(array(SaveLocationValidator), []),
19
+ });
20
+
21
+ export type FileRepoV1 = InferInput<typeof FileRepoValidatorV1>;
22
+ export type FileRepoV2 = InferInput<typeof FileRepoValidatorV2>;
23
+
24
+ export const FileRepoValidator = FileRepoValidatorV2;
25
+ export type FileRepo = InferInput<typeof FileRepoValidator>;