@uploadista/server 0.0.17 → 0.0.18-beta.10
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 +83 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +759 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +759 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/docs/HEALTH_CHECKS.md +256 -0
- package/package.json +14 -12
- package/src/core/health-check-service.ts +367 -0
- package/src/core/http-handlers/dlq-http-handlers.ts +219 -0
- package/src/core/http-handlers/flow-http-handlers.ts +61 -6
- package/src/core/http-handlers/health-http-handlers.ts +150 -0
- package/src/core/http-handlers/http-handlers.ts +50 -0
- package/src/core/http-handlers/upload-http-handlers.ts +56 -3
- package/src/core/routes.ts +171 -0
- package/src/core/server.ts +45 -4
- package/src/core/types.ts +114 -0
- package/src/index.ts +2 -0
- package/src/permissions/errors.ts +105 -0
- package/src/permissions/index.ts +9 -0
- package/src/permissions/matcher.ts +139 -0
- package/src/permissions/types.ts +151 -0
- package/src/plugins-typing.ts +1 -1
- package/src/service.ts +101 -3
- package/src/usage-hooks/index.ts +8 -0
- package/src/usage-hooks/service.ts +162 -0
- package/src/usage-hooks/types.ts +221 -0
- package/tests/core/health-check-service.test.ts +570 -0
- package/tests/core/http-handlers/health-handlers.test.ts +351 -0
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ This package provides framework-agnostic server components including authenticat
|
|
|
8
8
|
|
|
9
9
|
- **Authentication Context** - User identity and metadata management
|
|
10
10
|
- **Auth Caching** - LRU cache for auth contexts with TTL support
|
|
11
|
+
- **Health Checks** - Kubernetes-compatible liveness/readiness probes
|
|
11
12
|
- **Effect Layers** - Dependency injection for upload and flow servers
|
|
12
13
|
- **Error Handling** - Standardized error responses with HTTP status codes
|
|
13
14
|
- **HTTP Utilities** - Route parsing and error mapping helpers
|
|
@@ -391,6 +392,88 @@ const errorInfo = handleFlowError({
|
|
|
391
392
|
// => { status: 404, code: "FILE_NOT_FOUND", message: "File not found" }
|
|
392
393
|
```
|
|
393
394
|
|
|
395
|
+
## Health Check Endpoints
|
|
396
|
+
|
|
397
|
+
The server provides Kubernetes-compatible health check endpoints for production deployments.
|
|
398
|
+
|
|
399
|
+
### Available Endpoints
|
|
400
|
+
|
|
401
|
+
| Endpoint | Purpose | Checks |
|
|
402
|
+
|----------|---------|--------|
|
|
403
|
+
| `/{baseUrl}/health` | Liveness probe | None (always returns healthy) |
|
|
404
|
+
| `/{baseUrl}/ready` | Readiness probe | Storage, KV store, event broadcaster |
|
|
405
|
+
| `/{baseUrl}/health/components` | Detailed status | All components + circuit breakers + DLQ |
|
|
406
|
+
|
|
407
|
+
Alternative paths `/healthz` and `/readyz` are also supported for Kubernetes compatibility.
|
|
408
|
+
|
|
409
|
+
### Response Format
|
|
410
|
+
|
|
411
|
+
Health endpoints support content negotiation via the `Accept` header:
|
|
412
|
+
- `Accept: application/json` - JSON response with full details
|
|
413
|
+
- `Accept: text/plain` - Simple text response (`OK`, `DEGRADED`, `UNHEALTHY`)
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"status": "healthy",
|
|
418
|
+
"timestamp": "2024-11-27T10:30:00.000Z",
|
|
419
|
+
"version": "1.0.0",
|
|
420
|
+
"uptime": 3600000,
|
|
421
|
+
"components": {
|
|
422
|
+
"storage": {
|
|
423
|
+
"status": "healthy",
|
|
424
|
+
"latency": 15,
|
|
425
|
+
"lastCheck": "2024-11-27T10:30:00.000Z"
|
|
426
|
+
},
|
|
427
|
+
"kvStore": {
|
|
428
|
+
"status": "healthy",
|
|
429
|
+
"latency": 5,
|
|
430
|
+
"lastCheck": "2024-11-27T10:30:00.000Z"
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Configuration
|
|
437
|
+
|
|
438
|
+
Configure health checks in your server setup:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
442
|
+
|
|
443
|
+
const server = createUploadistaServer({
|
|
444
|
+
// ... other config
|
|
445
|
+
healthCheck: {
|
|
446
|
+
version: "1.0.0", // Application version
|
|
447
|
+
checkStorage: true, // Enable storage health checks
|
|
448
|
+
checkKvStore: true, // Enable KV store health checks
|
|
449
|
+
checkEventBroadcaster: false, // Disable event broadcaster checks
|
|
450
|
+
timeout: 5000, // Health check timeout in ms
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Kubernetes Integration
|
|
456
|
+
|
|
457
|
+
Example Kubernetes deployment configuration:
|
|
458
|
+
|
|
459
|
+
```yaml
|
|
460
|
+
livenessProbe:
|
|
461
|
+
httpGet:
|
|
462
|
+
path: /uploadista/health
|
|
463
|
+
port: 8080
|
|
464
|
+
initialDelaySeconds: 5
|
|
465
|
+
periodSeconds: 10
|
|
466
|
+
|
|
467
|
+
readinessProbe:
|
|
468
|
+
httpGet:
|
|
469
|
+
path: /uploadista/ready
|
|
470
|
+
port: 8080
|
|
471
|
+
initialDelaySeconds: 10
|
|
472
|
+
periodSeconds: 5
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
For complete documentation, see [docs/HEALTH_CHECKS.md](./docs/HEALTH_CHECKS.md).
|
|
476
|
+
|
|
394
477
|
## Framework Integration
|
|
395
478
|
|
|
396
479
|
This package is used by framework adapters:
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`./auth-Ck4gisA2.cjs`);let t=require(`effect`),n=require(`@uploadista/core/flow`),r=require(`@uploadista/core/types`),i=require(`@uploadista/core/utils`),a=require(`@uploadista/event-broadcaster-memory`),o=require(`@uploadista/event-emitter-websocket`),s=require(`@uploadista/observability`),c=require(`@uploadista/core/upload`),l=require(`@uploadista/core/errors`);var u=class extends t.Context.Tag(`AuthCacheService`)(){};const d=(e={})=>{let n=e.maxSize??1e4,r=e.ttl??36e5,i=new Map,a=()=>{let e=Date.now();for(let[t,n]of i.entries())e-n.timestamp>r&&i.delete(t)},o=()=>{if(i.size<=n)return;let e=null,t=1/0;for(let[n,r]of i.entries())r.timestamp<t&&(t=r.timestamp,e=n);e&&i.delete(e)};return t.Layer.succeed(u,{set:(e,n)=>t.Effect.sync(()=>{i.size%100==0&&a(),i.set(e,{authContext:n,timestamp:Date.now()}),o()}),get:e=>t.Effect.sync(()=>{let t=i.get(e);return t?Date.now()-t.timestamp>r?(i.delete(e),null):t.authContext:null}),delete:e=>t.Effect.sync(()=>{i.delete(e)}),clear:()=>t.Effect.sync(()=>{i.clear()}),size:()=>t.Effect.sync(()=>i.size)})},f=t.Layer.succeed(u,{set:()=>t.Effect.void,get:()=>t.Effect.succeed(null),delete:()=>t.Effect.void,clear:()=>t.Effect.void,size:()=>t.Effect.succeed(0)}),p=e=>e.split(`/`).filter(Boolean),m=e=>{let t=p(e);return t[t.length-1]},h=(e,t)=>e.includes(`${t}/api/`),g=(e,t)=>e.replace(`${t}/api/`,``).split(`/`).filter(Boolean),_=e=>{let t=500,n=`UNKNOWN_ERROR`,r=`Internal server error`,i;if(typeof e==`object`&&e){let a=e;if(`code`in a&&typeof a.code==`string`&&(n=a.code),`message`in a&&typeof a.message==`string`?r=a.message:`body`in a&&typeof a.body==`string`&&(r=a.body),`details`in a&&(i=a.details),`status`in a&&typeof a.status==`number`)t=a.status;else if(`code`in a)switch(a.code){case`FILE_NOT_FOUND`:case`FLOW_JOB_NOT_FOUND`:case`UPLOAD_ID_NOT_FOUND`:t=404;break;case`FLOW_JOB_ERROR`:case`VALIDATION_ERROR`:case`INVALID_METADATA`:case`INVALID_LENGTH`:case`ABORTED`:case`INVALID_TERMINATION`:t=400;break;case`INVALID_OFFSET`:t=409;break;case`ERR_SIZE_EXCEEDED`:case`ERR_MAX_SIZE_EXCEEDED`:t=413;break;case`FILE_NO_LONGER_EXISTS`:t=410;break;case`MISSING_OFFSET`:case`INVALID_CONTENT_TYPE`:t=403;break;default:t=500}`message`in a&&a.message===`Invalid JSON body`&&(t=400,n=`VALIDATION_ERROR`)}let a={status:t,code:n,message:r};return i!==void 0&&(a.details=i),a},v=e=>e[e.length-2],y=e=>({jobId:e[e.length-3],nodeId:e[e.length-1]}),b=e=>({storageId:e.pop(),flowId:e.pop()}),x=({kvStore:e,eventEmitter:n,dataStore:i,bufferedDataStore:a,generateId:o})=>{let s=t.Layer.provide(r.uploadFileKvStore,e),l=t.Layer.provide(i,s),u=a?t.Layer.provide(a,s):t.Layer.empty,d=t.Layer.provide(r.uploadEventEmitter,n),f=t.Layer.mergeAll(l,s,d,...o?[o]:[],u);return t.Layer.provide(c.uploadServer,f)},S=({kvStore:e,eventEmitter:i,flowProvider:a,uploadServer:o})=>{let s=t.Layer.provide(r.flowJobKvStore,e),c=t.Layer.provide(r.flowEventEmitter,i),l=t.Layer.mergeAll(a,c,s,o);return t.Layer.provide(n.flowServer,l)};var C=class extends t.Context.Tag(`AuthContextService`)(){};const w=e=>t.Layer.succeed(C,{getClientId:()=>t.Effect.succeed(e?.clientId??null),getMetadata:()=>t.Effect.succeed(e?.metadata??{}),hasPermission:n=>t.Effect.succeed(e?.permissions?.includes(n)??!1),getAuthContext:()=>t.Effect.succeed(e)}),T=w(null),E=({flowId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*(yield*C).getClientId();return i&&(yield*t.Effect.logInfo(`[Flow] Getting flow data: ${e}, client: ${i}`)),{status:200,body:yield*r.getFlowData(e,i)}}),D=({flowId:e,storageId:r,inputs:i})=>t.Effect.gen(function*(){let a=yield*n.FlowServer,o=yield*C,s=yield*u,c=yield*o.getClientId();c?(yield*t.Effect.logInfo(`[Flow] Executing flow: ${e}, storage: ${r}, client: ${c}`),yield*t.Effect.logInfo(JSON.stringify(i,null,2))):(yield*t.Effect.logInfo(`[Flow] Executing flow: ${e}, storage: ${r}`),yield*t.Effect.logInfo(`[Flow] Inputs: ${JSON.stringify(i,null,2)}`)),yield*t.Effect.logInfo(`[Flow] Calling flowServer.runFlow...`);let l=yield*a.runFlow({flowId:e,storageId:r,clientId:c,inputs:i}).pipe(t.Effect.tap(()=>t.Effect.logInfo(`[Flow] runFlow completed successfully`)),t.Effect.tapError(e=>t.Effect.logError(`[Flow] runFlow failed with error: ${e}`))),d=yield*o.getAuthContext();return d&&(yield*s.set(l.id,d)),yield*t.Effect.logInfo(`[Flow] Flow started with jobId: ${l.id}`),{status:200,body:l}}),O=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*C,a=yield*u,o=yield*i.getClientId();if(!e)throw Error(`No job id`);o&&(yield*t.Effect.logInfo(`[Flow] Getting job status: ${e}, client: ${o}`));let s=yield*r.getJobStatus(e);return(s.status===`completed`||s.status===`failed`)&&(yield*a.delete(e),o&&(yield*t.Effect.logInfo(`[Flow] Flow ${s.status}, cleared auth cache: ${e}`))),{status:200,body:s}}),k=({jobId:e,nodeId:r,newData:i})=>t.Effect.gen(function*(){let a=yield*n.FlowServer,o=yield*C,s=yield*u,c=yield*o.getClientId();if(c||=(yield*s.get(e))?.clientId??null,c&&(yield*t.Effect.logInfo(`[Flow] Continuing flow: jobId=${e}, nodeId=${r}, client: ${c}`)),i===void 0)throw Error(`Missing newData`);let l=yield*a.resumeFlow({jobId:e,nodeId:r,newData:i,clientId:c});return(l.status===`completed`||l.status===`failed`)&&(yield*s.delete(e),c&&(yield*t.Effect.logInfo(`[Flow] Flow ${l.status}, cleared auth cache: ${e}`))),{status:200,body:l}}),A=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*C,a=yield*u,o=yield*i.getClientId();o||=(yield*a.get(e))?.clientId??null,o&&(yield*t.Effect.logInfo(`[Flow] Pausing flow: jobId=${e}, client: ${o}`));let s=yield*r.pauseFlow(e,o);return o&&(yield*t.Effect.logInfo(`[Flow] Flow paused: ${e}, status: ${s.status}`)),{status:200,body:s}}),j=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*C,a=yield*u;if(!e)throw Error(`No job id`);let o=yield*i.getClientId();o||=(yield*a.get(e))?.clientId??null,o&&(yield*t.Effect.logInfo(`[Flow] Cancelling flow: jobId=${e}, client: ${o}`));let s=yield*r.cancelFlow(e,o);return yield*a.delete(e),o&&(yield*t.Effect.logInfo(`[Flow] Flow cancelled, cleared auth cache: ${e}`)),{status:200,body:s}});var M=class extends Error{constructor(e,t=500,n=`INTERNAL_ERROR`){super(e),this.statusCode=t,this.errorCode=n,this.name=`AdapterError`}},N=class extends M{constructor(e){super(e,400,`VALIDATION_ERROR`),this.name=`ValidationError`}},P=class extends M{constructor(e){super(`${e} not found`,404,`NOT_FOUND`),this.name=`NotFoundError`}},F=class extends M{constructor(e){super(e,400,`BAD_REQUEST`),this.name=`BadRequestError`}};const I=e=>({error:e.message,code:e.errorCode,timestamp:new Date().toISOString()}),L=e=>{let t={error:e.body,code:e.code,timestamp:new Date().toISOString()};return e.details!==void 0&&(t.details=e.details),t},R=(e=`Internal server error`)=>({error:e,code:`INTERNAL_ERROR`,timestamp:new Date().toISOString()}),ee=e=>t.Effect.gen(function*(){let n=yield*c.UploadServer,a=yield*C,o=yield*u,s=yield*a.getClientId();s&&(yield*t.Effect.logInfo(`[Upload] Creating upload for client: ${s}`));let l=yield*t.Effect.sync(()=>r.inputFileSchema.safeParse(e.data));if(!l.success)return yield*t.Effect.fail(new N(`Invalid input file schema`));if(l.data.checksumAlgorithm&&!(0,i.isSupportedAlgorithm)(l.data.checksumAlgorithm))return yield*t.Effect.fail(new N(`Unsupported checksum algorithm: ${l.data.checksumAlgorithm}. Supported algorithms: sha256`));let d=yield*n.createUpload(l.data,s),f=yield*a.getAuthContext();return f&&(yield*o.set(d.id,f)),s&&(yield*t.Effect.logInfo(`[Upload] Upload created: ${d.id} for client: ${s}`)),{status:200,body:d}}),z=({storageId:e})=>t.Effect.gen(function*(){let t=yield*c.UploadServer,n=yield*(yield*C).getClientId();return{status:200,body:{storageId:e,capabilities:yield*t.getCapabilities(e,n),timestamp:new Date().toISOString()}}}),B=({uploadId:e})=>t.Effect.gen(function*(){return{status:200,body:yield*(yield*c.UploadServer).getUpload(e)}}),V=e=>t.Effect.gen(function*(){let n=yield*c.UploadServer,r=yield*C,i=yield*u,a=yield*s.MetricsService,{uploadId:o,data:l}=e,d=yield*r.getClientId(),f=yield*r.getMetadata();if(!d){let e=yield*i.get(o);d=e?.clientId??null,f=e?.metadata??{}}d&&(yield*t.Effect.logInfo(`[Upload] Uploading chunk for upload: ${o}, client: ${d}`));let p=yield*n.uploadChunk(o,d,l);return p.size&&p.offset>=p.size&&(yield*i.delete(o),d&&(yield*t.Effect.logInfo(`[Upload] Upload completed, cleared auth cache: ${o}`)),d&&p.size?(yield*t.Effect.logInfo(`[Upload] Recording metrics for org: ${d}, size: ${p.size}`),yield*t.Effect.forkDaemon(a.recordUpload(d,p.size,f))):yield*t.Effect.logWarning(`[Upload] Cannot record metrics - missing organizationId or size`)),d&&(yield*t.Effect.logInfo(`[Upload] Chunk uploaded for upload: ${o}, client: ${d}`)),{status:200,body:p}}),H=e=>t.Effect.gen(function*(){switch(e.type){case`create-upload`:return yield*ee(e);case`get-capabilities`:return yield*z(e);case`get-upload`:return yield*B(e);case`upload-chunk`:return yield*V(e);case`get-flow`:return yield*E(e);case`run-flow`:return yield*D(e);case`job-status`:return yield*O(e);case`resume-flow`:return yield*k(e);case`pause-flow`:return yield*A(e);case`cancel-flow`:return yield*j(e);case`not-found`:return{status:404,headers:{"Content-Type":`application/json`},body:{error:`Not found`}};case`bad-request`:return{status:400,body:{error:`Bad request`,message:e.message}};case`method-not-allowed`:return{status:405,headers:{"Content-Type":`application/json`},body:{error:`Method not allowed`}};case`unsupported-content-type`:return{status:415,headers:{"Content-Type":`application/json`},body:{error:`Unsupported content type`}}}}),U=async({flows:e,dataStore:c,kvStore:l,plugins:u=[],eventEmitter:f,eventBroadcaster:p=a.memoryEventBroadcaster,withTracing:m=!1,baseUrl:h=`uploadista`,generateId:g=i.GenerateIdLive,metricsLayer:v,bufferedDataStore:y,adapter:b,authCacheConfig:C})=>{let T=f??(0,o.webSocketEventEmitter)(p),E=h.endsWith(`/`)?h.slice(0,-1):h,D=t.Layer.effect(n.FlowProvider,t.Effect.succeed({getFlow:(t,n)=>e(t,n)}));if(!T)throw Error(`eventEmitter is required. Provide an event emitter layer in the configuration.`);let O=x({kvStore:l,eventEmitter:T,dataStore:await(0,r.createDataStoreLayer)(c),bufferedDataStore:y,generateId:g}),k=S({kvStore:l,eventEmitter:T,flowProvider:D,uploadServer:O}),A=d(C),j=v??s.NoOpMetricsServiceLive,M=t.Layer.mergeAll(O,k,j,A,...u),N=t.ManagedRuntime.make(M);return{handler:async e=>{let r=t.Effect.gen(function*(){let r=yield*b.extractRequest(e,{baseUrl:E}),i=null;if(b.runAuthMiddleware){let n=yield*b.runAuthMiddleware(e).pipe(t.Effect.timeout(`5 seconds`),t.Effect.catchAll(()=>(console.error(`Auth middleware timeout exceeded (5 seconds)`),t.Effect.succeed({_tag:`TimeoutError`}))),t.Effect.catchAllCause(e=>(console.error(`Auth middleware error:`,e),t.Effect.succeed({_tag:`AuthError`,error:e}))));if(n&&typeof n==`object`&&`_tag`in n&&n._tag===`TimeoutError`)return yield*b.sendResponse({status:503,headers:{"Content-Type":`application/json`},body:{error:`Authentication service unavailable`,message:`Authentication took too long to respond. Please try again.`}},e);if(n&&typeof n==`object`&&`_tag`in n&&n._tag===`AuthError`)return yield*b.sendResponse({status:500,headers:{"Content-Type":`application/json`},body:{error:`Internal Server Error`,message:`An error occurred during authentication`}},e);if(n===null)return yield*b.sendResponse({status:401,headers:{"Content-Type":`application/json`},body:{error:`Unauthorized`,message:`Invalid credentials`}},e);i=n}let a=w(i),o=[];if(b.extractWaitUntil){let r=b.extractWaitUntil(e);r&&o.push(t.Layer.succeed(n.FlowWaitUntil,r))}let s=t.Layer.mergeAll(a,A,j,...u,...o);if(r.type===`not-found`)return yield*b.sendResponse({type:`not-found`,status:404,headers:{"Content-Type":`application/json`},body:{error:`Not found`}},e);let c=yield*H(r).pipe(t.Effect.provide(s));return yield*b.sendResponse(c,e)}).pipe(t.Effect.catchAll(t=>{let n=_(t),r={code:n.code,message:n.message};n.details!==void 0&&(r.details=n.details);let i={status:n.status,headers:{"Content-Type":`application/json`},body:r};return b.sendResponse(i,e)}));return m?N.runPromise(r.pipe(t.Effect.provide(s.NodeSdkLive))):N.runPromise(r)},websocketHandler:await N.runPromise(b.webSocketHandler({baseUrl:E})),baseUrl:E,dispose:()=>N.dispose()}};async function W(e){return U(e)}function G(e){return e}function K(e){return e}const q={ImagePlugin:{packageName:`@uploadista/flow-images-sharp`,variableName:`sharpImagePlugin`},ImageAiPlugin:{packageName:`@uploadista/flow-images-replicate`,variableName:`replicateImagePlugin`},ZipPlugin:{packageName:`@uploadista/flow-utility-zipjs`,variableName:`zipPlugin`},CredentialProvider:{packageName:`@uploadista/core`,variableName:`credentialProviderLayer`}};function J(e){try{let t=e;if(t._tag)return t._tag;if(t.constructor?.name)return t.constructor.name;if(t.context?.services){let e=Array.from(t.context.services.keys());if(e.length>0){let t=e[0];if(t.key)return t.key}}return null}catch{return null}}function Y(e){return e.map(e=>J(e)).filter(e=>e!==null)}function X(e){let{plugins:t,expectedServices:n=[]}=e,r=Y(t),i=n.filter(e=>!r.includes(e));return i.length===0?{success:!0}:{success:!1,required:n,provided:r,missing:i,suggestions:i.map(e=>{let t=q[e];return t?{name:e,packageName:t.packageName,importStatement:`import { ${t.variableName} } from '${t.packageName}';`}:null}).filter(e=>e!==null)}}function Z(e){let t=[`Server initialization failed: Missing required plugins`,``,`Required: ${e.required.join(`, `)}`,`Provided: ${e.provided.length>0?e.provided.join(`, `):`(none)`}`,`Missing: ${e.missing.join(`, `)}`,``];if(e.suggestions.length>0){t.push(`Add the missing plugins to your configuration:`),t.push(``);for(let n of e.suggestions)t.push(` ${n.importStatement}`);t.push(``),t.push(` const server = await createUploadistaServer({`),t.push(` plugins: [${[...e.provided,...e.missing.map(e=>q[e]?.variableName||e)].join(`, `)}],`),t.push(` // ...`),t.push(` });`)}else t.push(`Note: Could not determine package names for missing plugins.`),t.push(`Please ensure all required plugin layers are provided.`);return t.join(`
|
|
2
|
-
`)}function
|
|
1
|
+
const e=require(`./auth-Ck4gisA2.cjs`);let t=require(`effect`),n=require(`@uploadista/core/flow`),r=require(`@uploadista/core/types`),i=require(`@uploadista/core/utils`),a=require(`@uploadista/event-broadcaster-memory`),o=require(`@uploadista/event-emitter-websocket`),s=require(`@uploadista/observability`),c=require(`@uploadista/core/upload`),l=require(`@uploadista/core/errors`);var u=class extends t.Context.Tag(`AuthCacheService`)(){};const d=(e={})=>{let n=e.maxSize??1e4,r=e.ttl??36e5,i=new Map,a=()=>{let e=Date.now();for(let[t,n]of i.entries())e-n.timestamp>r&&i.delete(t)},o=()=>{if(i.size<=n)return;let e=null,t=1/0;for(let[n,r]of i.entries())r.timestamp<t&&(t=r.timestamp,e=n);e&&i.delete(e)};return t.Layer.succeed(u,{set:(e,n)=>t.Effect.sync(()=>{i.size%100==0&&a(),i.set(e,{authContext:n,timestamp:Date.now()}),o()}),get:e=>t.Effect.sync(()=>{let t=i.get(e);return t?Date.now()-t.timestamp>r?(i.delete(e),null):t.authContext:null}),delete:e=>t.Effect.sync(()=>{i.delete(e)}),clear:()=>t.Effect.sync(()=>{i.clear()}),size:()=>t.Effect.sync(()=>i.size)})},f=t.Layer.succeed(u,{set:()=>t.Effect.void,get:()=>t.Effect.succeed(null),delete:()=>t.Effect.void,clear:()=>t.Effect.void,size:()=>t.Effect.succeed(0)}),p=e=>e.split(`/`).filter(Boolean),m=e=>{let t=p(e);return t[t.length-1]},h=(e,t)=>e.includes(`${t}/api/`),ee=(e,t)=>e.replace(`${t}/api/`,``).split(`/`).filter(Boolean),g=e=>{let t=500,n=`UNKNOWN_ERROR`,r=`Internal server error`,i;if(typeof e==`object`&&e){let a=e;if(`code`in a&&typeof a.code==`string`&&(n=a.code),`message`in a&&typeof a.message==`string`?r=a.message:`body`in a&&typeof a.body==`string`&&(r=a.body),`details`in a&&(i=a.details),`status`in a&&typeof a.status==`number`)t=a.status;else if(`code`in a)switch(a.code){case`FILE_NOT_FOUND`:case`FLOW_JOB_NOT_FOUND`:case`UPLOAD_ID_NOT_FOUND`:t=404;break;case`FLOW_JOB_ERROR`:case`VALIDATION_ERROR`:case`INVALID_METADATA`:case`INVALID_LENGTH`:case`ABORTED`:case`INVALID_TERMINATION`:t=400;break;case`INVALID_OFFSET`:t=409;break;case`ERR_SIZE_EXCEEDED`:case`ERR_MAX_SIZE_EXCEEDED`:t=413;break;case`FILE_NO_LONGER_EXISTS`:t=410;break;case`MISSING_OFFSET`:case`INVALID_CONTENT_TYPE`:t=403;break;default:t=500}`message`in a&&a.message===`Invalid JSON body`&&(t=400,n=`VALIDATION_ERROR`)}let a={status:t,code:n,message:r};return i!==void 0&&(a.details=i),a},_=e=>e[e.length-2],te=e=>({jobId:e[e.length-3],nodeId:e[e.length-1]}),v=e=>({storageId:e.pop(),flowId:e.pop()}),y=({kvStore:e,eventEmitter:n,dataStore:i,bufferedDataStore:a,generateId:o})=>{let s=t.Layer.provide(r.uploadFileKvStore,e),l=t.Layer.provide(i,s),u=a?t.Layer.provide(a,s):t.Layer.empty,d=t.Layer.provide(r.uploadEventEmitter,n),f=t.Layer.mergeAll(l,s,d,...o?[o]:[],u);return t.Layer.provide(c.uploadServer,f)},b=({kvStore:e,eventEmitter:i,flowProvider:a,uploadServer:o})=>{let s=t.Layer.provide(r.flowJobKvStore,e),c=t.Layer.provide(r.flowEventEmitter,i),l=t.Layer.mergeAll(a,c,s,o);return t.Layer.provide(n.flowServer,l)},x={ALL:`engine:*`,HEALTH:`engine:health`,READINESS:`engine:readiness`,METRICS:`engine:metrics`,DLQ:`engine:dlq`,DLQ_READ:`engine:dlq:read`,DLQ_WRITE:`engine:dlq:write`},S={ALL:`flow:*`,EXECUTE:`flow:execute`,CANCEL:`flow:cancel`,STATUS:`flow:status`},C={ALL:`upload:*`,CREATE:`upload:create`,READ:`upload:read`,CANCEL:`upload:cancel`},w={ENGINE:x,FLOW:S,UPLOAD:C},ne={ADMIN:[x.ALL],ORGANIZATION_OWNER:[S.ALL,C.ALL],ORGANIZATION_MEMBER:[S.ALL,C.ALL],API_KEY:[S.EXECUTE,C.CREATE]},T={[x.DLQ]:[x.DLQ_READ,x.DLQ_WRITE]},E=(e,t)=>{if(e===t)return!0;if(e.endsWith(`:*`)){let n=e.slice(0,-1);if(t.startsWith(n))return!0}return!!T[e]?.includes(t)},D=(e,t)=>e.some(e=>E(e,t)),O=(e,t)=>t.some(t=>D(e,t)),k=(e,t)=>t.every(t=>D(e,t)),A=e=>{let t=[e],n=T[e];return n&&t.push(...n),t};var j=class extends Error{constructor(e,t=500,n=`INTERNAL_ERROR`){super(e),this.statusCode=t,this.errorCode=n,this.name=`AdapterError`}},M=class extends j{constructor(e){super(e,400,`VALIDATION_ERROR`),this.name=`ValidationError`}},N=class extends j{constructor(e){super(`${e} not found`,404,`NOT_FOUND`),this.name=`NotFoundError`}},P=class extends j{constructor(e){super(e,400,`BAD_REQUEST`),this.name=`BadRequestError`}};const F=e=>({error:e.message,code:e.errorCode,timestamp:new Date().toISOString()}),I=e=>{let t={error:e.body,code:e.code,timestamp:new Date().toISOString()};return e.details!==void 0&&(t.details=e.details),t},re=(e=`Internal server error`)=>({error:e,code:`INTERNAL_ERROR`,timestamp:new Date().toISOString()});var L=class extends j{requiredPermission;constructor(e,t){super(t??`Permission denied: ${e} required`,403,`PERMISSION_DENIED`),this.name=`AuthorizationError`,this.requiredPermission=e}},R=class extends j{constructor(e=`Authentication required`){super(e,401,`AUTHENTICATION_REQUIRED`),this.name=`AuthenticationRequiredError`}},ie=class extends j{constructor(e=`Access denied: resource belongs to another organization`){super(e,403,`ORGANIZATION_MISMATCH`),this.name=`OrganizationMismatchError`}},z=class extends j{constructor(e=`Quota exceeded`,t=`QUOTA_EXCEEDED`){super(e,402,t),this.name=`QuotaExceededError`}};const ae=e=>({error:e.message,code:e.errorCode,requiredPermission:e.requiredPermission,timestamp:new Date().toISOString()});var B=class extends t.Context.Tag(`AuthContextService`)(){};const V=e=>{let n=e?.permissions??[];return t.Layer.succeed(B,{getClientId:()=>t.Effect.succeed(e?.clientId??null),getMetadata:()=>t.Effect.succeed(e?.metadata??{}),hasPermission:e=>t.Effect.succeed(D(n,e)),hasAnyPermission:e=>t.Effect.succeed(O(n,e)),requirePermission:r=>t.Effect.gen(function*(){if(!e)return yield*t.Effect.logDebug(`[Auth] Permission check failed: authentication required for '${r}'`),yield*t.Effect.fail(new R);if(!D(n,r))return yield*t.Effect.logDebug(`[Auth] Permission denied: '${r}' for client '${e.clientId}'`),yield*t.Effect.fail(new L(r));yield*t.Effect.logDebug(`[Auth] Permission granted: '${r}' for client '${e.clientId}'`)}),requireAuthentication:()=>e?t.Effect.succeed(e):t.Effect.fail(new R),getPermissions:()=>t.Effect.succeed(n),getAuthContext:()=>t.Effect.succeed(e)})},oe=V(null),H=()=>({action:`continue`}),se=(e,t)=>({action:`abort`,reason:e,code:t}),U=5e3;var W=class extends t.Context.Tag(`UsageHookService`)(){};const G=e=>{let n=e?.hooks,r=e?.timeout??U;return t.Layer.succeed(W,{onUploadStart:e=>n?.onUploadStart?n.onUploadStart(e).pipe(t.Effect.timeout(r),t.Effect.map(e=>e??H()),t.Effect.catchAll(e=>t.Effect.gen(function*(){return yield*t.Effect.logWarning(`onUploadStart hook failed: ${e}. Proceeding with upload.`),H()}))):t.Effect.succeed(H()),onUploadComplete:e=>n?.onUploadComplete?n.onUploadComplete(e).pipe(t.Effect.timeout(r),t.Effect.asVoid,t.Effect.catchAll(e=>t.Effect.logWarning(`onUploadComplete hook failed: ${e}. Upload already completed.`))):t.Effect.void,onFlowStart:e=>n?.onFlowStart?n.onFlowStart(e).pipe(t.Effect.timeout(r),t.Effect.map(e=>e??H()),t.Effect.catchAll(e=>t.Effect.gen(function*(){return yield*t.Effect.logWarning(`onFlowStart hook failed: ${e}. Proceeding with flow.`),H()}))):t.Effect.succeed(H()),onFlowComplete:e=>n?.onFlowComplete?n.onFlowComplete(e).pipe(t.Effect.timeout(r),t.Effect.asVoid,t.Effect.catchAll(e=>t.Effect.logWarning(`onFlowComplete hook failed: ${e}. Flow already completed.`))):t.Effect.void})},ce=G(),le=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_READ);let t=yield*(yield*n.DeadLetterQueueService).list(e.options);return{type:`dlq-list`,status:200,headers:{"Content-Type":`application/json`},body:t}}),ue=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_READ);let t=yield*(yield*n.DeadLetterQueueService).get(e.itemId);return{type:`dlq-get`,status:200,headers:{"Content-Type":`application/json`},body:t}}),de=e=>t.Effect.gen(function*(){return yield*(yield*B).requirePermission(w.ENGINE.DLQ_WRITE),yield*(yield*n.DeadLetterQueueService).markRetrying(e.itemId),{type:`dlq-retry`,status:200,headers:{"Content-Type":`application/json`},body:{success:!0}}}),fe=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_WRITE);let r=yield*n.DeadLetterQueueService,{items:i}=yield*r.list({status:e.options?.status,flowId:e.options?.flowId}),a=0,o=0;for(let e of i)(yield*t.Effect.either(r.markRetrying(e.id)))._tag===`Right`?a++:o++;return{type:`dlq-retry-all`,status:200,headers:{"Content-Type":`application/json`},body:{retried:i.length,succeeded:a,failed:o}}}),pe=e=>t.Effect.gen(function*(){return yield*(yield*B).requirePermission(w.ENGINE.DLQ_WRITE),yield*(yield*n.DeadLetterQueueService).delete(e.itemId),{type:`dlq-delete`,status:200,headers:{"Content-Type":`application/json`},body:{success:!0}}}),me=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_WRITE);let t=yield*(yield*n.DeadLetterQueueService).markResolved(e.itemId);return{type:`dlq-resolve`,status:200,headers:{"Content-Type":`application/json`},body:t}}),he=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_WRITE);let t=yield*(yield*n.DeadLetterQueueService).cleanup(e.options);return{type:`dlq-cleanup`,status:200,headers:{"Content-Type":`application/json`},body:t}}),ge=e=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.DLQ_READ);let e=yield*(yield*n.DeadLetterQueueService).getStats();return{type:`dlq-stats`,status:200,headers:{"Content-Type":`application/json`},body:e}}),_e=({flowId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*B,a=yield*i.getClientId();return yield*i.requirePermission(w.FLOW.STATUS),a&&(yield*t.Effect.logInfo(`[Flow] Getting flow data: ${e}, client: ${a}`)),{status:200,body:yield*r.getFlowData(e,a)}}),ve=({flowId:e,storageId:r,inputs:i})=>t.Effect.gen(function*(){let a=yield*n.FlowServer,o=yield*B,s=yield*u,c=yield*W,l=yield*o.getClientId();if(yield*o.requirePermission(w.FLOW.EXECUTE),l?(yield*t.Effect.logInfo(`[Flow] Executing flow: ${e}, storage: ${r}, client: ${l}`),yield*t.Effect.logInfo(JSON.stringify(i,null,2))):(yield*t.Effect.logInfo(`[Flow] Executing flow: ${e}, storage: ${r}`),yield*t.Effect.logInfo(`[Flow] Inputs: ${JSON.stringify(i,null,2)}`)),l){let n=yield*c.onFlowStart({clientId:l,operation:`flow`,metadata:{flowId:e}});if(n.action===`abort`)return yield*t.Effect.fail(new z(n.reason,n.code??`SUBSCRIPTION_REQUIRED`))}yield*t.Effect.logInfo(`[Flow] Calling flowServer.runFlow...`);let d=yield*a.runFlow({flowId:e,storageId:r,clientId:l,inputs:i}).pipe(t.Effect.tap(()=>t.Effect.logInfo(`[Flow] runFlow completed successfully`)),t.Effect.tapError(e=>t.Effect.logError(`[Flow] runFlow failed with error: ${e}`))),f=yield*o.getAuthContext();return f&&(yield*s.set(d.id,f)),yield*t.Effect.logInfo(`[Flow] Flow started with jobId: ${d.id}`),{status:200,body:d}}),ye=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*B,a=yield*u,o=yield*i.getClientId();if(yield*i.requirePermission(w.FLOW.STATUS),!e)throw Error(`No job id`);o&&(yield*t.Effect.logInfo(`[Flow] Getting job status: ${e}, client: ${o}`));let s=yield*r.getJobStatus(e);return(s.status===`completed`||s.status===`failed`)&&(yield*a.delete(e),o&&(yield*t.Effect.logInfo(`[Flow] Flow ${s.status}, cleared auth cache: ${e}`))),{status:200,body:s}}),be=({jobId:e,nodeId:r,newData:i})=>t.Effect.gen(function*(){let a=yield*n.FlowServer,o=yield*B,s=yield*u;yield*o.requirePermission(w.FLOW.EXECUTE);let c=yield*o.getClientId();if(c||=(yield*s.get(e))?.clientId??null,c&&(yield*t.Effect.logInfo(`[Flow] Continuing flow: jobId=${e}, nodeId=${r}, client: ${c}`)),i===void 0)throw Error(`Missing newData`);let l=yield*a.resumeFlow({jobId:e,nodeId:r,newData:i,clientId:c});return(l.status===`completed`||l.status===`failed`)&&(yield*s.delete(e),c&&(yield*t.Effect.logInfo(`[Flow] Flow ${l.status}, cleared auth cache: ${e}`))),{status:200,body:l}}),xe=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*B,a=yield*u;yield*i.requirePermission(w.FLOW.CANCEL);let o=yield*i.getClientId();o||=(yield*a.get(e))?.clientId??null,o&&(yield*t.Effect.logInfo(`[Flow] Pausing flow: jobId=${e}, client: ${o}`));let s=yield*r.pauseFlow(e,o);return o&&(yield*t.Effect.logInfo(`[Flow] Flow paused: ${e}, status: ${s.status}`)),{status:200,body:s}}),Se=({jobId:e})=>t.Effect.gen(function*(){let r=yield*n.FlowServer,i=yield*B,a=yield*u,o=yield*W;if(yield*i.requirePermission(w.FLOW.CANCEL),!e)throw Error(`No job id`);let s=yield*i.getClientId();s||=(yield*a.get(e))?.clientId??null,s&&(yield*t.Effect.logInfo(`[Flow] Cancelling flow: jobId=${e}, client: ${s}`));let c=yield*r.cancelFlow(e,s);return yield*a.delete(e),s&&(yield*t.Effect.logInfo(`[Flow] Flow cancelled, cleared auth cache: ${e}`),yield*t.Effect.forkDaemon(o.onFlowComplete({clientId:s,operation:`flow`,metadata:{jobId:e,status:`cancelled`}}))),{status:200,body:c}}),Ce=Date.now();function K(){return Date.now()-Ce}function q(){return new Date().toISOString()}function we(e){return{status:`healthy`,timestamp:q(),version:e?.version,uptime:K()}}function J(e){let t=[];if(e.storage&&t.push(e.storage.status),e.kvStore&&t.push(e.kvStore.status),t.includes(`unhealthy`))return`unhealthy`;let n=[...t];return e.eventBroadcaster&&n.push(e.eventBroadcaster.status),e.circuitBreaker&&n.push(e.circuitBreaker.status),e.deadLetterQueue&&n.push(e.deadLetterQueue.status),n.includes(`degraded`)?`degraded`:`healthy`}function Y(e){let n=Date.now();return t.Effect.succeed({status:`healthy`,latency:Date.now()-n,message:`Storage backend configured`,lastCheck:q()})}function X(e){let n=Date.now();return t.Effect.succeed({status:`healthy`,latency:Date.now()-n,message:`KV store configured`,lastCheck:q()})}function Z(e){let n=Date.now();return t.Effect.succeed({status:`healthy`,latency:Date.now()-n,message:`Event broadcaster configured`,lastCheck:q()})}function Te(){return t.Effect.gen(function*(){let e=yield*t.Effect.serviceOption(r.CircuitBreakerStoreService);if(t.Option.isNone(e))return;let n=e.value,i=yield*t.Effect.either(n.getAllStats());if(i._tag===`Left`)return{status:`degraded`,openCircuits:0,totalCircuits:0};let a=i.right,o=Array.from(a.values()),s=o.filter(e=>e.state===`open`).length,c=o.length,l=`healthy`;return s>0&&(l=`degraded`),{status:l,openCircuits:s,totalCircuits:c,circuits:o.map(e=>({nodeType:e.nodeType,state:e.state,failureCount:e.failureCount,timeSinceLastStateChange:e.timeSinceLastStateChange}))}})}function Ee(){return t.Effect.gen(function*(){let e=yield*n.DeadLetterQueueService.optional;if(t.Option.isNone(e))return;let r=e.value,i=yield*t.Effect.either(r.getStats());if(i._tag===`Left`)return{status:`degraded`,pendingItems:0,exhaustedItems:0};let a=i.right,o=`healthy`;return a.byStatus.exhausted>0&&(o=`degraded`),{status:o,pendingItems:a.byStatus.pending,exhaustedItems:a.byStatus.exhausted,oldestItem:a.oldestItem?.toISOString()}})}function De(e={}){let n={...r.DEFAULT_HEALTH_CHECK_CONFIG,...e};return t.Effect.gen(function*(){let t={};return n.checkStorage&&(t.storage=yield*Y(n)),n.checkKvStore&&(t.kvStore=yield*X(n)),n.checkEventBroadcaster&&(t.eventBroadcaster=yield*Z(n)),{status:J(t),timestamp:q(),version:e.version,uptime:K(),components:t}})}function Oe(e={}){let n={...r.DEFAULT_HEALTH_CHECK_CONFIG,...e};return t.Effect.gen(function*(){let t={};n.checkStorage&&(t.storage=yield*Y(n)),n.checkKvStore&&(t.kvStore=yield*X(n)),n.checkEventBroadcaster&&(t.eventBroadcaster=yield*Z(n));let r=yield*Te();r&&(t.circuitBreaker=r);let i=yield*Ee();return i&&(t.deadLetterQueue=i),{status:J(t),timestamp:q(),version:e.version,uptime:K(),components:t}})}const ke=(e,n)=>t.Effect.sync(()=>{let t=we(n);return(0,r.getHealthResponseFormat)(e.acceptHeader)===`text`?{type:`health`,status:200,headers:{"Content-Type":`text/plain`},body:(0,r.formatHealthAsText)(t.status)}:{type:`health`,status:200,headers:{"Content-Type":`application/json`},body:t}}),Ae=(e,n)=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.READINESS);let t=yield*De(n),i=(0,r.getHealthResponseFormat)(e.acceptHeader),a=t.status===`unhealthy`?503:200;return i===`text`?{type:`health-ready`,status:a,headers:{"Content-Type":`text/plain`},body:(0,r.formatHealthAsText)(t.status)}:{type:`health-ready`,status:a,headers:{"Content-Type":`application/json`},body:t}}),je=(e,n)=>t.Effect.gen(function*(){yield*(yield*B).requirePermission(w.ENGINE.READINESS);let t=yield*Oe(n);return(0,r.getHealthResponseFormat)(e.acceptHeader)===`text`?{type:`health-components`,status:200,headers:{"Content-Type":`text/plain`},body:(0,r.formatHealthAsText)(t.status)}:{type:`health-components`,status:200,headers:{"Content-Type":`application/json`},body:t}}),Me=e=>t.Effect.gen(function*(){let n=yield*c.UploadServer,a=yield*B,o=yield*u,s=yield*W,l=yield*a.getClientId();yield*a.requirePermission(w.UPLOAD.CREATE),l&&(yield*t.Effect.logInfo(`[Upload] Creating upload for client: ${l}`));let d=yield*t.Effect.sync(()=>r.inputFileSchema.safeParse(e.data));if(!d.success)return yield*t.Effect.fail(new M(`Invalid input file schema`));if(d.data.checksumAlgorithm&&!(0,i.isSupportedAlgorithm)(d.data.checksumAlgorithm))return yield*t.Effect.fail(new M(`Unsupported checksum algorithm: ${d.data.checksumAlgorithm}. Supported algorithms: sha256`));if(l){let e=yield*s.onUploadStart({clientId:l,operation:`upload`,metadata:{fileSize:d.data.size,mimeType:d.data.type,fileName:d.data.fileName}});if(e.action===`abort`)return yield*t.Effect.fail(new z(e.reason,e.code??`QUOTA_EXCEEDED`))}let f=yield*n.createUpload(d.data,l),p=yield*a.getAuthContext();return p&&(yield*o.set(f.id,p)),l&&(yield*t.Effect.logInfo(`[Upload] Upload created: ${f.id} for client: ${l}`)),{status:200,body:f}}),Ne=({storageId:e})=>t.Effect.gen(function*(){let t=yield*c.UploadServer,n=yield*B,r=yield*n.getClientId();return yield*n.requirePermission(w.UPLOAD.READ),{status:200,body:{storageId:e,capabilities:yield*t.getCapabilities(e,r),timestamp:new Date().toISOString()}}}),Pe=({uploadId:e})=>t.Effect.gen(function*(){let t=yield*c.UploadServer;return yield*(yield*B).requirePermission(w.UPLOAD.READ),{status:200,body:yield*t.getUpload(e)}}),Fe=e=>t.Effect.gen(function*(){let n=yield*c.UploadServer,r=yield*B,i=yield*u,a=yield*s.MetricsService,o=yield*W,{uploadId:l,data:d}=e;yield*r.requirePermission(w.UPLOAD.CREATE);let f=yield*r.getClientId(),p=yield*r.getMetadata();if(!f){let e=yield*i.get(l);f=e?.clientId??null,p=e?.metadata??{}}f&&(yield*t.Effect.logInfo(`[Upload] Uploading chunk for upload: ${l}, client: ${f}`));let m=Date.now(),h=yield*n.uploadChunk(l,f,d);if(h.size&&h.offset>=h.size)if(yield*i.delete(l),f&&(yield*t.Effect.logInfo(`[Upload] Upload completed, cleared auth cache: ${l}`)),f&&h.size){yield*t.Effect.logInfo(`[Upload] Recording metrics for org: ${f}, size: ${h.size}`),yield*t.Effect.forkDaemon(a.recordUpload(f,h.size,p));let e=Date.now()-m;yield*t.Effect.forkDaemon(o.onUploadComplete({clientId:f,operation:`upload`,metadata:{uploadId:l,fileSize:h.size,duration:e}}))}else yield*t.Effect.logWarning(`[Upload] Cannot record metrics - missing organizationId or size`);return f&&(yield*t.Effect.logInfo(`[Upload] Chunk uploaded for upload: ${l}, client: ${f}`)),{status:200,body:h}}),Ie=(e,n)=>t.Effect.gen(function*(){switch(e.type){case`create-upload`:return yield*Me(e);case`get-capabilities`:return yield*Ne(e);case`get-upload`:return yield*Pe(e);case`upload-chunk`:return yield*Fe(e);case`get-flow`:return yield*_e(e);case`run-flow`:return yield*ve(e);case`job-status`:return yield*ye(e);case`resume-flow`:return yield*be(e);case`pause-flow`:return yield*xe(e);case`cancel-flow`:return yield*Se(e);case`dlq-list`:return yield*le(e);case`dlq-get`:return yield*ue(e);case`dlq-retry`:return yield*de(e);case`dlq-retry-all`:return yield*fe(e);case`dlq-delete`:return yield*pe(e);case`dlq-resolve`:return yield*me(e);case`dlq-cleanup`:return yield*he(e);case`dlq-stats`:return yield*ge(e);case`health`:return yield*ke(e,n?.healthCheckConfig);case`health-ready`:return yield*Ae(e,n?.healthCheckConfig);case`health-components`:return yield*je(e,n?.healthCheckConfig);case`not-found`:return{status:404,headers:{"Content-Type":`application/json`},body:{error:`Not found`}};case`bad-request`:return{status:400,body:{error:`Bad request`,message:e.message}};case`method-not-allowed`:return{status:405,headers:{"Content-Type":`application/json`},body:{error:`Method not allowed`}};case`unsupported-content-type`:return{status:415,headers:{"Content-Type":`application/json`},body:{error:`Unsupported content type`}}}}),Le=async({flows:e,dataStore:c,kvStore:l,plugins:u=[],eventEmitter:f,eventBroadcaster:p=a.memoryEventBroadcaster,withTracing:m=!1,baseUrl:h=`uploadista`,generateId:ee=i.GenerateIdLive,metricsLayer:_,bufferedDataStore:te,adapter:v,authCacheConfig:x,circuitBreaker:S=!0,deadLetterQueue:C=!1,healthCheck:w,usageHooks:ne})=>{let T=f??(0,o.webSocketEventEmitter)(p),E=h.endsWith(`/`)?h.slice(0,-1):h,D=t.Layer.effect(n.FlowProvider,t.Effect.succeed({getFlow:(t,n)=>e(t,n)}));if(!T)throw Error(`eventEmitter is required. Provide an event emitter layer in the configuration.`);let O=y({kvStore:l,eventEmitter:T,dataStore:await(0,r.createDataStoreLayer)(c),bufferedDataStore:te,generateId:ee}),k=b({kvStore:l,eventEmitter:T,flowProvider:D,uploadServer:O}),A=d(x),j=_??s.NoOpMetricsServiceLive,M=S?n.kvCircuitBreakerStoreLayer.pipe(t.Layer.provide(l)):null,N=C?n.deadLetterQueueService.pipe(t.Layer.provide(r.deadLetterQueueKvStore),t.Layer.provide(l)):null,P=G(ne),F=t.Layer.mergeAll(O,k,j,A,P,...u,...M?[M]:[],...N?[N]:[]),I=t.ManagedRuntime.make(F);return{handler:async e=>{let r=t.Effect.gen(function*(){let r=yield*v.extractRequest(e,{baseUrl:E}),i=null;if(v.runAuthMiddleware){let n=yield*v.runAuthMiddleware(e).pipe(t.Effect.timeout(`5 seconds`),t.Effect.catchAll(()=>(console.error(`Auth middleware timeout exceeded (5 seconds)`),t.Effect.succeed({_tag:`TimeoutError`}))),t.Effect.catchAllCause(e=>(console.error(`Auth middleware error:`,e),t.Effect.succeed({_tag:`AuthError`,error:e}))));if(n&&typeof n==`object`&&`_tag`in n&&n._tag===`TimeoutError`)return yield*v.sendResponse({status:503,headers:{"Content-Type":`application/json`},body:{error:`Authentication service unavailable`,message:`Authentication took too long to respond. Please try again.`}},e);if(n&&typeof n==`object`&&`_tag`in n&&n._tag===`AuthError`)return yield*v.sendResponse({status:500,headers:{"Content-Type":`application/json`},body:{error:`Internal Server Error`,message:`An error occurred during authentication`}},e);if(n===null)return yield*v.sendResponse({status:401,headers:{"Content-Type":`application/json`},body:{error:`Unauthorized`,message:`Invalid credentials`}},e);i=n}let a=V(i),o=[];if(v.extractWaitUntil){let r=v.extractWaitUntil(e);r&&o.push(t.Layer.succeed(n.FlowWaitUntil,r))}let s=t.Layer.mergeAll(a,A,j,P,...u,...o),c=M?t.Layer.merge(s,M):s,l=N?t.Layer.merge(c,N):c;if(r.type===`not-found`)return yield*v.sendResponse({type:`not-found`,status:404,headers:{"Content-Type":`application/json`},body:{error:`Not found`}},e);let d=yield*Ie(r,{healthCheckConfig:w}).pipe(t.Effect.provide(l));return yield*v.sendResponse(d,e)}).pipe(t.Effect.catchAll(t=>{let n=g(t),r={code:n.code,message:n.message};n.details!==void 0&&(r.details=n.details);let i={status:n.status,headers:{"Content-Type":`application/json`},body:r};return v.sendResponse(i,e)}));return m?I.runPromise(r.pipe(t.Effect.provide(s.NodeSdkLive))):I.runPromise(r)},websocketHandler:await I.runPromise(v.webSocketHandler({baseUrl:E})),baseUrl:E,dispose:()=>I.dispose()}};async function Re(e){return Le(e)}function ze(e){return e}function Be(e){return e}const Ve={ImagePlugin:{packageName:`@uploadista/flow-images-sharp`,variableName:`sharpImagePlugin`},ImageAiPlugin:{packageName:`@uploadista/flow-images-replicate`,variableName:`replicateImagePlugin`},ZipPlugin:{packageName:`@uploadista/flow-utility-zipjs`,variableName:`zipPlugin`},CredentialProvider:{packageName:`@uploadista/core`,variableName:`credentialProviderLayer`}};function He(e){try{let t=e;if(t._tag)return t._tag;if(t.constructor?.name)return t.constructor.name;if(t.context?.services){let e=Array.from(t.context.services.keys());if(e.length>0){let t=e[0];if(t.key)return t.key}}return null}catch{return null}}function Ue(e){return e.map(e=>He(e)).filter(e=>e!==null)}function Q(e){let{plugins:t,expectedServices:n=[]}=e,r=Ue(t),i=n.filter(e=>!r.includes(e));return i.length===0?{success:!0}:{success:!1,required:n,provided:r,missing:i,suggestions:i.map(e=>{let t=Ve[e];return t?{name:e,packageName:t.packageName,importStatement:`import { ${t.variableName} } from '${t.packageName}';`}:null}).filter(e=>e!==null)}}function $(e){let t=[`Server initialization failed: Missing required plugins`,``,`Required: ${e.required.join(`, `)}`,`Provided: ${e.provided.length>0?e.provided.join(`, `):`(none)`}`,`Missing: ${e.missing.join(`, `)}`,``];if(e.suggestions.length>0){t.push(`Add the missing plugins to your configuration:`),t.push(``);for(let n of e.suggestions)t.push(` ${n.importStatement}`);t.push(``),t.push(` const server = await createUploadistaServer({`),t.push(` plugins: [${[...e.provided,...e.missing.map(e=>Ve[e]?.variableName||e)].join(`, `)}],`),t.push(` // ...`),t.push(` });`)}else t.push(`Note: Could not determine package names for missing plugins.`),t.push(`Please ensure all required plugin layers are provided.`);return t.join(`
|
|
2
|
+
`)}function We(e){return t.Effect.sync(()=>{let t=Q(e);if(!t.success){let e=$(t);throw Error(e)}})}function Ge(e){let t=Q(e);if(!t.success){let e=$(t);throw Error(e)}}const Ke=(e,n,r)=>t.Effect.gen(function*(){if(!n){yield*t.Effect.sync(()=>{r.send(JSON.stringify({type:`error`,message:`Job ID is required for flow event subscription`,code:`MISSING_JOB_ID`}))});return}yield*e.subscribeToFlowEvents(n,r)}),qe=(e,n)=>t.Effect.gen(function*(){n&&(yield*e.unsubscribeFromFlowEvents(n))}),Je=(e,n,r)=>t.Effect.gen(function*(){if(!n){yield*t.Effect.sync(()=>{r.send(JSON.stringify({type:`error`,message:`Upload ID is required for upload event subscription`,code:`MISSING_UPLOAD_ID`}))});return}yield*e.subscribeToUploadEvents(n,r)}),Ye=(e,n)=>t.Effect.gen(function*(){n&&(yield*e.unsubscribeFromUploadEvents(n))}),Xe=(e,n,r)=>{let{connection:i,isFlowRoute:a,isUploadRoute:o,jobId:s,uploadId:c,eventId:u}=e;return t.Effect.gen(function*(){a&&(yield*Ke(r,s,i)),o&&(yield*Je(n,c,i)),i.send(JSON.stringify({type:`connection`,message:`Uploadista WebSocket connected`,id:u,jobId:s,uploadId:c,timestamp:new Date().toISOString()}))}).pipe(t.Effect.catchAll(e=>t.Effect.sync(()=>{console.error(`Error subscribing to events:`,e);let t=e instanceof l.UploadistaError?e.body:`Failed to subscribe to events`;i.send(JSON.stringify({type:`error`,message:t,code:e instanceof l.UploadistaError?e.code:`SUBSCRIPTION_ERROR`}))})))},Ze=(e,n)=>t.Effect.sync(()=>{try{JSON.parse(e).type===`ping`&&n.send(JSON.stringify({type:`pong`,timestamp:new Date().toISOString()}))}catch(e){console.error(`Error handling WebSocket message:`,e),n.send(JSON.stringify({type:`error`,message:`Invalid message format`}))}}),Qe=(e,n,r)=>{let{isFlowRoute:i,isUploadRoute:a,jobId:o,uploadId:s}=e;return t.Effect.gen(function*(){i&&(yield*qe(r,o)),a&&(yield*Ye(n,s))}).pipe(t.Effect.catchAll(e=>t.Effect.sync(()=>{console.error(`Error unsubscribing from events:`,e instanceof l.UploadistaError?e.body:e)})))},$e=(e,n)=>t.Effect.sync(()=>{console.error(`WebSocket error for event ${n}:`,e)});exports.AdapterError=j,exports.AuthCacheService=u,exports.AuthCacheServiceLive=d,exports.AuthContextService=B,exports.AuthContextServiceLive=V,exports.AuthenticationRequiredError=R,exports.AuthorizationError=L,exports.BadRequestError=P,exports.DEFAULT_USAGE_HOOK_TIMEOUT=U,exports.ENGINE_PERMISSIONS=x,exports.FLOW_PERMISSIONS=S,exports.NoAuthCacheServiceLive=f,exports.NoAuthContextServiceLive=oe,exports.NoUsageHookServiceLive=ce,exports.NotFoundError=N,exports.OrganizationMismatchError=ie,exports.PERMISSIONS=w,exports.PERMISSION_HIERARCHY=T,exports.PERMISSION_SETS=ne,exports.QuotaExceededError=z,exports.UPLOAD_PERMISSIONS=C,exports.UsageHookService=W,exports.UsageHookServiceLive=G,exports.ValidationError=M,exports.abortResult=se,exports.continueResult=H,exports.createAuthorizationErrorResponseBody=ae,exports.createErrorResponseBody=F,exports.createFlowServerLayer=b,exports.createGenericErrorResponseBody=re,exports.createTypeSafeServer=Re,exports.createUploadServerLayer=y,exports.createUploadistaErrorResponseBody=I,exports.createUploadistaServer=Le,exports.defineFlow=ze,exports.defineSimpleFlow=Be,exports.expandPermission=A,exports.extractFlowAndStorageId=v,exports.extractJobAndNodeId=te,exports.extractJobIdFromStatus=_,exports.extractServiceIdentifiers=Ue,exports.formatPluginValidationError=$,exports.getAuthCredentials=e.t,exports.getLastSegment=m,exports.getRouteSegments=ee,exports.handleFlowError=g,exports.handleWebSocketClose=Qe,exports.handleWebSocketError=$e,exports.handleWebSocketMessage=Ze,exports.handleWebSocketOpen=Xe,exports.hasAllPermissions=k,exports.hasAnyPermission=O,exports.hasBasePath=h,exports.hasPermission=D,exports.matchesPermission=E,exports.parseUrlSegments=p,exports.validatePluginRequirements=Q,exports.validatePluginRequirementsEffect=We,exports.validatePluginsOrThrow=Ge;
|