@edgestore/shared 0.0.0-canary-20260730114359

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/errors/EdgeStoreError.d.ts +47 -0
  4. package/dist/errors/EdgeStoreError.d.ts.map +1 -0
  5. package/dist/errors/EdgeStoreError.js +43 -0
  6. package/dist/errors/index.d.ts +9 -0
  7. package/dist/errors/index.d.ts.map +1 -0
  8. package/dist/errors/index.js +20 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3 -0
  12. package/dist/internals/bucketBuilder.d.ts +315 -0
  13. package/dist/internals/bucketBuilder.d.ts.map +1 -0
  14. package/dist/internals/bucketBuilder.js +230 -0
  15. package/dist/internals/createPathParamProxy.d.ts +21 -0
  16. package/dist/internals/createPathParamProxy.d.ts.map +1 -0
  17. package/dist/internals/createPathParamProxy.js +29 -0
  18. package/dist/internals/providerCapabilities.d.ts +44 -0
  19. package/dist/internals/providerCapabilities.d.ts.map +1 -0
  20. package/dist/internals/providerTypes.d.ts +270 -0
  21. package/dist/internals/providerTypes.d.ts.map +1 -0
  22. package/dist/internals/sharedFuncTypes.d.ts +30 -0
  23. package/dist/internals/sharedFuncTypes.d.ts.map +1 -0
  24. package/dist/internals/types.d.ts +47 -0
  25. package/dist/internals/types.d.ts.map +1 -0
  26. package/dist/types.d.ts +94 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/package.json +81 -0
  29. package/src/errors/EdgeStoreError.ts +89 -0
  30. package/src/errors/index.ts +14 -0
  31. package/src/index.ts +7 -0
  32. package/src/internals/bucketBuilder.ts +677 -0
  33. package/src/internals/createPathParamProxy.ts +40 -0
  34. package/src/internals/providerCapabilities.ts +126 -0
  35. package/src/internals/providerTypes.ts +388 -0
  36. package/src/internals/sharedFuncTypes.ts +38 -0
  37. package/src/internals/types.ts +52 -0
  38. package/src/types.ts +146 -0
