@instantdb/admin 0.22.99-experimental.add-user-perm-rules.20792844601.1 → 0.22.99-experimental.add-user-perm-rules.20792984656.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/dist/commonjs/__types__/typesTests.js +63 -82
- package/dist/commonjs/__types__/typesTests.js.map +1 -1
- package/dist/commonjs/index.js +472 -453
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/subscribe.js +17 -19
- package/dist/commonjs/subscribe.js.map +1 -1
- package/dist/esm/__types__/typesTests.js +63 -82
- package/dist/esm/__types__/typesTests.js.map +1 -1
- package/dist/esm/index.js +472 -453
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/subscribe.js +17 -19
- package/dist/esm/subscribe.js.map +1 -1
- package/package.json +3 -3
package/dist/commonjs/index.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -27,14 +18,14 @@ function configWithDefaults(config) {
|
|
|
27
18
|
const defaultConfig = {
|
|
28
19
|
apiURI: 'https://api.instantdb.com',
|
|
29
20
|
};
|
|
30
|
-
const r =
|
|
21
|
+
const r = { ...defaultConfig, ...config };
|
|
31
22
|
return r;
|
|
32
23
|
}
|
|
33
24
|
function instantConfigWithDefaults(config) {
|
|
34
25
|
const defaultConfig = {
|
|
35
26
|
apiURI: 'https://api.instantdb.com',
|
|
36
27
|
};
|
|
37
|
-
const r =
|
|
28
|
+
const r = { ...defaultConfig, ...config };
|
|
38
29
|
return r;
|
|
39
30
|
}
|
|
40
31
|
function withImpersonation(headers, opts) {
|
|
@@ -97,32 +88,32 @@ function isNextJSVersionThatCachesFetchByDefault() {
|
|
|
97
88
|
function getDefaultFetchOpts() {
|
|
98
89
|
return isNextJSVersionThatCachesFetchByDefault() ? { cache: 'no-store' } : {};
|
|
99
90
|
}
|
|
100
|
-
function jsonReject(rejectFn, res) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
});
|
|
91
|
+
async function jsonReject(rejectFn, res) {
|
|
92
|
+
const body = await res.text();
|
|
93
|
+
try {
|
|
94
|
+
const json = JSON.parse(body);
|
|
95
|
+
return rejectFn(new core_1.InstantAPIError({ status: res.status, body: json }));
|
|
96
|
+
}
|
|
97
|
+
catch (_e) {
|
|
98
|
+
return rejectFn(new core_1.InstantAPIError({
|
|
99
|
+
status: res.status,
|
|
100
|
+
body: { type: undefined, message: body },
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
114
103
|
}
|
|
115
|
-
function jsonFetch(input, init) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
104
|
+
async function jsonFetch(input, init) {
|
|
105
|
+
const defaultFetchOpts = getDefaultFetchOpts();
|
|
106
|
+
const headers = {
|
|
107
|
+
...(init?.headers || {}),
|
|
108
|
+
'Instant-Admin-Version': version_ts_1.default,
|
|
109
|
+
'Instant-Core-Version': core_1.version,
|
|
110
|
+
};
|
|
111
|
+
const res = await fetch(input, { ...defaultFetchOpts, ...init, headers });
|
|
112
|
+
if (res.status === 200) {
|
|
113
|
+
const json = await res.json();
|
|
114
|
+
return Promise.resolve(json);
|
|
115
|
+
}
|
|
116
|
+
return jsonReject((x) => Promise.reject(x), res);
|
|
126
117
|
}
|
|
127
118
|
/**
|
|
128
119
|
*
|
|
@@ -154,8 +145,12 @@ function init(
|
|
|
154
145
|
// Allows config with missing `useDateObjects`, but keeps `UseDates`
|
|
155
146
|
// as a non-nullable in the InstantConfig type.
|
|
156
147
|
config) {
|
|
157
|
-
|
|
158
|
-
|
|
148
|
+
const configStrict = {
|
|
149
|
+
...config,
|
|
150
|
+
appId: config.appId?.trim(),
|
|
151
|
+
adminToken: config.adminToken?.trim(),
|
|
152
|
+
useDateObjects: (config.useDateObjects ?? false),
|
|
153
|
+
};
|
|
159
154
|
return new InstantAdminDatabase(configStrict);
|
|
160
155
|
}
|
|
161
156
|
/**
|
|
@@ -179,175 +174,171 @@ function steps(inputChunks) {
|
|
|
179
174
|
return chunks.flatMap(core_1.getOps);
|
|
180
175
|
}
|
|
181
176
|
class Rooms {
|
|
177
|
+
config;
|
|
182
178
|
constructor(config) {
|
|
183
179
|
this.config = config;
|
|
184
180
|
}
|
|
185
|
-
getPresence(roomType, roomId) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
headers: authorizedHeaders(this.config),
|
|
190
|
-
});
|
|
191
|
-
return res.sessions || {};
|
|
181
|
+
async getPresence(roomType, roomId) {
|
|
182
|
+
const res = await jsonFetch(`${this.config.apiURI}/admin/rooms/presence?room-type=${String(roomType)}&room-id=${roomId}`, {
|
|
183
|
+
method: 'GET',
|
|
184
|
+
headers: authorizedHeaders(this.config),
|
|
192
185
|
});
|
|
186
|
+
return res.sessions || {};
|
|
193
187
|
}
|
|
194
188
|
}
|
|
195
189
|
class Auth {
|
|
190
|
+
config;
|
|
196
191
|
constructor(config) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
192
|
+
this.config = config;
|
|
193
|
+
this.createToken = this.createToken.bind(this);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Generates a magic code for the user with the given email.
|
|
197
|
+
* This is useful if you want to use your own email provider
|
|
198
|
+
* to send magic codes.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* // Generate a magic code
|
|
202
|
+
* const { code } = await db.auth.generateMagicCode({ email })
|
|
203
|
+
* // Send the magic code to the user with your own email provider
|
|
204
|
+
* await customEmailProvider.sendMagicCode(email, code)
|
|
205
|
+
*
|
|
206
|
+
* @see https://instantdb.com/docs/backend#custom-magic-codes
|
|
207
|
+
*/
|
|
208
|
+
generateMagicCode = async (email) => {
|
|
209
|
+
return jsonFetch(`${this.config.apiURI}/admin/magic_code`, {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: authorizedHeaders(this.config),
|
|
212
|
+
body: JSON.stringify({ email }),
|
|
216
213
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
})
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Sends a magic code to the user with the given email.
|
|
217
|
+
* This uses Instant's built-in email provider.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* // Send an email to user with magic code
|
|
221
|
+
* await db.auth.sendMagicCode({ email })
|
|
222
|
+
*
|
|
223
|
+
* @see https://instantdb.com/docs/backend#custom-magic-codes
|
|
224
|
+
*/
|
|
225
|
+
sendMagicCode = async (email) => {
|
|
226
|
+
return jsonFetch(`${this.config.apiURI}/admin/send_magic_code`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: authorizedHeaders(this.config),
|
|
229
|
+
body: JSON.stringify({ email }),
|
|
233
230
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
249
|
-
return user;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Verifies a magic code for the user with the given email.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const user = await db.auth.verifyMagicCode({ email, code })
|
|
237
|
+
* console.log("Verified user:", user)
|
|
238
|
+
*
|
|
239
|
+
* @see https://instantdb.com/docs/backend#custom-magic-codes
|
|
240
|
+
*/
|
|
241
|
+
verifyMagicCode = async (email, code) => {
|
|
242
|
+
const { user } = await jsonFetch(`${this.config.apiURI}/admin/verify_magic_code`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: authorizedHeaders(this.config),
|
|
245
|
+
body: JSON.stringify({ email, code }),
|
|
250
246
|
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
* const user = await db.auth.verifyToken(req.headers['token'])
|
|
260
|
-
* if (!user) {
|
|
261
|
-
* return res.status(401).send('Uh oh, you are not authenticated')
|
|
262
|
-
* }
|
|
263
|
-
* // ...
|
|
264
|
-
* })
|
|
265
|
-
* @see https://instantdb.com/docs/backend#custom-endpoints
|
|
266
|
-
*/
|
|
267
|
-
this.verifyToken = (token) => __awaiter(this, void 0, void 0, function* () {
|
|
268
|
-
const res = yield jsonFetch(`${this.config.apiURI}/runtime/auth/verify_refresh_token`, {
|
|
269
|
-
method: 'POST',
|
|
270
|
-
headers: { 'content-type': 'application/json' },
|
|
271
|
-
body: JSON.stringify({
|
|
272
|
-
'app-id': this.config.appId,
|
|
273
|
-
'refresh-token': token,
|
|
274
|
-
}),
|
|
275
|
-
});
|
|
276
|
-
return res.user;
|
|
247
|
+
return user;
|
|
248
|
+
};
|
|
249
|
+
async createToken(input) {
|
|
250
|
+
const body = typeof input === 'string' ? { email: input } : input;
|
|
251
|
+
const ret = await jsonFetch(`${this.config.apiURI}/admin/refresh_tokens`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: authorizedHeaders(this.config),
|
|
254
|
+
body: JSON.stringify(body),
|
|
277
255
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
256
|
+
return ret.user.refresh_token;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Verifies a given token and returns the associated user.
|
|
260
|
+
*
|
|
261
|
+
* This is often useful for writing custom endpoints, where you need
|
|
262
|
+
* to authenticate users.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* app.post('/custom_endpoint', async (req, res) => {
|
|
266
|
+
* const user = await db.auth.verifyToken(req.headers['token'])
|
|
267
|
+
* if (!user) {
|
|
268
|
+
* return res.status(401).send('Uh oh, you are not authenticated')
|
|
269
|
+
* }
|
|
270
|
+
* // ...
|
|
271
|
+
* })
|
|
272
|
+
* @see https://instantdb.com/docs/backend#custom-endpoints
|
|
273
|
+
*/
|
|
274
|
+
verifyToken = async (token) => {
|
|
275
|
+
const res = await jsonFetch(`${this.config.apiURI}/runtime/auth/verify_refresh_token`, {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
headers: { 'content-type': 'application/json' },
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
'app-id': this.config.appId,
|
|
280
|
+
'refresh-token': token,
|
|
281
|
+
}),
|
|
300
282
|
});
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return response.deleted;
|
|
283
|
+
return res.user;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Retrieves an app user by id, email, or refresh token.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* try {
|
|
290
|
+
* const user = await db.auth.getUser({ email })
|
|
291
|
+
* console.log("Found user:", user)
|
|
292
|
+
* } catch (err) {
|
|
293
|
+
* console.error("Failed to retrieve user:", err.message);
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* @see https://instantdb.com/docs/backend#retrieve-a-user
|
|
297
|
+
*/
|
|
298
|
+
getUser = async (params) => {
|
|
299
|
+
const qs = Object.entries(params)
|
|
300
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
301
|
+
.join('&');
|
|
302
|
+
const response = await jsonFetch(`${this.config.apiURI}/admin/users?${qs}`, {
|
|
303
|
+
method: 'GET',
|
|
304
|
+
headers: authorizedHeaders(this.config),
|
|
324
305
|
});
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
306
|
+
return response.user;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Deletes an app user by id, email, or refresh token.
|
|
310
|
+
*
|
|
311
|
+
* NB: This _only_ deletes the user; it does not delete all user data.
|
|
312
|
+
* You will need to handle this manually.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* try {
|
|
316
|
+
* const deletedUser = await db.auth.deleteUser({ email })
|
|
317
|
+
* console.log("Deleted user:", deletedUser)
|
|
318
|
+
* } catch (err) {
|
|
319
|
+
* console.error("Failed to delete user:", err.message);
|
|
320
|
+
* }
|
|
321
|
+
*
|
|
322
|
+
* @see https://instantdb.com/docs/backend#delete-a-user
|
|
323
|
+
*/
|
|
324
|
+
deleteUser = async (params) => {
|
|
325
|
+
const qs = Object.entries(params).map(([k, v]) => `${k}=${v}`);
|
|
326
|
+
const response = await jsonFetch(`${this.config.apiURI}/admin/users?${qs}`, {
|
|
327
|
+
method: 'DELETE',
|
|
328
|
+
headers: authorizedHeaders(this.config),
|
|
337
329
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
});
|
|
330
|
+
return response.deleted;
|
|
331
|
+
};
|
|
332
|
+
async signOut(input) {
|
|
333
|
+
// If input is a string, we assume it's an email.
|
|
334
|
+
// This is because of backwards compatibility: we used to only
|
|
335
|
+
// accept email strings. Eventually we can remove this
|
|
336
|
+
const params = typeof input === 'string' ? { email: input } : input;
|
|
337
|
+
const config = this.config;
|
|
338
|
+
await jsonFetch(`${config.apiURI}/admin/sign_out`, {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: authorizedHeaders(config),
|
|
341
|
+
body: JSON.stringify(params),
|
|
351
342
|
});
|
|
352
343
|
}
|
|
353
344
|
}
|
|
@@ -360,127 +351,137 @@ const isWebReadable = (v) => v && typeof v.getReader === 'function';
|
|
|
360
351
|
* Functions to manage file storage.
|
|
361
352
|
*/
|
|
362
353
|
class Storage {
|
|
354
|
+
config;
|
|
355
|
+
impersonationOpts;
|
|
363
356
|
constructor(config, impersonationOpts) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
357
|
+
this.config = config;
|
|
358
|
+
this.impersonationOpts = impersonationOpts;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Uploads file at the provided path. Accepts a Buffer or a Readable stream.
|
|
362
|
+
*
|
|
363
|
+
* @see https://instantdb.com/docs/storage
|
|
364
|
+
* @example
|
|
365
|
+
* const buffer = fs.readFileSync('demo.png');
|
|
366
|
+
* const isSuccess = await db.storage.uploadFile('photos/demo.png', buffer);
|
|
367
|
+
*/
|
|
368
|
+
uploadFile = async (path, file, metadata = {}) => {
|
|
369
|
+
const headers = {
|
|
370
|
+
...authorizedHeaders(this.config, this.impersonationOpts),
|
|
371
|
+
path,
|
|
372
|
+
};
|
|
373
|
+
if (metadata.contentDisposition) {
|
|
374
|
+
headers['content-disposition'] = metadata.contentDisposition;
|
|
375
|
+
}
|
|
376
|
+
// headers.content-type will become "undefined" (string)
|
|
377
|
+
// if not removed from the object
|
|
378
|
+
delete headers['content-type'];
|
|
379
|
+
if (metadata.contentType) {
|
|
380
|
+
headers['content-type'] = metadata.contentType;
|
|
381
|
+
}
|
|
382
|
+
let duplex;
|
|
383
|
+
if (isNodeReadable(file)) {
|
|
384
|
+
duplex = 'half'; // one-way stream
|
|
385
|
+
}
|
|
386
|
+
if (isNodeReadable(file) || isWebReadable(file)) {
|
|
387
|
+
if (!metadata.fileSize) {
|
|
388
|
+
throw new Error('fileSize is required in metadata when uploading streams');
|
|
392
389
|
}
|
|
393
|
-
|
|
394
|
-
|
|
390
|
+
headers['content-length'] = metadata.fileSize.toString();
|
|
391
|
+
}
|
|
392
|
+
let options = {
|
|
393
|
+
method: 'PUT',
|
|
394
|
+
headers,
|
|
395
|
+
body: file,
|
|
396
|
+
...(duplex && { duplex }),
|
|
397
|
+
};
|
|
398
|
+
return jsonFetch(`${this.config.apiURI}/admin/storage/upload`, options);
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Deletes a file by its path name (e.g. "photos/demo.png").
|
|
402
|
+
*
|
|
403
|
+
* @see https://instantdb.com/docs/storage
|
|
404
|
+
* @example
|
|
405
|
+
* await db.storage.delete("photos/demo.png");
|
|
406
|
+
*/
|
|
407
|
+
delete = async (pathname) => {
|
|
408
|
+
return jsonFetch(`${this.config.apiURI}/admin/storage/files?filename=${encodeURIComponent(pathname)}`, {
|
|
409
|
+
method: 'DELETE',
|
|
410
|
+
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
395
411
|
});
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
412
|
+
};
|
|
413
|
+
/**
|
|
414
|
+
* Deletes multiple files by their path names (e.g. "photos/demo.png", "essays/demo.txt").
|
|
415
|
+
*
|
|
416
|
+
* @see https://instantdb.com/docs/storage
|
|
417
|
+
* @example
|
|
418
|
+
* await db.storage.deleteMany(["images/1.png", "images/2.png", "images/3.png"]);
|
|
419
|
+
*/
|
|
420
|
+
deleteMany = async (pathnames) => {
|
|
421
|
+
return jsonFetch(`${this.config.apiURI}/admin/storage/files/delete`, {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
424
|
+
body: JSON.stringify({ filenames: pathnames }),
|
|
408
425
|
});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
})
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* @deprecated. This method will be removed in the future. Use `uploadFile`
|
|
429
|
+
* instead
|
|
430
|
+
*/
|
|
431
|
+
upload = async (pathname, file, metadata = {}) => {
|
|
432
|
+
const { data: presignedUrl } = await jsonFetch(`${this.config.apiURI}/admin/storage/signed-upload-url`, {
|
|
433
|
+
method: 'POST',
|
|
434
|
+
headers: authorizedHeaders(this.config),
|
|
435
|
+
body: JSON.stringify({
|
|
436
|
+
app_id: this.config.appId,
|
|
437
|
+
filename: pathname,
|
|
438
|
+
}),
|
|
422
439
|
});
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
app_id: this.config.appId,
|
|
433
|
-
filename: pathname,
|
|
434
|
-
}),
|
|
435
|
-
});
|
|
436
|
-
const headers = {};
|
|
437
|
-
const contentType = metadata.contentType;
|
|
438
|
-
if (contentType) {
|
|
439
|
-
headers['Content-Type'] = contentType;
|
|
440
|
-
}
|
|
441
|
-
const { ok } = yield fetch(presignedUrl, {
|
|
442
|
-
method: 'PUT',
|
|
443
|
-
body: file,
|
|
444
|
-
headers,
|
|
445
|
-
});
|
|
446
|
-
return ok;
|
|
440
|
+
const headers = {};
|
|
441
|
+
const contentType = metadata.contentType;
|
|
442
|
+
if (contentType) {
|
|
443
|
+
headers['Content-Type'] = contentType;
|
|
444
|
+
}
|
|
445
|
+
const { ok } = await fetch(presignedUrl, {
|
|
446
|
+
method: 'PUT',
|
|
447
|
+
body: file,
|
|
448
|
+
headers,
|
|
447
449
|
});
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
450
|
+
return ok;
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* @deprecated. This method will be removed in the future. Use `query` instead
|
|
454
|
+
* @example
|
|
455
|
+
* const files = await db.query({ $files: {}})
|
|
456
|
+
*/
|
|
457
|
+
list = async () => {
|
|
458
|
+
const { data } = await jsonFetch(`${this.config.apiURI}/admin/storage/files`, {
|
|
459
|
+
method: 'GET',
|
|
460
|
+
headers: authorizedHeaders(this.config),
|
|
459
461
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
462
|
+
return data;
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* @deprecated. getDownloadUrl will be removed in the future.
|
|
466
|
+
* Use `query` instead to query and fetch for valid urls
|
|
467
|
+
*
|
|
468
|
+
* db.useQuery({
|
|
469
|
+
* $files: {
|
|
470
|
+
* $: {
|
|
471
|
+
* where: {
|
|
472
|
+
* path: "moop.png"
|
|
473
|
+
* }
|
|
474
|
+
* }
|
|
475
|
+
* }
|
|
476
|
+
* })
|
|
477
|
+
*/
|
|
478
|
+
getDownloadUrl = async (pathname) => {
|
|
479
|
+
const { data } = await jsonFetch(`${this.config.apiURI}/admin/storage/signed-download-url?app_id=${this.config.appId}&filename=${encodeURIComponent(pathname)}`, {
|
|
480
|
+
method: 'GET',
|
|
481
|
+
headers: authorizedHeaders(this.config),
|
|
480
482
|
});
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
483
|
+
return data;
|
|
484
|
+
};
|
|
484
485
|
}
|
|
485
486
|
/**
|
|
486
487
|
*
|
|
@@ -492,158 +493,72 @@ class Storage {
|
|
|
492
493
|
* const db = init({ appId: "my-app-id", adminToken: "my-admin-token" })
|
|
493
494
|
*/
|
|
494
495
|
class InstantAdminDatabase {
|
|
496
|
+
config;
|
|
497
|
+
auth;
|
|
498
|
+
storage;
|
|
499
|
+
rooms;
|
|
500
|
+
impersonationOpts;
|
|
501
|
+
tx = (0, core_1.txInit)();
|
|
495
502
|
constructor(_config) {
|
|
496
|
-
this.tx = (0, core_1.txInit)();
|
|
497
|
-
/**
|
|
498
|
-
* Sometimes you want to scope queries to a specific user.
|
|
499
|
-
*
|
|
500
|
-
* You can provide a user's auth token, email, or impersonate a guest.
|
|
501
|
-
*
|
|
502
|
-
* @see https://instantdb.com/docs/backend#impersonating-users
|
|
503
|
-
* @example
|
|
504
|
-
* await db.asUser({email: "stopa@instantdb.com"}).query({ goals: {} })
|
|
505
|
-
*/
|
|
506
|
-
this.asUser = (opts) => {
|
|
507
|
-
const newClient = new InstantAdminDatabase(Object.assign({}, this.config));
|
|
508
|
-
newClient.impersonationOpts = opts;
|
|
509
|
-
newClient.storage = new Storage(this.config, opts);
|
|
510
|
-
return newClient;
|
|
511
|
-
};
|
|
512
|
-
/**
|
|
513
|
-
* Use this to query your data!
|
|
514
|
-
*
|
|
515
|
-
* @see https://instantdb.com/docs/instaql
|
|
516
|
-
*
|
|
517
|
-
* @example
|
|
518
|
-
* // fetch all goals
|
|
519
|
-
* await db.query({ goals: {} })
|
|
520
|
-
*
|
|
521
|
-
* // goals where the title is "Get Fit"
|
|
522
|
-
* await db.query({ goals: { $: { where: { title: "Get Fit" } } } })
|
|
523
|
-
*
|
|
524
|
-
* // all goals, _alongside_ their todos
|
|
525
|
-
* await db.query({ goals: { todos: {} } })
|
|
526
|
-
*/
|
|
527
|
-
this.query = (query, opts = {}) => {
|
|
528
|
-
if (query && opts && 'ruleParams' in opts) {
|
|
529
|
-
query = Object.assign({ $$ruleParams: opts['ruleParams'] }, query);
|
|
530
|
-
}
|
|
531
|
-
if (!this.config.disableValidation) {
|
|
532
|
-
(0, core_1.validateQuery)(query, this.config.schema);
|
|
533
|
-
}
|
|
534
|
-
const fetchOpts = opts.fetchOpts || {};
|
|
535
|
-
const fetchOptsHeaders = fetchOpts['headers'] || {};
|
|
536
|
-
return jsonFetch(`${this.config.apiURI}/admin/query`, Object.assign(Object.assign({}, fetchOpts), { method: 'POST', headers: Object.assign(Object.assign({}, fetchOptsHeaders), authorizedHeaders(this.config, this.impersonationOpts)), body: JSON.stringify({
|
|
537
|
-
query: query,
|
|
538
|
-
'inference?': !!this.config.schema,
|
|
539
|
-
}) }));
|
|
540
|
-
};
|
|
541
|
-
/**
|
|
542
|
-
* Use this to write data! You can create, update, delete, and link objects
|
|
543
|
-
*
|
|
544
|
-
* @see https://instantdb.com/docs/instaml
|
|
545
|
-
*
|
|
546
|
-
* @example
|
|
547
|
-
* // Create a new object in the `goals` namespace
|
|
548
|
-
* const goalId = id();
|
|
549
|
-
* db.transact(db.tx.goals[goalId].update({title: "Get fit"}))
|
|
550
|
-
*
|
|
551
|
-
* // Update the title
|
|
552
|
-
* db.transact(db.tx.goals[goalId].update({title: "Get super fit"}))
|
|
553
|
-
*
|
|
554
|
-
* // Delete it
|
|
555
|
-
* db.transact(db.tx.goals[goalId].delete())
|
|
556
|
-
*
|
|
557
|
-
* // Or create an association:
|
|
558
|
-
* todoId = id();
|
|
559
|
-
* db.transact([
|
|
560
|
-
* db.tx.todos[todoId].update({ title: 'Go on a run' }),
|
|
561
|
-
* db.tx.goals[goalId].link({todos: todoId}),
|
|
562
|
-
* ])
|
|
563
|
-
*/
|
|
564
|
-
this.transact = (inputChunks) => {
|
|
565
|
-
if (!this.config.disableValidation) {
|
|
566
|
-
(0, core_1.validateTransactions)(inputChunks, this.config.schema);
|
|
567
|
-
}
|
|
568
|
-
return jsonFetch(`${this.config.apiURI}/admin/transact`, {
|
|
569
|
-
method: 'POST',
|
|
570
|
-
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
571
|
-
body: JSON.stringify({
|
|
572
|
-
steps: steps(inputChunks),
|
|
573
|
-
'throw-on-missing-attrs?': !!this.config.schema,
|
|
574
|
-
}),
|
|
575
|
-
});
|
|
576
|
-
};
|
|
577
|
-
/**
|
|
578
|
-
* Like `query`, but returns debugging information
|
|
579
|
-
* for permissions checks along with the result.
|
|
580
|
-
* Useful for inspecting the values returned by the permissions checks.
|
|
581
|
-
* Note, this will return debug information for *all* entities
|
|
582
|
-
* that match the query's `where` clauses.
|
|
583
|
-
*
|
|
584
|
-
* Requires a user/guest context to be set with `asUser`,
|
|
585
|
-
* since permissions checks are user-specific.
|
|
586
|
-
*
|
|
587
|
-
* Accepts an optional configuration object with a `rules` key.
|
|
588
|
-
* The provided rules will override the rules in the database for the query.
|
|
589
|
-
*
|
|
590
|
-
* @see https://instantdb.com/docs/instaql
|
|
591
|
-
*
|
|
592
|
-
* @example
|
|
593
|
-
* await db.asUser({ guest: true }).debugQuery(
|
|
594
|
-
* { goals: {} },
|
|
595
|
-
* { rules: { goals: { allow: { read: "auth.id != null" } } }
|
|
596
|
-
* )
|
|
597
|
-
*/
|
|
598
|
-
this.debugQuery = (query, opts) => __awaiter(this, void 0, void 0, function* () {
|
|
599
|
-
if (query && opts && 'ruleParams' in opts) {
|
|
600
|
-
query = Object.assign({ $$ruleParams: opts['ruleParams'] }, query);
|
|
601
|
-
}
|
|
602
|
-
const response = yield jsonFetch(`${this.config.apiURI}/admin/query_perms_check`, {
|
|
603
|
-
method: 'POST',
|
|
604
|
-
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
605
|
-
body: JSON.stringify({ query, 'rules-override': opts === null || opts === void 0 ? void 0 : opts.rules }),
|
|
606
|
-
});
|
|
607
|
-
return {
|
|
608
|
-
result: response.result,
|
|
609
|
-
checkResults: response['check-results'],
|
|
610
|
-
};
|
|
611
|
-
});
|
|
612
|
-
/**
|
|
613
|
-
* Like `transact`, but does not write to the database.
|
|
614
|
-
* Returns debugging information for permissions checks.
|
|
615
|
-
* Useful for inspecting the values returned by the permissions checks.
|
|
616
|
-
*
|
|
617
|
-
* Requires a user/guest context to be set with `asUser`,
|
|
618
|
-
* since permissions checks are user-specific.
|
|
619
|
-
*
|
|
620
|
-
* Accepts an optional configuration object with a `rules` key.
|
|
621
|
-
* The provided rules will override the rules in the database for the duration of the transaction.
|
|
622
|
-
*
|
|
623
|
-
* @example
|
|
624
|
-
* const goalId = id();
|
|
625
|
-
* db.asUser({ guest: true }).debugTransact(
|
|
626
|
-
* [db.tx.goals[goalId].update({title: "Get fit"})],
|
|
627
|
-
* { rules: { goals: { allow: { update: "auth.id != null" } } }
|
|
628
|
-
* )
|
|
629
|
-
*/
|
|
630
|
-
this.debugTransact = (inputChunks, opts) => {
|
|
631
|
-
return jsonFetch(`${this.config.apiURI}/admin/transact_perms_check`, {
|
|
632
|
-
method: 'POST',
|
|
633
|
-
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
634
|
-
body: JSON.stringify({
|
|
635
|
-
steps: steps(inputChunks),
|
|
636
|
-
'rules-override': opts === null || opts === void 0 ? void 0 : opts.rules,
|
|
637
|
-
// @ts-expect-error because we're using a private API (for now)
|
|
638
|
-
'dangerously-commit-tx': opts === null || opts === void 0 ? void 0 : opts.__dangerouslyCommit,
|
|
639
|
-
}),
|
|
640
|
-
});
|
|
641
|
-
};
|
|
642
503
|
this.config = instantConfigWithDefaults(_config);
|
|
643
504
|
this.auth = new Auth(this.config);
|
|
644
505
|
this.storage = new Storage(this.config, this.impersonationOpts);
|
|
645
506
|
this.rooms = new Rooms(this.config);
|
|
646
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Sometimes you want to scope queries to a specific user.
|
|
510
|
+
*
|
|
511
|
+
* You can provide a user's auth token, email, or impersonate a guest.
|
|
512
|
+
*
|
|
513
|
+
* @see https://instantdb.com/docs/backend#impersonating-users
|
|
514
|
+
* @example
|
|
515
|
+
* await db.asUser({email: "stopa@instantdb.com"}).query({ goals: {} })
|
|
516
|
+
*/
|
|
517
|
+
asUser = (opts) => {
|
|
518
|
+
const newClient = new InstantAdminDatabase({
|
|
519
|
+
...this.config,
|
|
520
|
+
});
|
|
521
|
+
newClient.impersonationOpts = opts;
|
|
522
|
+
newClient.storage = new Storage(this.config, opts);
|
|
523
|
+
return newClient;
|
|
524
|
+
};
|
|
525
|
+
/**
|
|
526
|
+
* Use this to query your data!
|
|
527
|
+
*
|
|
528
|
+
* @see https://instantdb.com/docs/instaql
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* // fetch all goals
|
|
532
|
+
* await db.query({ goals: {} })
|
|
533
|
+
*
|
|
534
|
+
* // goals where the title is "Get Fit"
|
|
535
|
+
* await db.query({ goals: { $: { where: { title: "Get Fit" } } } })
|
|
536
|
+
*
|
|
537
|
+
* // all goals, _alongside_ their todos
|
|
538
|
+
* await db.query({ goals: { todos: {} } })
|
|
539
|
+
*/
|
|
540
|
+
query = (query, opts = {}) => {
|
|
541
|
+
if (query && opts && 'ruleParams' in opts) {
|
|
542
|
+
query = { $$ruleParams: opts['ruleParams'], ...query };
|
|
543
|
+
}
|
|
544
|
+
if (!this.config.disableValidation) {
|
|
545
|
+
(0, core_1.validateQuery)(query, this.config.schema);
|
|
546
|
+
}
|
|
547
|
+
const fetchOpts = opts.fetchOpts || {};
|
|
548
|
+
const fetchOptsHeaders = fetchOpts['headers'] || {};
|
|
549
|
+
return jsonFetch(`${this.config.apiURI}/admin/query`, {
|
|
550
|
+
...fetchOpts,
|
|
551
|
+
method: 'POST',
|
|
552
|
+
headers: {
|
|
553
|
+
...fetchOptsHeaders,
|
|
554
|
+
...authorizedHeaders(this.config, this.impersonationOpts),
|
|
555
|
+
},
|
|
556
|
+
body: JSON.stringify({
|
|
557
|
+
query: query,
|
|
558
|
+
'inference?': !!this.config.schema,
|
|
559
|
+
}),
|
|
560
|
+
});
|
|
561
|
+
};
|
|
647
562
|
/**
|
|
648
563
|
* Use this to to get a live view of your data!
|
|
649
564
|
*
|
|
@@ -681,14 +596,17 @@ class InstantAdminDatabase {
|
|
|
681
596
|
*/
|
|
682
597
|
subscribeQuery(query, cb, opts = {}) {
|
|
683
598
|
if (query && opts && 'ruleParams' in opts) {
|
|
684
|
-
query =
|
|
599
|
+
query = { $$ruleParams: opts['ruleParams'], ...query };
|
|
685
600
|
}
|
|
686
601
|
if (!this.config.disableValidation) {
|
|
687
602
|
(0, core_1.validateQuery)(query, this.config.schema);
|
|
688
603
|
}
|
|
689
604
|
const fetchOpts = opts.fetchOpts || {};
|
|
690
605
|
const fetchOptsHeaders = fetchOpts['headers'] || {};
|
|
691
|
-
const headers =
|
|
606
|
+
const headers = {
|
|
607
|
+
...fetchOptsHeaders,
|
|
608
|
+
...authorizedHeaders(this.config, this.impersonationOpts),
|
|
609
|
+
};
|
|
692
610
|
const inference = !!this.config.schema;
|
|
693
611
|
return (0, subscribe_ts_1.subscribe)(query, cb, {
|
|
694
612
|
headers,
|
|
@@ -696,5 +614,106 @@ class InstantAdminDatabase {
|
|
|
696
614
|
apiURI: this.config.apiURI,
|
|
697
615
|
});
|
|
698
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Use this to write data! You can create, update, delete, and link objects
|
|
619
|
+
*
|
|
620
|
+
* @see https://instantdb.com/docs/instaml
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* // Create a new object in the `goals` namespace
|
|
624
|
+
* const goalId = id();
|
|
625
|
+
* db.transact(db.tx.goals[goalId].update({title: "Get fit"}))
|
|
626
|
+
*
|
|
627
|
+
* // Update the title
|
|
628
|
+
* db.transact(db.tx.goals[goalId].update({title: "Get super fit"}))
|
|
629
|
+
*
|
|
630
|
+
* // Delete it
|
|
631
|
+
* db.transact(db.tx.goals[goalId].delete())
|
|
632
|
+
*
|
|
633
|
+
* // Or create an association:
|
|
634
|
+
* todoId = id();
|
|
635
|
+
* db.transact([
|
|
636
|
+
* db.tx.todos[todoId].update({ title: 'Go on a run' }),
|
|
637
|
+
* db.tx.goals[goalId].link({todos: todoId}),
|
|
638
|
+
* ])
|
|
639
|
+
*/
|
|
640
|
+
transact = (inputChunks) => {
|
|
641
|
+
if (!this.config.disableValidation) {
|
|
642
|
+
(0, core_1.validateTransactions)(inputChunks, this.config.schema);
|
|
643
|
+
}
|
|
644
|
+
return jsonFetch(`${this.config.apiURI}/admin/transact`, {
|
|
645
|
+
method: 'POST',
|
|
646
|
+
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
647
|
+
body: JSON.stringify({
|
|
648
|
+
steps: steps(inputChunks),
|
|
649
|
+
'throw-on-missing-attrs?': !!this.config.schema,
|
|
650
|
+
}),
|
|
651
|
+
});
|
|
652
|
+
};
|
|
653
|
+
/**
|
|
654
|
+
* Like `query`, but returns debugging information
|
|
655
|
+
* for permissions checks along with the result.
|
|
656
|
+
* Useful for inspecting the values returned by the permissions checks.
|
|
657
|
+
* Note, this will return debug information for *all* entities
|
|
658
|
+
* that match the query's `where` clauses.
|
|
659
|
+
*
|
|
660
|
+
* Requires a user/guest context to be set with `asUser`,
|
|
661
|
+
* since permissions checks are user-specific.
|
|
662
|
+
*
|
|
663
|
+
* Accepts an optional configuration object with a `rules` key.
|
|
664
|
+
* The provided rules will override the rules in the database for the query.
|
|
665
|
+
*
|
|
666
|
+
* @see https://instantdb.com/docs/instaql
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
* await db.asUser({ guest: true }).debugQuery(
|
|
670
|
+
* { goals: {} },
|
|
671
|
+
* { rules: { goals: { allow: { read: "auth.id != null" } } }
|
|
672
|
+
* )
|
|
673
|
+
*/
|
|
674
|
+
debugQuery = async (query, opts) => {
|
|
675
|
+
if (query && opts && 'ruleParams' in opts) {
|
|
676
|
+
query = { $$ruleParams: opts['ruleParams'], ...query };
|
|
677
|
+
}
|
|
678
|
+
const response = await jsonFetch(`${this.config.apiURI}/admin/query_perms_check`, {
|
|
679
|
+
method: 'POST',
|
|
680
|
+
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
681
|
+
body: JSON.stringify({ query, 'rules-override': opts?.rules }),
|
|
682
|
+
});
|
|
683
|
+
return {
|
|
684
|
+
result: response.result,
|
|
685
|
+
checkResults: response['check-results'],
|
|
686
|
+
};
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* Like `transact`, but does not write to the database.
|
|
690
|
+
* Returns debugging information for permissions checks.
|
|
691
|
+
* Useful for inspecting the values returned by the permissions checks.
|
|
692
|
+
*
|
|
693
|
+
* Requires a user/guest context to be set with `asUser`,
|
|
694
|
+
* since permissions checks are user-specific.
|
|
695
|
+
*
|
|
696
|
+
* Accepts an optional configuration object with a `rules` key.
|
|
697
|
+
* The provided rules will override the rules in the database for the duration of the transaction.
|
|
698
|
+
*
|
|
699
|
+
* @example
|
|
700
|
+
* const goalId = id();
|
|
701
|
+
* db.asUser({ guest: true }).debugTransact(
|
|
702
|
+
* [db.tx.goals[goalId].update({title: "Get fit"})],
|
|
703
|
+
* { rules: { goals: { allow: { update: "auth.id != null" } } }
|
|
704
|
+
* )
|
|
705
|
+
*/
|
|
706
|
+
debugTransact = (inputChunks, opts) => {
|
|
707
|
+
return jsonFetch(`${this.config.apiURI}/admin/transact_perms_check`, {
|
|
708
|
+
method: 'POST',
|
|
709
|
+
headers: authorizedHeaders(this.config, this.impersonationOpts),
|
|
710
|
+
body: JSON.stringify({
|
|
711
|
+
steps: steps(inputChunks),
|
|
712
|
+
'rules-override': opts?.rules,
|
|
713
|
+
// @ts-expect-error because we're using a private API (for now)
|
|
714
|
+
'dangerously-commit-tx': opts?.__dangerouslyCommit,
|
|
715
|
+
}),
|
|
716
|
+
});
|
|
717
|
+
};
|
|
699
718
|
}
|
|
700
719
|
//# sourceMappingURL=index.js.map
|