@uploadista/server 0.0.20-beta.6 → 0.0.20-beta.8
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/README.md +16 -16
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +188 -188
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +188 -188
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
- package/src/adapter/types.ts +3 -3
- package/src/core/http-handlers/flow-http-handlers.ts +14 -14
- package/src/core/http-handlers/upload-http-handlers.ts +16 -9
- package/src/core/plugin-types.ts +10 -10
- package/src/core/routes.ts +4 -1
- package/src/core/server.ts +7 -9
- package/src/core/websocket-handlers/flow-websocket-handlers.ts +5 -5
- package/src/core/websocket-handlers/upload-websocket-handlers.ts +5 -5
- package/src/core/websocket-handlers/websocket-handlers.ts +10 -10
- package/src/error-types.ts +1 -1
- package/src/layer-utils.ts +24 -24
- package/src/permissions/errors.ts +3 -1
- package/src/permissions/index.ts +2 -2
- package/src/permissions/types.ts +3 -12
- package/src/plugins-typing.ts +15 -13
- package/src/service.ts +9 -4
- package/src/usage-hooks/index.ts +1 -1
- package/src/usage-hooks/service.ts +51 -59
- package/src/usage-hooks/types.ts +2 -4
package/src/layer-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FlowProvider } from "@uploadista/core/flow";
|
|
2
|
-
import {
|
|
2
|
+
import { flowEngine } from "@uploadista/core/flow";
|
|
3
3
|
import {
|
|
4
4
|
type BaseEventEmitterService,
|
|
5
5
|
type BaseKvStoreService,
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
uploadEventEmitter,
|
|
12
12
|
uploadFileKvStore,
|
|
13
13
|
} from "@uploadista/core/types";
|
|
14
|
-
import { type
|
|
14
|
+
import { type UploadEngine, uploadEngine } from "@uploadista/core/upload";
|
|
15
15
|
import type { GenerateId } from "@uploadista/core/utils";
|
|
16
16
|
import { Layer } from "effect";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Configuration for creating upload
|
|
20
|
-
* Specifies all dependencies needed by the upload
|
|
19
|
+
* Configuration for creating upload engine layers.
|
|
20
|
+
* Specifies all dependencies needed by the upload engine Effect Layer.
|
|
21
21
|
*
|
|
22
22
|
* @property kvStore - Key-value store for upload metadata
|
|
23
23
|
* @property eventEmitter - Event emitter for upload progress events
|
|
@@ -27,16 +27,16 @@ import { Layer } from "effect";
|
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* ```typescript
|
|
30
|
-
* import {
|
|
30
|
+
* import { createUploadEngineLayer } from "@uploadista/server";
|
|
31
31
|
*
|
|
32
|
-
* const uploadLayerConfig:
|
|
32
|
+
* const uploadLayerConfig: UploadEngineLayerConfig = {
|
|
33
33
|
* kvStore: redisKvStore,
|
|
34
34
|
* eventEmitter: webSocketEventEmitter,
|
|
35
35
|
* dataStore: s3DataStore,
|
|
36
36
|
* };
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
export interface
|
|
39
|
+
export interface UploadEngineLayerConfig {
|
|
40
40
|
kvStore: Layer.Layer<BaseKvStoreService>;
|
|
41
41
|
eventEmitter: Layer.Layer<BaseEventEmitterService>;
|
|
42
42
|
dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;
|
|
@@ -55,7 +55,7 @@ export interface UploadServerLayerConfig {
|
|
|
55
55
|
* @property kvStore - Key-value store for flow job metadata
|
|
56
56
|
* @property eventEmitter - Event emitter for flow progress events
|
|
57
57
|
* @property flowProvider - Factory function for creating flows
|
|
58
|
-
* @property
|
|
58
|
+
* @property uploadEngine - Upload engine layer (used by flows for uploads)
|
|
59
59
|
*
|
|
60
60
|
* @example
|
|
61
61
|
* ```typescript
|
|
@@ -65,7 +65,7 @@ export interface UploadServerLayerConfig {
|
|
|
65
65
|
* kvStore: redisKvStore,
|
|
66
66
|
* eventEmitter: webSocketEventEmitter,
|
|
67
67
|
* flowProvider: createFlowsEffect,
|
|
68
|
-
*
|
|
68
|
+
* uploadEngine: uploadEngineLayer,
|
|
69
69
|
* };
|
|
70
70
|
* ```
|
|
71
71
|
*/
|
|
@@ -73,7 +73,7 @@ export interface FlowServerLayerConfig {
|
|
|
73
73
|
kvStore: Layer.Layer<BaseKvStoreService>;
|
|
74
74
|
eventEmitter: Layer.Layer<BaseEventEmitterService>;
|
|
75
75
|
flowProvider: Layer.Layer<FlowProvider>;
|
|
76
|
-
|
|
76
|
+
uploadEngine: Layer.Layer<UploadEngine>;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -88,30 +88,30 @@ export interface FlowServerLayerConfig {
|
|
|
88
88
|
* - Optional custom ID generator
|
|
89
89
|
*
|
|
90
90
|
* @param config - Upload server layer configuration
|
|
91
|
-
* @returns Effect Layer providing
|
|
91
|
+
* @returns Effect Layer providing UploadEngine
|
|
92
92
|
*
|
|
93
93
|
* @example
|
|
94
94
|
* ```typescript
|
|
95
|
-
* import {
|
|
95
|
+
* import { createUploadEngineLayer } from "@uploadista/server";
|
|
96
96
|
* import { Layer } from "effect";
|
|
97
97
|
*
|
|
98
|
-
* const
|
|
98
|
+
* const uploadEngineLayer = createUploadEngineLayer({
|
|
99
99
|
* kvStore: redisKvStore,
|
|
100
100
|
* eventEmitter: webSocketEventEmitter,
|
|
101
101
|
* dataStore: s3DataStore,
|
|
102
102
|
* });
|
|
103
103
|
*
|
|
104
104
|
* // Use in application
|
|
105
|
-
* const app = Layer.provide(appLogic,
|
|
105
|
+
* const app = Layer.provide(appLogic, uploadEngineLayer);
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
|
-
export const
|
|
108
|
+
export const createUploadEngineLayer = ({
|
|
109
109
|
kvStore,
|
|
110
110
|
eventEmitter,
|
|
111
111
|
dataStore,
|
|
112
112
|
bufferedDataStore,
|
|
113
113
|
generateId,
|
|
114
|
-
}:
|
|
114
|
+
}: UploadEngineLayerConfig) => {
|
|
115
115
|
// Set up upload server dependencies
|
|
116
116
|
const uploadFileKVStoreLayer = Layer.provide(uploadFileKvStore, kvStore);
|
|
117
117
|
const uploadDataStoreLayer = Layer.provide(dataStore, uploadFileKVStoreLayer);
|
|
@@ -123,7 +123,7 @@ export const createUploadServerLayer = ({
|
|
|
123
123
|
eventEmitter,
|
|
124
124
|
);
|
|
125
125
|
|
|
126
|
-
const
|
|
126
|
+
const uploadEngineLayers = Layer.mergeAll(
|
|
127
127
|
uploadDataStoreLayer,
|
|
128
128
|
uploadFileKVStoreLayer,
|
|
129
129
|
uploadEventEmitterLayer,
|
|
@@ -131,7 +131,7 @@ export const createUploadServerLayer = ({
|
|
|
131
131
|
uploadBufferedDataStoreLayer,
|
|
132
132
|
);
|
|
133
133
|
|
|
134
|
-
return Layer.provide(
|
|
134
|
+
return Layer.provide(uploadEngine, uploadEngineLayers);
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
/**
|
|
@@ -156,29 +156,29 @@ export const createUploadServerLayer = ({
|
|
|
156
156
|
* kvStore: redisKvStore,
|
|
157
157
|
* eventEmitter: webSocketEventEmitter,
|
|
158
158
|
* flowProvider: createFlowsEffect,
|
|
159
|
-
*
|
|
159
|
+
* uploadEngine: uploadEngineLayer,
|
|
160
160
|
* });
|
|
161
161
|
*
|
|
162
162
|
* // Use in application
|
|
163
163
|
* const app = Layer.provide(appLogic, flowServerLayer);
|
|
164
164
|
* ```
|
|
165
165
|
*/
|
|
166
|
-
export const
|
|
166
|
+
export const createFlowEngineLayer = ({
|
|
167
167
|
kvStore,
|
|
168
168
|
eventEmitter,
|
|
169
169
|
flowProvider,
|
|
170
|
-
|
|
170
|
+
uploadEngine,
|
|
171
171
|
}: FlowServerLayerConfig) => {
|
|
172
172
|
// Set up flow server dependencies
|
|
173
173
|
const flowJobKVStoreLayer = Layer.provide(flowJobKvStore, kvStore);
|
|
174
174
|
const flowEventEmitterLayer = Layer.provide(flowEventEmitter, eventEmitter);
|
|
175
175
|
|
|
176
|
-
const
|
|
176
|
+
const flowEngineLayers = Layer.mergeAll(
|
|
177
177
|
flowProvider,
|
|
178
178
|
flowEventEmitterLayer,
|
|
179
179
|
flowJobKVStoreLayer,
|
|
180
|
-
|
|
180
|
+
uploadEngine,
|
|
181
181
|
);
|
|
182
182
|
|
|
183
|
-
return Layer.provide(
|
|
183
|
+
return Layer.provide(flowEngine, flowEngineLayers);
|
|
184
184
|
};
|
|
@@ -64,7 +64,9 @@ export class AuthenticationRequiredError extends AdapterError {
|
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
66
|
export class OrganizationMismatchError extends AdapterError {
|
|
67
|
-
constructor(
|
|
67
|
+
constructor(
|
|
68
|
+
message = "Access denied: resource belongs to another organization",
|
|
69
|
+
) {
|
|
68
70
|
super(message, 403, "ORGANIZATION_MISMATCH");
|
|
69
71
|
this.name = "OrganizationMismatchError";
|
|
70
72
|
}
|
package/src/permissions/index.ts
CHANGED
package/src/permissions/types.ts
CHANGED
|
@@ -121,22 +121,13 @@ export const PERMISSION_SETS = {
|
|
|
121
121
|
ADMIN: [ENGINE_PERMISSIONS.ALL] as const,
|
|
122
122
|
|
|
123
123
|
/** Organization owner - all flow and upload permissions */
|
|
124
|
-
ORGANIZATION_OWNER: [
|
|
125
|
-
FLOW_PERMISSIONS.ALL,
|
|
126
|
-
UPLOAD_PERMISSIONS.ALL,
|
|
127
|
-
] as const,
|
|
124
|
+
ORGANIZATION_OWNER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,
|
|
128
125
|
|
|
129
126
|
/** Organization member - same as owner for now */
|
|
130
|
-
ORGANIZATION_MEMBER: [
|
|
131
|
-
FLOW_PERMISSIONS.ALL,
|
|
132
|
-
UPLOAD_PERMISSIONS.ALL,
|
|
133
|
-
] as const,
|
|
127
|
+
ORGANIZATION_MEMBER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,
|
|
134
128
|
|
|
135
129
|
/** API key - limited to execute flows and create uploads */
|
|
136
|
-
API_KEY: [
|
|
137
|
-
FLOW_PERMISSIONS.EXECUTE,
|
|
138
|
-
UPLOAD_PERMISSIONS.CREATE,
|
|
139
|
-
] as const,
|
|
130
|
+
API_KEY: [FLOW_PERMISSIONS.EXECUTE, UPLOAD_PERMISSIONS.CREATE] as const,
|
|
140
131
|
} as const;
|
|
141
132
|
|
|
142
133
|
/**
|
package/src/plugins-typing.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @module plugins-typing
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { Flow,
|
|
17
|
+
import type { Flow, UploadEngine } from "@uploadista/core";
|
|
18
18
|
import type { ExtractLayerServices } from "@uploadista/core/flow";
|
|
19
19
|
import type { Effect, Layer } from "effect";
|
|
20
20
|
import type z from "zod";
|
|
@@ -50,14 +50,15 @@ export type FlowSuccess<
|
|
|
50
50
|
flowId: string,
|
|
51
51
|
clientId: string | null,
|
|
52
52
|
) => Effect.Effect<unknown, unknown, unknown>,
|
|
53
|
-
> =
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
> =
|
|
54
|
+
ReturnType<TFlows> extends Effect.Effect<infer Success, unknown, unknown>
|
|
55
|
+
? Success
|
|
56
|
+
: never;
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* @deprecated Use `ExtractFlowPluginRequirements` from `@uploadista/server/core/plugin-types` instead.
|
|
59
60
|
*
|
|
60
|
-
* Extracts plugin requirements from a flow function, excluding
|
|
61
|
+
* Extracts plugin requirements from a flow function, excluding UploadEngine.
|
|
61
62
|
*
|
|
62
63
|
* @example Migration
|
|
63
64
|
* ```typescript
|
|
@@ -75,13 +76,14 @@ export type FlowRequirementsOf<
|
|
|
75
76
|
flowId: string,
|
|
76
77
|
clientId: string | null,
|
|
77
78
|
) => Effect.Effect<unknown, unknown, unknown>,
|
|
78
|
-
> =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
> =
|
|
80
|
+
FlowSuccess<TFlows> extends Flow<
|
|
81
|
+
z.ZodSchema<unknown>,
|
|
82
|
+
z.ZodSchema<unknown>,
|
|
83
|
+
infer R
|
|
84
|
+
>
|
|
85
|
+
? Exclude<R, UploadEngine>
|
|
86
|
+
: never;
|
|
85
87
|
|
|
86
88
|
/**
|
|
87
89
|
* @deprecated Use `ExtractFlowPluginRequirements` from `@uploadista/server/core/plugin-types` instead.
|
|
@@ -104,7 +106,7 @@ export type RequiredPluginsOf<
|
|
|
104
106
|
flowId: string,
|
|
105
107
|
clientId: string | null,
|
|
106
108
|
) => Effect.Effect<unknown, unknown, unknown>,
|
|
107
|
-
> = Exclude<FlowRequirementsOf<TFlows>,
|
|
109
|
+
> = Exclude<FlowRequirementsOf<TFlows>, UploadEngine>;
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
112
|
* @deprecated Use `ValidatePlugins` from `@uploadista/server/core/plugin-types` instead.
|
package/src/service.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import type { AuthContext } from "./types";
|
|
3
2
|
import {
|
|
4
|
-
|
|
3
|
+
AuthenticationRequiredError,
|
|
4
|
+
AuthorizationError,
|
|
5
|
+
} from "./permissions/errors";
|
|
6
|
+
import {
|
|
5
7
|
hasAnyPermission as matchHasAnyPermission,
|
|
8
|
+
hasPermission as matchHasPermission,
|
|
6
9
|
} from "./permissions/matcher";
|
|
7
|
-
import {
|
|
10
|
+
import type { AuthContext } from "./types";
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Authentication Context Service
|
|
@@ -154,7 +157,9 @@ export const AuthContextServiceLive = (
|
|
|
154
157
|
hasAnyPermission: (requiredPermissions: readonly string[]) =>
|
|
155
158
|
bypassAuth
|
|
156
159
|
? Effect.succeed(true)
|
|
157
|
-
: Effect.succeed(
|
|
160
|
+
: Effect.succeed(
|
|
161
|
+
matchHasAnyPermission(permissions, requiredPermissions),
|
|
162
|
+
),
|
|
158
163
|
|
|
159
164
|
requirePermission: (permission: string) =>
|
|
160
165
|
Effect.gen(function* () {
|
package/src/usage-hooks/index.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import { Context, Effect, Layer } from "effect";
|
|
8
8
|
import type {
|
|
9
|
+
FlowUsageContext,
|
|
10
|
+
UploadUsageContext,
|
|
9
11
|
UsageHookConfig,
|
|
10
12
|
UsageHookResult,
|
|
11
|
-
UploadUsageContext,
|
|
12
|
-
FlowUsageContext,
|
|
13
13
|
} from "./types";
|
|
14
|
-
import {
|
|
14
|
+
import { continueResult, DEFAULT_USAGE_HOOK_TIMEOUT } from "./types";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Usage Hook Service
|
|
@@ -70,22 +70,20 @@ export const UsageHookServiceLive = (
|
|
|
70
70
|
return Effect.succeed(continueResult());
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
return hooks
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
),
|
|
88
|
-
);
|
|
73
|
+
return hooks.onUploadStart(ctx).pipe(
|
|
74
|
+
// Add timeout - proceed on timeout (fail-open)
|
|
75
|
+
Effect.timeout(timeout),
|
|
76
|
+
Effect.map((result) => result ?? continueResult()),
|
|
77
|
+
// On any error, log and continue (fail-open for availability)
|
|
78
|
+
Effect.catchAll((error) =>
|
|
79
|
+
Effect.gen(function* () {
|
|
80
|
+
yield* Effect.logWarning(
|
|
81
|
+
`onUploadStart hook failed: ${error}. Proceeding with upload.`,
|
|
82
|
+
);
|
|
83
|
+
return continueResult();
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
89
87
|
},
|
|
90
88
|
|
|
91
89
|
onUploadComplete: (ctx: UploadUsageContext) => {
|
|
@@ -93,19 +91,17 @@ export const UsageHookServiceLive = (
|
|
|
93
91
|
return Effect.void;
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
return hooks
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Effect.logWarning(
|
|
105
|
-
`onUploadComplete hook failed: ${error}. Upload already completed.`,
|
|
106
|
-
),
|
|
94
|
+
return hooks.onUploadComplete(ctx).pipe(
|
|
95
|
+
// Add timeout
|
|
96
|
+
Effect.timeout(timeout),
|
|
97
|
+
Effect.asVoid,
|
|
98
|
+
// On any error, just log (fire-and-forget)
|
|
99
|
+
Effect.catchAll((error) =>
|
|
100
|
+
Effect.logWarning(
|
|
101
|
+
`onUploadComplete hook failed: ${error}. Upload already completed.`,
|
|
107
102
|
),
|
|
108
|
-
)
|
|
103
|
+
),
|
|
104
|
+
);
|
|
109
105
|
},
|
|
110
106
|
|
|
111
107
|
onFlowStart: (ctx: FlowUsageContext) => {
|
|
@@ -113,22 +109,20 @@ export const UsageHookServiceLive = (
|
|
|
113
109
|
return Effect.succeed(continueResult());
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
return hooks
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
),
|
|
131
|
-
);
|
|
112
|
+
return hooks.onFlowStart(ctx).pipe(
|
|
113
|
+
// Add timeout - proceed on timeout (fail-open)
|
|
114
|
+
Effect.timeout(timeout),
|
|
115
|
+
Effect.map((result) => result ?? continueResult()),
|
|
116
|
+
// On any error, log and continue (fail-open for availability)
|
|
117
|
+
Effect.catchAll((error) =>
|
|
118
|
+
Effect.gen(function* () {
|
|
119
|
+
yield* Effect.logWarning(
|
|
120
|
+
`onFlowStart hook failed: ${error}. Proceeding with flow.`,
|
|
121
|
+
);
|
|
122
|
+
return continueResult();
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
);
|
|
132
126
|
},
|
|
133
127
|
|
|
134
128
|
onFlowComplete: (ctx: FlowUsageContext) => {
|
|
@@ -136,19 +130,17 @@ export const UsageHookServiceLive = (
|
|
|
136
130
|
return Effect.void;
|
|
137
131
|
}
|
|
138
132
|
|
|
139
|
-
return hooks
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Effect.logWarning(
|
|
148
|
-
`onFlowComplete hook failed: ${error}. Flow already completed.`,
|
|
149
|
-
),
|
|
133
|
+
return hooks.onFlowComplete(ctx).pipe(
|
|
134
|
+
// Add timeout
|
|
135
|
+
Effect.timeout(timeout),
|
|
136
|
+
Effect.asVoid,
|
|
137
|
+
// On any error, just log (fire-and-forget)
|
|
138
|
+
Effect.catchAll((error) =>
|
|
139
|
+
Effect.logWarning(
|
|
140
|
+
`onFlowComplete hook failed: ${error}. Flow already completed.`,
|
|
150
141
|
),
|
|
151
|
-
)
|
|
142
|
+
),
|
|
143
|
+
);
|
|
152
144
|
},
|
|
153
145
|
});
|
|
154
146
|
};
|
package/src/usage-hooks/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Used for usage tracking, quota enforcement, and billing integration.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Effect } from "effect";
|
|
8
|
+
import type { Effect } from "effect";
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Usage Hook Result Types
|
|
@@ -144,9 +144,7 @@ export type OnFlowStartHook = (
|
|
|
144
144
|
* Hook called after flow completes (success, failure, or cancellation).
|
|
145
145
|
* Used for recording usage. Errors are logged but don't fail the response.
|
|
146
146
|
*/
|
|
147
|
-
export type OnFlowCompleteHook = (
|
|
148
|
-
ctx: FlowUsageContext,
|
|
149
|
-
) => Effect.Effect<void>;
|
|
147
|
+
export type OnFlowCompleteHook = (ctx: FlowUsageContext) => Effect.Effect<void>;
|
|
150
148
|
|
|
151
149
|
// ============================================================================
|
|
152
150
|
// Usage Hooks Configuration
|