@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/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