@pipelab/shared 2.0.1-beta.20

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/.turbo/turbo-build.log +41 -0
  2. package/.turbo/turbo-lint.log +136 -0
  3. package/CHANGELOG.md +167 -0
  4. package/LICENSE +110 -0
  5. package/LICENSE.md +110 -0
  6. package/README.md +3 -0
  7. package/dist/index.d.mts +3603 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.mjs +43763 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/package.json +53 -0
  12. package/src/apis.ts +191 -0
  13. package/src/build-history.ts +127 -0
  14. package/src/config/migrators.ts +308 -0
  15. package/src/config/projects-definition.ts +25 -0
  16. package/src/config/projects-types.ts +25 -0
  17. package/src/config/projects.ts +1 -0
  18. package/src/config/settings-definition.ts +21 -0
  19. package/src/config/settings.ts +21 -0
  20. package/src/config.schema.ts +149 -0
  21. package/src/database.types.ts +95 -0
  22. package/src/errors.ts +11 -0
  23. package/src/evaluator.ts +58 -0
  24. package/src/fmt.ts +5 -0
  25. package/src/graph.ts +207 -0
  26. package/src/i18n/de_DE.json +86 -0
  27. package/src/i18n/en_US.json +147 -0
  28. package/src/i18n/es_ES.json +86 -0
  29. package/src/i18n/fr_FR.json +89 -0
  30. package/src/i18n/pt_BR.json +86 -0
  31. package/src/i18n/zh_CN.json +86 -0
  32. package/src/i18n-utils.ts +10 -0
  33. package/src/index.ts +51 -0
  34. package/src/ipc.types.ts +73 -0
  35. package/src/logger.ts +20 -0
  36. package/src/migrations/model.ts +1 -0
  37. package/src/migrations/projects.ts +1 -0
  38. package/src/migrations/settings.ts +1 -0
  39. package/src/model.test.ts +214 -0
  40. package/src/model.ts +233 -0
  41. package/src/plugins/definitions.ts +472 -0
  42. package/src/plugins.ts +20 -0
  43. package/src/quickjs.ts +98 -0
  44. package/src/save-location.ts +42 -0
  45. package/src/subscription-errors.ts +87 -0
  46. package/src/supabase.ts +28 -0
  47. package/src/tests/helpers.ts +5 -0
  48. package/src/types.ts +3 -0
  49. package/src/utils.ts +3 -0
  50. package/src/validation.ts +16 -0
  51. package/src/variables.ts +27 -0
  52. package/src/wasm.d.ts +9 -0
  53. package/src/websocket.types.ts +186 -0
  54. package/tsconfig.json +13 -0
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@pipelab/shared",
3
+ "version": "2.0.1-beta.20",
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": "1.0.1-beta.19"
41
+ },
42
+ "devDependencies": {
43
+ "esbuild-plugin-wasm": "1.1.0",
44
+ "tsdown": "0.21.2",
45
+ "typescript": "5.9.3",
46
+ "@pipelab/tsconfig": "1.0.1-beta.19"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "format": "oxfmt .",
51
+ "lint": "oxlint ."
52
+ }
53
+ }
package/src/apis.ts ADDED
@@ -0,0 +1,191 @@
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:get-storage-info": [
135
+ void,
136
+ EndEvent<{
137
+ totalEntries: number;
138
+ totalSize: number;
139
+ oldestEntry?: number;
140
+ newestEntry?: number;
141
+ }>,
142
+ ];
143
+ "build-history:configure": [
144
+ { config: Partial<BuildHistoryConfig> },
145
+ EndEvent<{ result: "ok" | "ko" }>,
146
+ ];
147
+ "agents:get": [void, EndEvent<{ agents: Agent[] }>];
148
+ "graph:execute": [
149
+ {
150
+ graph: any[];
151
+ variables: any[];
152
+ pipelineId?: string;
153
+ projectId?: string;
154
+ projectName?: string;
155
+ projectPath?: string;
156
+ },
157
+ (
158
+ | { type: "node-enter"; data: { nodeUid: string; nodeName: string } }
159
+ | { type: "node-exit"; data: { nodeUid: string; nodeName: string } }
160
+ | { type: "node-log"; data: { nodeUid: string; logData: any } }
161
+ | EndEvent<{ result: any; buildId: string }>
162
+ ),
163
+ ];
164
+ "auth:getUser": [void, EndEvent<{ user: User | null }>];
165
+ "auth:signInWithPassword": [{ email: string; password: string }, EndEvent<UserResponse>];
166
+ "auth:signUp": [{ email: string; password: string }, EndEvent<UserResponse>];
167
+ "auth:signOut": [void, EndEvent<void>];
168
+ "auth:resetPasswordForEmail": [{ email: string }, EndEvent<{ error: any | null }>];
169
+ "auth:invoke": [
170
+ { name: string; options?: any },
171
+ EndEvent<{ data: any | null; error: any | null }>,
172
+ ];
173
+ "agent:version:get": [void, EndEvent<{ version: string }>];
174
+ };
175
+
176
+ export type Channels = keyof IpcDefinition;
177
+
178
+ export const ShellChannels: Channels[] = ["dialog:showOpenDialog", "dialog:showSaveDialog"];
179
+
180
+ export type Data<KEY extends Channels> = IpcDefinition[KEY][0];
181
+ export type Events<KEY extends Channels> = IpcDefinition[KEY][1];
182
+ export type End<KEY extends Channels> = Extract<IpcDefinition[KEY][1], { type: "end" }>["data"];
183
+
184
+ export type IpcMessage = {
185
+ // the channel to communicate
186
+ requestId: RequestId;
187
+ data: any;
188
+ };
189
+ export type RequestId = Tagged<string, "request-id">;
190
+
191
+ // type Output = End<'fs:openFolder'>
@@ -0,0 +1,127 @@
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
+ cachePath: string;
80
+ userDataPath: string;
81
+ retentionPolicy: {
82
+ enabled: boolean;
83
+ maxEntries: number;
84
+ maxAge: number;
85
+ };
86
+ }>;
87
+ }
88
+
89
+ // Retention policy configuration
90
+ export interface RetentionPolicy {
91
+ enabled: boolean;
92
+ maxEntries: number;
93
+ maxAge: number; // in milliseconds
94
+ maxSize: number; // in bytes
95
+ keepFailedBuilds: boolean;
96
+ keepSuccessfulBuilds: boolean;
97
+ }
98
+
99
+ // Storage configuration
100
+ export interface BuildHistoryConfig {
101
+ storagePath: string;
102
+ indexFileName: string;
103
+ entryFilePrefix: string;
104
+ retentionPolicy: RetentionPolicy;
105
+ }
106
+
107
+ // Authorization and subscription types
108
+ export interface SubscriptionBenefit {
109
+ id: string;
110
+ name: string;
111
+ description?: string;
112
+ }
113
+
114
+ export interface SubscriptionError extends Error {
115
+ code: "SUBSCRIPTION_REQUIRED" | "SUBSCRIPTION_EXPIRED" | "BENEFIT_NOT_FOUND" | "UNAUTHORIZED";
116
+ benefit?: string;
117
+ userMessage: string;
118
+ }
119
+
120
+ export interface AuthorizationContext {
121
+ userId?: string;
122
+ hasBenefit: (benefitId: string) => boolean;
123
+ isPaidUser: boolean;
124
+ }
125
+
126
+ // Authorization check function type
127
+ export type AuthorizationCheck = (context: AuthorizationContext) => void;
@@ -0,0 +1,308 @@
1
+ import {
2
+ createMigration,
3
+ createMigrator,
4
+ finalVersion,
5
+ initialVersion,
6
+ OmitVersion,
7
+ SemVer,
8
+ } from "@pipelab/migration";
9
+ import {
10
+ AppConfig,
11
+ AppConfigV1,
12
+ AppConfigV2,
13
+ AppConfigV3,
14
+ AppConfigV4,
15
+ AppConfigV5,
16
+ AppConfigV6,
17
+ AppConfigV7,
18
+ } from "../config.schema";
19
+ import { FileRepoV1, FileRepoV2, FileRepo } from "./projects-types";
20
+ import { SavedFileV1, SavedFileV2, SavedFileV3, SavedFileV4, SavedFile } from "../model";
21
+
22
+ // --- Types ---
23
+
24
+ export interface Migrator<T> {
25
+ migrate: (data: any, options?: any) => Promise<T>;
26
+ defaultValue: T;
27
+ }
28
+
29
+ // --- Settings Migrator ---
30
+
31
+ const settingsMigratorInternal = createMigrator<AppConfigV1, AppConfig>();
32
+
33
+ export const defaultAppSettings = settingsMigratorInternal.createDefault({
34
+ cacheFolder: "",
35
+ clearTemporaryFoldersOnPipelineEnd: false,
36
+ locale: "en-US",
37
+ theme: "light",
38
+ version: "7.0.0" as SemVer,
39
+ autosave: true,
40
+ agents: [],
41
+ tours: {
42
+ dashboard: {
43
+ step: 0,
44
+ completed: false,
45
+ },
46
+ editor: {
47
+ step: 0,
48
+ completed: false,
49
+ },
50
+ },
51
+ });
52
+
53
+ export const appSettingsMigrator = settingsMigratorInternal.createMigrations({
54
+ defaultValue: defaultAppSettings,
55
+ migrations: [
56
+ createMigration<never, AppConfigV1, AppConfigV2>({
57
+ version: "1.0.0" as SemVer,
58
+ up: (state) => state satisfies OmitVersion<AppConfigV2>,
59
+ down: initialVersion,
60
+ }),
61
+ createMigration<AppConfigV1, AppConfigV2, AppConfigV3>({
62
+ version: "2.0.0" as SemVer,
63
+ up: (state) => {
64
+ return {
65
+ ...state,
66
+ clearTemporaryFoldersOnPipelineEnd: false,
67
+ } satisfies OmitVersion<AppConfigV3>;
68
+ },
69
+ down: () => {
70
+ throw new Error("Can't migrate down from 2.0.0");
71
+ },
72
+ }),
73
+ createMigration<AppConfigV2, AppConfigV3, AppConfigV4>({
74
+ version: "3.0.0" as SemVer,
75
+ up: (state) => ({
76
+ ...state,
77
+ locale: "en-US" as const,
78
+ }),
79
+ down: (state) => {
80
+ const { locale, ...rest } = state as AppConfigV4;
81
+ return rest as unknown as AppConfigV3;
82
+ },
83
+ }),
84
+ createMigration<AppConfigV3, AppConfigV4, AppConfigV5>({
85
+ version: "4.0.0" as SemVer,
86
+ up: (state) => ({
87
+ ...state,
88
+ tours: {
89
+ dashboard: {
90
+ step: 0,
91
+ completed: false,
92
+ },
93
+ editor: {
94
+ step: 0,
95
+ completed: false,
96
+ },
97
+ },
98
+ }),
99
+ down: (state) => {
100
+ const { tours, ...rest } = state as AppConfigV5;
101
+ return rest as unknown as AppConfigV4;
102
+ },
103
+ }),
104
+ createMigration<AppConfigV4, AppConfigV5, AppConfigV6>({
105
+ version: "5.0.0" as SemVer,
106
+ up: (state) => ({
107
+ ...state,
108
+ autosave: true,
109
+ }),
110
+ down: (state) => {
111
+ const { autosave, ...rest } = state as AppConfigV6;
112
+ return rest as unknown as AppConfigV5;
113
+ },
114
+ }),
115
+ createMigration<AppConfigV5, AppConfigV6, AppConfigV7>({
116
+ version: "6.0.0" as SemVer,
117
+ up: (state) => ({
118
+ ...state,
119
+ agents: [],
120
+ }),
121
+ down: (state) => {
122
+ const { agents, ...rest } = state as AppConfigV7;
123
+ return rest as unknown as AppConfigV6;
124
+ },
125
+ }),
126
+ createMigration<AppConfigV6, AppConfigV7, never>({
127
+ version: "7.0.0" as SemVer,
128
+ up: finalVersion,
129
+ down: () => {
130
+ throw new Error("Can't migrate down from 7.0.0");
131
+ },
132
+ }),
133
+ ],
134
+ });
135
+
136
+ // --- Projects Migrator ---
137
+
138
+ const fileRepoMigratorInternal = createMigrator<FileRepoV1, FileRepo>();
139
+
140
+ export const defaultFileRepo = fileRepoMigratorInternal.createDefault({
141
+ version: "2.0.0",
142
+ projects: [
143
+ {
144
+ id: "main",
145
+ name: "Default project",
146
+ description: "The initial default project",
147
+ },
148
+ ],
149
+ pipelines: [],
150
+ proxies: [],
151
+ });
152
+
153
+ export const fileRepoMigrations = fileRepoMigratorInternal.createMigrations({
154
+ defaultValue: defaultFileRepo,
155
+ migrations: [
156
+ createMigration<never, 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
+ version: "2.0.0",
170
+ projects: [
171
+ {
172
+ id: "main",
173
+ name: "Default project",
174
+ description: "The initial default project",
175
+ },
176
+ ],
177
+ pipelines: pipelines,
178
+ proxies: [],
179
+ } as any;
180
+ },
181
+ down: initialVersion,
182
+ }),
183
+ createMigration<FileRepoV1, FileRepoV2, never>({
184
+ version: "2.0.0",
185
+ up: finalVersion,
186
+ down: (state) => {
187
+ throw new Error("Cannot downgrade to version 1.0.0");
188
+ },
189
+ }),
190
+ ],
191
+ });
192
+
193
+ // --- Saved File Migrator ---
194
+
195
+ const savedFileMigratorInternal = createMigrator<SavedFileV1, SavedFile>();
196
+ const savedFileDefaultValue = savedFileMigratorInternal.createDefault({
197
+ canvas: {
198
+ triggers: [],
199
+ blocks: [],
200
+ },
201
+ description: "",
202
+ name: "",
203
+ variables: [],
204
+ type: "default",
205
+ version: "4.0.0" as SemVer,
206
+ });
207
+
208
+ export const savedFileMigrator = savedFileMigratorInternal.createMigrations({
209
+ defaultValue: savedFileDefaultValue,
210
+ migrations: [
211
+ createMigration<never, SavedFileV1, SavedFileV2>({
212
+ version: "1.0.0" as SemVer,
213
+ up: (state) => {
214
+ const blocks = state.canvas.blocks;
215
+
216
+ const triggers: any[] = [];
217
+ const newBlocks: any[] = [];
218
+
219
+ for (const block of blocks) {
220
+ if (block.type === "event") {
221
+ triggers.push(block);
222
+ } else {
223
+ newBlocks.push(block);
224
+ }
225
+ }
226
+
227
+ return {
228
+ canvas: {
229
+ blocks: newBlocks,
230
+ triggers: triggers,
231
+ },
232
+ description: state.description,
233
+ name: state.name,
234
+ variables: state.variables,
235
+ } as any;
236
+ },
237
+ down: initialVersion,
238
+ }),
239
+ createMigration<SavedFileV1, SavedFileV2, SavedFileV3>({
240
+ version: "2.0.0" as SemVer,
241
+ up: (state) => {
242
+ const { canvas, ...rest } = state;
243
+ const { blocks, triggers } = canvas;
244
+
245
+ const newBlocks: any[] = [];
246
+
247
+ for (const block of blocks) {
248
+ const newParams: any = {};
249
+
250
+ for (const data of Object.entries(block.params)) {
251
+ if (data === undefined) {
252
+ throw new Error("Can't migrate block with undefined params");
253
+ } else {
254
+ const [key, value] = data;
255
+ newParams[key] = {
256
+ editor: "editor",
257
+ value,
258
+ };
259
+ }
260
+ }
261
+
262
+ newBlocks.push({
263
+ ...block,
264
+ params: newParams,
265
+ });
266
+ }
267
+
268
+ return {
269
+ ...rest,
270
+ canvas: {
271
+ triggers,
272
+ blocks: newBlocks,
273
+ },
274
+ } as any;
275
+ },
276
+ down: () => {
277
+ throw new Error("Migration down not implemented");
278
+ },
279
+ }),
280
+ createMigration<SavedFileV2, SavedFileV3, SavedFileV4>({
281
+ version: "3.0.0" as SemVer,
282
+ up: (state) => {
283
+ return {
284
+ ...state,
285
+ type: "default",
286
+ } as any;
287
+ },
288
+ down: () => {
289
+ throw new Error("Migration down not implemented");
290
+ },
291
+ }),
292
+ createMigration<SavedFileV3, SavedFileV4, SavedFileV4>({
293
+ version: "4.0.0" as SemVer,
294
+ up: finalVersion,
295
+ down: () => {
296
+ throw new Error("Migration down not implemented");
297
+ },
298
+ }),
299
+ ],
300
+ });
301
+
302
+ // --- Registry ---
303
+
304
+ export const configRegistry: Record<string, Migrator<any>> = {
305
+ settings: appSettingsMigrator,
306
+ projects: fileRepoMigrations,
307
+ pipeline: savedFileMigrator,
308
+ };
@@ -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>;