mikro-orm-attachments 1.3.2 → 2.0.0

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.
@@ -0,0 +1 @@
1
+ import{a as o}from"../shared/chunk-4thj50nc.js";import{b as i}from"../shared/chunk-cm81a68q.js";import{fileTypeFromBuffer as n}from"file-type";class m extends o{options;constructor(t){super();this.options=t}async supports(t){return t.mimeType.startsWith("image/")}async handle(t){let e=await(await i("imgkit")).transform(t.buffer,this.options??{}),r=await n(new Uint8Array(e.buffer));return{buffer:e,mimeType:r?.mime??t.mimeType,extname:r?.ext??t.extname}}}export{m as ImgkitConverter};
@@ -0,0 +1,38 @@
1
+ type VariantSpec = BaseConverter<ConvertInput, ConvertOutput>;
2
+ interface ConvertInput {
3
+ buffer: Buffer;
4
+ size: number;
5
+ mimeType: string;
6
+ variantName?: string;
7
+ variant?: VariantSpec;
8
+ extname: string;
9
+ }
10
+ interface ConvertOutput {
11
+ buffer: Buffer;
12
+ mimeType: string;
13
+ extname: string;
14
+ }
15
+ declare abstract class BaseConverter<
16
+ TInput extends ConvertInput,
17
+ TOutput extends ConvertOutput
18
+ > {
19
+ abstract supports(input: TInput): Promise<boolean>;
20
+ abstract handle(input: TInput): Promise<TOutput>;
21
+ }
22
+ import * as Sharp from "sharp";
23
+ type SharpFormat = "jpg" | "jpeg" | "png" | "webp" | "avif" | "gif" | "tiff" | "heic" | "heif";
24
+ interface SharpConverterOptions {
25
+ resize?: Sharp.ResizeOptions;
26
+ autoOrient?: boolean;
27
+ format?: SharpFormat | {
28
+ format: SharpFormat;
29
+ options: Sharp.PngOptions | Sharp.JpegOptions | Sharp.WebpOptions | Sharp.AvifOptions | Sharp.GifOptions | Sharp.TiffOptions | Sharp.HeifOptions;
30
+ };
31
+ }
32
+ declare class SharpConverter extends BaseConverter<ConvertInput, ConvertOutput> {
33
+ private readonly options?;
34
+ constructor(options?: SharpConverterOptions | undefined);
35
+ supports(input: ConvertInput): Promise<boolean>;
36
+ handle(input: ConvertInput): Promise<ConvertOutput>;
37
+ }
38
+ export { SharpConverter };
@@ -0,0 +1 @@
1
+ import{a as n}from"../shared/chunk-4thj50nc.js";import{b as a}from"../shared/chunk-cm81a68q.js";import{fileTypeFromBuffer as h}from"file-type";class u extends n{options;constructor(t){super();this.options=t}async supports(t){return t.mimeType.startsWith("image/")}async handle(t){let s=await a("sharp"),f=this.options?.resize||{},e=this.options?.format||"webp",m=this.options?.autoOrient||!0,r={};if(typeof e!=="string")r=e?.options,e=e.format;let o=s(t.buffer).withMetadata();if(m)o.autoOrient();let p=await o.resize(f).toFormat(e,r).toBuffer(),i=await h(p);return{buffer:p,mimeType:i?.mime??t.mimeType,extname:i?.ext??t.extname}}}export{u as SharpConverter};
package/dist/index.d.ts CHANGED
@@ -1,143 +1,32 @@
1
1
  import { PropertyOptions } from "@mikro-orm/core";
