@adonis-agora/media 0.1.1 → 0.2.1
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/CHANGELOG.md +87 -0
- package/dist/configure.d.ts +2 -2
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +3 -2
- package/dist/configure.js.map +1 -1
- package/dist/providers/media_provider.d.ts +15 -0
- package/dist/providers/media_provider.d.ts.map +1 -1
- package/dist/providers/media_provider.js +224 -12
- package/dist/providers/media_provider.js.map +1 -1
- package/dist/src/attachment.d.ts +1 -0
- package/dist/src/attachment.d.ts.map +1 -1
- package/dist/src/attachment.js +57 -21
- package/dist/src/attachment.js.map +1 -1
- package/dist/src/define_config.d.ts +83 -5
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +3 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/diagnostics.d.ts +32 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/disks/factory.d.ts +62 -0
- package/dist/src/disks/factory.d.ts.map +1 -0
- package/dist/src/disks/factory.js +57 -0
- package/dist/src/disks/factory.js.map +1 -0
- package/dist/src/disks/s3.d.ts +76 -0
- package/dist/src/disks/s3.d.ts.map +1 -0
- package/dist/src/disks/s3.js +357 -0
- package/dist/src/disks/s3.js.map +1 -0
- package/dist/src/disks/xml_fallback.d.ts +29 -0
- package/dist/src/disks/xml_fallback.d.ts.map +1 -0
- package/dist/src/disks/xml_fallback.js +68 -0
- package/dist/src/disks/xml_fallback.js.map +1 -0
- package/dist/src/errors.d.ts +34 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +60 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/extended_disk.d.ts +10 -0
- package/dist/src/extended_disk.d.ts.map +1 -0
- package/dist/src/extended_disk.js +19 -0
- package/dist/src/extended_disk.js.map +1 -0
- package/dist/src/index.d.ts +17 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/media_library.d.ts +4 -2
- package/dist/src/media_library.d.ts.map +1 -1
- package/dist/src/media_library.js +83 -32
- package/dist/src/media_library.js.map +1 -1
- package/dist/src/media_manager.d.ts +48 -0
- package/dist/src/media_manager.d.ts.map +1 -1
- package/dist/src/media_manager.js +68 -0
- package/dist/src/media_manager.js.map +1 -1
- package/dist/src/media_store.d.ts +40 -0
- package/dist/src/media_store.d.ts.map +1 -1
- package/dist/src/media_store.js +28 -1
- package/dist/src/media_store.js.map +1 -1
- package/dist/src/multipart.d.ts +10 -0
- package/dist/src/multipart.d.ts.map +1 -0
- package/dist/src/multipart.js +16 -0
- package/dist/src/multipart.js.map +1 -0
- package/dist/src/resumable_upload.d.ts +131 -0
- package/dist/src/resumable_upload.d.ts.map +1 -0
- package/dist/src/resumable_upload.js +177 -0
- package/dist/src/resumable_upload.js.map +1 -0
- package/dist/src/services/main.d.ts +14 -0
- package/dist/src/services/main.d.ts.map +1 -0
- package/dist/src/services/main.js +18 -0
- package/dist/src/services/main.js.map +1 -0
- package/dist/src/stores/factory.d.ts +12 -4
- package/dist/src/stores/factory.d.ts.map +1 -1
- package/dist/src/stores/factory.js +20 -1
- package/dist/src/stores/factory.js.map +1 -1
- package/dist/src/stores/lucid.d.ts +2 -1
- package/dist/src/stores/lucid.d.ts.map +1 -1
- package/dist/src/stores/lucid.js +42 -7
- package/dist/src/stores/lucid.js.map +1 -1
- package/dist/src/telescope/dashboard.d.ts +19 -0
- package/dist/src/telescope/dashboard.d.ts.map +1 -0
- package/dist/src/telescope/dashboard.js +116 -0
- package/dist/src/telescope/dashboard.js.map +1 -0
- package/dist/src/telescope/data-providers.d.ts +36 -0
- package/dist/src/telescope/data-providers.d.ts.map +1 -0
- package/dist/src/telescope/data-providers.js +286 -0
- package/dist/src/telescope/data-providers.js.map +1 -0
- package/dist/src/telescope/extension.d.ts +26 -0
- package/dist/src/telescope/extension.d.ts.map +1 -0
- package/dist/src/telescope/extension.js +42 -0
- package/dist/src/telescope/extension.js.map +1 -0
- package/dist/src/telescope/index.d.ts +5 -0
- package/dist/src/telescope/index.d.ts.map +1 -0
- package/dist/src/telescope/index.js +4 -0
- package/dist/src/telescope/index.js.map +1 -0
- package/dist/src/telescope/telescope-sdk.d.ts +125 -0
- package/dist/src/telescope/telescope-sdk.d.ts.map +1 -0
- package/dist/src/telescope/telescope-sdk.js +14 -0
- package/dist/src/telescope/telescope-sdk.js.map +1 -0
- package/dist/src/testing/in_memory_disk.d.ts +10 -0
- package/dist/src/testing/in_memory_disk.d.ts.map +1 -1
- package/dist/src/testing/in_memory_disk.js +11 -0
- package/dist/src/testing/in_memory_disk.js.map +1 -1
- package/dist/src/testing/in_memory_media_store.d.ts +2 -1
- package/dist/src/testing/in_memory_media_store.d.ts.map +1 -1
- package/dist/src/testing/in_memory_media_store.js +28 -0
- package/dist/src/testing/in_memory_media_store.js.map +1 -1
- package/dist/src/testing/in_memory_upload_session_store.d.ts +18 -0
- package/dist/src/testing/in_memory_upload_session_store.d.ts.map +1 -0
- package/dist/src/testing/in_memory_upload_session_store.js +40 -0
- package/dist/src/testing/in_memory_upload_session_store.js.map +1 -0
- package/dist/src/testing/index.d.ts +1 -0
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/tus.d.ts +57 -0
- package/dist/src/tus.d.ts.map +1 -0
- package/dist/src/tus.js +143 -0
- package/dist/src/tus.js.map +1 -0
- package/dist/src/types.d.ts +125 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/upload_manager.d.ts +127 -0
- package/dist/src/upload_manager.d.ts.map +1 -0
- package/dist/src/upload_manager.js +152 -0
- package/dist/src/upload_manager.js.map +1 -0
- package/dist/src/upload_mode.d.ts +27 -0
- package/dist/src/upload_mode.d.ts.map +1 -0
- package/dist/src/upload_mode.js +22 -0
- package/dist/src/upload_mode.js.map +1 -0
- package/dist/src/upload_sessions/factory.d.ts +62 -0
- package/dist/src/upload_sessions/factory.d.ts.map +1 -0
- package/dist/src/upload_sessions/factory.js +60 -0
- package/dist/src/upload_sessions/factory.js.map +1 -0
- package/dist/src/upload_sessions/lucid.d.ts +35 -0
- package/dist/src/upload_sessions/lucid.d.ts.map +1 -0
- package/dist/src/upload_sessions/lucid.js +105 -0
- package/dist/src/upload_sessions/lucid.js.map +1 -0
- package/dist/stubs/config/media.stub +16 -1
- package/dist/stubs/database/migrations/create_media_upload_sessions_table.stub +45 -0
- package/package.json +37 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,mBAAmB,CASjF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural guard: does this disk implement the optional {@link MultipartUploadDisk} surface?
|
|
3
|
+
* The base {@link Disk} contract deliberately omits multipart, so the upload layer detects it here
|
|
4
|
+
* at runtime (the bundled `disks.s3()` disk provides it; the in-memory/Drive disks do not) rather
|
|
5
|
+
* than depending on it statically. A disk is treated as multipart-capable when it exposes the whole
|
|
6
|
+
* create/upload/presign/complete/abort surface.
|
|
7
|
+
*/
|
|
8
|
+
export function isMultipartCapable(disk) {
|
|
9
|
+
const d = disk;
|
|
10
|
+
return (typeof d.createMultipartUpload === 'function' &&
|
|
11
|
+
typeof d.presignUploadPart === 'function' &&
|
|
12
|
+
typeof d.completeMultipartUpload === 'function' &&
|
|
13
|
+
typeof d.abortMultipartUpload === 'function' &&
|
|
14
|
+
typeof d.uploadPart === 'function');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=multipart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.js","sourceRoot":"","sources":["../../src/multipart.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAU;IAC3C,MAAM,CAAC,GAAG,IAAoC,CAAC;IAC/C,OAAO,CACL,OAAO,CAAC,CAAC,qBAAqB,KAAK,UAAU;QAC7C,OAAO,CAAC,CAAC,iBAAiB,KAAK,UAAU;QACzC,OAAO,CAAC,CAAC,uBAAuB,KAAK,UAAU;QAC/C,OAAO,CAAC,CAAC,oBAAoB,KAAK,UAAU;QAC5C,OAAO,CAAC,CAAC,UAAU,KAAK,UAAU,CACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { StorageManager } from './storage_manager.js';
|
|
2
|
+
import type { MultipartPart } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One in-flight resumable upload. Persisted through an {@link UploadSessionStore} so a dropped
|
|
5
|
+
* connection resumes from {@link offset} rather than re-sending. Bytes accumulate as temporary
|
|
6
|
+
* parts on the target disk (native S3 multipart when the disk is multipart-capable, otherwise
|
|
7
|
+
* buffered part objects) until {@link ResumableUploadManager.complete} assembles the final object.
|
|
8
|
+
*/
|
|
9
|
+
export interface UploadSession {
|
|
10
|
+
id: string;
|
|
11
|
+
disk: string;
|
|
12
|
+
/** Final object key the assembled upload will be written to. */
|
|
13
|
+
key: string;
|
|
14
|
+
contentType: string | undefined;
|
|
15
|
+
/** Total expected size in bytes, if declared up front (TUS `Upload-Length`). */
|
|
16
|
+
size: number | undefined;
|
|
17
|
+
/** Bytes received so far — the resume offset. */
|
|
18
|
+
offset: number;
|
|
19
|
+
/** Number of chunk parts written so far. */
|
|
20
|
+
parts: number;
|
|
21
|
+
/** S3 (or other native-multipart) upload id, when the target disk is multipart-capable. */
|
|
22
|
+
multipartUploadId?: string;
|
|
23
|
+
/** When the session was created. */
|
|
24
|
+
createdAt?: Date;
|
|
25
|
+
/** When the session expires (TUS `expiration` extension); absent = never expires. */
|
|
26
|
+
expiresAt?: Date;
|
|
27
|
+
/** Decoded TUS `Upload-Metadata`, retained for finalization/inspection. */
|
|
28
|
+
metadata?: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/** Optional filter for {@link UploadSessionStore.list}. */
|
|
31
|
+
export interface UploadSessionListFilter {
|
|
32
|
+
/** Only sessions on this disk. */
|
|
33
|
+
disk?: string;
|
|
34
|
+
/** Only sessions whose `key` starts with this prefix. */
|
|
35
|
+
keyPrefix?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Persistence SPI for resumable upload sessions. Ships with an in-memory driver
|
|
39
|
+
* (`@adonis-agora/media/testing`) and a Lucid driver (`@adonis-agora/media/upload_sessions/lucid`),
|
|
40
|
+
* mirroring the {@link MediaStore} pattern. `addPart`/`listParts` are optional and only needed when
|
|
41
|
+
* the target disk is multipart-capable (they record the S3 part ETags for finalization).
|
|
42
|
+
*/
|
|
43
|
+
export interface UploadSessionStore {
|
|
44
|
+
create(session: UploadSession): Promise<UploadSession>;
|
|
45
|
+
get(id: string): Promise<UploadSession | null>;
|
|
46
|
+
update(session: UploadSession): Promise<UploadSession>;
|
|
47
|
+
delete(id: string): Promise<void>;
|
|
48
|
+
/** Record one part's ETag, keyed by partNumber (multipart-capable disks only). */
|
|
49
|
+
addPart?(id: string, part: MultipartPart): Promise<void>;
|
|
50
|
+
/** All recorded parts for a session (unordered). Used by `complete()` + resume. */
|
|
51
|
+
listParts?(id: string): Promise<MultipartPart[]>;
|
|
52
|
+
/**
|
|
53
|
+
* List currently-stored (in-progress) sessions, optionally filtered. Admin-facing (an "uploads in
|
|
54
|
+
* progress" view), not a hot path. Optional: stores that cannot enumerate omit it and callers
|
|
55
|
+
* degrade to an empty list.
|
|
56
|
+
*/
|
|
57
|
+
list?(filter?: UploadSessionListFilter): Promise<UploadSession[]>;
|
|
58
|
+
}
|
|
59
|
+
export interface CreateUploadInput {
|
|
60
|
+
disk: string;
|
|
61
|
+
key: string;
|
|
62
|
+
size?: number;
|
|
63
|
+
contentType?: string;
|
|
64
|
+
metadata?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
export interface ResumableUploadManagerOptions {
|
|
67
|
+
storage: StorageManager;
|
|
68
|
+
sessions: UploadSessionStore;
|
|
69
|
+
/** Prefix for temporary chunk parts on the target disk (buffered path). Default `.uploads`. */
|
|
70
|
+
tmpPrefix?: string;
|
|
71
|
+
/** Session lifetime in seconds (TUS `expiration`). Omit for sessions that never expire. */
|
|
72
|
+
sessionTtlSeconds?: number;
|
|
73
|
+
idGenerator?: () => string;
|
|
74
|
+
/** Injectable clock (tests drive expiry deterministically). Default `() => new Date()`. */
|
|
75
|
+
clock?: () => Date;
|
|
76
|
+
/** Emit `upload.*` diagnostics events (default true). */
|
|
77
|
+
emitDiagnostics?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Framework-agnostic resumable-upload engine — the storage core the TUS HTTP handler drives. Bytes
|
|
81
|
+
* flow through the backend in chunks; each chunk is written immediately as a part on the target disk
|
|
82
|
+
* (native S3 multipart when available, else a buffered part object), so a dropped connection resumes
|
|
83
|
+
* from `offset` without re-sending. {@link complete} assembles the parts into the final object.
|
|
84
|
+
*
|
|
85
|
+
* This never imports AdonisJS; the provider exposes it over TUS routes and {@link MediaManager}
|
|
86
|
+
* surfaces it as `media.resumable`.
|
|
87
|
+
*/
|
|
88
|
+
export declare class ResumableUploadManager {
|
|
89
|
+
private readonly storage;
|
|
90
|
+
private readonly sessions;
|
|
91
|
+
private readonly tmpPrefix;
|
|
92
|
+
private readonly ttlSeconds;
|
|
93
|
+
private readonly newId;
|
|
94
|
+
private readonly now;
|
|
95
|
+
private readonly emitDiagnostics;
|
|
96
|
+
constructor(options: ResumableUploadManagerOptions);
|
|
97
|
+
private partKey;
|
|
98
|
+
/** Begin a resumable upload session and reserve any native multipart upload the disk supports. */
|
|
99
|
+
createUpload(input: CreateUploadInput): Promise<UploadSession>;
|
|
100
|
+
/** Append a chunk at `offset` (must equal the session's current offset). Returns the new offset. */
|
|
101
|
+
writeChunk(id: string, offset: number, chunk: Uint8Array): Promise<{
|
|
102
|
+
offset: number;
|
|
103
|
+
}>;
|
|
104
|
+
/** Current offset, declared size, and expiry for a session (drives the TUS `HEAD` response). */
|
|
105
|
+
status(id: string): Promise<{
|
|
106
|
+
offset: number;
|
|
107
|
+
size: number | undefined;
|
|
108
|
+
expiresAt: Date | undefined;
|
|
109
|
+
}>;
|
|
110
|
+
/** Assemble all parts into the final object, clean up, and return the final key/disk/size. */
|
|
111
|
+
complete(id: string): Promise<{
|
|
112
|
+
key: string;
|
|
113
|
+
disk: string;
|
|
114
|
+
size: number;
|
|
115
|
+
}>;
|
|
116
|
+
/** Discard an in-flight session, freeing its native multipart upload or buffered parts. */
|
|
117
|
+
abort(id: string): Promise<void>;
|
|
118
|
+
/** All recorded parts for a session (multipart-capable disks). */
|
|
119
|
+
listParts(id: string): Promise<MultipartPart[]>;
|
|
120
|
+
/** In-progress sessions, optionally filtered. Empty when the store cannot enumerate. */
|
|
121
|
+
list(filter?: UploadSessionListFilter): Promise<UploadSession[]>;
|
|
122
|
+
private emit;
|
|
123
|
+
private cleanup;
|
|
124
|
+
/**
|
|
125
|
+
* Load a session, treating an expired one as gone: its native/buffered parts are cleaned up, the
|
|
126
|
+
* record is deleted, and an {@link UploadSessionExpiredError} is thrown (the TUS handler maps this
|
|
127
|
+
* to `410 Gone`).
|
|
128
|
+
*/
|
|
129
|
+
private require;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=resumable_upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable_upload.d.ts","sourceRoot":"","sources":["../../src/resumable_upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { publishMedia } from './diagnostics.js';
|
|
3
|
+
import { UploadNotSupportedError, UploadOffsetConflictError, UploadSessionExpiredError, UploadSessionNotFoundError, } from './errors.js';
|
|
4
|
+
import { isMultipartCapable } from './multipart.js';
|
|
5
|
+
/**
|
|
6
|
+
* Framework-agnostic resumable-upload engine — the storage core the TUS HTTP handler drives. Bytes
|
|
7
|
+
* flow through the backend in chunks; each chunk is written immediately as a part on the target disk
|
|
8
|
+
* (native S3 multipart when available, else a buffered part object), so a dropped connection resumes
|
|
9
|
+
* from `offset` without re-sending. {@link complete} assembles the parts into the final object.
|
|
10
|
+
*
|
|
11
|
+
* This never imports AdonisJS; the provider exposes it over TUS routes and {@link MediaManager}
|
|
12
|
+
* surfaces it as `media.resumable`.
|
|
13
|
+
*/
|
|
14
|
+
export class ResumableUploadManager {
|
|
15
|
+
storage;
|
|
16
|
+
sessions;
|
|
17
|
+
tmpPrefix;
|
|
18
|
+
ttlSeconds;
|
|
19
|
+
newId;
|
|
20
|
+
now;
|
|
21
|
+
emitDiagnostics;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.storage = options.storage;
|
|
24
|
+
this.sessions = options.sessions;
|
|
25
|
+
this.tmpPrefix = options.tmpPrefix ?? '.uploads';
|
|
26
|
+
this.ttlSeconds = options.sessionTtlSeconds;
|
|
27
|
+
this.newId = options.idGenerator ?? (() => randomUUID());
|
|
28
|
+
this.now = options.clock ?? (() => new Date());
|
|
29
|
+
this.emitDiagnostics = options.emitDiagnostics ?? true;
|
|
30
|
+
}
|
|
31
|
+
partKey(session, part) {
|
|
32
|
+
return `${this.tmpPrefix}/${session.id}/${part}`;
|
|
33
|
+
}
|
|
34
|
+
/** Begin a resumable upload session and reserve any native multipart upload the disk supports. */
|
|
35
|
+
async createUpload(input) {
|
|
36
|
+
const disk = this.storage.disk(input.disk);
|
|
37
|
+
const multipart = isMultipartCapable(disk)
|
|
38
|
+
? {
|
|
39
|
+
multipartUploadId: (await disk.createMultipartUpload(input.key, input.contentType ? { contentType: input.contentType } : undefined)).uploadId,
|
|
40
|
+
}
|
|
41
|
+
: {};
|
|
42
|
+
const createdAt = this.now();
|
|
43
|
+
const session = await this.sessions.create({
|
|
44
|
+
id: this.newId(),
|
|
45
|
+
disk: input.disk,
|
|
46
|
+
key: input.key,
|
|
47
|
+
contentType: input.contentType,
|
|
48
|
+
size: input.size,
|
|
49
|
+
offset: 0,
|
|
50
|
+
parts: 0,
|
|
51
|
+
createdAt,
|
|
52
|
+
...(this.ttlSeconds !== undefined
|
|
53
|
+
? { expiresAt: new Date(createdAt.getTime() + this.ttlSeconds * 1000) }
|
|
54
|
+
: {}),
|
|
55
|
+
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
56
|
+
...multipart,
|
|
57
|
+
});
|
|
58
|
+
this.emit('upload.start', {
|
|
59
|
+
id: session.id,
|
|
60
|
+
disk: session.disk,
|
|
61
|
+
key: session.key,
|
|
62
|
+
mode: 'proxy',
|
|
63
|
+
size: session.size,
|
|
64
|
+
contentType: session.contentType,
|
|
65
|
+
});
|
|
66
|
+
return session;
|
|
67
|
+
}
|
|
68
|
+
/** Append a chunk at `offset` (must equal the session's current offset). Returns the new offset. */
|
|
69
|
+
async writeChunk(id, offset, chunk) {
|
|
70
|
+
const session = await this.require(id);
|
|
71
|
+
if (offset !== session.offset) {
|
|
72
|
+
throw new UploadOffsetConflictError(session.offset, offset);
|
|
73
|
+
}
|
|
74
|
+
const disk = this.storage.disk(session.disk);
|
|
75
|
+
if (session.multipartUploadId && isMultipartCapable(disk)) {
|
|
76
|
+
if (typeof this.sessions.addPart !== 'function') {
|
|
77
|
+
throw new UploadNotSupportedError(session.disk, 'resumable multipart part recording');
|
|
78
|
+
}
|
|
79
|
+
const part = await disk.uploadPart(session.key, session.multipartUploadId, session.parts + 1, chunk);
|
|
80
|
+
await this.sessions.addPart(id, part);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await disk.put(this.partKey(session, session.parts), chunk);
|
|
84
|
+
}
|
|
85
|
+
session.parts += 1;
|
|
86
|
+
session.offset += chunk.byteLength;
|
|
87
|
+
await this.sessions.update(session);
|
|
88
|
+
this.emit('upload.progress', {
|
|
89
|
+
id: session.id,
|
|
90
|
+
offset: session.offset,
|
|
91
|
+
parts: session.parts,
|
|
92
|
+
size: session.size,
|
|
93
|
+
});
|
|
94
|
+
return { offset: session.offset };
|
|
95
|
+
}
|
|
96
|
+
/** Current offset, declared size, and expiry for a session (drives the TUS `HEAD` response). */
|
|
97
|
+
async status(id) {
|
|
98
|
+
const session = await this.require(id);
|
|
99
|
+
return { offset: session.offset, size: session.size, expiresAt: session.expiresAt };
|
|
100
|
+
}
|
|
101
|
+
/** Assemble all parts into the final object, clean up, and return the final key/disk/size. */
|
|
102
|
+
async complete(id) {
|
|
103
|
+
const session = await this.require(id);
|
|
104
|
+
const disk = this.storage.disk(session.disk);
|
|
105
|
+
if (session.multipartUploadId && isMultipartCapable(disk)) {
|
|
106
|
+
const parts = [...((await this.sessions.listParts?.(id)) ?? [])].sort((a, b) => a.partNumber - b.partNumber);
|
|
107
|
+
await disk.completeMultipartUpload(session.key, session.multipartUploadId, parts);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const chunks = [];
|
|
111
|
+
for (let part = 0; part < session.parts; part += 1) {
|
|
112
|
+
chunks.push(Buffer.from(await disk.getBytes(this.partKey(session, part))));
|
|
113
|
+
}
|
|
114
|
+
await disk.put(session.key, new Uint8Array(Buffer.concat(chunks)), session.contentType ? { contentType: session.contentType } : {});
|
|
115
|
+
}
|
|
116
|
+
await this.cleanup(session);
|
|
117
|
+
await this.sessions.delete(id);
|
|
118
|
+
const size = session.size ?? session.offset;
|
|
119
|
+
this.emit('upload.complete', { id: session.id, disk: session.disk, key: session.key });
|
|
120
|
+
return { key: session.key, disk: session.disk, size };
|
|
121
|
+
}
|
|
122
|
+
/** Discard an in-flight session, freeing its native multipart upload or buffered parts. */
|
|
123
|
+
async abort(id) {
|
|
124
|
+
const session = await this.sessions.get(id);
|
|
125
|
+
if (!session)
|
|
126
|
+
return;
|
|
127
|
+
const disk = this.storage.disk(session.disk);
|
|
128
|
+
if (session.multipartUploadId && isMultipartCapable(disk)) {
|
|
129
|
+
await disk.abortMultipartUpload(session.key, session.multipartUploadId);
|
|
130
|
+
}
|
|
131
|
+
await this.cleanup(session);
|
|
132
|
+
await this.sessions.delete(id);
|
|
133
|
+
this.emit('upload.abort', { id: session.id, disk: session.disk, key: session.key });
|
|
134
|
+
}
|
|
135
|
+
/** All recorded parts for a session (multipart-capable disks). */
|
|
136
|
+
async listParts(id) {
|
|
137
|
+
await this.require(id);
|
|
138
|
+
return (await this.sessions.listParts?.(id)) ?? [];
|
|
139
|
+
}
|
|
140
|
+
/** In-progress sessions, optionally filtered. Empty when the store cannot enumerate. */
|
|
141
|
+
async list(filter) {
|
|
142
|
+
return (await this.sessions.list?.(filter)) ?? [];
|
|
143
|
+
}
|
|
144
|
+
emit(event, payload) {
|
|
145
|
+
if (this.emitDiagnostics)
|
|
146
|
+
publishMedia(event, payload);
|
|
147
|
+
}
|
|
148
|
+
async cleanup(session) {
|
|
149
|
+
if (session.multipartUploadId)
|
|
150
|
+
return;
|
|
151
|
+
const disk = this.storage.disk(session.disk);
|
|
152
|
+
for (let part = 0; part < session.parts; part += 1) {
|
|
153
|
+
await disk.delete(this.partKey(session, part)).catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Load a session, treating an expired one as gone: its native/buffered parts are cleaned up, the
|
|
158
|
+
* record is deleted, and an {@link UploadSessionExpiredError} is thrown (the TUS handler maps this
|
|
159
|
+
* to `410 Gone`).
|
|
160
|
+
*/
|
|
161
|
+
async require(id) {
|
|
162
|
+
const session = await this.sessions.get(id);
|
|
163
|
+
if (!session)
|
|
164
|
+
throw new UploadSessionNotFoundError(id);
|
|
165
|
+
if (session.expiresAt && session.expiresAt.getTime() <= this.now().getTime()) {
|
|
166
|
+
const disk = this.storage.disk(session.disk);
|
|
167
|
+
if (session.multipartUploadId && isMultipartCapable(disk)) {
|
|
168
|
+
await disk.abortMultipartUpload(session.key, session.multipartUploadId).catch(() => { });
|
|
169
|
+
}
|
|
170
|
+
await this.cleanup(session);
|
|
171
|
+
await this.sessions.delete(id);
|
|
172
|
+
throw new UploadSessionExpiredError(id);
|
|
173
|
+
}
|
|
174
|
+
return session;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=resumable_upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable_upload.js","sourceRoot":"","sources":["../../src/resumable_upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MediaManager } from '../media_manager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Service-singleton for the {@link MediaManager}, resolved from the container once the app has
|
|
4
|
+
* booted. Import it for a ready-to-use manager instead of resolving the binding by hand:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import media from '@adonis-agora/media/services/main'
|
|
8
|
+
*
|
|
9
|
+
* await media.library.attach({ ownerType: 'Post', ownerId: '1', collection: 'gallery', ... })
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare let media: MediaManager;
|
|
13
|
+
export { media as default };
|
|
14
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/services/main.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;;;;;;;;GASG;AACH,QAAA,IAAI,KAAK,EAAE,YAAY,CAAC;AAMxB,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import app from '@adonisjs/core/services/app';
|
|
2
|
+
import { MediaManager } from '../media_manager.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service-singleton for the {@link MediaManager}, resolved from the container once the app has
|
|
5
|
+
* booted. Import it for a ready-to-use manager instead of resolving the binding by hand:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import media from '@adonis-agora/media/services/main'
|
|
9
|
+
*
|
|
10
|
+
* await media.library.attach({ ownerType: 'Post', ownerId: '1', collection: 'gallery', ... })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
let media;
|
|
14
|
+
await app.booted(async () => {
|
|
15
|
+
media = await app.container.make(MediaManager);
|
|
16
|
+
});
|
|
17
|
+
export { media as default };
|
|
18
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../../src/services/main.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,6BAA6B,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;;;;;;;;GASG;AACH,IAAI,KAAmB,CAAC;AAExB,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;IAC1B,KAAK,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -21,9 +21,6 @@ export interface LucidStoreConfig {
|
|
|
21
21
|
/** Table name. Default `media`. */
|
|
22
22
|
table?: string;
|
|
23
23
|
}
|
|
24
|
-
/** Options for the in-memory media store (single-process, non-durable). */
|
|
25
|
-
export interface MemoryStoreConfig {
|
|
26
|
-
}
|
|
27
24
|
/**
|
|
28
25
|
* The store factory namespace used in `config/media.ts`:
|
|
29
26
|
*
|
|
@@ -44,8 +41,19 @@ export interface MemoryStoreConfig {
|
|
|
44
41
|
*/
|
|
45
42
|
export declare const stores: {
|
|
46
43
|
/** In-memory store — single-process, non-durable. Handy for tests and scratch apps. */
|
|
47
|
-
memory(
|
|
44
|
+
memory(): StoreFactory;
|
|
48
45
|
/** Persist media records in SQL via `@adonisjs/lucid`. */
|
|
49
46
|
lucid(config?: LucidStoreConfig): StoreFactory;
|
|
50
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Build the media store selected by `config.store` from the `config.stores` map. When a store IS
|
|
50
|
+
* named but has no matching factory (a typo or a missing `stores` entry), this THROWS a
|
|
51
|
+
* {@link StoreNotConfiguredError} rather than silently falling back to the in-memory store — a
|
|
52
|
+
* non-durable fallback would lose data on restart. Only the zero-config path (no `store` named at
|
|
53
|
+
* all) resolves to the in-memory store.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveStore(config: {
|
|
56
|
+
store?: string;
|
|
57
|
+
stores?: Record<string, StoreFactory>;
|
|
58
|
+
}, ctx: StoreContext): Promise<MediaStore>;
|
|
51
59
|
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/stores/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/stores/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,kBAAkB,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,MAAM;IACjB,uFAAuF;cAC7E,YAAY;IAOtB,0DAA0D;mBAC5C,gBAAgB,GAAQ,YAAY;CAUnD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,EACjE,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,UAAU,CAAC,CASrB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StoreNotConfiguredError } from '../errors.js';
|
|
1
2
|
/**
|
|
2
3
|
* The store factory namespace used in `config/media.ts`:
|
|
3
4
|
*
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
*/
|
|
19
20
|
export const stores = {
|
|
20
21
|
/** In-memory store — single-process, non-durable. Handy for tests and scratch apps. */
|
|
21
|
-
memory(
|
|
22
|
+
memory() {
|
|
22
23
|
return async () => {
|
|
23
24
|
const { InMemoryMediaStore } = await import('../testing/in_memory_media_store.js');
|
|
24
25
|
return new InMemoryMediaStore();
|
|
@@ -36,4 +37,22 @@ export const stores = {
|
|
|
36
37
|
};
|
|
37
38
|
},
|
|
38
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Build the media store selected by `config.store` from the `config.stores` map. When a store IS
|
|
42
|
+
* named but has no matching factory (a typo or a missing `stores` entry), this THROWS a
|
|
43
|
+
* {@link StoreNotConfiguredError} rather than silently falling back to the in-memory store — a
|
|
44
|
+
* non-durable fallback would lose data on restart. Only the zero-config path (no `store` named at
|
|
45
|
+
* all) resolves to the in-memory store.
|
|
46
|
+
*/
|
|
47
|
+
export async function resolveStore(config, ctx) {
|
|
48
|
+
const name = config.store;
|
|
49
|
+
if (name) {
|
|
50
|
+
const factory = config.stores?.[name];
|
|
51
|
+
if (!factory)
|
|
52
|
+
throw new StoreNotConfiguredError(name);
|
|
53
|
+
return factory(ctx);
|
|
54
|
+
}
|
|
55
|
+
const { InMemoryMediaStore } = await import('../testing/in_memory_media_store.js');
|
|
56
|
+
return new InMemoryMediaStore();
|
|
57
|
+
}
|
|
39
58
|
//# sourceMappingURL=factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/stores/factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/stores/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AA2BvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,uFAAuF;IACvF,MAAM;QACJ,OAAO,KAAK,IAAI,EAAE;YAChB,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,qCAAqC,CAAC,CAAC;YACnF,OAAO,IAAI,kBAAkB,EAAE,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,SAA2B,EAAE;QACjC,OAAO,KAAK,IAAI,EAAE;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC,CAAC,OAAO,CAAC;YACjE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;YACvD,OAAO,IAAI,eAAe,CAAC,EAAE,EAAE;gBAC7B,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjF,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/D,CAAC,CAAC;QACL,CAAC,CAAC;IACJ,CAAC;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAiE,EACjE,GAAiB;IAEjB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC;IAC1B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,qCAAqC,CAAC,CAAC;IACnF,OAAO,IAAI,kBAAkB,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from '@adonisjs/lucid/database';
|
|
2
2
|
import type { MediaRecord } from '../media_record.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type MediaListOptions, type MediaListPage, type MediaStore } from '../media_store.js';
|
|
4
4
|
export interface LucidMediaStoreOptions {
|
|
5
5
|
/** The Lucid connection name. Defaults to the `Database` default connection. */
|
|
6
6
|
connectionName?: string;
|
|
@@ -23,6 +23,7 @@ export declare class LucidMediaStore implements MediaStore {
|
|
|
23
23
|
save(record: MediaRecord): Promise<MediaRecord>;
|
|
24
24
|
find(id: string): Promise<MediaRecord | null>;
|
|
25
25
|
listByOwner(ownerType: string, ownerId: string, collection?: string): Promise<MediaRecord[]>;
|
|
26
|
+
list(options?: MediaListOptions): Promise<MediaListPage>;
|
|
26
27
|
delete(id: string): Promise<void>;
|
|
27
28
|
nextOrder(ownerType: string, ownerId: string, collection: string): Promise<number>;
|
|
28
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lucid.d.ts","sourceRoot":"","sources":["../../../src/stores/lucid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"lucid.d.ts","sourceRoot":"","sources":["../../../src/stores/lucid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EAIhB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAK9C,OAAO,CAAC,QAAQ,CAAC,EAAE;IAJrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAGjC,EAAE,EAAE,QAAQ,EAC7B,OAAO,GAAE,sBAA2B;IAMtC,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IAIP,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ/C,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAK7C,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC;IAOnB,IAAI,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC;IAwC5D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAUzF"}
|
package/dist/src/stores/lucid.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clampMediaListLimit, decodeMediaCursor, encodeMediaCursor, } from '../media_store.js';
|
|
1
2
|
/**
|
|
2
3
|
* Lucid-backed {@link MediaStore}. Persists media records in a `media` table via `@adonisjs/lucid`'s
|
|
3
4
|
* query builder (Knex), so it works across SQLite / Postgres / MySQL. JSON payloads
|
|
@@ -21,13 +22,9 @@ export class LucidMediaStore {
|
|
|
21
22
|
}
|
|
22
23
|
async save(record) {
|
|
23
24
|
const row = toRow(record);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
await this.client().table(this.table).insert(row);
|
|
30
|
-
}
|
|
25
|
+
// Single atomic upsert (portable Knex `INSERT ... ON CONFLICT (id) DO UPDATE`) instead of a
|
|
26
|
+
// racy read-then-branch: concurrent saves of the same id can't interleave into a lost write.
|
|
27
|
+
await this.client().table(this.table).insert(row).onConflict('id').merge();
|
|
31
28
|
return { ...record };
|
|
32
29
|
}
|
|
33
30
|
async find(id) {
|
|
@@ -41,6 +38,44 @@ export class LucidMediaStore {
|
|
|
41
38
|
const rows = (await q.orderBy('order', 'asc'));
|
|
42
39
|
return rows.map(fromRow);
|
|
43
40
|
}
|
|
41
|
+
async list(options = {}) {
|
|
42
|
+
const limit = clampMediaListLimit(options.limit);
|
|
43
|
+
let q = this.query();
|
|
44
|
+
if (options.ownerType !== undefined)
|
|
45
|
+
q = q.where('owner_type', options.ownerType);
|
|
46
|
+
if (options.ownerId !== undefined)
|
|
47
|
+
q = q.where('owner_id', options.ownerId);
|
|
48
|
+
if (options.collection !== undefined)
|
|
49
|
+
q = q.where('collection', options.collection);
|
|
50
|
+
if (options.prefix !== undefined && options.prefix !== '') {
|
|
51
|
+
q = q.where('path', 'like', `${options.prefix}%`);
|
|
52
|
+
}
|
|
53
|
+
// Keyset pagination on the (created_at desc, id desc) ordering: resume strictly *after* the cursor
|
|
54
|
+
// row, so pages don't drift or duplicate when rows are inserted concurrently.
|
|
55
|
+
const cursor = decodeMediaCursor(options.cursor);
|
|
56
|
+
if (cursor) {
|
|
57
|
+
q = q.where((builder) => {
|
|
58
|
+
builder
|
|
59
|
+
.where('created_at', '<', cursor.createdAt)
|
|
60
|
+
.orWhere((eq) => eq.where('created_at', cursor.createdAt).where('id', '<', cursor.id));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Fetch one extra row to detect whether a further page exists without a second COUNT query.
|
|
64
|
+
const rows = (await q
|
|
65
|
+
.orderBy('created_at', 'desc')
|
|
66
|
+
.orderBy('id', 'desc')
|
|
67
|
+
.limit(limit + 1));
|
|
68
|
+
const hasMore = rows.length > limit;
|
|
69
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
70
|
+
const items = page.map(fromRow);
|
|
71
|
+
const last = page[page.length - 1];
|
|
72
|
+
return {
|
|
73
|
+
items,
|
|
74
|
+
nextCursor: hasMore && last
|
|
75
|
+
? encodeMediaCursor({ createdAt: Number(last.created_at), id: last.id })
|
|
76
|
+
: null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
44
79
|
async delete(id) {
|
|
45
80
|
await this.query().where('id', id).delete();
|
|
46
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lucid.js","sourceRoot":"","sources":["../../../src/stores/lucid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lucid.js","sourceRoot":"","sources":["../../../src/stores/lucid.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DashboardSpec } from './telescope-sdk.js';
|
|
2
|
+
/** Options for the media "Media" dashboard. */
|
|
3
|
+
export interface MediaDashboardOptions {
|
|
4
|
+
/**
|
|
5
|
+
* URL template for deep-linking a completed-upload row (a `LinkSpec.href` with `{key}` placeholders
|
|
6
|
+
* filled from the row, e.g. `/media/uploads/{id}`). Omit to render plain ids with no link.
|
|
7
|
+
*/
|
|
8
|
+
uploadHref?: string;
|
|
9
|
+
/** URL template for deep-linking a conversion row, e.g. `/media/{id}`. Omit for plain ids. */
|
|
10
|
+
conversionHref?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The "Media" overview dashboard — upload health up top (active / success rate / throughput), then
|
|
14
|
+
* upload activity (over-time + recent completed uploads), then storage operations and conversions.
|
|
15
|
+
* Pure data: panels bind to the `media.*` data providers by name; the `*Href` options add an optional
|
|
16
|
+
* `LinkSpec` on the id column so a host media SPA can deep-link a row.
|
|
17
|
+
*/
|
|
18
|
+
export declare function mediaDashboard(opts?: MediaDashboardOptions): DashboardSpec;
|
|
19
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/telescope/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,qBAA0B,GAAG,aAAa,CA4G9E"}
|