@riloservices/base 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +255 -2
- package/dist/browser.cjs +39 -0
- package/dist/browser.d.cts +21 -0
- package/dist/browser.d.mts +21 -0
- package/dist/browser.mjs +37 -0
- package/dist/cli.mjs +307 -0
- package/dist/errors-DwVg5fNj.d.cts +24 -0
- package/dist/errors-DwVg5fNj.d.mts +24 -0
- package/dist/index.cjs +124 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.mjs +121 -0
- package/dist/key-B7koHuaE.mjs +272 -0
- package/dist/key-DPXUxbGX.cjs +289 -0
- package/dist/mongoose.cjs +21 -0
- package/dist/mongoose.d.cts +29 -0
- package/dist/mongoose.d.mts +29 -0
- package/dist/mongoose.mjs +19 -0
- package/dist/types-CD9NBWdA.d.cts +88 -0
- package/dist/types-CD9NBWdA.d.mts +88 -0
- package/package.json +72 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rilo
|
|
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,256 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @riloservices/base
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use a [Rilo DB](https://db.services.rilo.studio) Space from your server with one app key. The key works for your databases and for Files.
|
|
4
|
+
|
|
5
|
+
- **Databases.** `rilo.db('name')` gives you the MongoDB driver's own `Db`. Everything you know about the driver still applies.
|
|
6
|
+
- **Files.** Ask for an upload, send the file from the browser straight to storage, and make links to files.
|
|
7
|
+
- **Mongoose.** One call connects it with the same key.
|
|
8
|
+
- **A `rilodb://` link**, if you only need the database. See [below](#the-rilodb-link-databases-only).
|
|
9
|
+
|
|
10
|
+
Rilo DB works with MongoDB drivers and tools. MongoDB is a trademark of MongoDB, Inc.; Rilo DB is not affiliated with or endorsed by it.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @riloservices/base
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This also installs the official MongoDB driver (`mongodb`, version 6.10 or later): it's a peer dependency, so your app keeps one copy of it, and npm, pnpm and Bun add it for you. With Yarn, add it yourself: `yarn add @riloservices/base mongodb`.
|
|
19
|
+
|
|
20
|
+
It needs Node 20.19 or later, and works from ES modules and from CommonJS.
|
|
21
|
+
|
|
22
|
+
## Your app key
|
|
23
|
+
|
|
24
|
+
Make an app key on the **App keys** page of your Space. It looks like this:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
rilo_in_k3m9x2qz.app.<database secret>.<files secret>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It is shown once, so copy it straight away. Put it in your server's environment as `RILO_KEY`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# .env (on your server, never in git)
|
|
34
|
+
RILO_KEY=rilo_in_k3m9x2qz.app.…
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Keep `RILO_KEY` on your server.** Never put it in browser code, a mobile app or a public repository. The key can read and write your data, so treat it like a password. If it leaks, reset it on the App keys page.
|
|
38
|
+
|
|
39
|
+
`RILO_KEY` is the main way in, and the only value your app needs: it works for your databases and for Files.
|
|
40
|
+
|
|
41
|
+
### Allow your app's address
|
|
42
|
+
|
|
43
|
+
Your Space only accepts database connections from its **Allowed places**. Before your app connects, add the address of the server it runs on (and your own, while you develop): open your Space, then **Settings**, then **Allowed places**. Until then, database calls fail to connect. Files doesn't use Allowed places, so it works from anywhere with the key.
|
|
44
|
+
|
|
45
|
+
## The `rilodb://` link (databases only)
|
|
46
|
+
|
|
47
|
+
If a service only needs a database, give it a `rilodb://` link instead of the whole key. `rilo-db env` prints one from your key (see [below](#the-rilo-db-env-command)). It looks like this:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
rilodb://app:<database secret>@k3m9x2qz.in.db.services.rilo.studio/salon-prod
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It holds the key's name, the database secret, the address and a database name, using the names you chose. Put it in the environment as `RILODB_URL` and pass it to the same functions:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const rilo = createClient(process.env.RILODB_URL);
|
|
57
|
+
const bookings = rilo.db().collection('bookings'); // the database the link names
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- **Only this package reads it.** MongoDB drivers and tools accept only `mongodb://` or `mongodb+srv://` links, so they can't use a `rilodb://` link. Give them the `mongodb://` link, which is `RILODB_URI` in the output of `rilo-db env`.
|
|
61
|
+
- **Databases only.** The link has no files part. Using `rilo.files` with it throws a `RiloKeyError`: Files needs your app key (`RILO_KEY`).
|
|
62
|
+
- **It brings its own address.** The `spacesDomain` and `port` options are ignored with a link. `tlsCAFile` and `appName` still apply.
|
|
63
|
+
- **The link's database is the default.** `rilo.db()` opens it. A `database` option or a name given to `rilo.db('name')` wins.
|
|
64
|
+
- **It holds a secret.** Treat `RILODB_URL` like `RILO_KEY`: keep it on your server, out of git and logs. If it leaks, reset the key on the App keys page.
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { createClient } from '@riloservices/base';
|
|
70
|
+
|
|
71
|
+
const rilo = createClient(process.env.RILO_KEY);
|
|
72
|
+
|
|
73
|
+
// Databases: use the name you gave the database. The client adds your Space's id for you.
|
|
74
|
+
const bookings = rilo.db('salon-prod').collection('bookings');
|
|
75
|
+
await bookings.insertOne({ client: 'Aroha', at: new Date() });
|
|
76
|
+
console.log(await bookings.find({ client: 'Aroha' }).toArray());
|
|
77
|
+
|
|
78
|
+
// Close the connections when your app shuts down.
|
|
79
|
+
await rilo.close();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`createClient` checks the key straight away and throws a [`RiloKeyError`](#errors) if it is missing or isn't an app key (or a `rilodb://` link). Nothing connects until you first call `rilo.db()`, and then one connection pool is opened and reused.
|
|
83
|
+
|
|
84
|
+
### Upload a file from the browser
|
|
85
|
+
|
|
86
|
+
Files don't pass through your server or through Rilo. Your server asks for a signed upload, the browser sends the file straight to storage with it, and the upload carries no key.
|
|
87
|
+
|
|
88
|
+
On your server:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
const photos = rilo.files.bucket('photos');
|
|
92
|
+
|
|
93
|
+
// For example in a route that your page calls before it uploads.
|
|
94
|
+
const ticket = await photos.createUpload({
|
|
95
|
+
path: 'avatars/user-42.jpg',
|
|
96
|
+
contentType: 'image/jpeg',
|
|
97
|
+
size: 204800, // the file's exact size in bytes
|
|
98
|
+
});
|
|
99
|
+
// Send `ticket` to the browser as JSON.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
In the browser:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { sendUpload } from '@riloservices/base/browser';
|
|
106
|
+
|
|
107
|
+
await sendUpload(ticket, file); // `file` is a File or Blob
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`sendUpload` posts every signed field, then the file last, with no `Authorization` header. If storage refuses the file, it throws a `RiloUploadError` with storage's own status and reason. The upload link lasts 10 minutes. If a person cancels, stop the browser's request first (pass an `AbortSignal` as `{ signal }`), then call `photos.cancelUpload(ticket.uploadId)` on your server.
|
|
111
|
+
|
|
112
|
+
### Links, lists and deletes
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const photos = rilo.files.bucket('photos');
|
|
116
|
+
|
|
117
|
+
// A link to view a file. A private bucket's links expire (one hour unless you say).
|
|
118
|
+
const { url, expiresAt } = await photos.link('avatars/user-42.jpg', { expiresInSeconds: 900 });
|
|
119
|
+
|
|
120
|
+
// Up to 100 links at once, in the order you asked.
|
|
121
|
+
const links = await photos.links(['avatars/user-42.jpg', 'avatars/user-43.jpg']);
|
|
122
|
+
|
|
123
|
+
// One folder level at a time, a page at a time.
|
|
124
|
+
const page = await photos.list({ prefix: 'avatars/', delimiter: '/', limit: 100 });
|
|
125
|
+
// page.files, page.folders, page.nextCursor (pass it back as `cursor` for the next page)
|
|
126
|
+
|
|
127
|
+
const file = await photos.get('avatars/user-42.jpg'); // size, contentType, updatedAt
|
|
128
|
+
await photos.delete('avatars/user-42.jpg');
|
|
129
|
+
|
|
130
|
+
const { buckets, usage } = await rilo.files.buckets();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Uploading and deleting need an app key with **Read and write** access. A **Read only** key can read data, list and get files, and make links.
|
|
134
|
+
|
|
135
|
+
Paths are sent to Files with each part encoded, so spaces and symbols in names are fine. The slashes between folders stay as they are.
|
|
136
|
+
|
|
137
|
+
## Mongoose
|
|
138
|
+
|
|
139
|
+
Mongoose isn't a dependency of this package. Pass the one your app already has:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import mongoose from 'mongoose';
|
|
143
|
+
import { connectMongoose } from '@riloservices/base/mongoose';
|
|
144
|
+
|
|
145
|
+
await connectMongoose(mongoose, process.env.RILO_KEY, { database: 'salon-prod' });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
It connects with the same link that `createClient` uses, and opens `salon-prod` in your Space. It takes a `rilodb://` link in place of the key too; then `database` is optional, and the link's database is used when you leave it out.
|
|
149
|
+
|
|
150
|
+
## Options
|
|
151
|
+
|
|
152
|
+
`createClient(keyOrLink, options)` and `connectMongoose(mongoose, keyOrLink, { database, ...options })` take these. Rilo's own addresses are the defaults, so you don't need any of them in production.
|
|
153
|
+
|
|
154
|
+
| Option | What it does |
|
|
155
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `database` | The database `rilo.db()` opens when you give it no name. (`connectMongoose` needs it, unless a `rilodb://` link names one.) |
|
|
157
|
+
| `appName` | A name for your app, shown in connection details so you can tell apps apart. |
|
|
158
|
+
| `spacesDomain` | The domain Spaces live under. Default `db.services.rilo.studio`. Ignored with a `rilodb://` link, which has its own. |
|
|
159
|
+
| `port` | The database port. Default `27017`. Ignored with a `rilodb://` link, which has its own. |
|
|
160
|
+
| `tlsCAFile` | A CA file to trust. Only for a local stand-in with its own certificate. |
|
|
161
|
+
| `filesUrl` | The Files address. Default `https://files.<region>.<spacesDomain>`, with the region from the key. |
|
|
162
|
+
|
|
163
|
+
## The `rilo-db env` command
|
|
164
|
+
|
|
165
|
+
Some tools want the plain values instead of the key: Compass, mongosh, a language without this package, or Mongoose set up on its own. Drivers and tools need the `mongodb://` link, which is `RILODB_URI`. With `RILO_KEY` in your environment, or in a `.env` file in the current folder, run:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npx @riloservices/base env
|
|
169
|
+
npx @riloservices/base env --database salon-prod
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
It prints four lines:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
RILODB_URI=mongodb://app_k3m9x2qz:…@k3m9x2qz.in.db.services.rilo.studio:27017/?tls=true&…
|
|
176
|
+
RILODB_URL=rilodb://app:…@k3m9x2qz.in.db.services.rilo.studio/
|
|
177
|
+
RILO_FILES_URL=https://files.in.db.services.rilo.studio
|
|
178
|
+
RILO_FILES_KEY=rdbf_k3m9x2qz_…
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `RILODB_URI` is the `mongodb://` link, for MongoDB drivers and tools.
|
|
182
|
+
- `RILODB_URL` is the `rilodb://` link, for this package only.
|
|
183
|
+
- `RILO_FILES_URL` and `RILO_FILES_KEY` are for the Files API.
|
|
184
|
+
|
|
185
|
+
With `--database salon-prod`, both links name that database.
|
|
186
|
+
|
|
187
|
+
It runs on your machine and sends nothing anywhere. The output holds your secrets, so keep it out of chat, tickets, logs and git. If the key is missing or isn't an app key, it says so and exits with status 1.
|
|
188
|
+
|
|
189
|
+
## Other languages
|
|
190
|
+
|
|
191
|
+
This package is a convenience for Node. Nothing in Rilo DB depends on it, and the key format is stable, so any language can use `RILO_KEY` with its own MongoDB driver and an HTTP client.
|
|
192
|
+
|
|
193
|
+
An app key has four parts, separated by dots:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
rilo_<region>_<spaceId>.<name>.<databaseSecret>.<filesSecret>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
| | How to make it |
|
|
200
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
201
|
+
| Database login | `<name>_<spaceId>`, with `<databaseSecret>` as the password |
|
|
202
|
+
| Database address | `<spaceId>.<region>.db.services.rilo.studio`, port `27017` |
|
|
203
|
+
| Database name | `<your database>_<spaceId>` |
|
|
204
|
+
| Link options | `tls=true&directConnection=true&retryWrites=false&authSource=admin&maxPoolSize=10` |
|
|
205
|
+
| Files address | `https://files.<region>.db.services.rilo.studio` |
|
|
206
|
+
| Files key | `rdbf_<spaceId>_<filesSecret>`, sent as `Authorization: Bearer …`. **Never send the whole app key to Files:** it answers `app_key_sent`. |
|
|
207
|
+
|
|
208
|
+
For example, in Python:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
import os
|
|
212
|
+
from pymongo import MongoClient
|
|
213
|
+
|
|
214
|
+
ident, name, db_secret, files_secret = os.environ["RILO_KEY"].split(".")
|
|
215
|
+
_, region, space = ident.split("_") # rilo_in_k3m9x2qz
|
|
216
|
+
|
|
217
|
+
client = MongoClient(
|
|
218
|
+
f"mongodb://{name}_{space}:{db_secret}@{space}.{region}.db.services.rilo.studio:27017/"
|
|
219
|
+
"?tls=true&directConnection=true&retryWrites=false&authSource=admin&maxPoolSize=10"
|
|
220
|
+
)
|
|
221
|
+
db = client[f"salon-prod_{space}"]
|
|
222
|
+
files_key = f"rdbf_{space}_{files_secret}" # the Bearer token for the Files API
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Errors
|
|
226
|
+
|
|
227
|
+
- **`RiloKeyError`** is thrown when the key or link is missing, isn't an app key or a `rilodb://` link, or when you use `rilo.files` with a `rilodb://` link. Its message says what is wrong, for example that you passed a files key, a `mongodb://` link or a broken `rilodb://` link, and never repeats what you gave it.
|
|
228
|
+
- **`RiloFilesError`** is thrown when a Files call fails. It has the HTTP `status`, a stable `code` such as `read_only_key` or `file_too_large`, a plain `message`, and where the Files API sends them, `details` (extra data, like `maxFileSize`) and `fields` (what to fix, on `validation_failed`). Decide what to do from `status` and `code`, not the message.
|
|
229
|
+
- **`RiloUploadError`** (from `@riloservices/base/browser`) is thrown when storage refuses an upload. It has the `status` and storage's own `code`, such as `AccessDenied` when the link has expired.
|
|
230
|
+
- **Database errors** are the MongoDB driver's own.
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
import { RiloFilesError } from '@riloservices/base';
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await rilo.files
|
|
237
|
+
.bucket('photos')
|
|
238
|
+
.createUpload({ path: 'a.jpg', contentType: 'image/jpeg', size });
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof RiloFilesError && error.code === 'file_too_large') {
|
|
241
|
+
console.log(`The most allowed is ${String(error.details?.maxFileSize)} bytes.`);
|
|
242
|
+
} else {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Files calls aren't retried for you. Try again after a moment on a `429` or `503`, and give up after a few tries. Any other `4xx` gives the same answer when repeated, so fix the request instead.
|
|
249
|
+
|
|
250
|
+
## What it never does
|
|
251
|
+
|
|
252
|
+
- It never logs anything, and never reads `process.env`. You pass the key in. (Only the `rilo-db` command reads `RILO_KEY`, as a command line tool.)
|
|
253
|
+
- It sends Files only the `rdbf_…` part of your key, in the `Authorization` header. The database secret is used only to open the database connection. A `rilodb://` link never reaches Files.
|
|
254
|
+
- It adds no network calls of its own: your data goes through the MongoDB driver, and files through the Files API and storage.
|
|
255
|
+
|
|
256
|
+
Rilo DB works with MongoDB drivers and tools. MongoDB is a trademark of MongoDB, Inc.; Rilo DB is not affiliated with or endorsed by it.
|
package/dist/browser.cjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
// src/browser.ts
|
|
3
|
+
/** Storage refused the file. `code` and the message are storage's own, when it sent them. */
|
|
4
|
+
var RiloUploadError = class extends Error {
|
|
5
|
+
name = "RiloUploadError";
|
|
6
|
+
/** The HTTP status storage answered with. */
|
|
7
|
+
status;
|
|
8
|
+
/** Storage's error code, like `EntityTooLarge` or `AccessDenied`; null when it sent none. */
|
|
9
|
+
code;
|
|
10
|
+
constructor(status, code, reason) {
|
|
11
|
+
super(`Storage refused the upload (status ${status}${code === null ? "" : `, ${code}`})` + (reason === null ? "." : `: ${reason}`));
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function xmlValue(xml, tag) {
|
|
17
|
+
const match = new RegExp(`<${tag}>([^<]*)</${tag}>`).exec(xml);
|
|
18
|
+
return match?.[1] === void 0 || match[1] === "" ? null : match[1];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sends a file to storage with the upload your server asked for. Every signed field goes first,
|
|
22
|
+
* then the file, which must be last. There is no key here: the signed fields are the authority.
|
|
23
|
+
* Resolves when storage has the file; throws a `RiloUploadError` when it refuses it.
|
|
24
|
+
*/
|
|
25
|
+
async function sendUpload(ticket, file, options = {}) {
|
|
26
|
+
const form = new FormData();
|
|
27
|
+
for (const [name, value] of Object.entries(ticket.fields)) form.append(name, value);
|
|
28
|
+
form.append("file", file);
|
|
29
|
+
const response = await fetch(ticket.url, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: form,
|
|
32
|
+
signal: options.signal
|
|
33
|
+
});
|
|
34
|
+
if (response.ok) return;
|
|
35
|
+
const xml = await response.text().catch(() => "");
|
|
36
|
+
throw new RiloUploadError(response.status, xmlValue(xml, "Code"), xmlValue(xml, "Message"));
|
|
37
|
+
}
|
|
38
|
+
exports.RiloUploadError = RiloUploadError;
|
|
39
|
+
exports.sendUpload = sendUpload;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { d as UploadTicket } from "./types-CD9NBWdA.cjs";
|
|
2
|
+
/** The two parts of an upload ticket that the browser needs. */
|
|
3
|
+
export type UploadForm = Pick<UploadTicket, 'url' | 'fields'>;
|
|
4
|
+
/** Storage refused the file. `code` and the message are storage's own, when it sent them. */
|
|
5
|
+
export declare class RiloUploadError extends Error {
|
|
6
|
+
readonly name = "RiloUploadError";
|
|
7
|
+
/** The HTTP status storage answered with. */
|
|
8
|
+
readonly status: number;
|
|
9
|
+
/** Storage's error code, like `EntityTooLarge` or `AccessDenied`; null when it sent none. */
|
|
10
|
+
readonly code: string | null;
|
|
11
|
+
constructor(status: number, code: string | null, reason: string | null);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sends a file to storage with the upload your server asked for. Every signed field goes first,
|
|
15
|
+
* then the file, which must be last. There is no key here: the signed fields are the authority.
|
|
16
|
+
* Resolves when storage has the file; throws a `RiloUploadError` when it refuses it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sendUpload(ticket: UploadForm, file: Blob, options?: {
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export type { UploadTicket };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { d as UploadTicket } from "./types-CD9NBWdA.mjs";
|
|
2
|
+
/** The two parts of an upload ticket that the browser needs. */
|
|
3
|
+
export type UploadForm = Pick<UploadTicket, 'url' | 'fields'>;
|
|
4
|
+
/** Storage refused the file. `code` and the message are storage's own, when it sent them. */
|
|
5
|
+
export declare class RiloUploadError extends Error {
|
|
6
|
+
readonly name = "RiloUploadError";
|
|
7
|
+
/** The HTTP status storage answered with. */
|
|
8
|
+
readonly status: number;
|
|
9
|
+
/** Storage's error code, like `EntityTooLarge` or `AccessDenied`; null when it sent none. */
|
|
10
|
+
readonly code: string | null;
|
|
11
|
+
constructor(status: number, code: string | null, reason: string | null);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sends a file to storage with the upload your server asked for. Every signed field goes first,
|
|
15
|
+
* then the file, which must be last. There is no key here: the signed fields are the authority.
|
|
16
|
+
* Resolves when storage has the file; throws a `RiloUploadError` when it refuses it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sendUpload(ticket: UploadForm, file: Blob, options?: {
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export type { UploadTicket };
|
package/dist/browser.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/browser.ts
|
|
2
|
+
/** Storage refused the file. `code` and the message are storage's own, when it sent them. */
|
|
3
|
+
var RiloUploadError = class extends Error {
|
|
4
|
+
name = "RiloUploadError";
|
|
5
|
+
/** The HTTP status storage answered with. */
|
|
6
|
+
status;
|
|
7
|
+
/** Storage's error code, like `EntityTooLarge` or `AccessDenied`; null when it sent none. */
|
|
8
|
+
code;
|
|
9
|
+
constructor(status, code, reason) {
|
|
10
|
+
super(`Storage refused the upload (status ${status}${code === null ? "" : `, ${code}`})` + (reason === null ? "." : `: ${reason}`));
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function xmlValue(xml, tag) {
|
|
16
|
+
const match = new RegExp(`<${tag}>([^<]*)</${tag}>`).exec(xml);
|
|
17
|
+
return match?.[1] === void 0 || match[1] === "" ? null : match[1];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sends a file to storage with the upload your server asked for. Every signed field goes first,
|
|
21
|
+
* then the file, which must be last. There is no key here: the signed fields are the authority.
|
|
22
|
+
* Resolves when storage has the file; throws a `RiloUploadError` when it refuses it.
|
|
23
|
+
*/
|
|
24
|
+
async function sendUpload(ticket, file, options = {}) {
|
|
25
|
+
const form = new FormData();
|
|
26
|
+
for (const [name, value] of Object.entries(ticket.fields)) form.append(name, value);
|
|
27
|
+
form.append("file", file);
|
|
28
|
+
const response = await fetch(ticket.url, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: form,
|
|
31
|
+
signal: options.signal
|
|
32
|
+
});
|
|
33
|
+
if (response.ok) return;
|
|
34
|
+
const xml = await response.text().catch(() => "");
|
|
35
|
+
throw new RiloUploadError(response.status, xmlValue(xml, "Code"), xmlValue(xml, "Message"));
|
|
36
|
+
}
|
|
37
|
+
export { RiloUploadError, sendUpload };
|