@maravilla-labs/platform 0.2.1 → 0.2.2
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/config.d.ts +236 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -0
- package/dist/events.d.ts +167 -0
- package/dist/events.js +45 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +470 -51
- package/dist/index.js +253 -2
- package/dist/index.js.map +1 -1
- package/dist/push.d.ts +67 -0
- package/dist/push.js +173 -0
- package/dist/push.js.map +1 -0
- package/dist/ren-D0DCQ0Fs.d.ts +48 -0
- package/package.json +13 -1
- package/src/config.ts +276 -0
- package/src/events.ts +271 -0
- package/src/index.ts +1 -0
- package/src/push.ts +280 -0
- package/src/remote-client.ts +101 -1
- package/src/types.ts +514 -1
- package/tsup.config.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -175,6 +175,52 @@ var RemoteDatabase = class {
|
|
|
175
175
|
async deleteMany(collection, filter) {
|
|
176
176
|
return this.deleteOne(collection, filter);
|
|
177
177
|
}
|
|
178
|
+
async createVectorIndex(collection, spec) {
|
|
179
|
+
await this.fetch(`${this.baseUrl}/api/db/${collection}/vectorIndexes`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body: JSON.stringify(spec)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async dropVectorIndex(collection, field) {
|
|
185
|
+
const response = await this.fetch(
|
|
186
|
+
`${this.baseUrl}/api/db/${collection}/vectorIndexes/${encodeURIComponent(field)}`,
|
|
187
|
+
{ method: "DELETE" }
|
|
188
|
+
);
|
|
189
|
+
return response.json();
|
|
190
|
+
}
|
|
191
|
+
async listVectorIndexes(collection) {
|
|
192
|
+
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/vectorIndexes`, {
|
|
193
|
+
method: "GET"
|
|
194
|
+
});
|
|
195
|
+
return response.json();
|
|
196
|
+
}
|
|
197
|
+
async findSimilar(collection, query) {
|
|
198
|
+
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/vectorSearch`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: JSON.stringify(query)
|
|
201
|
+
});
|
|
202
|
+
return response.json();
|
|
203
|
+
}
|
|
204
|
+
async createIndex(collection, spec) {
|
|
205
|
+
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/indexes`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
body: JSON.stringify(spec)
|
|
208
|
+
});
|
|
209
|
+
return response.json();
|
|
210
|
+
}
|
|
211
|
+
async dropIndex(collection, name) {
|
|
212
|
+
const response = await this.fetch(
|
|
213
|
+
`${this.baseUrl}/api/db/${collection}/indexes/${encodeURIComponent(name)}`,
|
|
214
|
+
{ method: "DELETE" }
|
|
215
|
+
);
|
|
216
|
+
return response.json();
|
|
217
|
+
}
|
|
218
|
+
async listIndexes(collection) {
|
|
219
|
+
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/indexes`, {
|
|
220
|
+
method: "GET"
|
|
221
|
+
});
|
|
222
|
+
return response.json();
|
|
223
|
+
}
|
|
178
224
|
};
|
|
179
225
|
var RemoteStorage = class _RemoteStorage {
|
|
180
226
|
constructor(baseUrl, headers) {
|
|
@@ -481,6 +527,38 @@ var RemoteAuthService = class {
|
|
|
481
527
|
}
|
|
482
528
|
};
|
|
483
529
|
}
|
|
530
|
+
// ── Request-scoped identity + authorization ──
|
|
531
|
+
//
|
|
532
|
+
// These APIs operate on per-request state inside the platform runtime and
|
|
533
|
+
// don't have a remote equivalent. Throw so callers get a clear message
|
|
534
|
+
// instead of silently wrong behavior.
|
|
535
|
+
setCurrentUser(_token) {
|
|
536
|
+
return Promise.reject(new Error(
|
|
537
|
+
"platform.auth.setCurrentUser is only available inside the Maravilla runtime. Remote clients should pass the Authorization header with each request instead."
|
|
538
|
+
));
|
|
539
|
+
}
|
|
540
|
+
getCurrentUser() {
|
|
541
|
+
throw new Error(
|
|
542
|
+
"platform.auth.getCurrentUser is only available inside the Maravilla runtime. Remote clients have no per-request caller context."
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
can(_action, _resourceId, _node) {
|
|
546
|
+
return Promise.reject(new Error(
|
|
547
|
+
"platform.auth.can is only available inside the Maravilla runtime. Remote clients cannot evaluate per-request policies because there is no bound caller."
|
|
548
|
+
));
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var RemotePolicyService = class {
|
|
552
|
+
setEnabled(_enabled) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"platform.policy.setEnabled is only available inside the Maravilla runtime."
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
isEnabled() {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"platform.policy.isEnabled is only available inside the Maravilla runtime."
|
|
560
|
+
);
|
|
561
|
+
}
|
|
484
562
|
};
|
|
485
563
|
function createRemoteClient(baseUrl, tenant) {
|
|
486
564
|
const headers = {
|
|
@@ -497,6 +575,7 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
497
575
|
const media = new RemoteMediaService(baseUrl, headers);
|
|
498
576
|
const realtime = new RemoteRealtimeService(baseUrl, headers);
|
|
499
577
|
const auth = new RemoteAuthService(baseUrl, headers);
|
|
578
|
+
const policy = new RemotePolicyService();
|
|
500
579
|
return {
|
|
501
580
|
env: {
|
|
502
581
|
KV: kvProxy,
|
|
@@ -505,7 +584,8 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
505
584
|
},
|
|
506
585
|
media,
|
|
507
586
|
realtime,
|
|
508
|
-
auth
|
|
587
|
+
auth,
|
|
588
|
+
policy
|
|
509
589
|
};
|
|
510
590
|
}
|
|
511
591
|
|
|
@@ -1173,6 +1253,174 @@ var MediaRoom = class _MediaRoom {
|
|
|
1173
1253
|
}
|
|
1174
1254
|
};
|
|
1175
1255
|
|
|
1256
|
+
// src/push.ts
|
|
1257
|
+
var DEFAULT_BASE_PATH = "/_rt/push";
|
|
1258
|
+
var DEFAULT_SW_PATH = "/_rt/push/sw.js";
|
|
1259
|
+
var VISITOR_STORAGE_KEY = "maravilla.push.visitorId";
|
|
1260
|
+
var REGISTER_TIMEOUT_MS = 1e4;
|
|
1261
|
+
function assertPushSupported() {
|
|
1262
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
1263
|
+
throw new Error("Web Push is not supported: serviceWorker is unavailable");
|
|
1264
|
+
}
|
|
1265
|
+
if (typeof window === "undefined" || !("PushManager" in window)) {
|
|
1266
|
+
throw new Error("Web Push is not supported: PushManager is unavailable");
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function base64UrlToArrayBuffer(input) {
|
|
1270
|
+
const padding = "=".repeat((4 - input.length % 4) % 4);
|
|
1271
|
+
const base64 = (input + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
1272
|
+
const raw = atob(base64);
|
|
1273
|
+
const buffer = new ArrayBuffer(raw.length);
|
|
1274
|
+
const view = new Uint8Array(buffer);
|
|
1275
|
+
for (let i = 0; i < raw.length; i++) {
|
|
1276
|
+
view[i] = raw.charCodeAt(i);
|
|
1277
|
+
}
|
|
1278
|
+
return buffer;
|
|
1279
|
+
}
|
|
1280
|
+
function arrayBufferToBase64Url(buffer) {
|
|
1281
|
+
if (!buffer) return void 0;
|
|
1282
|
+
const bytes = new Uint8Array(buffer);
|
|
1283
|
+
let binary = "";
|
|
1284
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1285
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1286
|
+
}
|
|
1287
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1288
|
+
}
|
|
1289
|
+
function randomUuid() {
|
|
1290
|
+
const c = typeof crypto !== "undefined" ? crypto : void 0;
|
|
1291
|
+
if (c && typeof c.randomUUID === "function") {
|
|
1292
|
+
return c.randomUUID();
|
|
1293
|
+
}
|
|
1294
|
+
const bytes = new Uint8Array(16);
|
|
1295
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
1296
|
+
c.getRandomValues(bytes);
|
|
1297
|
+
} else {
|
|
1298
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1299
|
+
}
|
|
1300
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1301
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1302
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1303
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1304
|
+
}
|
|
1305
|
+
function resolveVisitorId(userId, visitorId) {
|
|
1306
|
+
if (visitorId) return visitorId;
|
|
1307
|
+
if (userId) return null;
|
|
1308
|
+
try {
|
|
1309
|
+
const stored = window.localStorage.getItem(VISITOR_STORAGE_KEY);
|
|
1310
|
+
if (stored) return stored;
|
|
1311
|
+
const fresh = randomUuid();
|
|
1312
|
+
window.localStorage.setItem(VISITOR_STORAGE_KEY, fresh);
|
|
1313
|
+
return fresh;
|
|
1314
|
+
} catch {
|
|
1315
|
+
return randomUuid();
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
async function fetchVapidPublicKey(basePath) {
|
|
1319
|
+
const res = await fetch(`${basePath}/vapid-public-key`, {
|
|
1320
|
+
method: "GET",
|
|
1321
|
+
credentials: "same-origin",
|
|
1322
|
+
headers: { Accept: "application/json" }
|
|
1323
|
+
});
|
|
1324
|
+
if (!res.ok) {
|
|
1325
|
+
throw new Error(`Failed to fetch VAPID public key: ${res.status} ${res.statusText}`);
|
|
1326
|
+
}
|
|
1327
|
+
const body = await res.json();
|
|
1328
|
+
if (!body || typeof body.publicKey !== "string" || body.publicKey.length === 0) {
|
|
1329
|
+
throw new Error("VAPID public key response is missing `publicKey`");
|
|
1330
|
+
}
|
|
1331
|
+
return body.publicKey;
|
|
1332
|
+
}
|
|
1333
|
+
function extractKeys(sub) {
|
|
1334
|
+
return {
|
|
1335
|
+
p256dh: arrayBufferToBase64Url(sub.getKey("p256dh")),
|
|
1336
|
+
auth: arrayBufferToBase64Url(sub.getKey("auth"))
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
async function registerPush(opts = {}) {
|
|
1340
|
+
assertPushSupported();
|
|
1341
|
+
const basePath = opts.basePath ?? DEFAULT_BASE_PATH;
|
|
1342
|
+
const swPath = opts.swPath ?? DEFAULT_SW_PATH;
|
|
1343
|
+
const topics = opts.topics ?? [];
|
|
1344
|
+
const userId = opts.userId ?? null;
|
|
1345
|
+
const visitorId = resolveVisitorId(userId, opts.visitorId);
|
|
1346
|
+
const timeout = new Promise(
|
|
1347
|
+
(_, reject) => setTimeout(
|
|
1348
|
+
() => reject(new Error(`registerPush timed out after ${REGISTER_TIMEOUT_MS}ms`)),
|
|
1349
|
+
REGISTER_TIMEOUT_MS
|
|
1350
|
+
)
|
|
1351
|
+
);
|
|
1352
|
+
const flow = (async () => {
|
|
1353
|
+
const publicKey = await fetchVapidPublicKey(basePath);
|
|
1354
|
+
const registration = await navigator.serviceWorker.register(swPath);
|
|
1355
|
+
const existing = await registration.pushManager.getSubscription();
|
|
1356
|
+
const subscription = existing ?? await registration.pushManager.subscribe({
|
|
1357
|
+
userVisibleOnly: true,
|
|
1358
|
+
applicationServerKey: base64UrlToArrayBuffer(publicKey)
|
|
1359
|
+
});
|
|
1360
|
+
const { p256dh, auth } = extractKeys(subscription);
|
|
1361
|
+
const res = await fetch(`${basePath}/subscribe`, {
|
|
1362
|
+
method: "POST",
|
|
1363
|
+
credentials: "same-origin",
|
|
1364
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1365
|
+
body: JSON.stringify({
|
|
1366
|
+
provider: "web-push",
|
|
1367
|
+
endpoint: subscription.endpoint,
|
|
1368
|
+
p256dh,
|
|
1369
|
+
auth,
|
|
1370
|
+
userId,
|
|
1371
|
+
visitorId,
|
|
1372
|
+
topics
|
|
1373
|
+
})
|
|
1374
|
+
});
|
|
1375
|
+
if (!res.ok) {
|
|
1376
|
+
throw new Error(`Subscribe failed: ${res.status} ${res.statusText}`);
|
|
1377
|
+
}
|
|
1378
|
+
const saved = await res.json();
|
|
1379
|
+
if (!saved || typeof saved.id !== "string" || saved.id.length === 0) {
|
|
1380
|
+
throw new Error("Subscribe response is missing `id`");
|
|
1381
|
+
}
|
|
1382
|
+
return { subscription, subscriptionId: saved.id };
|
|
1383
|
+
})();
|
|
1384
|
+
return Promise.race([flow, timeout]);
|
|
1385
|
+
}
|
|
1386
|
+
async function unregisterPush(subscriptionId, opts = {}) {
|
|
1387
|
+
if (!subscriptionId) {
|
|
1388
|
+
throw new Error("subscriptionId is required");
|
|
1389
|
+
}
|
|
1390
|
+
const basePath = opts.basePath ?? DEFAULT_BASE_PATH;
|
|
1391
|
+
const res = await fetch(`${basePath}/unsubscribe`, {
|
|
1392
|
+
method: "POST",
|
|
1393
|
+
credentials: "same-origin",
|
|
1394
|
+
headers: { "Content-Type": "application/json" },
|
|
1395
|
+
body: JSON.stringify({ subscriptionId })
|
|
1396
|
+
});
|
|
1397
|
+
if (!res.ok && res.status !== 404) {
|
|
1398
|
+
throw new Error(`Unsubscribe failed: ${res.status} ${res.statusText}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function offsetBefore(anchor, offset) {
|
|
1402
|
+
const anchorDate = anchor instanceof Date ? anchor : new Date(anchor);
|
|
1403
|
+
if (Number.isNaN(anchorDate.getTime())) {
|
|
1404
|
+
throw new Error(`offsetBefore: invalid anchor "${String(anchor)}"`);
|
|
1405
|
+
}
|
|
1406
|
+
const match = /^(\d+)\s*(s|m|h|d|w)$/i.exec(offset.trim());
|
|
1407
|
+
if (!match) {
|
|
1408
|
+
throw new Error(
|
|
1409
|
+
`offsetBefore: invalid offset "${offset}" \u2014 expected something like "30m", "1h", "2d", "1w"`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
const amount = Number(match[1]);
|
|
1413
|
+
const unit = match[2].toLowerCase();
|
|
1414
|
+
const UNIT_MS = {
|
|
1415
|
+
s: 1e3,
|
|
1416
|
+
m: 6e4,
|
|
1417
|
+
h: 36e5,
|
|
1418
|
+
d: 864e5,
|
|
1419
|
+
w: 6048e5
|
|
1420
|
+
};
|
|
1421
|
+
return new Date(anchorDate.getTime() - amount * UNIT_MS[unit]);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1176
1424
|
// src/index.ts
|
|
1177
1425
|
var cachedPlatform = void 0;
|
|
1178
1426
|
function getPlatform(options) {
|
|
@@ -1218,8 +1466,11 @@ export {
|
|
|
1218
1466
|
detachTrack,
|
|
1219
1467
|
getOrCreateClientId,
|
|
1220
1468
|
getPlatform,
|
|
1469
|
+
offsetBefore,
|
|
1470
|
+
registerPush,
|
|
1221
1471
|
renFetch,
|
|
1222
1472
|
storageDelete,
|
|
1223
|
-
storageUpload
|
|
1473
|
+
storageUpload,
|
|
1474
|
+
unregisterPush
|
|
1224
1475
|
};
|
|
1225
1476
|
//# sourceMappingURL=index.js.map
|