@uploadista/server 0.0.3

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 (91) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +34 -0
  3. package/LICENSE +21 -0
  4. package/README.md +503 -0
  5. package/dist/auth/cache.d.ts +87 -0
  6. package/dist/auth/cache.d.ts.map +1 -0
  7. package/dist/auth/cache.js +121 -0
  8. package/dist/auth/cache.test.d.ts +2 -0
  9. package/dist/auth/cache.test.d.ts.map +1 -0
  10. package/dist/auth/cache.test.js +209 -0
  11. package/dist/auth/get-auth-credentials.d.ts +73 -0
  12. package/dist/auth/get-auth-credentials.d.ts.map +1 -0
  13. package/dist/auth/get-auth-credentials.js +55 -0
  14. package/dist/auth/index.d.ts +2 -0
  15. package/dist/auth/index.d.ts.map +1 -0
  16. package/dist/auth/index.js +1 -0
  17. package/dist/auth/jwt/index.d.ts +38 -0
  18. package/dist/auth/jwt/index.d.ts.map +1 -0
  19. package/dist/auth/jwt/index.js +36 -0
  20. package/dist/auth/jwt/types.d.ts +77 -0
  21. package/dist/auth/jwt/types.d.ts.map +1 -0
  22. package/dist/auth/jwt/types.js +1 -0
  23. package/dist/auth/jwt/validate.d.ts +58 -0
  24. package/dist/auth/jwt/validate.d.ts.map +1 -0
  25. package/dist/auth/jwt/validate.js +226 -0
  26. package/dist/auth/jwt/validate.test.d.ts +2 -0
  27. package/dist/auth/jwt/validate.test.d.ts.map +1 -0
  28. package/dist/auth/jwt/validate.test.js +492 -0
  29. package/dist/auth/service.d.ts +63 -0
  30. package/dist/auth/service.d.ts.map +1 -0
  31. package/dist/auth/service.js +43 -0
  32. package/dist/auth/service.test.d.ts +2 -0
  33. package/dist/auth/service.test.d.ts.map +1 -0
  34. package/dist/auth/service.test.js +195 -0
  35. package/dist/auth/types.d.ts +38 -0
  36. package/dist/auth/types.d.ts.map +1 -0
  37. package/dist/auth/types.js +1 -0
  38. package/dist/cache.d.ts +87 -0
  39. package/dist/cache.d.ts.map +1 -0
  40. package/dist/cache.js +121 -0
  41. package/dist/cache.test.d.ts +2 -0
  42. package/dist/cache.test.d.ts.map +1 -0
  43. package/dist/cache.test.js +209 -0
  44. package/dist/cloudflare-config.d.ts +72 -0
  45. package/dist/cloudflare-config.d.ts.map +1 -0
  46. package/dist/cloudflare-config.js +67 -0
  47. package/dist/error-types.d.ts +138 -0
  48. package/dist/error-types.d.ts.map +1 -0
  49. package/dist/error-types.js +155 -0
  50. package/dist/hono-adapter.d.ts +48 -0
  51. package/dist/hono-adapter.d.ts.map +1 -0
  52. package/dist/hono-adapter.js +58 -0
  53. package/dist/http-utils.d.ts +148 -0
  54. package/dist/http-utils.d.ts.map +1 -0
  55. package/dist/http-utils.js +233 -0
  56. package/dist/index.d.ts +9 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +8 -0
  59. package/dist/layer-utils.d.ts +121 -0
  60. package/dist/layer-utils.d.ts.map +1 -0
  61. package/dist/layer-utils.js +80 -0
  62. package/dist/metrics/service.d.ts +26 -0
  63. package/dist/metrics/service.d.ts.map +1 -0
  64. package/dist/metrics/service.js +20 -0
  65. package/dist/plugins-typing.d.ts +11 -0
  66. package/dist/plugins-typing.d.ts.map +1 -0
  67. package/dist/plugins-typing.js +1 -0
  68. package/dist/service.d.ts +63 -0
  69. package/dist/service.d.ts.map +1 -0
  70. package/dist/service.js +43 -0
  71. package/dist/service.test.d.ts +2 -0
  72. package/dist/service.test.d.ts.map +1 -0
  73. package/dist/service.test.js +195 -0
  74. package/dist/types.d.ts +38 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/dist/types.js +1 -0
  77. package/package.json +47 -0
  78. package/src/auth/get-auth-credentials.ts +97 -0
  79. package/src/auth/index.ts +1 -0
  80. package/src/cache.test.ts +306 -0
  81. package/src/cache.ts +204 -0
  82. package/src/error-types.ts +172 -0
  83. package/src/http-utils.ts +264 -0
  84. package/src/index.ts +8 -0
  85. package/src/layer-utils.ts +184 -0
  86. package/src/plugins-typing.ts +57 -0
  87. package/src/service.test.ts +275 -0
  88. package/src/service.ts +78 -0
  89. package/src/types.ts +40 -0
  90. package/tsconfig.json +13 -0
  91. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,275 @@