2
- import { DriverContract } from "flydrive/types";
3
- interface Converter {
4
- supports(input: ConvertInput, options: ConverterOptions): Promise<boolean>;
5
- handle(input: ConvertInput, options: ConverterOptions): Promise<ConvertOutput>;
2
+ import { DriverContract as DriverContract2 } from "flydrive/types";
3
+ declare abstract class BaseMetadata<TMetadata> {
4
+ abstract metadata(input: ConvertInput): Promise<TMetadata>;
6
5
  }
7
- type BlurhashOptions = {
8
- enabled: boolean;
9
- componentX: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
10
- componentY: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
11
- };
12
- interface ConvertInput {
13
- buffer: Buffer;
14
- size: number;
15
- mimeType: string;
16
- variantName: string;
17
- variant: VariantSpec;
18
- extname: string;
6
+ import { DriverContract } from "flydrive/types";
7
+ interface EventSubscriber {
8
+ getSubscribedEvents(): string[];
9
+ onLoad(args: any): Promise<void>;
10
+ beforeFlush(args: any): Promise<void>;
19
11
  }
20
- interface ConvertOutput {
21
- buffer: Buffer;
22
- mimeType: string;
23
- extname: string;
12
+ declare class AttachmentSubscriber<
13
+ const TDrivers extends Record<string, DriverContract>,
14
+ const TVariants extends Record<string, VariantSpec>
15
+ > implements EventSubscriber {
16
+ private;
17
+ private readonly options;
18
+ private readonly disks;
19
+ constructor(options: AttachmentOptions<TDrivers, TVariants>);
20
+ onLoad(args: any): Promise<void>;
21
+ beforeFlush(args: any): Promise<void>;
22
+ AttachmentDecorator(options?: AttachmentDecoratorProps<AttachmentSubscriber<TDrivers, TVariants>>): (target: any, propertyKey: string) => void;
24
23
  }
25
- type jpeg = {
26
- format: "jpeg";
27
- options?: {
28
- quality?: number;
29
- progressive?: boolean;
30
- chromaSubsampling?: string;
31
- optimiseCoding?: boolean;
32
- optimizeCoding?: boolean;
33
- mozjpeg?: boolean;
34
- trellisQuantisation?: boolean;
35
- overshootDeringing?: boolean;
36
- optimiseScans?: boolean;
37
- optimizeScans?: boolean;
38
- quantisationTable?: number;
39
- quantizationTable?: number;
40
- force?: boolean;
41
- };
42
- };
43
- type png = {
44
- format: "png";
45
- options?: {
46
- quality?: number;
47
- progressive?: boolean;
48
- compressionLevel?: number;
49
- adaptiveFiltering?: boolean;
50
- palette?: boolean;
51
- effort?: number;
52
- colours?: number;
53
- colors?: number;
54
- dither?: number;
55
- force?: boolean;
56
- };
57
- };
58
- type gif = {
59
- format: "gif";
60
- options?: {
61
- reuse?: boolean;
62
- progressive?: boolean;
63
- colours?: number;
64
- colors?: number;
65
- effort?: number;
66
- dither?: number;
67
- interFrameMaxError?: number;
68
- interPaletteMaxError?: number;
69
- loop?: number;
70
- delay?: number | number[];
71
- force?: boolean;
72
- };
73
- };
74
- type webp = {
75
- format: "webp";
76
- options?: {
77
- quality?: number;
78
- alphaQuality?: number;
79
- lossless?: boolean;
80
- nearLossless?: boolean;
81
- smartSubsample?: boolean;
82
- preset?: string;
83
- effort?: number;
84
- loop?: number;
85
- delay?: number | number[];
86
- minSize?: boolean;
87
- mixed?: boolean;
88
- force?: boolean;
89
- };
90
- };
91
- type avif = {
92
- format: "avif";
93
- options?: {
94
- quality?: number;
95
- lossless?: boolean;
96
- effort?: number;
97
- chromaSubsampling?: string;
98
- bitdepth?: number;
99
- };
100
- };
101
- type heif = {
102
- format: "heif";
103
- options?: {
104
- compression?: string;
105
- quality?: number;
106
- lossless?: boolean;
107
- effort?: number;
108
- chromaSubsampling?: string;
109
- bitdepth?: number;
110
- };
111
- };
112
- type ConverterOptions = {
113
- resize?: number | {
114
- width?: number;
115
- height?: number;
116
- fit?: "contain" | "cover" | "fill" | "inside" | "outside";
117
- position?: string;
118
- background?: string | {
119
- r: number;
120
- g: number;
121
- b: number;
122
- alpha: number;
123
- };
124
- kernel?: string;
125
- withoutEnlargement?: boolean;
126
- withoutReduction?: boolean;
127
- fastShrinkOnLoad?: boolean;
128
- };
129
- format?: "jpeg" | "jpg" | "png" | "gif" | "webp" | "avif" | "heif" | "tiff" | "raw" | jpeg | png | gif | webp | avif | heif;
130
- autoOrient?: boolean;
131
- blurhash?: boolean | BlurhashOptions;
132
- startTime?: number;
133
- startPage?: number;
134
- };
135
24
  type StringKeyOf<T> = Extract<keyof T, string>;
136
25
  type DriversOf<S> = S extends AttachmentSubscriber<infer TDrivers, any> ? TDrivers : never;
137
26
  type VariantsOf<S> = S extends AttachmentSubscriber<any, infer TVariants> ? TVariants : never;
138
27
  type VariantSelection<TVariants extends Record<string, VariantSpec>> = ReadonlyArray<StringKeyOf<TVariants> | Record<string, VariantSpec>> | Partial<TVariants> | Record<string, VariantSpec>;
139
28
  interface AttachmentPropertyOptions<
140
- TDrivers extends Record<string, DriverContract> = Record<string, DriverContract>,
29
+ TDrivers extends Record<string, DriverContract2> = Record<string, DriverContract2>,
141
30
  TVariants extends Record<string, VariantSpec> = Record<string, VariantSpec>
142
31
  > {
143
32
  folder?: string;
@@ -147,14 +36,15 @@ interface AttachmentPropertyOptions<
147
36
  }
148
37
  type AttachmentPropertyOptionsFor<S extends AttachmentSubscriber<any, any>> = AttachmentPropertyOptions<DriversOf<S>, VariantsOf<S>>;
149
38
  interface AttachmentOptions<
150
- TDrivers extends Record<string, DriverContract> = Record<string, DriverContract>,
151
- TVariants extends Record<string, VariantSpec> = Record<string, VariantSpec>
39
+ TDrivers extends Record<string, DriverContract2> = Record<string, DriverContract2>,
40
+ TVariants extends Record<string, VariantSpec> = Record<string, VariantSpec>,
41
+ TMetadata extends BaseMetadata<any> = BaseMetadata<any>
152
42
  > {
153
43
  readonly drivers: TDrivers;
154
44
  readonly defaultDriver: StringKeyOf<TDrivers>;
155
45
  readonly rename?: boolean | ((file: File, columnName: string, entity: any) => string);
156
- readonly converters?: Converter[];
157
46
  readonly variants?: TVariants;
47
+ readonly metadata?: TMetadata;
158
48
  }
159
49
  declare const ALLOWED_PROPERTY_OPTIONS: readonly ["accessor", "comment", "customOrder", "getter", "getterName", "hidden", "index", "lazy", "name", "nullable", "serializedName", "serializedPrimaryKey", "serializer", "version"];
160
50
  type AllowedPropertyOptions = (typeof ALLOWED_PROPERTY_OPTIONS)[number];
@@ -167,28 +57,42 @@ interface AttachmentBase {
167
57
  mimeType: string;
168
58
  path: string;
169
59
  originalName: string;
60
+ meta?: ConvertMetadata;
170
61
  variants: Omit<AttachmentBase, "variants" | "originalName" | "drive">[];
171
62
  }
172
- interface VariantSpec extends ConverterOptions {}
173
- import { DriverContract as DriverContract2 } from "flydrive/types";
174
- interface EventSubscriber {
175
- getSubscribedEvents(): string[];
176
- onLoad(args: any): Promise<void>;
177
- beforeFlush(args: any): Promise<void>;
63
+ type VariantSpec = BaseConverter<ConvertInput, ConvertOutput>;
64
+ type BlurhashOptions = {
65
+ enabled: boolean;
66
+ componentX: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
67
+ componentY: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
68
+ };
69
+ interface ConvertInput {
70
+ buffer: Buffer;
71
+ size: number;
72
+ mimeType: string;
73
+ variantName?: string;
74
+ variant?: VariantSpec;
75
+ extname: string;
178
76
  }
179
- declare class AttachmentSubscriber<
180
- const TDrivers extends Record<string, DriverContract2>,
181
- const TVariants extends Record<string, VariantSpec>
182
- > implements EventSubscriber {
183
- private;
184
- private readonly options;
185
- private readonly disks;
186
- constructor(options: AttachmentOptions<TDrivers, TVariants>);
187
- getSubscribedEvents(): string[];
188
- onLoad(args: any): Promise<void>;
189
- beforeFlush(args: any): Promise<void>;
190
- AttachmentDecorator(options?: AttachmentDecoratorProps<AttachmentSubscriber<TDrivers, TVariants>>): (target: any, propertyKey: string) => void;
77
+ interface ConvertOutput {
78
+ buffer: Buffer;
79
+ mimeType: string;
80
+ extname: string;
81
+ }
82
+ interface ConvertMetadata {
83
+ dimension: {
84
+ width: number;
85
+ height: number;
86
+ };
87
+ }
88
+ declare abstract class BaseConverter<
89
+ TInput extends ConvertInput,
90
+ TOutput extends ConvertOutput
91
+ > {
92
+ abstract supports(input: TInput): Promise<boolean>;
93
+ abstract handle(input: TInput): Promise<TOutput>;
191
94
  }
95
+ import { Buffer as Buffer2 } from "node:buffer";
192
96
  import { Disk } from "flydrive";
193
97
  import { SignedURLOptions } from "flydrive/types";
194
98
  import { Readable as Readable_d9sohc } from "node:stream";
@@ -205,18 +109,21 @@ declare class Attachment<Variants extends string = string> {
205
109
  * @throws An error if the file cannot be downloaded.
206
110
  */
207
111
  static fromUrl(url: string): Promise<Attachment>;
208
- url(variant?: string): string | Promise<string>;
209
- preSignedUrl(variantNameOrOptions?: string | SignedURLOptions, signedUrlOptions?: SignedURLOptions): Promise<string>;
112
+ url(variant?: Variants): string | Promise<string> | undefined;
113
+ originalName(): string;
114
+ size(variant?: Variants): number | undefined;
115
+ meta(variant?: Variants): ConvertMetadata | undefined;
116
+ preSignedUrl(variantNameOrOptions?: string | SignedURLOptions, signedUrlOptions?: SignedURLOptions): Promise<string> | undefined;
210
117
  blurhash(): string | undefined;
211
- getDisk(): Disk;
118
+ getDisk(): Disk | undefined;
212
119
  getDrive(): string;
213
- getBytes(variantName?: Variants): Promise<Uint8Array<ArrayBufferLike>>;
214
- getBuffer(variantName?: Variants): Promise<Buffer<ArrayBuffer>>;
215
- getStream(variantName?: Variants): Promise<Readable_d9sohc>;
120
+ getBytes(variantName?: Variants): Promise<Uint8Array<ArrayBufferLike> | undefined>;
121
+ getBuffer(variantName?: Variants): Promise<Buffer2<ArrayBuffer> | null>;
122
+ getStream(variantName?: Variants): Promise<Readable_d9sohc | undefined>;
216
123
  getMimeType(variantName?: Variants): string;
217
124
  toJSON(): {
218
- url: string | Promise<string>;
125
+ url: string | Promise<string> | undefined;
219
126
  blurhash: string | undefined;
220
127
  };
221
128
  }
222
- export { AttachmentSubscriber, AttachmentPropertyOptions, AttachmentOptions, Attachment };
129
+ export { AttachmentSubscriber, AttachmentPropertyOptions, AttachmentOptions, AttachmentDecoratorProps, Attachment };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{Disk as j}from"flydrive";var l=Symbol("attachment:loaded"),G=Symbol("attachment:options"),c=Symbol("attachment:file"),a=Symbol("attachment:disk"),y=Symbol("attachment:fn:process"),u=Symbol("attachment:fn:load"),w=Symbol("attachment:fn:save");class h{[l]=!1;[c];[a];data;constructor(e){if(e instanceof File)this[c]=e;else this[l]=!0,this.data=e}#e(){if(!this[l])throw Error("Attachment is not processed, please flush the entity first.")}get#t(){return this.#e(),this[a]}[w](){return this.data??{}}static[u](e){return new h(e)}[y](e){if(this[l])return;this.data=e,this[l]=!0}static fromFile(e){return new h(e)}static async fromUrl(e){let t=await fetch(e),r=await t.arrayBuffer(),o=new File([r],e.split("/").pop(),{type:t.headers.get("content-type")??""});return new h(o)}#r(e){this.#e();let t=this.data?.variants.find((r)=>r.name===e)??null;if(!t)throw Error(`Attachment: Variant '${e}' not found`);return t}url(e){if(this.#e(),e)return this.#t.getUrl(this.#r(e).path);return this.data?.url}preSignedUrl(e,t){if(this.#e(),typeof e==="string")return this.#t.getSignedUrl(this.#r(e).path,t);return this.#t.getSignedUrl(this.data?.path??"",t)}blurhash(){return this.#e(),this.data?.blurhash}getDisk(){return this.#t}getDrive(){return this.data?.drive??""}async getBytes(e){this.#e();let t=e?this.#r(e).path:this.data?.path??"";return this.#t.getBytes(t)}async getBuffer(e){return this.#e(),Buffer.from(await this.getBytes(e))}async getStream(e){this.#e();let t=e?this.#r(e).path:this.data?.path??"";return this.#t.getStream(t)}getMimeType(e){return this.#e(),(e?this.#r(e):this.data)?.mimeType??""}toJSON(){return{url:this.url(),blurhash:this.blurhash()}}}import{join as J}from"path";import{v7 as W}from"uuid";import{fileTypeFromBuffer as X}from"file-type";import{createRequire as P}from"module";import{encode as q}from"blurhash";var V=P(import.meta.url);async function d(e){try{let t=await import(e);if(t.default)return t.default;return t}catch(t){throw Error(`Module ${e} not found, please install it first.`)}}function C(e){for(let t of e)try{return V(t)}catch{continue}throw Error(`Could not resolve any of the following modules: ${e.join(", ")}`)}function T(e,t){let{componentX:r,componentY:o}=t??{enabled:!0,componentX:4,componentY:4};return new Promise(async(i,n)=>{try{let s=await d("sharp"),{data:f,info:m}=await s(e).raw().ensureAlpha().toBuffer({resolveWithObject:!0}),p=q(new Uint8ClampedArray(f),m.width,m.height,r,o);return i(p)}catch(s){return n(s)}})}class g{att;file;disk;buffer;modelOptions;config;entity;columnName;diskName;fileInfo;constructor(e,t){this.att=e;let{disk:r,options:o,config:i,entity:n,columnName:s,diskName:f}=t;if(this.disk=r,this.modelOptions=o,this.config=i,this.entity=n,this.columnName=s,this.diskName=f,e[l])throw Error("Attachment already processed, please use the Attachment.fromFile method to create a new attachment");this.file=this.att[c]}done(e){this.att[y](e)}generateKey(e,...t){let r=this.modelOptions.folder??"";return r=r.replace(/:([A-Za-z0-9_]+)/g,(o,i)=>{let n=this.entity[i];if(n===void 0||n===null)throw Error(`Missing value for Attachment path "${i}" in entity ${this.entity.constructor.name}. Please ensure that the referenced property is computed before the attachment is processed. (Auto incrementing fields are not supported.)`);return this.normalizeFileName(String(n))}),J(r,e,...t)}normalizeFileName(e){return encodeURIComponent(e.replace(/[^a-zA-Z0-9.-]/g,"_")).toLowerCase()}async fileToBuffer(){if(this.buffer)return this.buffer;let e=await this.file.arrayBuffer(),t=Buffer.from(e);return this.buffer=t,t}getFileName(){if(!this.config.rename)return this.normalizeFileName(this.file.name);if(typeof this.config.rename==="function")return this.normalizeFileName(this.config.rename(this.file,this.columnName,this.entity));return this.normalizeFileName(W())}async getFileType(){return X(await this.fileToBuffer())}async analyseFile(){let e=await this.getFileType();this.fileInfo={extname:e?.ext??this.file.name.split(".").pop()??"",mimeType:e?.mime??this.file.type,size:this.file.size}}async uploadFile(e,t){await this.disk.put(e,t)}async pickConverter(e,t){if(!this.fileInfo)throw Error("Attachment Converter: File info not found");for(let r of this.config.converters??[])if(await r.supports({size:this.fileInfo.size,mimeType:this.fileInfo.mimeType,variantName:e,variant:t,buffer:await this.fileToBuffer(),extname:this.fileInfo.extname},this.modelOptions))return r;return null}async process(){await this.analyseFile();let e=await this.fileToBuffer(),t=this.getFileName(),r=this.fileInfo?.extname??"",o=this.generateKey(t,`${t}.${r}`);await this.uploadFile(o,e);let i={name:t,extname:r,size:this.file.size,drive:this.diskName,mimeType:this.file.type,path:o,url:await this.disk.getUrl(o),originalName:this.file.name,variants:[]};if(this.modelOptions.variants)for(let[n,s]of Object.entries(this.modelOptions.variants)){let f=await this.pickConverter(n,s);if(f){let m=await f.handle({buffer:e,size:this.fileInfo?.size??0,mimeType:this.fileInfo?.mimeType??"",extname:this.fileInfo?.extname??"",variantName:n,variant:s},this.modelOptions.variants[n]),p=this.generateKey(o.split("/").pop().split(".").shift(),`${n}.${m.extname}`);await this.uploadFile(p,m.buffer),i.variants.push({name:n,extname:m.extname,size:m.buffer.length,mimeType:m.mimeType,path:p})}}if(this.fileInfo?.mimeType.startsWith("image/")){if(typeof this.modelOptions.blurhash==="boolean"?this.modelOptions.blurhash:this.modelOptions.blurhash?.enabled)i.blurhash=await T(e,typeof this.modelOptions.blurhash==="object"?this.modelOptions.blurhash:void 0)}this.done(i)}}import{Type as Z}from"@mikro-orm/core";class F extends Z{options={folder:"attachments",blurhash:!0};constructor(e){super();this.options={...this.options,...e}}convertToDatabaseValue(e){return e[w]()}convertToJSValue(e){try{return h[u](JSON.parse(e))}catch(t){try{return h[u](e)}catch(r){throw Error("Invalid attachment data")}}}getColumnType(){return"json"}}import{fileTypeFromBuffer as R}from"file-type";class B{async supports(e,t){return e.mimeType.startsWith("image/")}async handle(e,t){let r=await d("sharp"),o=t?.resize||{},i=t?.format||"webp",n=t?.autoOrient||!0,s={};if(typeof i!=="string")s=i?.options,i=i.format;let f=r(e.buffer).withMetadata();if(n)f.autoOrient();let m=await f.resize(o).toFormat(i,s).toBuffer(),p=await R(m);return{buffer:m,mimeType:p?.mime??e.mimeType,extname:p?.ext??e.extname}}}var I={blurhash:!0},b={rename:!0,converters:[new B]},O=["accessor","comment","customOrder","getter","getterName","hidden","index","lazy","name","nullable","serializedName","serializedPrimaryKey","serializer","version"];var{Property:Q}=C(["@mikro-orm/decorators/legacy","@mikro-orm/core"]),x=Symbol("attachment:props");function Y(e={}){return(t,r)=>{let{mikro:o,attachment:i}=M(e,O);i={...I,...i};let n=t.constructor;if(!n[x])n[x]={};return n[x][r]=i,Q({...o,type:new F(i)})(t,r)}}function S(e){return Y(e)}function z(e){let r=e.constructor[x];return r?r:{}}function M(e,t){let r=new Set(t),o={},i={};for(let n in e)if(r.has(n))o[n]=e[n];else i[n]=e[n];return{mikro:o,attachment:i}}class ${options;disks;constructor(e){this.options=e;this.options={...b,...this.options},this.disks=new Map(Object.entries(this.options.drivers).map(([t,r])=>[t,new j(r)]))}getSubscribedEvents(){return["onLoad","beforeFlush"]}async onLoad(e){let{entity:t}=e,r=z(t);for(let o of Object.keys(r)){let i=t[o],n=r[o];if(i instanceof h){let s=this.#e(n,i.getDrive());i[a]=s}}}#e(e,t){let r=this.disks.get(t??e.driver??this.options.defaultDriver);if(t&&!r)r=this.disks.get(e.driver??this.options.defaultDriver);if(!r)throw Error(`Unknown attachment driver "${String(t??e.driver??this.options.defaultDriver)}"`);return r}async beforeFlush(e){let{uow:t}=e,r=new Set([...t.getChangeSets().map((o)=>o.entity),...t.getPersistStack()]);for(let o of r)await this.#t(o)}async#t(e){let t=z(e);for(let r of Object.keys(t)){let o=e[r],i=t[r];if(o instanceof h){if(o[l])continue;let n=this.#e(i);o[a]=n;let s=this.#r(i.variants),f=new g(o,{disk:n,options:{...i,variants:s},config:this.options,entity:e,columnName:r,diskName:i.driver??this.options.defaultDriver});try{await f.process()}catch(m){throw console.error(m),Error("Failed to process attachment")}}}}AttachmentDecorator(e){return S(e)}#r(e){if(!e)return;if(Array.isArray(e)){let t={};for(let r of e)if(typeof r==="string"){let o=this.options.variants?.[r];if(!o)throw Error(`Unknown attachment variant "${r}"`);t[r]=o}else if(r&&typeof r==="object")Object.assign(t,r);return t}if(e&&typeof e==="object")return e;return}}export{$ as AttachmentSubscriber,h as Attachment};
1
+ import{a as C}from"./shared/chunk-4thj50nc.js";import{c as W,d as l}from"./shared/chunk-cm81a68q.js";import{Buffer as B}from"node:buffer";var x=Symbol("attachment:loaded"),D=Symbol("attachment:options"),y=Symbol("attachment:file"),i=Symbol("attachment:disk"),P=Symbol("attachment:fn:process"),V=Symbol("attachment:fn:load"),O=Symbol("attachment:fn:save");class p{[x]=!1;[y];[i];data;constructor(t){if(t instanceof File)this[y]=t;else this[x]=!0,this.data=t}#t(){if(!this[x])throw Error("Attachment is not processed, please flush the entity first.")}get#s(){return this.#t(),this[i]}[O](){return this.data??{}}static[V](t){return new p(t)}[P](t){if(this[x])return;this.data=t,this[x]=!0}static fromFile(t){return new p(t)}static async fromUrl(t){let r=await fetch(t),s=await r.arrayBuffer(),e,o=r.headers.get("content-disposition");if(o){let S=o.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i);if(S)e=S[1].replace(/['"]/g,"")}if(!e)e=t.split("?")[0].split("/").pop()||"download";let c=B.from(s),n=new File([c],e,{type:r.headers.get("content-type")??""});return new p(n)}#r(t){this.#t();let r=this.data?.variants.find((s)=>s.name===t)??null;if(!r)throw Error(`Attachment: Variant '${t}' not found`);return r}url(t){if(this.#t(),t)return this.#s?.getUrl(this.#r(t).path);return this.data?.url}originalName(){return this.data?.originalName??""}size(t){if(t)return this.#r(t).size;return this.data?.size}meta(t){if(t)return this.#r(t).meta;return this.data?.meta}preSignedUrl(t,r){if(this.#t(),typeof t==="string")return this.#s?.getSignedUrl(this.#r(t).path,r);return this.#s?.getSignedUrl(this.data?.path??"",r)}blurhash(){return this.#t(),this.data?.blurhash}getDisk(){return this.#s}getDrive(){return this.data?.drive??""}async getBytes(t){this.#t();let r=t?this.#r(t).path:this.data?.path??"";return this.#s?.getBytes(r)}async getBuffer(t){this.#t();let r=await this.getBytes(t);if(!r)return null;return B.from(r)}async getStream(t){this.#t();let r=t?this.#r(t).path:this.data?.path??"";return this.#s?.getStream(r)}getMimeType(t){return this.#t(),(t?this.#r(t):this.data)?.mimeType??""}toJSON(){return{url:this.url(),blurhash:this.blurhash()}}}import{Disk as M}from"flydrive";import{join as I}from"node:path";import{fileTypeFromBuffer as Q}from"file-type";import{v7 as X}from"uuid";class b{att;file;disk;buffer;modelOptions;config;entity;columnName;diskName;fileInfo;constructor(t,r){this.att=t;let{disk:s,options:e,config:o,entity:c,columnName:n,diskName:h}=r;if(this.disk=s,this.modelOptions=e,this.config=o,this.entity=c,this.columnName=n,this.diskName=h,t[x])throw Error("Attachment already processed, please use the Attachment.fromFile method to create a new attachment");this.file=this.att[y]}done(t){this.att[P](t)}generateKey(t,...r){let s=this.modelOptions.folder??"";return s=s.replace(/:([A-Za-z0-9_]+)/g,(e,o)=>{let c=this.entity[o];if(c===void 0||c===null)throw Error(`Missing value for Attachment path "${o}" in entity ${this.entity.constructor.name}. Please ensure that the referenced property is computed before the attachment is processed. (Auto incrementing fields are not supported.)`);return this.normalizeFileName(String(c))}),I(s,t,...r)}normalizeFileName(t){return encodeURIComponent(t.replace(/[^a-zA-Z0-9.-]/g,"_")).toLowerCase()}async fileToBuffer(){if(this.buffer)return this.buffer;let t=await this.file.arrayBuffer(),r=Buffer.from(t);return this.buffer=r,r}getFileName(){if(!this.config.rename)return this.normalizeFileName(this.file.name);if(typeof this.config.rename==="function")return this.normalizeFileName(this.config.rename(this.file,this.columnName,this.entity));return this.normalizeFileName(X())}async getFileType(){return Q(await this.fileToBuffer())}async analyseFile(){let t=await this.getFileType();this.fileInfo={extname:t?.ext??this.file.name.split(".").pop()??"",mimeType:t?.mime??this.file.type,size:this.file.size}}async uploadFile(t,r){await this.disk.put(t,r)}async pickConverter(t,r){if(!this.fileInfo)throw Error("Attachment Converter: File info not found");if(this.config.variants?.[t])r=this.config.variants[t];if(r instanceof C){if(await r.supports({size:this.fileInfo.size,mimeType:this.fileInfo.mimeType,buffer:await this.fileToBuffer(),extname:this.fileInfo.extname}))return r}return null}async process(){await this.analyseFile();let t=await this.fileToBuffer(),r=this.getFileName(),s=this.fileInfo?.extname??"",e=this.generateKey(r,`${r}.${s}`);await this.uploadFile(e,t);let o={name:r,extname:s,size:this.file.size,drive:this.diskName,mimeType:this.file.type,path:e,url:await this.disk.getUrl(e),originalName:this.file.name,variants:[]},c=await this.config.metadata?.metadata({buffer:t,size:this.fileInfo?.size??0,mimeType:this.fileInfo?.mimeType??"",extname:this.fileInfo?.extname??""});if(o.meta=c,this.modelOptions.variants)for(let[n,h]of Object.entries(this.modelOptions.variants)){let S=await this.pickConverter(n,h);if(!S)throw Error(`Attachment Converter: No converter for the variant ${n} found`);let m=await S.handle({buffer:t,size:this.fileInfo?.size??0,mimeType:this.fileInfo?.mimeType??"",extname:this.fileInfo?.extname??"",variantName:n,variant:h}),g=this.generateKey(e.split("/").pop().split(".").shift(),`${n}.${m.extname}`);await this.uploadFile(g,m.buffer),o.variants.push({name:n,extname:m.extname,size:m.buffer.length,mimeType:m.mimeType,path:g})}if(this.fileInfo?.mimeType.startsWith("image/")){if(typeof this.modelOptions.blurhash==="boolean"?this.modelOptions.blurhash:this.modelOptions.blurhash?.enabled)o.blurhash=await l(t,typeof this.modelOptions.blurhash==="object"?this.modelOptions.blurhash:void 0)}this.done(o)}}import{Type as Z,ValidationError as $}from"@mikro-orm/core";class R extends Z{options={folder:"attachments",blurhash:!0};constructor(t){super();this.options={...this.options,...t}}convertToDatabaseValue(t){if(t===null)return null;if(t instanceof p)return t[O]();return t}convertToJSValue(t){try{return p[V](JSON.parse(t))}catch(r){try{return p[V](t)}catch(s){throw $.invalidType(R,t,"database")}}}getColumnType(){return"json"}}var J={blurhash:!0},T={rename:!0},F=["accessor","comment","customOrder","getter","getterName","hidden","index","lazy","name","nullable","serializedName","serializedPrimaryKey","serializer","version"];var{Property:w}=W(["@mikro-orm/decorators/legacy","@mikro-orm/core"]),d=Symbol("attachment:props");function A(t={}){return(r,s)=>{let{mikro:e,attachment:o}=G(t,F);o={...J,...o};let c=r.constructor;if(!c[d])c[d]={};return c[d][s]=o,w({...e,type:new R(o)})(r,s)}}function Y(t){return A(t)}function z(t){let s=t.constructor[d];return s?s:{}}function G(t,r){let s=new Set(r),e={},o={};for(let c in t)if(s.has(c))e[c]=t[c];else o[c]=t[c];return{mikro:e,attachment:o}}class q{options;disks;constructor(t){this.options=t;this.options={...T,...this.options},this.disks=new Map(Object.entries(this.options.drivers).map(([r,s])=>[r,new M(s)]))}async onLoad(t){let{entity:r}=t,s=z(r);for(let e of Object.keys(s)){let o=r[e],c=s[e];if(o instanceof p){let n=this.#t(c,o.getDrive(),!1);if(!n)continue;o[i]=n}}}#t(t,r,s){let e=this.disks.get(r??t.driver??this.options.defaultDriver);if(r&&!e)e=this.disks.get(t.driver??this.options.defaultDriver);if(!e){if(s)throw Error(`Unknown attachment driver "${String(r??t.driver??this.options.defaultDriver)}"`);return null}return e}async beforeFlush(t){let{uow:r}=t,s=new Set([...r.getChangeSets().map((e)=>e.entity),...r.getPersistStack()]);for(let e of s)await this.#s(e)}async#s(t){let r=z(t);for(let s of Object.keys(r)){let e=t[s],o=r[s];if(e instanceof p){if(e[x])continue;let c=this.#t(o);if(!c)continue;e[i]=c;let n=this.#r(o.variants),h=new b(e,{disk:c,options:{...o,variants:n},config:this.options,entity:t,columnName:s,diskName:o.driver??this.options.defaultDriver});try{await h.process()}catch(S){throw console.error(S),Error("Failed to process attachment")}}}}AttachmentDecorator(t){return Y(t)}#r(t){if(!t)return;if(Array.isArray(t)){let r={};for(let s of t)if(typeof s==="string"){let e=this.options.variants?.[s];if(!e)throw Error(`Unknown attachment variant "${s}"`);r[s]=e}else if(s&&typeof s==="object")Object.assign(r,s);return r}if(t&&typeof t==="object")return t;return}}export{q as AttachmentSubscriber,p as Attachment};
@@ -0,0 +1,25 @@
1
+ declare abstract class BaseMetadata<TMetadata> {
2
+ abstract metadata(input: ConvertInput): Promise<TMetadata>;
3
+ }
4
+ type VariantSpec = BaseConverter<ConvertInput, ConvertOutput>;
5
+ interface ConvertInput {
6
+ buffer: Buffer;
7
+ size: number;
8
+ mimeType: string;
9
+ variantName?: string;
10
+ variant?: VariantSpec;
11
+ extname: string;
12
+ }
13
+ interface ConvertOutput {
14
+ buffer: Buffer;
15
+ mimeType: string;
16
+ extname: string;
17
+ }
18
+ declare abstract class BaseConverter<
19
+ TInput extends ConvertInput,
20
+ TOutput extends ConvertOutput
21
+ > {
22
+ abstract supports(input: TInput): Promise<boolean>;
23
+ abstract handle(input: TInput): Promise<TOutput>;
24
+ }
25
+ export { BaseMetadata };
@@ -0,0 +1 @@
1
+ import{e as a}from"../shared/chunk-523ncfdc.js";export{a as BaseMetadata};
@@ -0,0 +1,28 @@
1
+ declare abstract class BaseMetadata<TMetadata> {
2
+ abstract metadata(input: ConvertInput): Promise<TMetadata>;
3
+ }
4
+ type VariantSpec = BaseConverter<ConvertInput, ConvertOutput>;
5
+ interface ConvertInput {
6
+ buffer: Buffer;
7
+ size: number;
8
+ mimeType: string;
9
+ variantName?: string;
10
+ variant?: VariantSpec;
11
+ extname: string;
12
+ }
13
+ interface ConvertOutput {
14
+ buffer: Buffer;
15
+ mimeType: string;
16
+ extname: string;
17
+ }
18
+ declare abstract class BaseConverter<
19
+ TInput extends ConvertInput,
20
+ TOutput extends ConvertOutput
21
+ > {
22
+ abstract supports(input: TInput): Promise<boolean>;
23
+ abstract handle(input: TInput): Promise<TOutput>;
24
+ }
25
+ declare class ImgkitMetadata<T extends exports_index_d.ImageMetadata> extends BaseMetadata<T> {
26
+ metadata(input: ConvertInput): Promise<T>;
27
+ }
28
+ export { ImgkitMetadata };
@@ -0,0 +1 @@
1
+ import{b as t}from"../shared/chunk-cm81a68q.js";import{e as a}from"../shared/chunk-523ncfdc.js";class m extends a{async metadata(e){return await(await t("imgkit")).metadata(e.buffer)}}export{m as ImgkitMetadata};
@@ -0,0 +1 @@
1
+ import"../shared/chunk-cm81a68q.js";import"../shared/chunk-523ncfdc.js";
@@ -0,0 +1,2 @@
1
+ class t{}
2
+ export{t as a};
@@ -0,0 +1,2 @@
1
+ class t{}
2
+ export{t as e};
@@ -0,0 +1,2 @@
1
+ import{createRequire as l}from"module";import{encode as f}from"blurhash";var h=l(import.meta.url);async function p(t){try{let r=await import(t);if(r.default)return r.default;return r}catch(r){throw Error(`Module ${t} not found, please install it first.`)}}function w(t){for(let r of t)try{return h(r)}catch{continue}throw Error(`Could not resolve any of the following modules: ${t.join(", ")}`)}function y(t,r){let{componentX:n,componentY:a}=r??{enabled:!0,componentX:4,componentY:4};return new Promise(async(i,s)=>{try{let e=await p("sharp"),{data:u,info:o}=await e(t).raw().ensureAlpha().toBuffer({resolveWithObject:!0}),c=f(new Uint8ClampedArray(u),o.width,o.height,n,a);return i(c)}catch(e){return s(e)}})}
2
+ export{p as b,w as c,y as d};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikro-orm-attachments",
3
3
  "description": "Attachment handling for MikroORM with support for image processing, variants, and multiple storage drivers",
4
- "version": "1.3.2",
4
+ "version": "2.0.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -17,6 +17,36 @@
17
17
  "default": "./dist/index.js"
18
18
  }
19
19
  },
20
+ "./converters/SharpConverter": {
21
+ "import": {
22
+ "types": "./dist/converters/SharpConverter.d.ts",
23
+ "default": "./dist/converters/SharpConverter.js"
24
+ }
25
+ },
26
+ "./converters/BaseConverter": {
27
+ "import": "./dist/converters/BaseConverter.js"
28
+ },
29
+ "./converters/ImgkitConverter": {
30
+ "import": {
31
+ "types": "./dist/converters/ImgkitConverter.d.ts",
32
+ "default": "./dist/converters/ImgkitConverter.js"
33
+ }
34
+ },
35
+ "./metadata/ImgkitMetadata": {
36
+ "import": {
37
+ "types": "./dist/metadata/ImgkitMetadata.d.ts",
38
+ "default": "./dist/metadata/ImgkitMetadata.js"
39
+ }
40
+ },
41
+ "./metadata/BaseMetadata": {
42
+ "import": {
43
+ "types": "./dist/metadata/BaseMetadata.d.ts",
44
+ "default": "./dist/metadata/BaseMetadata.js"
45
+ }
46
+ },
47
+ "./metadata/SharpMetadata": {
48
+ "import": "./dist/metadata/SharpMetadata.js"
49
+ },
20
50
  "./package.json": "./package.json"
21
51
  },
22
52
  "scripts": {
@@ -43,24 +73,25 @@
43
73
  "url": "https://github.com/reptoxx/mikro-orm-attachments"
44
74
  },
45
75
  "optionalDependencies": {
46
- "@mikro-orm/decorators": "^7.0.0 || ^7.0.0-rc.1 || ^7.0.0-rc.2"
76
+ "@mikro-orm/decorators": "^7.0.9"
47
77
  },
48
78
  "peerDependencies": {
49
- "@mikro-orm/core": "^6.6.3 || ^7.0.0 || ^7.0.0-rc.1 || ^7.0.0-rc.2",
79
+ "@mikro-orm/core": "^7.0.9",
50
80
  "sharp": "^0.34.5",
51
81
  "blurhash": "^2.0.5",
52
- "flydrive": "^1.3.0"
82
+ "flydrive": "^2.1.0"
53
83
  },
54
84
  "devDependencies": {
55
- "@mikro-orm/core": "^6.6.3",
56
- "@typescript/native-preview": "^7.0.0-dev.20260224.1",
57
- "bun-types": "latest",
58
- "bunup": "^0.16.29",
85
+ "@mikro-orm/core": "^7.0.9",
86
+ "@typescript/native-preview": "^7.0.0-dev.20260407.1",
87
+ "bun-types": "1.3.11",
88
+ "bunup": "^0.16.31",
89
+ "imgkit": "^2.2.1",
59
90
  "standard-version": "^9.5.0",
60
- "typescript": "^5.0.0"
91
+ "typescript": "^6.0.2"
61
92
  },
62
93
  "dependencies": {
63
- "file-type": "^21.3.0",
94
+ "file-type": "^21.3.4",
64
95
  "uuid": "^13.0.0"
65
96
  },
66
97
  "engines": {