@maravilla-labs/platform 0.2.0 → 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/src/types.ts
CHANGED
|
@@ -248,6 +248,111 @@ export interface Database {
|
|
|
248
248
|
* ```
|
|
249
249
|
*/
|
|
250
250
|
deleteMany(collection: string, filter: any): Promise<{ deleted: number }>;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Register a vector index on a collection field. Inserts thereafter
|
|
254
|
+
* will have vectors in `spec.field` synced into an ANN-indexed `vec0`
|
|
255
|
+
* table inside the same SQLite transaction as the document write.
|
|
256
|
+
*
|
|
257
|
+
* Idempotent when the spec matches an existing index; errors on
|
|
258
|
+
* incompatible re-declaration.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* await db.createVectorIndex('products', {
|
|
263
|
+
* field: 'embedding',
|
|
264
|
+
* dimensions: 1536,
|
|
265
|
+
* metric: 'cosine',
|
|
266
|
+
* storage: 'int8',
|
|
267
|
+
* });
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
createVectorIndex(collection: string, spec: VectorIndexSpec): Promise<void>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Drop a vector index. Returns `{ removed: true }` if the index existed
|
|
274
|
+
* and was removed; `{ removed: false }` otherwise. The underlying
|
|
275
|
+
* collection's documents are untouched.
|
|
276
|
+
*/
|
|
277
|
+
dropVectorIndex(collection: string, field: string): Promise<{ removed: boolean }>;
|
|
278
|
+
|
|
279
|
+
/** List every registered vector index on a collection. */
|
|
280
|
+
listVectorIndexes(collection: string): Promise<VectorIndexDescriptor[]>;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Pure vector search — convenience wrapper. For hybrid metadata +
|
|
284
|
+
* vector search, use `find()` with `options.vector`.
|
|
285
|
+
*/
|
|
286
|
+
findSimilar(collection: string, query: VectorQueryWithFilter): Promise<VectorSearchHit[]>;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Register a MongoDB-style secondary index on a collection. Speeds up
|
|
290
|
+
* `find()` / `findOne()` queries whose filter matches the indexed keys.
|
|
291
|
+
*
|
|
292
|
+
* Idempotent when the spec matches an existing index; errors on name
|
|
293
|
+
* collision with a different spec.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* await db.createIndex('users', { keys: { email: 1 }, unique: true });
|
|
298
|
+
* await db.createIndex('posts', { keys: { authorId: 1, createdAt: -1 } });
|
|
299
|
+
* await db.createIndex('sessions', { keys: { createdAt: 1 }, expireAfterSeconds: 3600 });
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
createIndex(collection: string, spec: IndexSpec): Promise<IndexDescriptor>;
|
|
303
|
+
|
|
304
|
+
/** Drop an index by name. Returns `true` if it existed and was removed. */
|
|
305
|
+
dropIndex(collection: string, name: string): Promise<{ removed: boolean }>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* List every index on a collection — both document indexes and
|
|
309
|
+
* vector indexes — in a unified response.
|
|
310
|
+
*/
|
|
311
|
+
listIndexes(collection: string): Promise<IndexDescriptor[]>;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** MongoDB-style key direction: `1` = ascending, `-1` = descending. */
|
|
315
|
+
export type IndexDirection = 1 | -1;
|
|
316
|
+
|
|
317
|
+
/** Whether an index is a regular document index or a vector index. */
|
|
318
|
+
export type IndexKind = 'document' | 'vector';
|
|
319
|
+
|
|
320
|
+
export interface IndexSpec {
|
|
321
|
+
/** Optional index name. Falls back to an auto-derived name. */
|
|
322
|
+
name?: string;
|
|
323
|
+
/**
|
|
324
|
+
* Key shape. Accepts an ordered array of `[field, direction]` tuples
|
|
325
|
+
* (recommended) OR a plain object — note that object key order is
|
|
326
|
+
* language-dependent, so tuples are safer for compound indexes.
|
|
327
|
+
*/
|
|
328
|
+
keys: Array<[string, IndexDirection]> | Record<string, IndexDirection>;
|
|
329
|
+
/** Enforce uniqueness across the indexed columns. */
|
|
330
|
+
unique?: boolean;
|
|
331
|
+
/**
|
|
332
|
+
* Partial-index predicate. Only matching documents are included in
|
|
333
|
+
* the index. Supports `$eq`, `$ne`, `$gt/$gte/$lt/$lte`, `$in/$nin`,
|
|
334
|
+
* `$exists`, `$and`, `$or`. Rejects `$regex`, `$where`, `$text`.
|
|
335
|
+
*/
|
|
336
|
+
partial?: Record<string, any>;
|
|
337
|
+
/** Shorthand for `partial: { <field>: { $exists: true } }`. */
|
|
338
|
+
sparse?: boolean;
|
|
339
|
+
/**
|
|
340
|
+
* TTL in seconds. Requires a single-field index on a Unix-epoch-seconds
|
|
341
|
+
* field. The platform runs a background sweeper that deletes documents
|
|
342
|
+
* older than this.
|
|
343
|
+
*/
|
|
344
|
+
expireAfterSeconds?: number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface IndexDescriptor {
|
|
348
|
+
collection: string;
|
|
349
|
+
name: string;
|
|
350
|
+
keys: Array<[string, IndexDirection]>;
|
|
351
|
+
unique: boolean;
|
|
352
|
+
sparse: boolean;
|
|
353
|
+
partial?: Record<string, any>;
|
|
354
|
+
expireAfterSeconds?: number;
|
|
355
|
+
kind: IndexKind;
|
|
251
356
|
}
|
|
252
357
|
|
|
253
358
|
/**
|
|
@@ -261,8 +366,91 @@ export interface DbFindOptions {
|
|
|
261
366
|
skip?: number;
|
|
262
367
|
/** Sort order specification (MongoDB-style: { field: 1 } for ascending, { field: -1 } for descending) */
|
|
263
368
|
sort?: any;
|
|
369
|
+
/**
|
|
370
|
+
* Vector search clause. When set, `find()` performs a hybrid
|
|
371
|
+
* metadata + vector search using the collection's registered vector
|
|
372
|
+
* index on `vector.field`. The metadata filter in `filter` is applied
|
|
373
|
+
* after (or alongside, depending on the index type) the vector ranking.
|
|
374
|
+
*/
|
|
375
|
+
vector?: VectorQuery;
|
|
264
376
|
}
|
|
265
377
|
|
|
378
|
+
/** Distance metric used to compare vectors. */
|
|
379
|
+
export type VectorMetric = 'cosine' | 'l2' | 'hamming';
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* On-disk storage precision for vectors.
|
|
383
|
+
* - `float32` (default): 4 bytes per dim, highest accuracy
|
|
384
|
+
* - `int8`: 1 byte per dim, 4× smaller, <2% accuracy loss for most embeddings
|
|
385
|
+
* - `bit`: 1 bit per dim, 32× smaller; requires metric='hamming'
|
|
386
|
+
*/
|
|
387
|
+
export type VectorStorage = 'float32' | 'int8' | 'bit';
|
|
388
|
+
|
|
389
|
+
/** Query shape: single vector or an array of vectors (ColBERT-style). */
|
|
390
|
+
export type VectorQueryMode = 'single' | 'late-interaction';
|
|
391
|
+
|
|
392
|
+
/** How multi-vector distances are aggregated per document. */
|
|
393
|
+
export type VectorAggregation = 'max-sim' | 'sum';
|
|
394
|
+
|
|
395
|
+
export interface VectorIndexSpec {
|
|
396
|
+
/** JSON path of the vector field inside each document (dotted syntax OK). */
|
|
397
|
+
field: string;
|
|
398
|
+
/** Stored vector dimensionality. */
|
|
399
|
+
dimensions: number;
|
|
400
|
+
/** Distance metric; defaults to `cosine`. */
|
|
401
|
+
metric?: VectorMetric;
|
|
402
|
+
/** Storage precision; defaults to `float32`. Int8/bit quantize automatically on insert. */
|
|
403
|
+
storage?: VectorStorage;
|
|
404
|
+
/** Allow queries with vectors shorter than `dimensions`. Requires matryoshka-trained embeddings. */
|
|
405
|
+
matryoshka?: boolean;
|
|
406
|
+
/** Each document holds an array of vectors (one per chunk/token). */
|
|
407
|
+
multiVector?: boolean;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export interface VectorIndexDescriptor {
|
|
411
|
+
collection: string;
|
|
412
|
+
field: string;
|
|
413
|
+
dimensions: number;
|
|
414
|
+
metric: VectorMetric;
|
|
415
|
+
storage: VectorStorage;
|
|
416
|
+
matryoshka: boolean;
|
|
417
|
+
multiVector: boolean;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export interface VectorQuery {
|
|
421
|
+
/** Must match a registered vector index field on the collection. */
|
|
422
|
+
field: string;
|
|
423
|
+
/**
|
|
424
|
+
* Query vector (`number[]` for `queryMode: 'single'`) or array of
|
|
425
|
+
* query vectors (`number[][]` for `queryMode: 'late-interaction'`).
|
|
426
|
+
*/
|
|
427
|
+
value: number[] | number[][];
|
|
428
|
+
/** Top-k result count. Must be > 0. */
|
|
429
|
+
k: number;
|
|
430
|
+
/** Per-query metric override. Defaults to the index's configured metric. */
|
|
431
|
+
metric?: VectorMetric;
|
|
432
|
+
/** Drop results below this normalized score (0–1, higher = more similar). */
|
|
433
|
+
minScore?: number;
|
|
434
|
+
/** Defaults to `single`. */
|
|
435
|
+
queryMode?: VectorQueryMode;
|
|
436
|
+
/** Defaults to `max-sim`. Only meaningful for multi-vector indexes. */
|
|
437
|
+
aggregation?: VectorAggregation;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Convenience shape for the pure-vector `findSimilar()` helper. */
|
|
441
|
+
export interface VectorQueryWithFilter extends VectorQuery {
|
|
442
|
+
/** Optional metadata filter applied alongside the vector search. */
|
|
443
|
+
filter?: any;
|
|
444
|
+
/** Overall result cap (defaults to `k`). */
|
|
445
|
+
limit?: number;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Document returned from a vector search, with similarity metadata attached. */
|
|
449
|
+
export type VectorSearchHit = Record<string, any> & {
|
|
450
|
+
_score: number;
|
|
451
|
+
_distance: number;
|
|
452
|
+
};
|
|
453
|
+
|
|
266
454
|
/**
|
|
267
455
|
* Storage interface for object/file storage operations.
|
|
268
456
|
* Provides S3-compatible API for storing and retrieving files.
|
|
@@ -585,6 +773,26 @@ export interface AuthUser {
|
|
|
585
773
|
last_login_at?: number;
|
|
586
774
|
}
|
|
587
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Snapshot of whoever is currently bound to the request as the caller.
|
|
778
|
+
*
|
|
779
|
+
* Populated by {@link AuthService.login} (implicit), {@link AuthService.setCurrentUser}
|
|
780
|
+
* (explicit), or left anonymous if neither has run for this request.
|
|
781
|
+
* This is exactly what per-resource policies see as `auth.*` when they run.
|
|
782
|
+
*/
|
|
783
|
+
export interface AuthCaller {
|
|
784
|
+
/** Caller's user id, or `""` if anonymous */
|
|
785
|
+
user_id: string;
|
|
786
|
+
/** Caller's email, or `""` if anonymous */
|
|
787
|
+
email: string;
|
|
788
|
+
/** Admin flag from the session */
|
|
789
|
+
is_admin: boolean;
|
|
790
|
+
/** Role names (project-scoped) */
|
|
791
|
+
roles: string[];
|
|
792
|
+
/** `true` when no identity is bound to this request */
|
|
793
|
+
is_anonymous: boolean;
|
|
794
|
+
}
|
|
795
|
+
|
|
588
796
|
/**
|
|
589
797
|
* Session returned after successful login or token refresh.
|
|
590
798
|
*/
|
|
@@ -840,6 +1048,302 @@ export interface AuthService {
|
|
|
840
1048
|
withAuth<T extends (request: Request & { user: AuthUser }) => Promise<Response>>(
|
|
841
1049
|
handler: T
|
|
842
1050
|
): (request: Request) => Promise<Response>;
|
|
1051
|
+
|
|
1052
|
+
// ── Request-scoped identity + authorization ──
|
|
1053
|
+
//
|
|
1054
|
+
// These methods operate on the **current request's** caller context.
|
|
1055
|
+
// They're meaningful inside the platform runtime (one isolate serving a
|
|
1056
|
+
// Deno request). When called from a remote client — code running outside
|
|
1057
|
+
// the runtime — they throw, because there is no per-request context to
|
|
1058
|
+
// bind.
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Explicitly bind the caller for the remainder of this request.
|
|
1062
|
+
* Pass a JWT to validate + bind, or `null` / `""` to clear.
|
|
1063
|
+
*
|
|
1064
|
+
* `login()` already binds implicitly on success; reach for `setCurrentUser`
|
|
1065
|
+
* when you receive a JWT from an inbound `Authorization` header or cookie
|
|
1066
|
+
* and want subsequent KV/DB/realtime/media ops to run as that user.
|
|
1067
|
+
*
|
|
1068
|
+
* Not available on remote clients — throws.
|
|
1069
|
+
*/
|
|
1070
|
+
setCurrentUser(token: string | null): Promise<void>;
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Snapshot of the currently bound caller. Returns an anonymous caller
|
|
1074
|
+
* (`is_anonymous: true`) when no identity has been bound.
|
|
1075
|
+
*
|
|
1076
|
+
* Not available on remote clients — throws.
|
|
1077
|
+
*/
|
|
1078
|
+
getCurrentUser(): AuthCaller;
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Ask the policy engine whether the bound caller would be allowed to
|
|
1082
|
+
* perform `action` on `resourceId`, given the supplied `node` payload.
|
|
1083
|
+
* Returns a boolean — never throws on denial.
|
|
1084
|
+
*
|
|
1085
|
+
* The check runs the exact same evaluator that gates direct KV/DB/
|
|
1086
|
+
* realtime/media ops, so `can(...)` is authoritative.
|
|
1087
|
+
*
|
|
1088
|
+
* @example
|
|
1089
|
+
* ```typescript
|
|
1090
|
+
* const ok = await platform.auth.can("delete", "documents", {
|
|
1091
|
+
* owner: doc.owner,
|
|
1092
|
+
* status: doc.status,
|
|
1093
|
+
* });
|
|
1094
|
+
* if (!ok) return new Response("Forbidden", { status: 403 });
|
|
1095
|
+
* ```
|
|
1096
|
+
*
|
|
1097
|
+
* Not available on remote clients — throws.
|
|
1098
|
+
*/
|
|
1099
|
+
can(action: string, resourceId: string, node?: Record<string, unknown> | null): Promise<boolean>;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Per-request opt-out toggle for the Layer 2 policy evaluator.
|
|
1104
|
+
*
|
|
1105
|
+
* Flipping `setEnabled(false)` disables **per-resource policies only** for
|
|
1106
|
+
* the remainder of the current request. Layer 1 (tenant + owner isolation)
|
|
1107
|
+
* is always enforced — no call can ever escape its tenant. Every flip is
|
|
1108
|
+
* audit-logged server-side with the caller's identity.
|
|
1109
|
+
*
|
|
1110
|
+
* Intended for trusted in-app flows (first-run seeders, admin jobs). Do not
|
|
1111
|
+
* toggle based on untrusted input.
|
|
1112
|
+
*
|
|
1113
|
+
* Not available on remote clients — throws.
|
|
1114
|
+
*/
|
|
1115
|
+
export interface PolicyService {
|
|
1116
|
+
/** Disable or re-enable Layer 2 policy checks for this request. */
|
|
1117
|
+
setEnabled(enabled: boolean): void;
|
|
1118
|
+
/** `true` when Layer 2 is active for this request. */
|
|
1119
|
+
isEnabled(): boolean;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Target selector for Web Push sends. Combine fields to narrow — all
|
|
1124
|
+
* specified conditions must match for a subscription to receive the push.
|
|
1125
|
+
*
|
|
1126
|
+
* @example
|
|
1127
|
+
* ```typescript
|
|
1128
|
+
* // Every subscription tagged with "waitlist"
|
|
1129
|
+
* await platform.push.send({ topic: 'waitlist' }, notification);
|
|
1130
|
+
*
|
|
1131
|
+
* // Every device belonging to one authenticated user
|
|
1132
|
+
* await platform.push.send({ userId: 'u_42' }, notification);
|
|
1133
|
+
*
|
|
1134
|
+
* // "This specific user's subscription for this specific invite"
|
|
1135
|
+
* await platform.push.send({ userId: 'u_42', topic: 'invite:abc:rsvp' }, notification);
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
export interface PushTarget {
|
|
1139
|
+
userId?: string;
|
|
1140
|
+
visitorId?: string;
|
|
1141
|
+
topic?: string;
|
|
1142
|
+
userIds?: string[];
|
|
1143
|
+
topics?: string[];
|
|
1144
|
+
onlyActive?: boolean;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/** Shape of a Web Push notification payload. */
|
|
1148
|
+
export interface NotificationPayload {
|
|
1149
|
+
/** Required — the notification headline shown on lock screens and the notification shade. */
|
|
1150
|
+
title: string;
|
|
1151
|
+
body?: string;
|
|
1152
|
+
icon?: string;
|
|
1153
|
+
badge?: string;
|
|
1154
|
+
image?: string;
|
|
1155
|
+
/** Browsers dedupe notifications sharing a tag. */
|
|
1156
|
+
tag?: string;
|
|
1157
|
+
/** Where to navigate when the notification is clicked. */
|
|
1158
|
+
url?: string;
|
|
1159
|
+
/** Arbitrary JSON delivered to the service worker alongside the notification. */
|
|
1160
|
+
data?: Record<string, unknown>;
|
|
1161
|
+
/** Seconds the push service holds the message if the device is offline. */
|
|
1162
|
+
ttl?: number;
|
|
1163
|
+
urgency?: 'very-low' | 'low' | 'normal' | 'high';
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Options for `platform.push.schedule(...)`.
|
|
1168
|
+
*
|
|
1169
|
+
* @example
|
|
1170
|
+
* ```typescript
|
|
1171
|
+
* // Remind every RSVP'd guest one hour before the event.
|
|
1172
|
+
* await platform.push.schedule(
|
|
1173
|
+
* { topic: `invite:${invite.id}` },
|
|
1174
|
+
* { title: invite.title, body: 'Your event is in one hour' },
|
|
1175
|
+
* {
|
|
1176
|
+
* at: offsetBefore(invite.event_date, '1h'),
|
|
1177
|
+
* key: `invite:${invite.id}:reminder-1h`,
|
|
1178
|
+
* }
|
|
1179
|
+
* );
|
|
1180
|
+
* ```
|
|
1181
|
+
*/
|
|
1182
|
+
export interface ScheduleOptions {
|
|
1183
|
+
/**
|
|
1184
|
+
* When to send. Absolute `Date` or ISO-8601 string; the server treats
|
|
1185
|
+
* bare (no-offset) strings as UTC.
|
|
1186
|
+
*/
|
|
1187
|
+
at: Date | string;
|
|
1188
|
+
/**
|
|
1189
|
+
* Idempotency key scoped to your project. Re-calling `schedule` with the
|
|
1190
|
+
* same key atomically replaces the prior pending job — safe to call on
|
|
1191
|
+
* every save of an invite whose event date may change.
|
|
1192
|
+
*/
|
|
1193
|
+
key: string;
|
|
1194
|
+
/** Maximum delivery attempts before the job is marked failed. Defaults to 3. */
|
|
1195
|
+
maxAttempts?: number;
|
|
1196
|
+
/**
|
|
1197
|
+
* If set, the job re-queues after every successful send and fires again
|
|
1198
|
+
* this many seconds later. Use for daily digests (`86400`), weekly
|
|
1199
|
+
* updates (`604800`), or any fixed-interval loop. `cancelScheduled(key)`
|
|
1200
|
+
* stops the loop.
|
|
1201
|
+
*/
|
|
1202
|
+
everySeconds?: number;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** A single scheduled push job as returned by `listScheduled` / `getScheduled`. */
|
|
1206
|
+
export interface ScheduledJob {
|
|
1207
|
+
jobId: string;
|
|
1208
|
+
key?: string;
|
|
1209
|
+
/** Next fire time — unix seconds. Convert with `new Date(scheduledFor * 1000)`. */
|
|
1210
|
+
scheduledFor: number;
|
|
1211
|
+
status: 'pending' | 'running' | 'succeeded' | 'failed';
|
|
1212
|
+
attempts: number;
|
|
1213
|
+
maxAttempts: number;
|
|
1214
|
+
lastError?: string;
|
|
1215
|
+
createdAt: number;
|
|
1216
|
+
updatedAt: number;
|
|
1217
|
+
/** Populated once the job has fired at least once. */
|
|
1218
|
+
sentAt?: number;
|
|
1219
|
+
/** Set when the job recurs — `schedule()` was called with `everySeconds`. */
|
|
1220
|
+
recurringIntervalSecs?: number;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/** Filter passed to `listScheduled`. */
|
|
1224
|
+
export interface ListScheduledFilter {
|
|
1225
|
+
status?: 'pending' | 'running' | 'succeeded' | 'failed';
|
|
1226
|
+
limit?: number;
|
|
1227
|
+
offset?: number;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/** Counts by status, as returned by `queueStats`. */
|
|
1231
|
+
export interface QueueStats {
|
|
1232
|
+
pending: number;
|
|
1233
|
+
running: number;
|
|
1234
|
+
succeeded: number;
|
|
1235
|
+
failed: number;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/** Outcome of a single `platform.push.send(...)` fan-out. */
|
|
1239
|
+
export interface SendReport {
|
|
1240
|
+
attempted: number;
|
|
1241
|
+
succeeded: number;
|
|
1242
|
+
gone: number;
|
|
1243
|
+
failed: number;
|
|
1244
|
+
errors?: Array<{ subscriptionId: string; message: string }>;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/** Shape of a stored Web Push subscription. */
|
|
1248
|
+
export interface StoredPushSubscription {
|
|
1249
|
+
id: string;
|
|
1250
|
+
provider: 'web-push' | 'apns' | 'fcm';
|
|
1251
|
+
endpoint: string;
|
|
1252
|
+
p256dh?: string | null;
|
|
1253
|
+
auth?: string | null;
|
|
1254
|
+
userId?: string | null;
|
|
1255
|
+
visitorId?: string | null;
|
|
1256
|
+
userAgent?: string | null;
|
|
1257
|
+
topics: string[];
|
|
1258
|
+
createdAt: number;
|
|
1259
|
+
lastSeenAt?: number | null;
|
|
1260
|
+
expiresAt?: number | null;
|
|
1261
|
+
isActive: boolean;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/** Aggregate counts across every subscription in the project. */
|
|
1265
|
+
export interface SubscriptionCounts {
|
|
1266
|
+
total: number;
|
|
1267
|
+
byTopic: Array<[string, number]>;
|
|
1268
|
+
byProvider: Array<[string, number]>;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/** Public VAPID config for the current project. */
|
|
1272
|
+
export interface PublicPushConfig {
|
|
1273
|
+
vapidPublic: string;
|
|
1274
|
+
contactEmail: string;
|
|
1275
|
+
updatedAt: number;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Server-side Web Push service. Access via `platform.push` inside your
|
|
1280
|
+
* runtime code.
|
|
1281
|
+
*/
|
|
1282
|
+
export interface PushService {
|
|
1283
|
+
/**
|
|
1284
|
+
* Fan out a notification to every active subscription matching `target`.
|
|
1285
|
+
* Blocks until every device has been tried.
|
|
1286
|
+
*/
|
|
1287
|
+
send(target: PushTarget, notification: NotificationPayload): Promise<SendReport>;
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Fire-and-forget variant. The request handler can return immediately;
|
|
1291
|
+
* the dispatch continues in the background. Best-effort — a delivery
|
|
1292
|
+
* restart mid-dispatch loses in-flight sends.
|
|
1293
|
+
*/
|
|
1294
|
+
sendBackground(target: PushTarget, notification: NotificationPayload): Promise<void>;
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Queue a notification for a future time. Idempotent by `key` — repeated
|
|
1298
|
+
* calls with the same key replace the prior pending job. Set `everySeconds`
|
|
1299
|
+
* for recurring digests.
|
|
1300
|
+
*/
|
|
1301
|
+
schedule(
|
|
1302
|
+
target: PushTarget,
|
|
1303
|
+
notification: NotificationPayload,
|
|
1304
|
+
opts: ScheduleOptions,
|
|
1305
|
+
): Promise<{ jobId: string }>;
|
|
1306
|
+
|
|
1307
|
+
/** Cancel the pending scheduled job with this idempotency key. */
|
|
1308
|
+
cancelScheduled(key: string): Promise<{ canceled: number }>;
|
|
1309
|
+
|
|
1310
|
+
/** List scheduled jobs for this project, optionally filtered by status. */
|
|
1311
|
+
listScheduled(filter?: ListScheduledFilter): Promise<ScheduledJob[]>;
|
|
1312
|
+
|
|
1313
|
+
/** Look up a single scheduled job by idempotency key. */
|
|
1314
|
+
getScheduled(key: string): Promise<ScheduledJob | null>;
|
|
1315
|
+
|
|
1316
|
+
/** Per-status counts for the scheduled queue. */
|
|
1317
|
+
queueStats(): Promise<QueueStats>;
|
|
1318
|
+
|
|
1319
|
+
/** List subscriptions — useful for admin UIs and debugging. */
|
|
1320
|
+
list(filter?: {
|
|
1321
|
+
topic?: string;
|
|
1322
|
+
userId?: string;
|
|
1323
|
+
visitorId?: string;
|
|
1324
|
+
onlyActive?: boolean;
|
|
1325
|
+
limit?: number;
|
|
1326
|
+
offset?: number;
|
|
1327
|
+
}): Promise<StoredPushSubscription[]>;
|
|
1328
|
+
|
|
1329
|
+
/** Aggregate counts grouped by topic and provider. */
|
|
1330
|
+
counts(): Promise<SubscriptionCounts>;
|
|
1331
|
+
|
|
1332
|
+
/** Remove a subscription by id. */
|
|
1333
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
1334
|
+
|
|
1335
|
+
/** Remove a subscription by its push service endpoint URL. */
|
|
1336
|
+
unsubscribeByEndpoint(endpoint: string): Promise<void>;
|
|
1337
|
+
|
|
1338
|
+
/** Fetch the project's current VAPID public key + contact email. */
|
|
1339
|
+
getVapidConfig(): Promise<PublicPushConfig>;
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* Rotate the project's VAPID keypair. **Every existing subscription
|
|
1343
|
+
* silently stops working** — browsers bind subscriptions to the key they
|
|
1344
|
+
* saw at subscribe time. Confirm with your users before calling.
|
|
1345
|
+
*/
|
|
1346
|
+
rotateVapidKeys(): Promise<PublicPushConfig>;
|
|
843
1347
|
}
|
|
844
1348
|
|
|
845
1349
|
export interface Platform {
|
|
@@ -849,6 +1353,15 @@ export interface Platform {
|
|
|
849
1353
|
media?: import('./media.js').MediaService;
|
|
850
1354
|
/** Realtime service for pub/sub channels and presence */
|
|
851
1355
|
realtime: RealtimeService;
|
|
852
|
-
/** Auth service for end-user authentication and
|
|
1356
|
+
/** Auth service for end-user authentication, identity binding, and authorization checks */
|
|
853
1357
|
auth: AuthService;
|
|
1358
|
+
/** Per-request Layer 2 policy toggle (Layer 1 isolation always applies) */
|
|
1359
|
+
policy: PolicyService;
|
|
1360
|
+
/**
|
|
1361
|
+
* Web Push — send, schedule, or query browser push notifications for
|
|
1362
|
+
* logged-in users and anonymous visitors. Available when Push is enabled
|
|
1363
|
+
* in project settings; `undefined` when running against the dev-server
|
|
1364
|
+
* fallback that doesn't proxy to delivery.
|
|
1365
|
+
*/
|
|
1366
|
+
push?: PushService;
|
|
854
1367
|
}
|