@@ -0,0 +1,677 @@
1
+ import { z } from 'zod';
2
+ import { EdgeStoreError } from '../errors';
3
+ import { type KeysOfUnion, type MaybePromise, type Simplify } from '../types';
4
+ import { createPathParamProxy } from './createPathParamProxy';
5
+
6
+ type Merge<TType, TWith> = {
7
+ [TKey in keyof TType | keyof TWith]?: TKey extends keyof TType
8
+ ? TKey extends keyof TWith
9
+ ? TType[TKey] & TWith[TKey]
10
+ : TType[TKey]
11
+ : TWith[TKey & keyof TWith];
12
+ };
13
+
14
+ type ConvertStringToFunction<TType> = {
15
+ [K in keyof TType]: TType[K] extends object
16
+ ? Simplify<ConvertStringToFunction<TType[K]>>
17
+ : () => string;
18
+ };
19
+
20
+ type UnionToIntersection<TType> = (
21
+ TType extends any ? (k: TType) => void : never
22
+ ) extends (k: infer I) => void
23
+ ? I
24
+ : never;
25
+
26
+ export type InferBucketPathKeys<TBucket extends Builder<any, AnyDef>> =
27
+ KeysOfUnion<TBucket['_def']['path'][number]>;
28
+
29
+ type InferBucketPathKeysFromDef<TDef extends AnyDef> = KeysOfUnion<
30
+ TDef['path'][number]
31
+ >;
32
+
33
+ export type InferBucketPathObject<TBucket extends Builder<any, AnyDef>> =
34
+ InferBucketPathKeys<TBucket> extends never
35
+ ? Record<string, never>
36
+ : {
37
+ [TKey in InferBucketPathKeys<TBucket>]: string;
38
+ };
39
+
40
+ export type InferBucketPathOrder<TBucket extends Builder<any, AnyDef>> =
41
+ InferBucketPathKeys<TBucket> extends never
42
+ ? []
43
+ : InferBucketPathKeys<TBucket>[];
44
+
45
+ export type InferBucketPathObjectFromDef<TDef extends AnyDef> =
46
+ InferBucketPathKeysFromDef<TDef> extends never
47
+ ? Record<string, never>
48
+ : {
49
+ [TKey in InferBucketPathKeysFromDef<TDef>]: string;
50
+ };
51
+
52
+ type NormalizeMetadata<TMetadata> = string extends keyof TMetadata
53
+ ? Record<string, Exclude<TMetadata[string], null | undefined>>
54
+ : Simplify<
55
+ {
56
+ [
57
+ TKey in keyof TMetadata as Extract<
58
+ TMetadata[TKey],
59
+ null | undefined
60
+ > extends never
61
+ ? TKey
62
+ : never
63
+ ]: Exclude<TMetadata[TKey], null | undefined>;
64
+ } & {
65
+ [
66
+ TKey in keyof TMetadata as Extract<
67
+ TMetadata[TKey],
68
+ null | undefined
69
+ > extends never
70
+ ? never
71
+ : TKey
72
+ ]?: Exclude<TMetadata[TKey], null | undefined>;
73
+ }
74
+ >;
75
+
76
+ export type InferMetadataObject<TBucket extends Builder<any, AnyDef>> =
77
+ TBucket['_def']['metadata'] extends (...args: any) => any
78
+ ? NormalizeMetadata<Awaited<ReturnType<TBucket['_def']['metadata']>>>
79
+ : Record<string, never>;
80
+
81
+ type InferMetadataObjectFromDef<TDef extends AnyDef> =
82
+ TDef['metadata'] extends (...args: any) => any
83
+ ? NormalizeMetadata<Awaited<ReturnType<TDef['metadata']>>>
84
+ : Record<string, never>;
85
+
86
+ export type AnyContextValue = string | undefined;
87
+
88
+ /**
89
+ * Context shared by router hooks, path and metadata builders, and providers.
90
+ *
91
+ * Values are limited to strings so every supported provider receives the same
92
+ * context shape. Optional properties may be `undefined`; providers omit them
93
+ * when serializing the context.
94
+ */
95
+ export interface AnyContext {
96
+ [key: string]: AnyContextValue;
97
+ }
98
+
99
+ export type AnyInput = z.AnyZodObject | z.ZodNever;
100
+
101
+ export type AnyPath = Record<string, () => string>[];
102
+
103
+ type PathParam<TPath extends AnyPath> = {
104
+ path: keyof UnionToIntersection<TPath[number]>;
105
+ };
106
+
107
+ type Conditions<TPath extends AnyPath> = {
108
+ eq?: string | PathParam<TPath>;
109
+ lt?: string | PathParam<TPath>;
110
+ lte?: string | PathParam<TPath>;
111
+ gt?: string | PathParam<TPath>;
112
+ gte?: string | PathParam<TPath>;
113
+ contains?: string | PathParam<TPath>;
114
+ in?: string | PathParam<TPath> | (string | PathParam<TPath>)[];
115
+ not?: string | PathParam<TPath> | Conditions<TPath>;
116
+ };
117
+
118
+ export type AccessControlSchema<TCtx, TDef extends AnyDef> = Merge<
119
+ {
120
+ [TKey in keyof TCtx]?:
121
+ string | PathParam<TDef['path']> | Conditions<TDef['path']>;
122
+ },
123
+ {
124
+ OR?: AccessControlSchema<TCtx, TDef>[];
125
+ AND?: AccessControlSchema<TCtx, TDef>[];
126
+ NOT?: AccessControlSchema<TCtx, TDef>[];
127
+ }
128
+ >;
129
+
130
+ export type AccessControl<TCtx, TDef extends AnyDef> =
131
+ 'private' | AccessControlSchema<TCtx, TDef>;
132
+
133
+ export type AutoSignedUrlsConfig = {
134
+ expiresIn?: number;
135
+ includeThumbnails?: boolean;
136
+ };
137
+
138
+ type BucketConfig = {
139
+ /**
140
+ * Maximum size for a single file in bytes
141
+ *
142
+ * e.g. 1024 * 1024 * 10 = 10MB
143
+ */
144
+ maxSize?: number;
145
+ /**
146
+ * Accepted MIME types
147
+ *
148
+ * e.g. ['image/jpeg', 'image/png']
149
+ *
150
+ * You can also use wildcards after the slash:
151
+ *
152
+ * e.g. ['image/*']
153
+ */
154
+ accept?: string[];
155
+ };
156
+
157
+ type BeforeUploadFn<TCtx, TDef extends AnyDef> = (params: {
158
+ ctx: TCtx;
159
+ input: z.infer<TDef['input']>;
160
+ fileInfo: {
161
+ size: number;
162
+ type: string;
163
+ extension: string;
164
+ fileName?: string;
165
+ replaceTargetUrl?: string;
166
+ temporary: boolean;
167
+ };
168
+ }) => MaybePromise<boolean>;
169
+
170
+ type BeforeDeleteFn<TCtx, TDef extends AnyDef> = (params: {
171
+ ctx: TCtx;
172
+ fileInfo: {
173
+ url: string;
174
+ size: number;
175
+ uploadedAt: Date;
176
+ path: InferBucketPathObjectFromDef<TDef>;
177
+ metadata: InferMetadataObjectFromDef<TDef>;
178
+ };
179
+ }) => MaybePromise<boolean>;
180
+
181
+ export type AnyMetadata = Record<string, string | undefined | null>;
182
+
183
+ type MetadataFn<
184
+ TCtx,
185
+ TInput extends AnyInput,
186
+ TMetadata extends AnyMetadata,
187
+ > = (params: { ctx: TCtx; input: z.infer<TInput> }) => MaybePromise<TMetadata>;
188
+
189
+ export type AnyMetadataFn = MetadataFn<any, AnyInput, AnyMetadata>;
190
+
191
+ type BucketType = 'IMAGE' | 'FILE';
192
+
193
+ type Def<
194
+ TInput extends AnyInput,
195
+ TPath extends AnyPath,
196
+ TMetadata extends AnyMetadataFn,
197
+ > = {
198
+ type: BucketType;
199
+ input: TInput;
200
+ path: TPath;
201
+ metadata: TMetadata;
202
+ bucketConfig?: BucketConfig;
203
+ accessControl?: AccessControl<any, any>;
204
+ autoSignedUrls?: AutoSignedUrlsConfig;
205
+ beforeUpload?: BeforeUploadFn<any, any>;
206
+ beforeDelete?: BeforeDeleteFn<any, any>;
207
+ };
208
+
209
+ type AnyDef = Def<AnyInput, AnyPath, AnyMetadataFn>;
210
+
211
+ type Builder<TCtx, TDef extends AnyDef> = {
212
+ /** only used for types */
213
+ $config: {
214
+ ctx: TCtx;
215
+ };
216
+ /**
217
+ * @internal
218
+ */
219
+ _def: TDef;
220
+ /**
221
+ * You can set an input that will be required in every upload from the client.
222
+ *
223
+ * This can be used to add additional information to the file, like choose the file path or add metadata.
224
+ */
225
+ input<TInput extends AnyInput>(
226
+ input: TInput,
227
+ ): Builder<
228
+ TCtx,
229
+ {
230
+ type: TDef['type'];
231
+ input: TInput;
232
+ path: TDef['path'];
233
+ metadata: TDef['metadata'];
234
+ bucketConfig: TDef['bucketConfig'];
235
+ accessControl: TDef['accessControl'];
236
+ autoSignedUrls: TDef['autoSignedUrls'];
237
+ beforeUpload: TDef['beforeUpload'];
238
+ beforeDelete: TDef['beforeDelete'];
239
+ }
240
+ >;
241
+ /**
242
+ * The `path` is similar to folders in a file system.
243
+ * But in this case, every segment of the path must have a meaning.
244
+ *
245
+ * ```
246
+ * // e.g. 123/profile/file.jpg
247
+ * {
248
+ * author: '123',
249
+ * type: 'profile',
250
+ * }
251
+ * ```
252
+ */
253
+ path<TParams extends AnyPath>(
254
+ pathResolver: (params: {
255
+ ctx: Simplify<ConvertStringToFunction<TCtx>>;
256
+ input: Simplify<ConvertStringToFunction<z.infer<TDef['input']>>>;
257
+ }) => [...TParams],
258
+ ): Builder<
259
+ TCtx,
260
+ {
261
+ type: TDef['type'];
262
+ input: TDef['input'];
263
+ path: TParams;
264
+ metadata: TDef['metadata'];
265
+ bucketConfig: TDef['bucketConfig'];
266
+ accessControl: TDef['accessControl'];
267
+ autoSignedUrls: TDef['autoSignedUrls'];
268
+ beforeUpload: TDef['beforeUpload'];
269
+ beforeDelete: TDef['beforeDelete'];
270
+ }
271
+ >;
272
+ /**
273
+ * This metadata will be added to every file uploaded to this bucket.
274
+ *
275
+ * This can be used, for example, to filter files.
276
+ */
277
+ metadata<TMetadata extends AnyMetadata>(
278
+ metadata: MetadataFn<TCtx, TDef['input'], TMetadata>,
279
+ ): Builder<
280
+ TCtx,
281
+ {
282
+ type: TDef['type'];
283
+ input: TDef['input'];
284
+ path: TDef['path'];
285
+ metadata: MetadataFn<any, any, TMetadata>;
286
+ bucketConfig: TDef['bucketConfig'];
287
+ accessControl: TDef['accessControl'];
288
+ autoSignedUrls: TDef['autoSignedUrls'];
289
+ beforeUpload: TDef['beforeUpload'];
290
+ beforeDelete: TDef['beforeDelete'];
291
+ }
292
+ >;
293
+ /**
294
+ * If you set this, your bucket will automatically be configured as a protected bucket.
295
+ *
296
+ * This means that images will only be accessible from within your app.
297
+ * And only if it passes the check set in this function.
298
+ */
299
+ accessControl(accessControl: AccessControl<TCtx, TDef>): Builder<
300
+ TCtx,
301
+ {
302
+ type: TDef['type'];
303
+ input: TDef['input'];
304
+ path: TDef['path'];
305
+ metadata: TDef['metadata'];
306
+ bucketConfig: TDef['bucketConfig'];
307
+ accessControl: AccessControl<any, any>;
308
+ autoSignedUrls: TDef['autoSignedUrls'];
309
+ beforeUpload: TDef['beforeUpload'];
310
+ beforeDelete: TDef['beforeDelete'];
311
+ }
312
+ >;
313
+ /**
314
+ * Automatically return temporary signed read URLs after uploads.
315
+ *
316
+ * This requires explicit non-public access control.
317
+ */
318
+ autoSignedUrls(config?: AutoSignedUrlsConfig): Builder<
319
+ TCtx,
320
+ {
321
+ type: TDef['type'];
322
+ input: TDef['input'];
323
+ path: TDef['path'];
324
+ metadata: TDef['metadata'];
325
+ bucketConfig: TDef['bucketConfig'];
326
+ accessControl: TDef['accessControl'];
327
+ autoSignedUrls: AutoSignedUrlsConfig;
328
+ beforeUpload: TDef['beforeUpload'];
329
+ beforeDelete: TDef['beforeDelete'];
330
+ }
331
+ >;
332
+ /**
333
+ * return `true` to allow upload
334
+ *
335
+ * By default, every upload from your app is allowed.
336
+ */
337
+ beforeUpload(beforeUpload: BeforeUploadFn<TCtx, TDef>): Builder<
338
+ TCtx,
339
+ {
340
+ type: TDef['type'];
341
+ input: TDef['input'];
342
+ path: TDef['path'];
343
+ metadata: TDef['metadata'];
344
+ bucketConfig: TDef['bucketConfig'];
345
+ accessControl: TDef['accessControl'];
346
+ autoSignedUrls: TDef['autoSignedUrls'];
347
+ beforeUpload: BeforeUploadFn<any, any>;
348
+ beforeDelete: TDef['beforeDelete'];
349
+ }
350
+ >;
351
+ /**
352
+ * return `true` to allow delete
353
+ *
354
+ * This function must be defined if you want to delete files directly from the client.
355
+ */
356
+ beforeDelete(beforeDelete: BeforeDeleteFn<TCtx, TDef>): Builder<
357
+ TCtx,
358
+ {
359
+ type: TDef['type'];
360
+ input: TDef['input'];
361
+ path: TDef['path'];
362
+ metadata: TDef['metadata'];
363
+ bucketConfig: TDef['bucketConfig'];
364
+ accessControl: TDef['accessControl'];
365
+ autoSignedUrls: TDef['autoSignedUrls'];
366
+ beforeUpload: TDef['beforeUpload'];
367
+ beforeDelete: BeforeDeleteFn<any, any>;
368
+ }
369
+ >;
370
+ };
371
+
372
+ export type AnyBuilder = Builder<any, AnyDef>;
373
+
374
+ const createNewBuilder = (initDef: AnyDef, newDef: Partial<AnyDef>) => {
375
+ const mergedDef = {
376
+ ...initDef,
377
+ ...newDef,
378
+ };
379
+ return createBuilder(
380
+ {
381
+ type: mergedDef.type,
382
+ },
383
+ mergedDef,
384
+ );
385
+ };
386
+
387
+ function createBuilder<
388
+ TCtx,
389
+ TType extends BucketType,
390
+ TInput extends AnyInput = z.ZodNever,
391
+ TPath extends AnyPath = [],
392
+ TMetadata extends AnyMetadataFn = () => Record<string, never>,
393
+ >(
394
+ opts: { type: TType },
395
+ initDef?: Partial<AnyDef>,
396
+ ): Builder<
397
+ TCtx,
398
+ {
399
+ type: TType;
400
+ input: TInput;
401
+ path: TPath;
402
+ metadata: TMetadata;
403
+ bucketConfig?: BucketConfig;
404
+ accessControl?: AccessControl<any, any>;
405
+ autoSignedUrls?: AutoSignedUrlsConfig;
406
+ beforeUpload?: BeforeUploadFn<any, any>;
407
+ beforeDelete?: BeforeDeleteFn<any, any>;
408
+ }
409
+ > {
410
+ const _def: AnyDef = {
411
+ type: opts.type,
412
+ input: z.never(),
413
+ path: [],
414
+ metadata: () => ({}),
415
+ ...initDef,
416
+ };
417
+
418
+ return {
419
+ $config: {
420
+ ctx: undefined as TCtx,
421
+ },
422
+ // @ts-expect-error - I think it would be too much work to make this type correct.
423
+ _def,
424
+ input(input) {
425
+ return createNewBuilder(_def, {
426
+ input,
427
+ }) as any;
428
+ },
429
+ path(pathResolver) {
430
+ const pathParamProxy = createPathParamProxy();
431
+ const params = pathResolver(pathParamProxy);
432
+ const pathKeys = new Set<string>();
433
+ for (const param of params) {
434
+ const entries = Object.entries(param);
435
+ if (entries.length !== 1) {
436
+ const foundKeys = entries.map(([key]) => key);
437
+ throw new EdgeStoreError({
438
+ message: `Path params must have exactly one key. Found keys: ${
439
+ foundKeys.length > 0 ? foundKeys.join(', ') : '(none)'
440
+ }`,
441
+ code: 'SERVER_ERROR',
442
+ });
443
+ }
444
+ const key = entries[0]?.[0];
445
+ if (key !== undefined && pathKeys.has(key)) {
446
+ throw new EdgeStoreError({
447
+ message: `Duplicate path param found: ${key}`,
448
+ code: 'SERVER_ERROR',
449
+ });
450
+ }
451
+ if (key !== undefined) {
452
+ pathKeys.add(key);
453
+ }
454
+ }
455
+ return createNewBuilder(_def, {
456
+ path: params,
457
+ }) as any;
458
+ },
459
+ metadata(metadata) {
460
+ return createNewBuilder(_def, {
461
+ metadata,
462
+ }) as any;
463
+ },
464
+ accessControl(accessControl) {
465
+ if (
466
+ typeof accessControl === 'object' &&
467
+ Object.keys(accessControl).length === 0
468
+ ) {
469
+ throw new EdgeStoreError({
470
+ message:
471
+ 'Empty accessControl objects are not allowed. Use accessControl("private") for signed-URL-only private files.',
472
+ code: 'SERVER_ERROR',
473
+ });
474
+ }
475
+ return createNewBuilder(_def, {
476
+ accessControl: accessControl,
477
+ }) as any;
478
+ },
479
+ autoSignedUrls(config) {
480
+ if (_def.accessControl === undefined) {
481
+ throw new EdgeStoreError({
482
+ message:
483
+ 'autoSignedUrls requires a non-public bucket. Add accessControl("private") or an access-control schema first.',
484
+ code: 'SERVER_ERROR',
485
+ });
486
+ }
487
+ return createNewBuilder(_def, {
488
+ autoSignedUrls: {
489
+ expiresIn: config?.expiresIn,
490
+ includeThumbnails:
491
+ config?.includeThumbnails ?? (_def.type === 'IMAGE' ? true : false),
492
+ },
493
+ }) as any;
494
+ },
495
+ beforeUpload(beforeUpload) {
496
+ return createNewBuilder(_def, {
497
+ beforeUpload,
498
+ }) as any;
499
+ },
500
+ beforeDelete(beforeDelete) {
501
+ return createNewBuilder(_def, {
502
+ beforeDelete,
503
+ }) as any;
504
+ },
505
+ };
506
+ }
507
+
508
+ class EdgeStoreBuilder<TCtx = Record<string, never>> {
509
+ context<TNewContext extends AnyContext>() {
510
+ return new EdgeStoreBuilder<TNewContext>();
511
+ }
512
+
513
+ create() {
514
+ return createEdgeStoreInner<TCtx>()();
515
+ }
516
+ }
517
+
518
+ export type EdgeStoreRouter<
519
+ TCtx,
520
+ TBuckets extends Record<string, Builder<TCtx, AnyDef>> = Record<
521
+ string,
522
+ Builder<TCtx, AnyDef>
523
+ >,
524
+ > = {
525
+ /**
526
+ * Only used for types
527
+ * @internal
528
+ */
529
+ $config: {
530
+ ctx: TCtx;
531
+ };
532
+ buckets: TBuckets;
533
+ };
534
+
535
+ export type AnyRouter = EdgeStoreRouter<any, Record<string, AnyBuilder>>;
536
+
537
+ function createRouterFactory<TCtx>() {
538
+ return function createRouterInner<
539
+ TBuckets extends EdgeStoreRouter<TCtx>['buckets'],
540
+ >(buckets: TBuckets) {
541
+ return {
542
+ $config: {
543
+ ctx: undefined as TCtx,
544
+ },
545
+ buckets,
546
+ } satisfies EdgeStoreRouter<TCtx, TBuckets>;
547
+ };
548
+ }
549
+
550
+ function initBucket<TCtx, TType extends BucketType>(
551
+ type: TType,
552
+ config?: BucketConfig,
553
+ ) {
554
+ return createBuilder<TCtx, TType>({ type }, { bucketConfig: config });
555
+ }
556
+
557
+ function createEdgeStoreInner<TCtx>() {
558
+ return function initEdgeStoreInner() {
559
+ return {
560
+ /**
561
+ * Builder object for creating an image bucket
562
+ */
563
+ imageBucket(config?: BucketConfig) {
564
+ return initBucket<TCtx, 'IMAGE'>('IMAGE', config);
565
+ },
566
+ /**
567
+ * Builder object for creating a file bucket
568
+ */
569
+ fileBucket(config?: BucketConfig) {
570
+ return initBucket<TCtx, 'FILE'>('FILE', config);
571
+ },
572
+ /**
573
+ * Create a router
574
+ */
575
+ router: createRouterFactory<TCtx>(),
576
+ };
577
+ };
578
+ }
579
+
580
+ /**
581
+ * Initialize EdgeStore - be done exactly once per backend
582
+ */
583
+ export const initEdgeStore = new EdgeStoreBuilder();
584
+
585
+ // ↓↓↓ TYPE TESTS ↓↓↓
586
+
587
+ // type Context = {
588
+ // userId: string;
589
+ // userRole: 'admin' | 'visitor';
590
+ // };
591
+
592
+ // const es = initEdgeStore.context<Context>().create();
593
+
594
+ // const imagesBucket = es.imageBucket()
595
+ // .input(
596
+ // z.object({
597
+ // type: z.enum(['profile', 'post']),
598
+ // extension: z.string().optional(),
599
+ // }),
600
+ // )
601
+ // .path(({ ctx, input }) => [{ author: ctx.userId }, { type: input.type }])
602
+ // .metadata(({ ctx, input }) => ({
603
+ // extension: input.extension,
604
+ // role: ctx.userRole,
605
+ // }))
606
+ // .beforeUpload(() => {
607
+ // return true;
608
+ // });
609
+ // const a = es.imageBucket()
610
+ // .input(z.object({ type: z.string(), someMeta: z.string().optional() }))
611
+ // .path(({ ctx, input }) => [{ author: ctx.userId }, { type: input.type }])
612
+ // .metadata(({ ctx, input }) => ({
613
+ // role: ctx.userRole,
614
+ // someMeta: input.someMeta,
615
+ // }))
616
+ // .accessControl({
617
+ // OR: [
618
+ // {
619
+ // userId: { path: 'author' }, // this will check if the userId is the same as the author in the path parameter
620
+ // },
621
+ // {
622
+ // userRole: 'admin', // this is the same as { userRole: { eq: "admin" } }
623
+ // },
624
+ // ],
625
+ // })
626
+ // .beforeUpload(({ ctx, input }) => {
627
+ // return true;
628
+ // })
629
+ // .beforeDelete(({ ctx, file }) => {
630
+ // return true;
631
+ // });
632
+
633
+ // const b = es.imageBucket().path(({ ctx }) => [{ author: ctx.userId }]);
634
+
635
+ // const router = es.router({
636
+ // original: imagesBucket,
637
+ // imageBucket: a,
638
+ // imageBucket2: b,
639
+ // });
640
+
641
+ // export { router };
642
+
643
+ // type ListFilesResponse<TBucket extends AnyRouter['buckets'][string]> = {
644
+ // data: {
645
+ // // url: string;
646
+ // // size: number;
647
+ // // uploadedAt: Date;
648
+ // // metadata: InferMetadataObject<TBucket>;
649
+ // path: InferBucketPathKeys<TBucket> extends string ? {
650
+ // [key: string]: string;
651
+ // } :{
652
+ // [TKey in InferBucketPathKeys<TBucket>]: string;
653
+ // };
654
+ // }[];
655
+ // pagination: {
656
+ // currentPage: number;
657
+ // totalPages: number;
658
+ // totalCount: number;
659
+ // };
660
+ // };
661
+
662
+ // type TPathKeys = 'author' | 'type';
663
+ // type TPathKeys2 = InferBucketPathKeys<AnyBuilder>;
664
+
665
+ // type ObjectWithKeys<TKeys extends string> = {
666
+ // [TKey in TKeys]: string;
667
+ // };
668
+
669
+ // type Test1 = ObjectWithKeys<TPathKeys>;
670
+ // type Test2 = ObjectWithKeys<TPathKeys2>;
671
+ // type PathKeys = InferBucketPathKeys<typeof router.buckets.imageBucket>;
672
+
673
+ // type MetadataKeys = InferMetadataObject<typeof router.buckets.imageBucket>;
674
+
675
+ // type MyEdgeStoreRouter = typeof router;
676
+
677
+ // type MyAccessControl = AccessControlSchema<Context, AnyDef>;
@@ -0,0 +1,40 @@
1
+ type RecursivePathProxy = {
2
+ (): string;
3
+ ctx: any;
4
+ input: any;
5
+ };
6
+
7
+ /**
8
+ * Creates a Proxy that prints the path to the property when called.
9
+ *
10
+ * Example:
11
+ *
12
+ * ```ts
13
+ * const pathParamProxy = createPathParamProxy();
14
+ * console.log(pathParamProxy.ctx.user.id());
15
+ * // Logs: "ctx.user.id"
16
+ * console.log(pathParamProxy.input.type());
17
+ * // Logs: "input.type"
18
+ * ```
19
+ */
20
+ export function createPathParamProxy(): RecursivePathProxy {
21
+ const getPath = (
22
+ target: string,
23
+ _prop: string | symbol,
24
+ ): RecursivePathProxy => {
25
+ const proxyFunction: RecursivePathProxy = (() =>
26
+ target) as RecursivePathProxy;
27
+
28
+ return new Proxy(proxyFunction, {
29
+ get: (_target, propChild) => {
30
+ return getPath(`${target}.${String(propChild)}`, propChild);
31
+ },
32
+ });
33
+ };
34
+
35
+ return new Proxy((() => '') as RecursivePathProxy, {
36
+ get: (_target, prop) => {
37
+ return getPath(String(prop), String(prop));
38
+ },
39
+ });
40
+ }