@cedarjs/storage 0.0.4

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/UrlSigner.d.ts +46 -0
  4. package/dist/UrlSigner.d.ts.map +1 -0
  5. package/dist/UrlSigner.js +106 -0
  6. package/dist/adapters/BaseStorageAdapter.d.ts +31 -0
  7. package/dist/adapters/BaseStorageAdapter.d.ts.map +1 -0
  8. package/dist/adapters/BaseStorageAdapter.js +19 -0
  9. package/dist/adapters/FileSystemStorage/FileSystemStorage.d.ts +16 -0
  10. package/dist/adapters/FileSystemStorage/FileSystemStorage.d.ts.map +1 -0
  11. package/dist/adapters/FileSystemStorage/FileSystemStorage.js +38 -0
  12. package/dist/adapters/MemoryStorage/MemoryStorage.d.ts +15 -0
  13. package/dist/adapters/MemoryStorage/MemoryStorage.d.ts.map +1 -0
  14. package/dist/adapters/MemoryStorage/MemoryStorage.js +33 -0
  15. package/dist/cjs/UrlSigner.d.ts +46 -0
  16. package/dist/cjs/UrlSigner.d.ts.map +1 -0
  17. package/dist/cjs/UrlSigner.js +142 -0
  18. package/dist/cjs/adapters/BaseStorageAdapter.d.ts +31 -0
  19. package/dist/cjs/adapters/BaseStorageAdapter.d.ts.map +1 -0
  20. package/dist/cjs/adapters/BaseStorageAdapter.js +53 -0
  21. package/dist/cjs/adapters/FileSystemStorage/FileSystemStorage.d.ts +16 -0
  22. package/dist/cjs/adapters/FileSystemStorage/FileSystemStorage.d.ts.map +1 -0
  23. package/dist/cjs/adapters/FileSystemStorage/FileSystemStorage.js +72 -0
  24. package/dist/cjs/adapters/MemoryStorage/MemoryStorage.d.ts +15 -0
  25. package/dist/cjs/adapters/MemoryStorage/MemoryStorage.d.ts.map +1 -0
  26. package/dist/cjs/adapters/MemoryStorage/MemoryStorage.js +67 -0
  27. package/dist/cjs/createSavers.d.ts +15 -0
  28. package/dist/cjs/createSavers.d.ts.map +1 -0
  29. package/dist/cjs/createSavers.js +75 -0
  30. package/dist/cjs/fileToDataUri.d.ts +3 -0
  31. package/dist/cjs/fileToDataUri.d.ts.map +1 -0
  32. package/dist/cjs/fileToDataUri.js +32 -0
  33. package/dist/cjs/index.d.ts +40 -0
  34. package/dist/cjs/index.d.ts.map +1 -0
  35. package/dist/cjs/index.js +50 -0
  36. package/dist/cjs/package.json +1 -0
  37. package/dist/cjs/prismaExtension.d.ts +29 -0
  38. package/dist/cjs/prismaExtension.d.ts.map +1 -0
  39. package/dist/cjs/prismaExtension.js +240 -0
  40. package/dist/createSavers.d.ts +15 -0
  41. package/dist/createSavers.d.ts.map +1 -0
  42. package/dist/createSavers.js +50 -0
  43. package/dist/fileToDataUri.d.ts +3 -0
  44. package/dist/fileToDataUri.d.ts.map +1 -0
  45. package/dist/fileToDataUri.js +8 -0
  46. package/dist/index.d.ts +40 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +25 -0
  49. package/dist/prismaExtension.d.ts +29 -0
  50. package/dist/prismaExtension.d.ts.map +1 -0
  51. package/dist/prismaExtension.js +216 -0
  52. package/package.json +77 -0
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var prismaExtension_exports = {};
20
+ __export(prismaExtension_exports, {
21
+ createUploadsExtension: () => createUploadsExtension
22
+ });
23
+ module.exports = __toCommonJS(prismaExtension_exports);
24
+ var import_client = require("@prisma/client");
25
+ var import_extension = require("@prisma/client/extension");
26
+ var import_fileToDataUri = require("./fileToDataUri.js");
27
+ const createUploadsExtension = (config, storageAdapter, urlSigner) => {
28
+ const prismaInstance = new import_client.PrismaClient();
29
+ const queryExtends = {};
30
+ const resultExtends = {};
31
+ for (const modelName in config) {
32
+ const modelConfig = config[modelName];
33
+ if (!modelConfig) {
34
+ continue;
35
+ }
36
+ const uploadFields = Array.isArray(modelConfig.fields) ? modelConfig.fields : [modelConfig.fields];
37
+ queryExtends[modelName] = {
38
+ async create({ query, args }) {
39
+ try {
40
+ const result = await query(args);
41
+ return result;
42
+ } catch (e) {
43
+ await removeUploadedFiles(
44
+ uploadFields,
45
+ args.data
46
+ );
47
+ throw e;
48
+ }
49
+ },
50
+ async createMany({ query, args }) {
51
+ try {
52
+ const result = await query(args);
53
+ return result;
54
+ } catch (e) {
55
+ const createDatas = args.data;
56
+ for (const createData of createDatas) {
57
+ await removeUploadedFiles(uploadFields, createData);
58
+ }
59
+ throw e;
60
+ }
61
+ },
62
+ async update({ query, model, args }) {
63
+ const uploadFieldsToUpdate = uploadFields.filter(
64
+ (field) => (
65
+ // All of this non-sense is to make typescript happy. I'm not sure how data could be anything but an object
66
+ typeof args.data === "object" && args.data !== null && field in args.data
67
+ )
68
+ );
69
+ if (uploadFieldsToUpdate.length == 0) {
70
+ return query(args);
71
+ } else {
72
+ const originalRecord = await prismaInstance[
73
+ model
74
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
75
+ ].findFirstOrThrow({
76
+ where: args.where
77
+ // @TODO: should we select here to reduce the amount of data we're handling
78
+ });
79
+ try {
80
+ const result = await query(args);
81
+ await removeUploadedFiles(uploadFieldsToUpdate, originalRecord);
82
+ return result;
83
+ } catch (e) {
84
+ await removeUploadedFiles(
85
+ uploadFieldsToUpdate,
86
+ args.data
87
+ );
88
+ throw e;
89
+ }
90
+ }
91
+ },
92
+ async updateMany({ query, model, args }) {
93
+ const uploadFieldsToUpdate = uploadFields.filter(
94
+ (field) => (
95
+ // All of this non-sense is to make typescript happy. I'm not sure how data could be anything but an object
96
+ typeof args.data === "object" && args.data !== null && field in args.data
97
+ )
98
+ );
99
+ if (uploadFieldsToUpdate.length == 0) {
100
+ return query(args);
101
+ } else {
102
+ const originalRecords = await prismaInstance[
103
+ model
104
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
105
+ ].findMany({
106
+ where: args.where
107
+ // @TODO: should we select here to reduce the amount of data we're handling
108
+ });
109
+ try {
110
+ const result = await query(args);
111
+ for await (const originalRecord of originalRecords) {
112
+ await removeUploadedFiles(uploadFieldsToUpdate, originalRecord);
113
+ }
114
+ return result;
115
+ } catch (e) {
116
+ await removeUploadedFiles(
117
+ uploadFieldsToUpdate,
118
+ args.data
119
+ );
120
+ throw e;
121
+ }
122
+ }
123
+ },
124
+ async upsert({ query, model, args }) {
125
+ let isUpdate;
126
+ const uploadFieldsToUpdate = uploadFields.filter(
127
+ (field) => typeof args.update === "object" && args.update !== null && field in args.update
128
+ );
129
+ try {
130
+ let existingRecord;
131
+ if (args.update) {
132
+ existingRecord = await prismaInstance[
133
+ model
134
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
135
+ ].findUnique({
136
+ where: args.where
137
+ });
138
+ isUpdate = !!existingRecord;
139
+ }
140
+ const result = await query(args);
141
+ if (isUpdate && existingRecord) {
142
+ await removeUploadedFiles(uploadFieldsToUpdate, existingRecord);
143
+ }
144
+ return result;
145
+ } catch (e) {
146
+ await removeUploadedFiles(
147
+ // Only delete files we're updating on update
148
+ isUpdate ? uploadFieldsToUpdate : uploadFields,
149
+ isUpdate ? args.update : args.create
150
+ );
151
+ throw e;
152
+ }
153
+ },
154
+ async delete({ query, args }) {
155
+ const deleteResult = await query(args);
156
+ await removeUploadedFiles(
157
+ uploadFields,
158
+ // We don't know the exact type here
159
+ deleteResult
160
+ );
161
+ return deleteResult;
162
+ }
163
+ };
164
+ const needs = Object.fromEntries(uploadFields.map((field) => [field, true]));
165
+ resultExtends[modelName] = {
166
+ withDataUri: {
167
+ needs,
168
+ compute(modelData) {
169
+ return async () => {
170
+ const base64UploadFields = {};
171
+ for (const field of uploadFields) {
172
+ base64UploadFields[field] = await (0, import_fileToDataUri.fileToDataUri)(
173
+ modelData[field],
174
+ storageAdapter
175
+ );
176
+ }
177
+ return {
178
+ // modelData is of type unknown at this point
179
+ ...modelData,
180
+ ...base64UploadFields
181
+ };
182
+ };
183
+ }
184
+ },
185
+ withSignedUrl: {
186
+ needs,
187
+ compute(modelData) {
188
+ return ({ expiresIn } = {}) => {
189
+ if (!urlSigner) {
190
+ throw new Error(
191
+ "Please supply signed url settings in setupUpload()"
192
+ );
193
+ }
194
+ const signedUrlFields = {};
195
+ for (const field of uploadFields) {
196
+ if (!modelData[field]) {
197
+ continue;
198
+ }
199
+ signedUrlFields[field] = urlSigner.generateSignedUrl(
200
+ modelData[field],
201
+ expiresIn
202
+ );
203
+ }
204
+ return {
205
+ // modelData is of type unknown at this point
206
+ ...modelData,
207
+ ...signedUrlFields
208
+ };
209
+ };
210
+ }
211
+ }
212
+ };
213
+ }
214
+ return import_extension.Prisma.defineExtension((client) => {
215
+ return client.$extends({
216
+ name: "redwood-upload-prisma-plugin",
217
+ query: queryExtends,
218
+ result: resultExtends
219
+ });
220
+ });
221
+ async function removeUploadedFiles(fieldsToDelete, data) {
222
+ if (!data) {
223
+ console.warn("Empty data object passed to removeUploadedFiles");
224
+ return;
225
+ }
226
+ for (const field of fieldsToDelete) {
227
+ const uploadLocation = data?.[field];
228
+ if (uploadLocation) {
229
+ try {
230
+ await storageAdapter.remove(uploadLocation);
231
+ } catch {
232
+ }
233
+ }
234
+ }
235
+ }
236
+ };
237
+ // Annotate the CommonJS export names for ESM import in node:
238
+ 0 && (module.exports = {
239
+ createUploadsExtension
240
+ });
@@ -0,0 +1,15 @@
1
+ import type { SaveOptionsOverride, BaseStorageAdapter } from './adapters/BaseStorageAdapter.js';
2
+ import type { ModelNames, UploadsConfig } from './prismaExtension.js';
3
+ type MakeFilesString<T> = {
4
+ [K in keyof T]: T[K] extends File ? string : T[K];
5
+ };
6
+ export declare const createFileListSaver: (storage: BaseStorageAdapter) => (files?: File[], pathOverrideOnly?: {
7
+ path?: string;
8
+ }) => Promise<string[]>;
9
+ export declare const createUploadSavers: <MNames extends ModelNames = ModelNames>(uploadConfig: UploadsConfig<MNames>, storage: BaseStorageAdapter) => { [K in `for${Capitalize<MNames>}`]: <T extends Record<string, any>>(data: T, overrideSaveOptions?: SaveOptionsOverride) => Promise<MakeFilesString<T>>; } & {
10
+ inList: (files?: File[], pathOverrideOnly?: {
11
+ path?: string;
12
+ }) => Promise<string[]>;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=createSavers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSavers.d.ts","sourceRoot":"","sources":["../src/createSavers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGrE,KAAK,eAAe,CAAC,CAAC,IAAI;KACvB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;CAClD,CAAA;AAED,eAAO,MAAM,mBAAmB,YAAa,kBAAkB,cACxC,IAAI,EAAE,qBAA0B;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,sBAUvE,CAAA;AAMD,eAAO,MAAM,kBAAkB,GAAI,MAAM,SAAS,UAAU,6BAC5C,aAAa,CAAC,MAAM,CAAC,WAC1B,kBAAkB,QAMxB,CAAC,kCAAwB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAC/C,CAAC,wBACe,mBAAmB,KACtC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;qBA3Bb,IAAI,EAAE,qBAA0B;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;CA2EvE,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { ulid } from "ulid";
2
+ const createFileListSaver = (storage) => {
3
+ return async (files = [], pathOverrideOnly) => {
4
+ const locations = await Promise.all(
5
+ files.map(async (file) => {
6
+ const { location } = await storage.save(file, pathOverrideOnly);
7
+ return location;
8
+ })
9
+ );
10
+ return locations;
11
+ };
12
+ };
13
+ const createUploadSavers = (uploadConfig, storage) => {
14
+ const savers = {};
15
+ Object.keys(uploadConfig).forEach((model) => {
16
+ const modelKey = model;
17
+ const currentModelConfig = uploadConfig[modelKey];
18
+ if (!currentModelConfig) {
19
+ return;
20
+ }
21
+ const currentModelUploadFields = Array.isArray(currentModelConfig.fields) ? currentModelConfig.fields : [currentModelConfig.fields];
22
+ const capitalCaseModel = `${model.charAt(0).toUpperCase() + model.slice(1)}`;
23
+ const saverKey = `for${capitalCaseModel}`;
24
+ savers[saverKey] = async (data, overrideSaveOptions) => {
25
+ const updatedFields = {};
26
+ for (const field of currentModelUploadFields) {
27
+ if (data[field]) {
28
+ const file = data[field];
29
+ const saveOptions = overrideSaveOptions || {
30
+ fileName: `${model}-${field}-${ulid()}`
31
+ };
32
+ const { location } = await storage.save(file, saveOptions);
33
+ updatedFields[field] = location;
34
+ }
35
+ }
36
+ return {
37
+ ...data,
38
+ ...updatedFields
39
+ };
40
+ };
41
+ });
42
+ return {
43
+ ...savers,
44
+ inList: createFileListSaver(storage)
45
+ };
46
+ };
47
+ export {
48
+ createFileListSaver,
49
+ createUploadSavers
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { BaseStorageAdapter } from './adapters/BaseStorageAdapter.js';
2
+ export declare function fileToDataUri(filePath: string, storage: BaseStorageAdapter): Promise<string>;
3
+ //# sourceMappingURL=fileToDataUri.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileToDataUri.d.ts","sourceRoot":"","sources":["../src/fileToDataUri.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAE1E,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,mBAO5B"}
@@ -0,0 +1,8 @@
1
+ async function fileToDataUri(filePath, storage) {
2
+ const { contents, type: mimeType } = await storage.read(filePath);
3
+ const base64Data = Buffer.from(contents).toString("base64");
4
+ return `data:${mimeType};base64,${base64Data}`;
5
+ }
6
+ export {
7
+ fileToDataUri
8
+ };
@@ -0,0 +1,40 @@
1
+ import type { BaseStorageAdapter } from './adapters/BaseStorageAdapter.js';
2
+ import type { ModelNames, UploadConfigForModel, UploadsConfig } from './prismaExtension.js';
3
+ import type { UrlSigner } from './UrlSigner.js';
4
+ type SetupStorageOptions<MNames extends ModelNames> = {
5
+ uploadsConfig: UploadsConfig<MNames>;
6
+ storageAdapter: BaseStorageAdapter;
7
+ urlSigner?: UrlSigner;
8
+ };
9
+ export declare const setupStorage: <MNames extends ModelNames>({ uploadsConfig, storageAdapter, urlSigner, }: SetupStorageOptions<MNames>) => {
10
+ storagePrismaExtension: (client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/library").InternalArgs<{ [K in MNames]: {
11
+ withDataUri: {
12
+ needs: Record<string, boolean>;
13
+ compute: (modelData: Record<string, unknown>) => <T>(this: T) => Promise<T>;
14
+ };
15
+ withSignedUrl: {
16
+ needs: Record<string, boolean>;
17
+ compute: (modelData: Record<string, unknown>) => <T>(this: T, signArgs?: {
18
+ expiresIn?: number;
19
+ }) => T;
20
+ };
21
+ }; }, {}, {}, {}> & import("@prisma/client/runtime/library").DefaultArgs>;
22
+ saveFiles: { [K in `for${Capitalize<MNames>}`]: <T extends Record<string, any>>(data: T, overrideSaveOptions?: import("./adapters/BaseStorageAdapter.js").SaveOptionsOverride) => Promise<{ [K_1 in keyof T]: T[K_1] extends File ? string : T[K_1]; }>; } & {
23
+ inList: (files?: File[], pathOverrideOnly?: {
24
+ path?: string;
25
+ }) => Promise<string[]>;
26
+ };
27
+ };
28
+ /**
29
+ * This utility function ensures that you receive accurate type suggestions for your savers.
30
+ * If you use the type UploadsConfig directly, you may receive suggestions for saveFiles.forY where Y hasn't been configured.
31
+ * By using this utility function, you will only receive suggestions for the models that you have configured.
32
+ *
33
+ * @param uploadsConfig The uploads configuration object.
34
+ * @returns The same uploads configuration object, but with filtered types
35
+ */
36
+ export declare function createUploadsConfig<T extends Partial<{
37
+ [K in ModelNames]?: UploadConfigForModel<K>;
38
+ }>>(uploadsConfig: T): T;
39
+ export type { ModelNames, UploadsConfig } from './prismaExtension.js';
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAE1E,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,aAAa,EACd,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,KAAK,mBAAmB,CAAC,MAAM,SAAS,UAAU,IAAI;IACpD,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACpC,cAAc,EAAE,kBAAkB,CAAA;IAClC,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,UAAU,iDAInD,mBAAmB,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;gBARV,CAAC;;;CAqBpB,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,SAAS,OAAO,CAAC;KACf,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAC5C,CAAC,EACF,aAAa,EAAE,CAAC,GAAG,CAAC,CAErB;AAED,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import { createUploadSavers } from "./createSavers.js";
2
+ import { createUploadsExtension } from "./prismaExtension.js";
3
+ const setupStorage = ({
4
+ uploadsConfig,
5
+ storageAdapter,
6
+ urlSigner
7
+ }) => {
8
+ const prismaExtension = createUploadsExtension(
9
+ uploadsConfig,
10
+ storageAdapter,
11
+ urlSigner
12
+ );
13
+ const saveFiles = createUploadSavers(uploadsConfig, storageAdapter);
14
+ return {
15
+ storagePrismaExtension: prismaExtension,
16
+ saveFiles
17
+ };
18
+ };
19
+ function createUploadsConfig(uploadsConfig) {
20
+ return uploadsConfig;
21
+ }
22
+ export {
23
+ createUploadsConfig,
24
+ setupStorage
25
+ };
@@ -0,0 +1,29 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import type { Prisma } from '@prisma/client';
3
+ import type * as runtime from '@prisma/client/runtime/library';
4
+ import type { BaseStorageAdapter } from './adapters/BaseStorageAdapter.js';
5
+ import type { UrlSigner } from './UrlSigner.js';
6
+ type FilterOutDollarPrefixed<T> = T extends `$${string}` ? never : T extends symbol ? never : T;
7
+ export type ModelNames = FilterOutDollarPrefixed<keyof PrismaClient>;
8
+ type PrismaModelFields<MName extends ModelNames> = keyof Prisma.Result<PrismaClient[MName], any, 'findFirstOrThrow'>;
9
+ export type UploadConfigForModel<TPrismaModelName extends ModelNames> = {
10
+ fields: PrismaModelFields<TPrismaModelName> | PrismaModelFields<TPrismaModelName>[];
11
+ };
12
+ export type UploadsConfig<MNames extends ModelNames = ModelNames> = {
13
+ [K in MNames]?: UploadConfigForModel<K>;
14
+ };
15
+ type WithSignedUrlArgs = {
16
+ expiresIn?: number;
17
+ };
18
+ export declare const createUploadsExtension: <MNames extends ModelNames = ModelNames>(config: UploadsConfig<MNames>, storageAdapter: BaseStorageAdapter, urlSigner?: UrlSigner) => (client: any) => import("@prisma/client/extension").PrismaClientExtends<runtime.InternalArgs<{ [K in MNames]: {
19
+ withDataUri: {
20
+ needs: Record<string, boolean>;
21
+ compute: (modelData: Record<string, unknown>) => <T>(this: T) => Promise<T>;
22
+ };
23
+ withSignedUrl: {
24
+ needs: Record<string, boolean>;
25
+ compute: (modelData: Record<string, unknown>) => <T>(this: T, signArgs?: WithSignedUrlArgs) => T;
26
+ };
27
+ }; }, {}, {}, {}> & runtime.DefaultArgs>;
28
+ export {};
29
+ //# sourceMappingURL=prismaExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prismaExtension.d.ts","sourceRoot":"","sources":["../src/prismaExtension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAE5C,OAAO,KAAK,KAAK,OAAO,MAAM,gCAAgC,CAAA;AAE9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAE1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,KAAK,uBAAuB,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,GACpD,KAAK,GACL,CAAC,SAAS,MAAM,GACd,KAAK,GACL,CAAC,CAAA;AAGP,MAAM,MAAM,UAAU,GAAG,uBAAuB,CAAC,MAAM,YAAY,CAAC,CAAA;AAEpE,KAAK,iBAAiB,CAAC,KAAK,SAAS,UAAU,IAAI,MAAM,MAAM,CAAC,MAAM,CACpE,YAAY,CAAC,KAAK,CAAC,EACnB,GAAG,EACH,kBAAkB,CACnB,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,gBAAgB,SAAS,UAAU,IAAI;IACtE,MAAM,EACF,iBAAiB,CAAC,gBAAgB,CAAC,GACnC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,CAAA;CAC1C,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,MAAM,SAAS,UAAU,GAAG,UAAU,IAAI;KACjE,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC;CACxC,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,MAAM,SAAS,UAAU,uBACtD,aAAa,CAAC,MAAM,CAAC,kBACb,kBAAkB,cACtB,SAAS,qGAOlB,CAAC;iBACa;QACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC9B,OAAO,EAAE,CACP,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;KAChC;mBACc;QACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC9B,OAAO,EAAE,CACP,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,iBAAiB,KAAK,CAAC,CAAA;KACrD;wCA4RN,CAAA"}
@@ -0,0 +1,216 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ import { Prisma as PrismaExtension } from "@prisma/client/extension";
3
+ import { fileToDataUri } from "./fileToDataUri.js";
4
+ const createUploadsExtension = (config, storageAdapter, urlSigner) => {
5
+ const prismaInstance = new PrismaClient();
6
+ const queryExtends = {};
7
+ const resultExtends = {};
8
+ for (const modelName in config) {
9
+ const modelConfig = config[modelName];
10
+ if (!modelConfig) {
11
+ continue;
12
+ }
13
+ const uploadFields = Array.isArray(modelConfig.fields) ? modelConfig.fields : [modelConfig.fields];
14
+ queryExtends[modelName] = {
15
+ async create({ query, args }) {
16
+ try {
17
+ const result = await query(args);
18
+ return result;
19
+ } catch (e) {
20
+ await removeUploadedFiles(
21
+ uploadFields,
22
+ args.data
23
+ );
24
+ throw e;
25
+ }
26
+ },
27
+ async createMany({ query, args }) {
28
+ try {
29
+ const result = await query(args);
30
+ return result;
31
+ } catch (e) {
32
+ const createDatas = args.data;
33
+ for (const createData of createDatas) {
34
+ await removeUploadedFiles(uploadFields, createData);
35
+ }
36
+ throw e;
37
+ }
38
+ },
39
+ async update({ query, model, args }) {
40
+ const uploadFieldsToUpdate = uploadFields.filter(
41
+ (field) => (
42
+ // All of this non-sense is to make typescript happy. I'm not sure how data could be anything but an object
43
+ typeof args.data === "object" && args.data !== null && field in args.data
44
+ )
45
+ );
46
+ if (uploadFieldsToUpdate.length == 0) {
47
+ return query(args);
48
+ } else {
49
+ const originalRecord = await prismaInstance[
50
+ model
51
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
52
+ ].findFirstOrThrow({
53
+ where: args.where
54
+ // @TODO: should we select here to reduce the amount of data we're handling
55
+ });
56
+ try {
57
+ const result = await query(args);
58
+ await removeUploadedFiles(uploadFieldsToUpdate, originalRecord);
59
+ return result;
60
+ } catch (e) {
61
+ await removeUploadedFiles(
62
+ uploadFieldsToUpdate,
63
+ args.data
64
+ );
65
+ throw e;
66
+ }
67
+ }
68
+ },
69
+ async updateMany({ query, model, args }) {
70
+ const uploadFieldsToUpdate = uploadFields.filter(
71
+ (field) => (
72
+ // All of this non-sense is to make typescript happy. I'm not sure how data could be anything but an object
73
+ typeof args.data === "object" && args.data !== null && field in args.data
74
+ )
75
+ );
76
+ if (uploadFieldsToUpdate.length == 0) {
77
+ return query(args);
78
+ } else {
79
+ const originalRecords = await prismaInstance[
80
+ model
81
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
82
+ ].findMany({
83
+ where: args.where
84
+ // @TODO: should we select here to reduce the amount of data we're handling
85
+ });
86
+ try {
87
+ const result = await query(args);
88
+ for await (const originalRecord of originalRecords) {
89
+ await removeUploadedFiles(uploadFieldsToUpdate, originalRecord);
90
+ }
91
+ return result;
92
+ } catch (e) {
93
+ await removeUploadedFiles(
94
+ uploadFieldsToUpdate,
95
+ args.data
96
+ );
97
+ throw e;
98
+ }
99
+ }
100
+ },
101
+ async upsert({ query, model, args }) {
102
+ let isUpdate;
103
+ const uploadFieldsToUpdate = uploadFields.filter(
104
+ (field) => typeof args.update === "object" && args.update !== null && field in args.update
105
+ );
106
+ try {
107
+ let existingRecord;
108
+ if (args.update) {
109
+ existingRecord = await prismaInstance[
110
+ model
111
+ // @ts-expect-error TS in strict mode will error due to union type. We cannot narrow it down here.
112
+ ].findUnique({
113
+ where: args.where
114
+ });
115
+ isUpdate = !!existingRecord;
116
+ }
117
+ const result = await query(args);
118
+ if (isUpdate && existingRecord) {
119
+ await removeUploadedFiles(uploadFieldsToUpdate, existingRecord);
120
+ }
121
+ return result;
122
+ } catch (e) {
123
+ await removeUploadedFiles(
124
+ // Only delete files we're updating on update
125
+ isUpdate ? uploadFieldsToUpdate : uploadFields,
126
+ isUpdate ? args.update : args.create
127
+ );
128
+ throw e;
129
+ }
130
+ },
131
+ async delete({ query, args }) {
132
+ const deleteResult = await query(args);
133
+ await removeUploadedFiles(
134
+ uploadFields,
135
+ // We don't know the exact type here
136
+ deleteResult
137
+ );
138
+ return deleteResult;
139
+ }
140
+ };
141
+ const needs = Object.fromEntries(uploadFields.map((field) => [field, true]));
142
+ resultExtends[modelName] = {
143
+ withDataUri: {
144
+ needs,
145
+ compute(modelData) {
146
+ return async () => {
147
+ const base64UploadFields = {};
148
+ for (const field of uploadFields) {
149
+ base64UploadFields[field] = await fileToDataUri(
150
+ modelData[field],
151
+ storageAdapter
152
+ );
153
+ }
154
+ return {
155
+ // modelData is of type unknown at this point
156
+ ...modelData,
157
+ ...base64UploadFields
158
+ };
159
+ };
160
+ }
161
+ },
162
+ withSignedUrl: {
163
+ needs,
164
+ compute(modelData) {
165
+ return ({ expiresIn } = {}) => {
166
+ if (!urlSigner) {
167
+ throw new Error(
168
+ "Please supply signed url settings in setupUpload()"
169
+ );
170
+ }
171
+ const signedUrlFields = {};
172
+ for (const field of uploadFields) {
173
+ if (!modelData[field]) {
174
+ continue;
175
+ }
176
+ signedUrlFields[field] = urlSigner.generateSignedUrl(
177
+ modelData[field],
178
+ expiresIn
179
+ );
180
+ }
181
+ return {
182
+ // modelData is of type unknown at this point
183
+ ...modelData,
184
+ ...signedUrlFields
185
+ };
186
+ };
187
+ }
188
+ }
189
+ };
190
+ }
191
+ return PrismaExtension.defineExtension((client) => {
192
+ return client.$extends({
193
+ name: "redwood-upload-prisma-plugin",
194
+ query: queryExtends,
195
+ result: resultExtends
196
+ });
197
+ });
198
+ async function removeUploadedFiles(fieldsToDelete, data) {
199
+ if (!data) {
200
+ console.warn("Empty data object passed to removeUploadedFiles");
201
+ return;
202
+ }
203
+ for (const field of fieldsToDelete) {
204
+ const uploadLocation = data?.[field];
205
+ if (uploadLocation) {
206
+ try {
207
+ await storageAdapter.remove(uploadLocation);
208
+ } catch {
209
+ }
210
+ }
211
+ }
212
+ }
213
+ };
214
+ export {
215
+ createUploadsExtension
216
+ };