@jrmc/adonis-attachment 2.4.1 → 3.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.
package/build/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export { defineConfig } from './src/define_config.js';
6
6
  export { Attachmentable } from './src/mixins/attachmentable.js';
7
7
  export * as errors from './src/errors.js';
8
8
  export { attachmentManager };
9
+ export { type AttachmentVariants } from './src/types/config.js';
@@ -5,10 +5,10 @@
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
7
  import type { ApplicationService } from '@adonisjs/core/types';
8
- import type { AttachmentManager } from '../src/attachment_manager.js';
8
+ import { AttachmentService } from '../src/types/config.js';
9
9
  declare module '@adonisjs/core/types' {
10
10
  interface ContainerBindings {
11
- 'jrmc.attachment': AttachmentManager;
11
+ 'jrmc.attachment': AttachmentService;
12
12
  }
13
13
  }
14
14
  export default class AttachmentProvider {
@@ -4,6 +4,6 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
- import { AttachmentManager } from '../src/attachment_manager.js';
8
- declare let manager: AttachmentManager;
7
+ import { AttachmentService } from '../src/types/config.js';
8
+ declare let manager: AttachmentService;
9
9
  export { manager as default };
@@ -7,19 +7,18 @@
7
7
  import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
8
8
  import type { MultipartFile } from '@adonisjs/core/bodyparser';
9
9
  import type { AttachmentBase, Attachment as AttachmentType } from './types/attachment.js';
10
- import type { ResolvedAttachmentConfig } from './types/config.js';
11
10
  import { DeferQueue } from '@poppinss/defer';
12
- import { Attachment } from './attachments/attachment.js';
13
11
  import Converter from './converters/converter.js';
14
- export declare class AttachmentManager {
12
+ import { ResolvedAttachmentConfig } from './define_config.js';
13
+ export declare class AttachmentManager<KnownConverters extends Record<string, Converter>> {
15
14
  #private;
16
15
  queue: DeferQueue;
17
- constructor(config: ResolvedAttachmentConfig, drive: DriveService);
18
- getConfig(): ResolvedAttachmentConfig;
19
- createFromDbResponse(response: any): Attachment | null;
20
- createFromFile(file: MultipartFile): Promise<Attachment>;
21
- createFromBuffer(buffer: Buffer, name?: string): Promise<Attachment>;
22
- createFromBase64(data: string, name?: string): Promise<Attachment>;
16
+ constructor(config: ResolvedAttachmentConfig<KnownConverters>, drive: DriveService);
17
+ getConfig(): ResolvedAttachmentConfig<KnownConverters>;
18
+ createFromDbResponse(response: any): AttachmentType | null;
19
+ createFromFile(file: MultipartFile): Promise<AttachmentType>;
20
+ createFromBuffer(buffer: Buffer, name?: string): Promise<AttachmentType>;
21
+ createFromBase64(data: string, name?: string): Promise<AttachmentType>;
23
22
  getConverter(key: string): Promise<void | Converter>;
24
23
  computeUrl(attachment: AttachmentType | AttachmentBase, signedUrlOptions?: SignedURLOptions): Promise<void>;
25
24
  preComputeUrl(attachment: AttachmentType): Promise<void>;
@@ -33,7 +33,8 @@ export class AttachmentManager {
33
33
  throw new errors.E_CANNOT_CREATE_ATTACHMENT([attribute]);
34
34
  }
35
35
  });
36
- return new Attachment(this.#drive, attributes);
36
+ const attachment = new Attachment(this.#drive, attributes);
37
+ return this.#configureAttachment(attachment);
37
38
  }
38
39
  async createFromFile(file) {
39
40
  const attributes = {
@@ -45,14 +46,16 @@ export class AttachmentManager {
45
46
  if (!file.tmpPath) {
46
47
  throw new errors.ENOENT();
47
48
  }
48
- return new Attachment(this.#drive, attributes, file.tmpPath);
49
+ const attachment = new Attachment(this.#drive, attributes, file.tmpPath);
50
+ return this.#configureAttachment(attachment);
49
51
  }
50
52
  async createFromBuffer(buffer, name) {
51
53
  if (!Buffer.isBuffer(buffer)) {
52
54
  throw new errors.E_ISNOT_BUFFER();
53
55
  }
54
56
  const attributes = await createAttachmentAttributes(buffer, name);
55
- return new Attachment(this.#drive, attributes, buffer);
57
+ const attachment = new Attachment(this.#drive, attributes, buffer);
58
+ return this.#configureAttachment(attachment);
56
59
  }
57
60
  async createFromBase64(data, name) {
58
61
  const base64Data = data.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
@@ -64,11 +67,7 @@ export class AttachmentManager {
64
67
  }
65
68
  async getConverter(key) {
66
69
  if (this.#config.converters) {
67
- for (const c of this.#config.converters) {
68
- if (c.key === key) {
69
- return c.converter;
70
- }
71
- }
70
+ return this.#config.converters[key];
72
71
  }
73
72
  }
74
73
  async computeUrl(attachment, signedUrlOptions) {
@@ -111,8 +110,7 @@ export class AttachmentManager {
111
110
  }
112
111
  async delete(attachment) {
113
112
  if (attachment.path) {
114
- const filePath = attachment.path;
115
- await attachment.getDisk().delete(filePath);
113
+ await attachment.getDisk().delete(attachment.path);
116
114
  if (attachment instanceof Attachment) {
117
115
  if (attachment.variants) {
118
116
  const variantPath = attachment.variants[0].folder;
@@ -120,5 +118,21 @@ export class AttachmentManager {
120
118
  }
121
119
  }
122
120
  }
121
+ if (attachment.originalPath) {
122
+ await attachment.getDisk().delete(attachment.originalPath);
123
+ }
124
+ }
125
+ // private methods
126
+ #configureAttachment(attachment) {
127
+ if (this.#config.meta !== undefined) {
128
+ attachment.setOptions({ meta: this.#config.meta });
129
+ }
130
+ if (this.#config.rename !== undefined) {
131
+ attachment.setOptions({ rename: this.#config.rename });
132
+ }
133
+ if (this.#config.preComputeUrl !== undefined) {
134
+ attachment.setOptions({ preComputeUrl: this.#config.preComputeUrl });
135
+ }
136
+ return attachment;
123
137
  }
124
138
  }
@@ -13,11 +13,21 @@ export declare class Attachment extends AttachmentBase implements AttachmentInte
13
13
  originalName: string;
14
14
  variants?: Variant[];
15
15
  constructor(drive: DriveService, attributes: AttachmentAttributes, input?: Input);
16
+ /**
17
+ * Getters
18
+ */
19
+ get name(): string;
20
+ /**
21
+ * Methods
22
+ */
16
23
  createVariant(key: string, input: Input): Promise<Variant>;
17
24
  getVariant(variantName: string): Variant | undefined;
18
25
  getUrl(variantName?: string): Promise<string>;
19
26
  getSignedUrl(variantNameOrOptions?: string | SignedURLOptions, signedUrlOptions?: SignedURLOptions): Promise<string>;
20
27
  setOptions(options: LucidOptions): this;
28
+ /**
29
+ *
30
+ */
21
31
  toObject(): AttachmentAttributes;
22
32
  toJSON(): Object;
23
33
  }
@@ -22,6 +22,18 @@ export class Attachment extends AttachmentBase {
22
22
  });
23
23
  }
24
24
  }
25
+ /**
26
+ * Getters
27
+ */
28
+ get name() {
29
+ if (this.options && this.options.rename === false) {
30
+ return this.originalName;
31
+ }
32
+ return super.name;
33
+ }
34
+ /**
35
+ * Methods
36
+ */
25
37
  async createVariant(key, input) {
26
38
  const attributes = {
27
39
  ...(await createAttachmentAttributes(input)),
@@ -72,13 +84,6 @@ export class Attachment extends AttachmentBase {
72
84
  ...this.options,
73
85
  ...options,
74
86
  };
75
- if (!this.path) {
76
- if (!this.options.rename) {
77
- this.name = this.originalName;
78
- }
79
- this.folder = this.options.folder;
80
- this.path = path.join(this.folder, this.name);
81
- }
82
87
  if (this.variants) {
83
88
  this.variants.forEach((v) => {
84
89
  v.setOptions({
@@ -89,6 +94,9 @@ export class Attachment extends AttachmentBase {
89
94
  }
90
95
  return this;
91
96
  }
97
+ /**
98
+ *
99
+ */
92
100
  toObject() {
93
101
  const variants = this.variants?.map((v) => v.toObject());
94
102
  return {
@@ -8,22 +8,33 @@ import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
8
8
  import type { LucidOptions, AttachmentBaseAttributes, AttachmentBase as AttachmentBaseInterface } from '../types/attachment.js';
9
9
  import type { Exif, Input } from '../types/input.js';
10
10
  export declare class AttachmentBase implements AttachmentBaseInterface {
11
+ #private;
11
12
  drive: DriveService;
12
13
  input?: Input;
13
- name: string;
14
14
  size: number;
15
15
  extname: string;
16
16
  mimeType: string;
17
17
  meta?: Exif;
18
- folder?: string;
19
- path?: string;
18
+ originalPath?: string;
20
19
  url?: string;
21
20
  options?: LucidOptions;
22
21
  constructor(drive: DriveService, attributes: AttachmentBaseAttributes, input?: Input);
22
+ /**
23
+ * Getters
24
+ */
25
+ get name(): string;
26
+ get folder(): string | undefined;
27
+ get path(): string;
28
+ /**
29
+ * Methods
30
+ */
23
31
  getDisk(): import("flydrive").Disk;
24
32
  getUrl(): Promise<string>;
25
33
  getSignedUrl(signedUrlOptions?: SignedURLOptions): Promise<string>;
26
34
  setOptions(options: LucidOptions): this;
35
+ /**
36
+ *
37
+ */
27
38
  toObject(): AttachmentBaseAttributes;
28
39
  toJSON(): Object;
29
40
  }
@@ -4,18 +4,19 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import path from 'node:path';
7
8
  import { cuid } from '@adonisjs/core/helpers';
8
9
  import { defaultOptionsDecorator } from '../utils/default_values.js';
9
10
  export class AttachmentBase {
10
11
  drive;
11
12
  input;
12
- name;
13
+ #name;
14
+ #folder;
13
15
  size;
14
16
  extname;
15
17
  mimeType;
16
18
  meta;
17
- folder;
18
- path;
19
+ originalPath;
19
20
  url;
20
21
  options;
21
22
  constructor(drive, attributes, input) {
@@ -24,17 +25,35 @@ export class AttachmentBase {
24
25
  this.meta = attributes.meta;
25
26
  this.extname = attributes.extname;
26
27
  this.mimeType = attributes.mimeType;
27
- this.folder = attributes.folder;
28
- this.path = attributes.path;
29
- this.options = defaultOptionsDecorator;
30
- this.drive = drive;
28
+ this.originalPath = attributes.path;
29
+ this.#folder = attributes.folder;
31
30
  if (attributes.name) {
32
- this.name = attributes.name;
31
+ this.#name = attributes.name;
33
32
  }
34
33
  else {
35
- this.name = `${cuid()}.${this.extname}`;
34
+ this.#name = `${cuid()}.${this.extname}`;
35
+ }
36
+ this.options = defaultOptionsDecorator;
37
+ this.drive = drive;
38
+ }
39
+ /**
40
+ * Getters
41
+ */
42
+ get name() {
43
+ return this.#name;
44
+ }
45
+ get folder() {
46
+ if (this.options) {
47
+ return this.options?.folder;
36
48
  }
49
+ return this.#folder;
50
+ }
51
+ get path() {
52
+ return path.join(this.folder, this.name);
37
53
  }
54
+ /**
55
+ * Methods
56
+ */
38
57
  getDisk() {
39
58
  return this.drive.use(this.options?.disk);
40
59
  }
@@ -51,6 +70,9 @@ export class AttachmentBase {
51
70
  };
52
71
  return this;
53
72
  }
73
+ /**
74
+ *
75
+ */
54
76
  toObject() {
55
77
  return {
56
78
  name: this.name,
@@ -9,8 +9,15 @@ import type { VariantAttributes, Variant as VariantInterface } from '../types/at
9
9
  import type { Input } from '../types/input.js';
10
10
  import { AttachmentBase } from './attachment_base.js';
11
11
  export declare class Variant extends AttachmentBase implements VariantInterface {
12
+ #private;
12
13
  key: string;
13
- folder: string;
14
14
  constructor(drive: DriveService, attributes: VariantAttributes, input?: Input);
15
+ /**
16
+ * Getters
17
+ */
18
+ get folder(): string;
19
+ /**
20
+ *
21
+ */
15
22
  toObject(): VariantAttributes;
16
23
  }
@@ -4,17 +4,24 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
- import path from 'node:path';
8
7
  import { AttachmentBase } from './attachment_base.js';
9
8
  export class Variant extends AttachmentBase {
10
9
  key;
11
- folder;
10
+ #folder;
12
11
  constructor(drive, attributes, input) {
13
12
  super(drive, attributes, input);
14
13
  this.key = attributes.key;
15
- this.folder = attributes.folder;
16
- this.path = path.join(this.folder, this.name);
14
+ this.#folder = attributes.folder;
17
15
  }
16
+ /**
17
+ * Getters
18
+ */
19
+ get folder() {
20
+ return this.#folder;
21
+ }
22
+ /**
23
+ *
24
+ */
18
25
  toObject() {
19
26
  return {
20
27
  key: this.key,
@@ -12,23 +12,6 @@ export const attachment = (options) => {
12
12
  if (!target[optionsSym]) {
13
13
  target[optionsSym] = {};
14
14
  }
15
- const defaultConfig = attachmentManager.getConfig();
16
- const defaultOptions = {
17
- meta: defaultConfig.meta !== undefined ? defaultConfig.meta : defaultOptionsDecorator.meta,
18
- rename: defaultConfig.rename !== undefined ? defaultConfig.rename : defaultOptionsDecorator.rename,
19
- preComputeUrl: defaultConfig.preComputeUrl !== undefined
20
- ? defaultConfig.preComputeUrl
21
- : defaultOptionsDecorator.preComputeUrl,
22
- };
23
- if (!options || options?.meta === undefined) {
24
- options.meta = defaultOptions.meta;
25
- }
26
- if (!options || options?.rename === undefined) {
27
- options.rename = defaultOptions.rename;
28
- }
29
- if (!options || options?.preComputeUrl === undefined) {
30
- options.preComputeUrl = defaultOptions.preComputeUrl;
31
- }
32
15
  target[optionsSym][attributeName] = options;
33
16
  const Model = target.constructor;
34
17
  Model.boot();
@@ -40,7 +23,16 @@ export const attachment = (options) => {
40
23
  consume: (value) => {
41
24
  if (value) {
42
25
  const attachment = attachmentManager.createFromDbResponse(value);
43
- attachment?.setOptions({ disk, folder, variants, meta, rename });
26
+ attachment?.setOptions({ disk, folder, variants });
27
+ if (options && options?.meta !== undefined) {
28
+ attachment?.setOptions({ meta: options.meta });
29
+ }
30
+ if (options && options?.rename !== undefined) {
31
+ attachment?.setOptions({ rename: options.rename });
32
+ }
33
+ if (options && options?.preComputeUrl !== undefined) {
34
+ attachment?.setOptions({ preComputeUrl: options.preComputeUrl });
35
+ }
44
36
  return attachment;
45
37
  }
46
38
  else {
@@ -4,6 +4,20 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
- import type { AttachmentConfig, ResolvedAttachmentConfig } from './types/config.js';
7
+ import type { AttachmentConfig, BinPaths, ConverterConfig, Queue } from './types/config.js';
8
8
  import { ConfigProvider } from '@adonisjs/core/types';
9
- export declare function defineConfig(config: AttachmentConfig): ConfigProvider<ResolvedAttachmentConfig>;
9
+ import { Converter } from './types/converter.js';
10
+ /**
11
+ * Config resolved by the "defineConfig" method
12
+ */
13
+ export type ResolvedAttachmentConfig<KnownConverters extends Record<string, Converter>> = {
14
+ bin?: BinPaths;
15
+ meta?: boolean;
16
+ rename?: boolean;
17
+ preComputeUrl?: boolean;
18
+ converters?: {
19
+ [K in keyof KnownConverters]: KnownConverters[K];
20
+ };
21
+ queue?: Queue;
22
+ };
23
+ export declare function defineConfig<KnownConverter extends Record<string, ConverterConfig>>(config: AttachmentConfig<KnownConverter>): ConfigProvider<ResolvedAttachmentConfig<KnownConverter>>;
@@ -8,21 +8,20 @@ import { configProvider } from '@adonisjs/core';
8
8
  // export function defineConfig<T extends AttachmentConfig>(config: T): T {
9
9
  export function defineConfig(config) {
10
10
  return configProvider.create(async (_app) => {
11
- let convertersMap = [];
11
+ const convertersList = Object.keys(config.converters || {});
12
+ const converters = {};
12
13
  if (config.converters) {
13
- convertersMap = await Promise.all(config.converters.map(async (c) => {
14
+ for (let converterName of convertersList) {
15
+ const converter = config.converters[converterName];
14
16
  const binConfig = config.bin;
15
- const { default: value } = await c.converter();
17
+ const { default: value } = await converter.converter();
16
18
  const Converter = value;
17
- return {
18
- key: c.key,
19
- converter: new Converter(c.options, binConfig),
20
- };
21
- }));
19
+ converters[converterName] = new Converter(converter.options, binConfig);
20
+ }
22
21
  }
23
22
  return {
24
23
  ...config,
25
- converters: convertersMap,
24
+ converters,
26
25
  };
27
26
  });
28
27
  }
@@ -8,16 +8,18 @@ import type { DriveService } from '@adonisjs/drive/types';
8
8
  import type { Exif, Input } from './input.js';
9
9
  import type { Disk } from '@adonisjs/drive';
10
10
  import type { SignedURLOptions } from '@adonisjs/drive/types';
11
+ import type { AttachmentVariants } from '@jrmc/adonis-attachment';
11
12
  export type AttachmentBase = {
12
13
  drive: DriveService;
13
14
  input?: Input;
14
15
  name: string;
16
+ folder?: string;
17
+ path?: string;
15
18
  size: number;
16
19
  extname: string;
17
20
  mimeType: string;
18
21
  meta?: Exif;
19
- folder?: string;
20
- path?: string;
22
+ originalPath?: string;
21
23
  url?: string;
22
24
  options?: LucidOptions;
23
25
  getDisk(): Disk;
@@ -45,7 +47,7 @@ export type LucidOptions = {
45
47
  disk?: string;
46
48
  folder?: string;
47
49
  preComputeUrl?: boolean;
48
- variants?: string[];
50
+ variants?: (keyof AttachmentVariants)[];
49
51
  rename?: boolean;
50
52
  meta?: boolean;
51
53
  };
@@ -4,42 +4,45 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import { ConfigProvider } from '@adonisjs/core/types';
7
8
  import type { Converter, ConverterOptions } from './converter.js';
9
+ import { AttachmentManager } from '../attachment_manager.js';
8
10
  type ImportConverter = {
9
11
  default: unknown;
10
12
  };
11
- type ConverterConfig = {
12
- key: string;
13
+ export interface ConverterConfig {
13
14
  converter: () => Promise<ImportConverter>;
14
15
  options?: ConverterOptions;
15
- };
16
- type Queue = {
16
+ }
17
+ export interface Queue {
17
18
  concurrency: number;
18
- };
19
+ }
19
20
  export type BinPaths = {
20
21
  ffmpegPath?: string;
21
22
  ffprobePath?: string;
22
23
  pdftocairoBasePath?: string;
23
24
  libreofficePaths?: Array<string>;
24
25
  };
25
- export type AttachmentConfig = {
26
- bin?: BinPaths;
27
- meta?: boolean;
28
- rename?: boolean;
29
- preComputeUrl?: boolean;
30
- converters?: ConverterConfig[];
31
- queue?: Queue;
32
- };
33
- export type ResolvedConverter = {
34
- key: string;
35
- converter: Converter;
36
- };
37
- export type ResolvedAttachmentConfig = {
26
+ export type AttachmentConfig<KnownConverter extends Record<string, ConverterConfig>> = {
38
27
  bin?: BinPaths;
39
28
  meta?: boolean;
40
29
  rename?: boolean;
41
30
  preComputeUrl?: boolean;
42
- converters?: ResolvedConverter[];
31
+ converters?: {
32
+ [K in keyof KnownConverter]: KnownConverter[K];
33
+ };
43
34
  queue?: Queue;
44
35
  };
36
+ export interface AttachmentVariants {
37
+ }
38
+ export type InferConverters<Config extends ConfigProvider<{
39
+ bin?: unknown;
40
+ meta?: unknown;
41
+ rename?: unknown;
42
+ preComputeUrl?: unknown;
43
+ converters?: unknown;
44
+ queue?: unknown;
45
+ }>> = Exclude<Awaited<ReturnType<Config['resolver']>>['converters'], undefined>;
46
+ export interface AttachmentService extends AttachmentManager<AttachmentVariants extends Record<string, Converter> ? AttachmentVariants : never> {
47
+ }
45
48
  export {};
@@ -0,0 +1,5 @@
1
+ export * from './attachment.js';
2
+ export * from './config.js';
3
+ export * from './converter.js';
4
+ export * from './input.js';
5
+ export * from './mixin.js';
@@ -0,0 +1,5 @@
1
+ export * from './attachment.js';
2
+ export * from './config.js';
3
+ export * from './converter.js';
4
+ export * from './input.js';
5
+ export * from './mixin.js';
@@ -2,15 +2,21 @@
2
2
  exports({ to: app.configPath('attachment.ts') })
3
3
  }}}
4
4
  import { defineConfig } from '@jrmc/adonis-attachment'
5
+ import { InferConverters } from '@jrmc/adonis-attachment/types/config'
5
6
 
6
- export default defineConfig({
7
- converters: [
8
- {
9
- key: 'thumbnail',
7
+ const attachmentConfig = defineConfig({
8
+ converters: {
9
+ thumbnail: {
10
10
  converter: () => import('@jrmc/adonis-attachment/converters/image_converter'),
11
11
  options: {
12
12
  resize: 300,
13
13
  }
14
14
  }
15
- ]
15
+ }
16
16
  })
17
+
18
+ export default attachmentConfig
19
+
20
+ declare module '@jrmc/adonis-attachment' {
21
+ interface AttachmentVariants extends InferConverters<typeof attachmentConfig> {}
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrmc/adonis-attachment",
3
- "version": "2.4.1",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "Turn any field on your Lucid model to an attachment data type",
6
6
  "engines": {