1
+ import { Effect } from "effect";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ AuthContextService,
5
+ AuthContextServiceLive,
6
+ NoAuthContextServiceLive,
7
+ } from "./service";
8
+ import type { AuthContext } from "./types";
9
+
10
+ describe("AuthContextService", () => {
11
+ describe("AuthContextServiceLive", () => {
12
+ it("should return userId when auth context is provided", async () => {
13
+ const authContext: AuthContext = {
14
+ clientId: "user-123",
15
+ metadata: { role: "admin" },
16
+ permissions: ["upload:create", "flow:execute"],
17
+ };
18
+
19
+ const layer = AuthContextServiceLive(authContext);
20
+ const program = Effect.gen(function* () {
21
+ const service = yield* AuthContextService;
22
+ return yield* service.getClientId();
23
+ });
24
+
25
+ const result = await Effect.runPromise(
26
+ program.pipe(Effect.provide(layer)),
27
+ );
28
+ expect(result).toBe("user-123");
29
+ });
30
+
31
+ it("should return null when auth context is null", async () => {
32
+ const layer = AuthContextServiceLive(null);
33
+ const program = Effect.gen(function* () {
34
+ const service = yield* AuthContextService;
35
+ return yield* service.getClientId();
36
+ });
37
+
38
+ const result = await Effect.runPromise(
39
+ program.pipe(Effect.provide(layer)),
40
+ );
41
+ expect(result).toBeNull();
42
+ });
43
+
44
+ it("should return metadata when auth context is provided", async () => {
45
+ const authContext: AuthContext = {
46
+ clientId: "user-123",
47
+ metadata: { role: "admin", tier: "premium" },
48
+ permissions: [],
49
+ };
50
+
51
+ const layer = AuthContextServiceLive(authContext);
52
+ const program = Effect.gen(function* () {
53
+ const service = yield* AuthContextService;
54
+ return yield* service.getMetadata();
55
+ });
56
+
57
+ const result = await Effect.runPromise(
58
+ program.pipe(Effect.provide(layer)),
59
+ );
60
+ expect(result).toEqual({ role: "admin", tier: "premium" });
61
+ });
62
+
63
+ it("should return empty object when auth context has no metadata", async () => {
64
+ const authContext: AuthContext = {
65
+ clientId: "user-123",
66
+ };
67
+
68
+ const layer = AuthContextServiceLive(authContext);
69
+ const program = Effect.gen(function* () {
70
+ const service = yield* AuthContextService;
71
+ return yield* service.getMetadata();
72
+ });
73
+
74
+ const result = await Effect.runPromise(
75
+ program.pipe(Effect.provide(layer)),
76
+ );
77
+ expect(result).toEqual({});
78
+ });
79
+
80
+ it("should return empty object when auth context is null", async () => {
81
+ const layer = AuthContextServiceLive(null);
82
+ const program = Effect.gen(function* () {
83
+ const service = yield* AuthContextService;
84
+ return yield* service.getMetadata();
85
+ });
86
+
87
+ const result = await Effect.runPromise(
88
+ program.pipe(Effect.provide(layer)),
89
+ );
90
+ expect(result).toEqual({});
91
+ });
92
+
93
+ it("should return true for existing permission", async () => {
94
+ const authContext: AuthContext = {
95
+ clientId: "user-123",
96
+ permissions: ["upload:create", "flow:execute", "admin:read"],
97
+ };
98
+
99
+ const layer = AuthContextServiceLive(authContext);
100
+ const program = Effect.gen(function* () {
101
+ const service = yield* AuthContextService;
102
+ return yield* service.hasPermission("flow:execute");
103
+ });
104
+
105
+ const result = await Effect.runPromise(
106
+ program.pipe(Effect.provide(layer)),
107
+ );
108
+ expect(result).toBe(true);
109
+ });
110
+
111
+ it("should return false for non-existing permission", async () => {
112
+ const authContext: AuthContext = {
113
+ clientId: "user-123",
114
+ permissions: ["upload:create", "flow:execute"],
115
+ };
116
+
117
+ const layer = AuthContextServiceLive(authContext);
118
+ const program = Effect.gen(function* () {
119
+ const service = yield* AuthContextService;
120
+ return yield* service.hasPermission("admin:write");
121
+ });
122
+
123
+ const result = await Effect.runPromise(
124
+ program.pipe(Effect.provide(layer)),
125
+ );
126
+ expect(result).toBe(false);
127
+ });
128
+
129
+ it("should return false for permission check when no permissions array", async () => {
130
+ const authContext: AuthContext = {
131
+ clientId: "user-123",
132
+ };
133
+
134
+ const layer = AuthContextServiceLive(authContext);
135
+ const program = Effect.gen(function* () {
136
+ const service = yield* AuthContextService;
137
+ return yield* service.hasPermission("upload:create");
138
+ });
139
+
140
+ const result = await Effect.runPromise(
141
+ program.pipe(Effect.provide(layer)),
142
+ );
143
+ expect(result).toBe(false);
144
+ });
145
+
146
+ it("should return false for permission check when auth context is null", async () => {
147
+ const layer = AuthContextServiceLive(null);
148
+ const program = Effect.gen(function* () {
149
+ const service = yield* AuthContextService;
150
+ return yield* service.hasPermission("upload:create");
151
+ });
152
+
153
+ const result = await Effect.runPromise(
154
+ program.pipe(Effect.provide(layer)),
155
+ );
156
+ expect(result).toBe(false);
157
+ });
158
+
159
+ it("should return full auth context when provided", async () => {
160
+ const authContext: AuthContext = {
161
+ clientId: "user-123",
162
+ metadata: { role: "admin" },
163
+ permissions: ["upload:create"],
164
+ };
165
+
166
+ const layer = AuthContextServiceLive(authContext);
167
+ const program = Effect.gen(function* () {
168
+ const service = yield* AuthContextService;
169
+ return yield* service.getAuthContext();
170
+ });
171
+
172
+ const result = await Effect.runPromise(
173
+ program.pipe(Effect.provide(layer)),
174
+ );
175
+ expect(result).toEqual(authContext);
176
+ });
177
+
178
+ it("should return null auth context when not provided", async () => {
179
+ const layer = AuthContextServiceLive(null);
180
+ const program = Effect.gen(function* () {
181
+ const service = yield* AuthContextService;
182
+ return yield* service.getAuthContext();
183
+ });
184
+
185
+ const result = await Effect.runPromise(
186
+ program.pipe(Effect.provide(layer)),
187
+ );
188
+ expect(result).toBeNull();
189
+ });
190
+ });
191
+
192
+ describe("NoAuthContextServiceLive", () => {
193
+ it("should return null for getUserId", async () => {
194
+ const program = Effect.gen(function* () {
195
+ const service = yield* AuthContextService;
196
+ return yield* service.getClientId();
197
+ });
198
+
199
+ const result = await Effect.runPromise(
200
+ program.pipe(Effect.provide(NoAuthContextServiceLive)),
201
+ );
202
+ expect(result).toBeNull();
203
+ });
204
+
205
+ it("should return empty object for getMetadata", async () => {
206
+ const program = Effect.gen(function* () {
207
+ const service = yield* AuthContextService;
208
+ return yield* service.getMetadata();
209
+ });
210
+
211
+ const result = await Effect.runPromise(
212
+ program.pipe(Effect.provide(NoAuthContextServiceLive)),
213
+ );
214
+ expect(result).toEqual({});
215
+ });
216
+
217
+ it("should return false for any permission check", async () => {
218
+ const program = Effect.gen(function* () {
219
+ const service = yield* AuthContextService;
220
+ const result1 = yield* service.hasPermission("upload:create");
221
+ const result2 = yield* service.hasPermission("admin:write");
222
+ return { result1, result2 };
223
+ });
224
+
225
+ const result = await Effect.runPromise(
226
+ program.pipe(Effect.provide(NoAuthContextServiceLive)),
227
+ );
228
+ expect(result.result1).toBe(false);
229
+ expect(result.result2).toBe(false);
230
+ });
231
+
232
+ it("should return null for getAuthContext", async () => {
233
+ const program = Effect.gen(function* () {
234
+ const service = yield* AuthContextService;
235
+ return yield* service.getAuthContext();
236
+ });
237
+
238
+ const result = await Effect.runPromise(
239
+ program.pipe(Effect.provide(NoAuthContextServiceLive)),
240
+ );
241
+ expect(result).toBeNull();
242
+ });
243
+ });
244
+
245
+ describe("Effect Layer composition", () => {
246
+ it("should work in composed effect programs", async () => {
247
+ const authContext: AuthContext = {
248
+ clientId: "user-456",
249
+ metadata: { department: "engineering" },
250
+ permissions: ["flow:execute"],
251
+ };
252
+
253
+ const layer = AuthContextServiceLive(authContext);
254
+
255
+ const program = Effect.gen(function* () {
256
+ const service = yield* AuthContextService;
257
+ const clientId = yield* service.getClientId();
258
+ const metadata = yield* service.getMetadata();
259
+ const hasPermission = yield* service.hasPermission("flow:execute");
260
+
261
+ return { clientId, metadata, hasPermission };
262
+ });
263
+
264
+ const result = await Effect.runPromise(
265
+ program.pipe(Effect.provide(layer)),
266
+ );
267
+
268
+ expect(result).toEqual({
269
+ clientId: "user-456",
270
+ metadata: { department: "engineering" },
271
+ hasPermission: true,
272
+ });
273
+ });
274
+ });
275
+ });
package/src/service.ts ADDED
@@ -0,0 +1,78 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import type { AuthContext } from "./types";
3
+
4
+ /**
5
+ * Authentication Context Service
6
+ *
7
+ * Provides access to the current authentication context throughout
8
+ * the upload and flow processing pipeline. The service is provided
9
+ * via Effect Layer and can be accessed using Effect.service().
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Effect } from "effect";
14
+ * import { AuthContextService } from "@uploadista/server";
15
+ *
16
+ * const uploadHandler = Effect.gen(function* () {
17
+ * const authService = yield* AuthContextService;
18
+ * const clientId = yield* authService.getClientId();
19
+ * if (clientId) {
20
+ * console.log(`Processing upload for client: ${clientId}`);
21
+ * }
22
+ * });
23
+ * ```
24
+ */
25
+ export class AuthContextService extends Context.Tag("AuthContextService")<
26
+ AuthContextService,
27
+ {
28
+ /**
29
+ * Get the current client ID from auth context.
30
+ * Returns null if no authentication context is available.
31
+ */
32
+ readonly getClientId: () => Effect.Effect<string | null>;
33
+
34
+ /**
35
+ * Get the current auth metadata.
36
+ * Returns empty object if no authentication context or no metadata.
37
+ */
38
+ readonly getMetadata: () => Effect.Effect<Record<string, unknown>>;
39
+
40
+ /**
41
+ * Check if the current client has a specific permission.
42
+ * Returns false if no authentication context or permission not found.
43
+ */
44
+ readonly hasPermission: (permission: string) => Effect.Effect<boolean>;
45
+
46
+ /**
47
+ * Get the full authentication context if available.
48
+ * Returns null if no authentication context is available.
49
+ */
50
+ readonly getAuthContext: () => Effect.Effect<AuthContext | null>;
51
+ }
52
+ >() {}
53
+
54
+ /**
55
+ * Creates an AuthContextService Layer from an AuthContext.
56
+ * This is typically called by adapters after successful authentication.
57
+ *
58
+ * @param authContext - The authentication context from middleware
59
+ * @returns Effect Layer providing AuthContextService
60
+ */
61
+ export const AuthContextServiceLive = (
62
+ authContext: AuthContext | null,
63
+ ): Layer.Layer<AuthContextService> =>
64
+ Layer.succeed(AuthContextService, {
65
+ getClientId: () => Effect.succeed(authContext?.clientId ?? null),
66
+ getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),
67
+ hasPermission: (permission: string) =>
68
+ Effect.succeed(authContext?.permissions?.includes(permission) ?? false),
69
+ getAuthContext: () => Effect.succeed(authContext),
70
+ });
71
+
72
+ /**
73
+ * No-auth implementation of AuthContextService.
74
+ * Returns null/empty values for all operations.
75
+ * Used when no authentication middleware is configured (backward compatibility).
76
+ */
77
+ export const NoAuthContextServiceLive: Layer.Layer<AuthContextService> =
78
+ AuthContextServiceLive(null);
package/src/types.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Authentication context containing user identity and authorization metadata.
3
+ * This context is extracted from authentication middleware and made available
4
+ * throughout the upload and flow processing pipeline via Effect Layer.
5
+ */
6
+ export type AuthContext = {
7
+ /**
8
+ * Unique identifier for the authenticated user.
9
+ * This is typically extracted from JWT claims (sub), session data, or API key metadata.
10
+ */
11
+ clientId: string;
12
+
13
+ /**
14
+ * Optional metadata for authorization and tracking purposes.
15
+ * Can include rate limits, quotas, permissions, or custom application data.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * {
20
+ * permissions: ['upload:create', 'flow:execute'],
21
+ * rateLimit: { requests: 1000, period: 3600 },
22
+ * quota: { storage: 10737418240, used: 5368709120 }
23
+ * }
24
+ * ```
25
+ */
26
+ metadata?: Record<string, unknown>;
27
+
28
+ /**
29
+ * Optional list of permissions granted to the user.
30
+ * These can be used for fine-grained access control in the future.
31
+ */
32
+ permissions?: string[];
33
+ };
34
+
35
+ /**
36
+ * Result type for authentication middleware.
37
+ * - AuthContext: Successful authentication with user identity
38
+ * - null: Authentication failed or not authenticated
39
+ */
40
+ export type AuthResult = AuthContext | null;
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@uploadista/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "types": ["@cloudflare/workers-types"],
7
+ "composite": true,
8
+ "incremental": true,
9
+ "moduleResolution": "bundler"
10
+ },
11
+ "include": ["src/**/*"],
12
+ "exclude": ["node_modules", "dist"]
13
+ }