@vedratech/anydrop-core 0.0.0-stage → 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jayesh Gupta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,297 @@
1
- # Temporary Holding Version
1
+ # @vedratech/anydrop-core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Headless folder upload engine: drag and drop folders, chunked and resumable uploads, pause and resume, retry, offline handling. Part of [AnyDrop](https://github.com/vedratech/anydrop).**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vedratech/anydrop-core.svg)](https://www.npmjs.com/package/@vedratech/anydrop-core)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@vedratech/anydrop-core.svg)](https://www.npmjs.com/package/@vedratech/anydrop-core)
7
+ [![bundle size](https://deno.bundlejs.com/badge?q=@vedratech/anydrop-core)](https://bundlejs.com/?q=@vedratech/anydrop-core)
8
+ [![CI](https://github.com/vedratech/anydrop/actions/workflows/ci.yml/badge.svg)](https://github.com/vedratech/anydrop/actions/workflows/ci.yml)
9
+ [![license: MIT](https://img.shields.io/npm/l/@vedratech/anydrop-core.svg)](https://github.com/vedratech/anydrop/blob/main/LICENSE)
10
+ [![docs](https://img.shields.io/badge/docs-vedratech.github.io%2Fanydrop-1d4ed8.svg)](https://vedratech.github.io/anydrop/)
11
+
12
+ Headless engine for AnyDrop: folder-first, chunked, resumable uploads, configured with plain options. No runtime dependencies, no DOM rendering, safe to import during server-side rendering (nothing touches `window` or `document` at import time).
13
+
14
+ It reads input (drop, paste, file and folder inputs), validates, builds the folder tree, uploads whole files or chunks with retry, sends a folder manifest, pauses and resumes at three levels, resumes after a reload (IndexedDB), pauses by itself when the browser is offline, the page is hidden or the server cannot be reached, and keeps a persistent error list. The UI is in [`@vedratech/anydrop-ui`](https://www.npmjs.com/package/@vedratech/anydrop-ui).
15
+
16
+ **Documentation: https://vedratech.github.io/anydrop/** ([guide](https://vedratech.github.io/anydrop/guide/headless-core), [API reference](https://vedratech.github.io/anydrop/api/), [backend contract](https://vedratech.github.io/anydrop/protocol), [live demo](https://vedratech.github.io/anydrop/demo))
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install @vedratech/anydrop-core
22
+ ```
23
+
24
+ Or from a CDN (pin the minor version; the global is `AnyDropCore`):
25
+
26
+ ```html
27
+ <script src="https://cdn.jsdelivr.net/npm/@vedratech/anydrop-core@0.1"></script>
28
+ <script>
29
+ const core = new AnyDropCore.AnyDropCore({ url: '/upload' });
30
+ </script>
31
+ ```
32
+
33
+ ## Example
34
+
35
+ With an element that receives drops and a folder input in your page:
36
+
37
+ ```html
38
+ <div id="upload-area">Drop files or folders here</div>
39
+ <input id="folder-input" type="file" webkitdirectory />
40
+ ```
41
+
42
+ ```ts
43
+ import { AnyDropCore } from '@vedratech/anydrop-core';
44
+
45
+ const core = new AnyDropCore({
46
+ url: '/upload',
47
+ acceptedFiles: 'image/*,.pdf',
48
+ maxFilesize: 2048, // MiB
49
+ chunking: true,
50
+ chunkSize: 5 * 1024 * 1024,
51
+ sendManifest: true,
52
+ manifestUrl: '/upload/manifest',
53
+ finalizeUrl: '/upload/finalize',
54
+ });
55
+
56
+ const off = core.on('addedfolder', (folder) => console.log('new folder', folder.path));
57
+ core.on('folderprogress', (folder, pct) => console.log(folder.path, Math.round(pct), '%'));
58
+ core.on('error', (file, message) => console.warn(file.relativePath, message));
59
+ core.on('queuecomplete', () => console.log('all done', core.getErrors()));
60
+
61
+ const area = document.querySelector('#upload-area');
62
+ area.addEventListener('dragover', (e) => e.preventDefault());
63
+ area.addEventListener('drop', (e) => {
64
+ e.preventDefault();
65
+ core.drop(e); // call synchronously: the browser clears the dropped items afterwards
66
+ });
67
+
68
+ const folderInput = document.querySelector('#folder-input');
69
+ folderInput.addEventListener('change', () => {
70
+ core.addFromInput(folderInput);
71
+ folderInput.value = '';
72
+ });
73
+
74
+ const tree = core.getTree(); // live folder tree
75
+ off(); // remove one listener
76
+ core.destroy(); // remove everything
77
+ ```
78
+
79
+ Change an option later with `core.setOption(name, value)`.
80
+
81
+ ## Options
82
+
83
+ Types and defaults. Descriptions and examples: [Options reference](https://vedratech.github.io/anydrop/api/options).
84
+
85
+ ### Request and queue
86
+
87
+ | Option | Type | Default |
88
+ | ------------------- | ------------------------------------------------- | -------- |
89
+ | `url` | `string \| (files, dataBlocks) => string \| null` | `null` |
90
+ | `method` | `string \| (files, dataBlocks) => string` | `'post'` |
91
+ | `withCredentials` | `boolean` | `false` |
92
+ | `timeout` | `number \| null \| (files) => number \| null` | `null` |
93
+ | `headers` | `Record<string, string> \| null` | `null` |
94
+ | `defaultHeaders` | `boolean` | `true` |
95
+ | `params` | `object \| (files, xhr, chunk) => object \| null` | `null` |
96
+ | `paramName` | `string \| (n) => string` | `'file'` |
97
+ | `uploadMultiple` | `boolean` | `false` |
98
+ | `binaryBody` | `boolean` | `false` |
99
+ | `parallelUploads` | `number` | `2` |
100
+ | `maxChunksInFlight` | `number \| null` | `null` |
101
+ | `autoQueue` | `boolean` | `true` |
102
+ | `autoProcessQueue` | `boolean` | `true` |
103
+ | `transport` | `Transport \| null` | `null` |
104
+ | `transformFile` | `(file, done) => void \| null` | `null` |
105
+ | `renameFile` | `((file) => string) \| null` | `null` |
106
+
107
+ ### Chunking
108
+
109
+ | Option | Type | Default |
110
+ | ---------------------- | ---------------------------------------- | ----------------- |
111
+ | `chunking` | `boolean` | `false` |
112
+ | `forceChunking` | `boolean` | `false` |
113
+ | `chunkSize` | `number` | `2097152` (2 MiB) |
114
+ | `parallelChunkUploads` | `boolean \| number` | `false` |
115
+ | `retryChunks` | `boolean` | `true` |
116
+ | `retryChunksLimit` | `number` | `3` |
117
+ | `chunksUploaded` | `(file, done, instance) => void \| null` | `null` |
118
+ | `finalizeUrl` | `string \| null` | `null` |
119
+ | `chunkStatusUrl` | `string \| null` | `null` |
120
+
121
+ Every chunk request is a `multipart/form-data` POST with the fields `adUploadId`, `adChunkIndex`, `adChunkCount`, `adChunkSize`, `adChunkOffset` and `adTotalSize`, plus `adRelativePath` and `adSessionId`. Rename or drop a field with the `params` option (a `null` value drops one). The full contract is in the [backend contract](https://vedratech.github.io/anydrop/protocol).
122
+
123
+ ### Validation
124
+
125
+ | Option | Type | Default |
126
+ | ------------------- | ---------------------- | -------------- |
127
+ | `acceptedFiles` | `string \| null` | `null` |
128
+ | `maxFilesize` | `number \| null` | `256` |
129
+ | `maxFiles` | `number \| null` | `null` |
130
+ | `accept` | `(file, done) => void` | calls `done()` |
131
+ | `ignoreHiddenFiles` | `boolean` | `true` |
132
+ | `filesizeBase` | `number` | `1000` |
133
+
134
+ ### Folders
135
+
136
+ | Option | Type | Default |
137
+ | ---------------------- | ---------------- | ------- |
138
+ | `folders` | `boolean` | `true` |
139
+ | `preserveEmptyFolders` | `boolean` | `true` |
140
+ | `sendManifest` | `boolean` | `false` |
141
+ | `manifestUrl` | `string \| null` | `null` |
142
+
143
+ ### Pause, resume and persistence
144
+
145
+ | Option | Type | Default |
146
+ | -------------- | -------------------------- | --------- |
147
+ | `pauseMode` | `'abort' \| 'finishChunk'` | `'abort'` |
148
+ | `persistState` | `boolean` | `true` |
149
+ | `persistKey` | `string \| null` | `null` |
150
+
151
+ ### Connectivity
152
+
153
+ | Option | Type | Default |
154
+ | ----------------------- | ---------------- | ------- |
155
+ | `autoPauseOnHidden` | `boolean` | `true` |
156
+ | `autoResumeOnReconnect` | `boolean` | `true` |
157
+ | `stallTimeout` | `number \| null` | `30000` |
158
+ | `connectivityProbeUrl` | `string \| null` | `null` |
159
+
160
+ ### Messages
161
+
162
+ `dictFileTooBig`, `dictInvalidFileType`, `dictMaxFilesExceeded`, `dictResponseError`, `dictUploadCanceled`, `dictFileUnreadable`, `dictInvalidPath`, `dictFileSizeUnits`: the texts of validation and upload errors. Any event name can also be passed as an option (`new AnyDropCore({ addedfile(file) { ... } })`).
163
+
164
+ ## Methods
165
+
166
+ | Method | Description |
167
+ | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
168
+ | `drop(event)` | Handles a `drop` event: emits `drop`, reads the files and folders (with `webkitGetAsEntry`, calling `readEntries()` until it returns nothing) and resolves after `addedfiles`. **Call it synchronously** from the listener, because the browser clears the dropped items afterwards |
169
+ | `paste(event)` | Handles a `paste` event: emits `paste` and adds pasted files |
170
+ | `addDataTransfer(dataTransfer)` | Like `drop()`, without emitting the `drop` event |
171
+ | `addFromInput(input)` | Adds the files of an `<input type="file">` or `<input webkitdirectory>` (paths come from `webkitRelativePath`), a `FileList` or an array |
172
+ | `addFile(file, folderPath?)` | Adds one file. `folderPath` puts it in a folder; without it a non-empty `webkitRelativePath` is used |
173
+ | `addFiles(files)` | Adds several files as one batch |
174
+ | `files` | Every file, in add order |
175
+ | `getTree()` | The virtual root folder (`path: ''`) |
176
+ | `getFolder(path)` | A folder by path |
177
+ | `getFile(id)` | A file by its `file.id` |
178
+ | `getAcceptedFiles()` and `getRejectedFiles()` | Files by `file.accepted` |
179
+ | `getFilesWithStatus(status)` | Files with one status: `'added'`, `'queued'`, `'uploading'`, `'paused'`, `'success'`, `'error'` or `'canceled'` |
180
+ | `getAddedFiles()`, `getQueuedFiles()`, `getUploadingFiles()` and `getPausedFiles()` | Files by status. `getUploadingFiles()` excludes paused files |
181
+ | `getActiveFiles()` | Files that are `queued`, `uploading` or `paused` |
182
+ | `filesize(bytes)` | Size text such as `<strong>1.5</strong> MB`, using `filesizeBase` and `dictFileSizeUnits` |
183
+ | `enqueueFile(file)` and `enqueueFiles(files)` | Queue added, accepted files (needed with `autoQueue: false`) |
184
+ | `processQueue()` | Starts queued files in add order until `parallelUploads` files are uploading |
185
+ | `processFile(file)` and `processFiles(files)` | Start these `queued` files now, even beyond `parallelUploads` |
186
+ | `uploadFile(file)` and `uploadFiles(files)` | Aliases of `processFile(s)` |
187
+ | `cancelUpload(file)` | Cancels a file that is not finished: aborts its requests, sets the status `canceled`, then emits `canceled` and `complete` (no `error`) |
188
+ | `cancelFolder(path)` | Cancels every unfinished file under a folder (`canceled` and `complete` for each), then emits `foldercomplete` |
189
+ | `removeFile(file)` | Removes a file from the instance and the tree |
190
+ | `removeAllFiles(cancelIfNecessary = false)` | Removes every file that is not uploading (or every file with `true`), then every folder left without files, including folders dropped empty |
191
+ | `disable()` and `enable()` | `disable()` sets `disabled` to `true` and cancels every file; adding files still works. `enable()` clears the flag |
192
+ | `disabled` | Set by `disable()` |
193
+ | `pause()` and `resume()` | `pause()` pauses every file. `resume()` clears the instance pause and every system reason (offline, hidden, unreachable); folder and file pauses stay |
194
+ | `pauseFolder(path)` and `resumeFolder(path)` | Pause every file under a folder (also files added to it later), and clear that folder's own pause |
195
+ | `pauseFile(file | id)`and`resumeFile(file | id)` | Pause one file, and clear its own pause |
196
+ | `reconnect()` | "Retry now": probes at once while the connection is `unreachable`, and lifts an `offline` pause the browser no longer reports |
197
+ | `getConnectionState()` | Returns `{ state, attempt, retryAt }`. `state` is `'online'`, `'offline'` or `'unreachable'`; `attempt` is the probe number (0 when online) and `retryAt` the `Date.now()` time of the next probe, or `null` |
198
+ | `getErrors()` | A copy of the error list: one entry per file currently in `error` |
199
+ | `clearErrors()` | Empties the error list |
200
+ | `exportErrors(format = 'json')` | The error list as `'json'` or `'csv'` (RFC 4180, CRLF) |
201
+ | `retryFailedOnly(folderPath?)` | Re-queues the failed files (all, or under `folderPath`), except validation rejections, and returns how many |
202
+ | `retryFile(file | id)` | Re-queues one failed file like `retryFailedOnly` |
203
+ | `clearPersistedState()` | Deletes this `persistKey`'s saved progress, for a "start over" button |
204
+ | `options` | The resolved options object |
205
+ | `setOption(name, value)` | Changes an option at runtime |
206
+ | `getOption(name)` | Reads an option |
207
+ | `resolveOption(option, ...args)` | Calls a function option with `this` set to the instance, otherwise returns the value |
208
+ | `on(event, fn)` | Adds a listener and returns a function that removes it |
209
+ | `once(event, fn)` | A listener that runs once |
210
+ | `off(event?, fn?)` | `off()` removes every listener, `off(event)` every listener of that event, `off(event, fn)` one listener (also one added with `once`) |
211
+ | `emit(event, ...args)` | Emits an event |
212
+ | `hasListeners(event)` | Whether anyone listens to the event |
213
+ | `destroy()` | Aborts every request, ends folder walks, removes every listener and timer, and empties the instance |
214
+ | `isDestroyed` | Whether `destroy()` was called |
215
+
216
+ Statics and exports: `AnyDropCore.ADDED`, `QUEUED`, `ACCEPTED`, `UPLOADING`, `PROCESSING`, `PAUSED`, `SUCCESS`, `ERROR`, `CANCELED`, `isValidFile`, `uuidv4`, `version`; named exports `defaultOptions()`, `xhrTransport`, `VERSION` and every public type. Details and examples: [Methods reference](https://vedratech.github.io/anydrop/api/methods).
217
+
218
+ ## Events
219
+
220
+ | Event | Arguments | Description |
221
+ | --------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
222
+ | `drop` | `(event: DragEvent)` | `drop()` was called with a `dataTransfer` |
223
+ | `paste` | `(event: ClipboardEvent)` | `paste()` was called with `clipboardData.items` |
224
+ | `dragstart`, `dragenter`, `dragover`, `dragleave` and `dragend` | `(event: DragEvent)` | Drag events over the element |
225
+ | `addedfile` | `(file: AnyDropFile)` | Each added file, before its validation has finished |
226
+ | `addedfiles` | `(files: AnyDropFile[])` | Once per batch, after the walk and validation, with every file of the batch (accepted and rejected) |
227
+ | `emptyfolder` | `(path: string)` | A dropped directory has no entries at all |
228
+ | `removedfile` | `(file: AnyDropFile)` | `removeFile()` or `removeAllFiles()` removed a file |
229
+ | `reset` | `()` | The last file was removed |
230
+ | `thumbnail` | `(file: AnyDropFile, dataUrl: string)` | A thumbnail was created for an accepted image |
231
+ | `maxfilesexceeded` | `(file: AnyDropFile)` | A file was rejected because of `maxFiles` (after its `error` and `complete`) |
232
+ | `maxfilesreached` | `(files: AnyDropFile[])` | A check found exactly `maxFiles` accepted files |
233
+ | `processing` | `(file: AnyDropFile)` | A file starts uploading (its status becomes `uploading`) |
234
+ | `processingmultiple` | `(files: AnyDropFile[])` | Only with `uploadMultiple`: a request with these files started |
235
+ | `sending` | `(file: AnyDropFile, xhr: XMLHttpRequest, formData?: FormData)` | Before each request body is sent |
236
+ | `sendingmultiple` | `(files: AnyDropFile[], xhr: XMLHttpRequest, formData?: FormData)` | Only with `uploadMultiple`: before a request body is sent |
237
+ | `uploadprogress` | `(file: AnyDropFile, progress: number, bytesSent: number)` | Each progress event of a request, not throttled. `progress` is 0 to 100 |
238
+ | `totaluploadprogress` | `(progress: number, totalBytes: number, totalBytesSent: number)` | Overall progress after each `uploadprogress` and `removedfile`, over the files that are `queued`, `uploading` or `paused` |
239
+ | `success` | `(file: AnyDropFile, response: unknown, e: null)` | A file is uploaded. `response` is the finalize response when one was sent, else the last upload response (parsed JSON when the server sent JSON) |
240
+ | `successmultiple` | `(files: AnyDropFile[], response: unknown, e: null)` | Only with `uploadMultiple`: once per successful request |
241
+ | `complete` | `(file: AnyDropFile)` | A file is finished: uploaded, failed, rejected or canceled |
242
+ | `completemultiple` | `(files: AnyDropFile[])` | Only with `uploadMultiple`: once per finished request |
243
+ | `canceled` | `(file: AnyDropFile)` | `cancelUpload()` canceled a file |
244
+ | `canceledmultiple` | `(files: AnyDropFile[])` | Only with `uploadMultiple`: the files of a canceled request |
245
+ | `queuecomplete` | `()` | After a `complete` (deferred by `setTimeout` 0), when no file is added, queued, uploading or paused, no batch is still being read and no manifest is in flight |
246
+ | `addedfolder` | `(folder: FolderNode)` | Once per new folder node, during the walk |
247
+ | `removedfolder` | `(folder: FolderNode)` | A folder was removed from the tree because its last file was removed |
248
+ | `folderprogress` | `(folder: FolderNode, progress: number, bytesSent: number)` | For every ancestor folder of a file that made progress (not the root), throttled to one event per folder per 100 ms, and always sent at 100 |
249
+ | `foldercomplete` | `(folder: FolderNode)` | Every file in the folder is finished, or an empty folder reached `success` |
250
+ | `paused` | `(target: PauseTarget)` | Once per `pause*()` call or automatic pause, after the files changed status. `target` is `{ scope: 'all', reason }` with reason `'user'`, `'offline'`, `'hidden'` or `'unreachable'`, or `{ scope: 'folder', folder, reason: 'user' }`, or `{ scope: 'file', file, reason: 'user' }` |
251
+ | `resumed` | `(target: PauseTarget)` | The same shape, once per `resume*()` call or automatic resume (`reason: 'offline'` when back online, `'hidden'` when the page is visible again, `'unreachable'` when the server answered a probe) |
252
+ | `offline` | `()` | The browser's `offline` event, whatever the auto-resume settings are |
253
+ | `online` | `()` | The browser's `online` event |
254
+ | `reconnecting` | `(attempt: number, delayMs: number)` | While the connection is lost, before each wait until the next probe. `attempt` counts from 1 |
255
+ | `error` | `(file: AnyDropFile, message: unknown, xhr?: XMLHttpRequest)` | A file was rejected, or its upload finally failed (once, after any retries). `message` is the built-in message, what your `accept()` passed, the parsed JSON error body, or a string |
256
+ | `errormultiple` | `(files: AnyDropFile[], message: unknown, xhr?: XMLHttpRequest)` | Only with `uploadMultiple`: once per failed request (or once for a single rejected file), after the `error` event of each file |
257
+ | `errorlistchanged` | `(errors: ErrorEntry[])` | The error list changed |
258
+ | `actionclicked` | `(action: string, target: AnyDropFile \| FolderNode)` | (UI) A row action was triggered (built-in or custom, button or shortcut), before it runs. Not cancellable. Emitted by `@vedratech/anydrop-ui`; core only declares the name and types |
259
+
260
+ Order for one uploaded file: `processing`, then per request `sending` and `uploadprogress`, then `success`, `complete`, `foldercomplete`, and `queuecomplete` after the last file. Events marked (UI) are emitted by `@vedratech/anydrop-ui`. [Events reference](https://vedratech.github.io/anydrop/api/events).
261
+
262
+ ## Error codes
263
+
264
+ `NETWORK`, `TIMEOUT`, `STALLED`, `SERVER_ERROR`, `HTTP_ERROR`, `AUTH`, `TOO_LARGE`, `INVALID_PATH`, `INVALID_CHUNK`, `CHUNK_MISMATCH`, `REJECTED_SIZE`, `REJECTED_TYPE`, `MAX_FILES`, `REJECTED`, `FINALIZE_FAILED`, `UNREADABLE`, `TRANSFORM_FAILED`, `REQUEST_SETUP_FAILED`. What each means and which are retried: [Error codes](https://vedratech.github.io/anydrop/api/error-codes).
265
+
266
+ ## Transport
267
+
268
+ Every request goes through a transport with one method, so S3, tus or fetch adapters can replace the built-in XMLHttpRequest one:
269
+
270
+ ```ts
271
+ import { AnyDropCore, xhrTransport, type Transport } from '@vedratech/anydrop-core';
272
+
273
+ const logging: Transport = {
274
+ send(request) {
275
+ const handle = xhrTransport.send(request); // { abort(), onProgress?, promise }
276
+ handle.promise.then((res) => console.log(request.url, res.status));
277
+ return handle;
278
+ },
279
+ };
280
+ new AnyDropCore({ url: '/upload', transport: logging });
281
+ ```
282
+
283
+ The rules a transport must follow are in the [custom transport guide](https://vedratech.github.io/anydrop/guide/custom-transport).
284
+
285
+ ## Documentation
286
+
287
+ Everything is on the website: **https://vedratech.github.io/anydrop/**
288
+
289
+ - [Guide](https://vedratech.github.io/anydrop/guide/introduction) and [quick start](https://vedratech.github.io/anydrop/guide/quick-start)
290
+ - [API reference](https://vedratech.github.io/anydrop/api/): [options](https://vedratech.github.io/anydrop/api/options), [methods](https://vedratech.github.io/anydrop/api/methods), [events](https://vedratech.github.io/anydrop/api/events), [error codes](https://vedratech.github.io/anydrop/api/error-codes), [types](https://vedratech.github.io/anydrop/api/types)
291
+ - [Backend contract](https://vedratech.github.io/anydrop/protocol), with [Express](https://vedratech.github.io/anydrop/backend/express) and [Laravel](https://vedratech.github.io/anydrop/backend/laravel) examples
292
+ - [Live demo](https://vedratech.github.io/anydrop/demo)
293
+ - [Size comparison](https://vedratech.github.io/anydrop/size-comparison)
294
+
295
+ ## License
296
+
297
+ MIT © 2026 Jayesh Gupta.