@mpgd/platform 0.22.0 → 0.23.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.
@@ -0,0 +1,427 @@
1
+ import { type tags } from 'typia';
2
+ import { type NotificationCapabilities, type NotificationChannel, type NotificationConsentKind, type NotificationConsentSource, type NotificationConsentState, type NotificationDeferralReason, type NotificationDeliveryStatus, type NotificationDeployment, type NotificationNoticeKind, type NotificationNoticeSettings, type NotificationNoticeVariant, type NotificationPermissionOutcome, type NotificationPermissionState, type NotificationReminderRoute, type NotificationReminderStatus, type NotificationRequiredAction, type NotificationSuppressionReason, type NotificationTopicDeclaration, type NotificationUnavailableReason, type RecordNotificationNoticeInput } from './notifications.js';
3
+ /**
4
+ * Conformance vector format for docs/specs/notifications/vectors/*.json (RFC
5
+ * "Vector format"). Every expectation item is partial: only the listed fields
6
+ * are asserted, while each list is matched exactly in length and order, and a
7
+ * field set to `null` asserts that the field is absent.
8
+ */
9
+ type NonEmptyString = string & tags.MinLength<1>;
10
+ type IsoTimestamp = string & tags.Format<'date-time'>;
11
+ type NonNegativeInteger = number & tags.Type<'uint32'>;
12
+ type PositiveInteger = number & tags.Type<'uint32'> & tags.Minimum<1>;
13
+ type TemplateData = Readonly<Record<string, string | number | boolean>>;
14
+ type CalendarDate = string & tags.Format<'date'>;
15
+ /** The default locale a vector runs in, unless the runner's options name another. */
16
+ export declare const notificationConformanceDefaultLocale = "ko";
17
+ /** A capability path such as `permission.prompt` or `remote.purposes`. */
18
+ type CapabilityPath = string & tags.Pattern<'^[a-zA-Z]+(?:\\.[a-zA-Z]+)*$'>;
19
+ /** A predicate on the provider's capabilities; every predicate must hold for the vector to run. */
20
+ export type NotificationConformancePredicate = {
21
+ readonly path: CapabilityPath;
22
+ readonly equals: string | number | boolean;
23
+ } | {
24
+ readonly path: CapabilityPath;
25
+ readonly includes: string;
26
+ } | {
27
+ readonly path: CapabilityPath;
28
+ readonly excludes: string;
29
+ };
30
+ /** Per-recipient limits of the provider's registry entry. */
31
+ export interface NotificationConformanceProfileLimits {
32
+ /** Deliveries to one player within any 24 hours. */
33
+ readonly perUserPerDay?: PositiveInteger;
34
+ /** Deliveries to one player within any 60 seconds. */
35
+ readonly perUserPerMinute?: PositiveInteger;
36
+ }
37
+ export interface NotificationConformanceProfile {
38
+ readonly provider: NonEmptyString;
39
+ readonly capabilities: NotificationCapabilities;
40
+ /** Server-side limits of the provider's registry entry. */
41
+ readonly limits?: NotificationConformanceProfileLimits;
42
+ }
43
+ export interface NotificationConformanceContextFixture {
44
+ readonly player: NonEmptyString;
45
+ /** The fake clock's start. */
46
+ readonly now: IsoTimestamp;
47
+ /** The device's zone, which the runtime registers. */
48
+ readonly timeZone: NonEmptyString;
49
+ }
50
+ export interface NotificationConformanceHostFixture {
51
+ /**
52
+ * Starts the fixture provider in `availability: 'action-required'` with
53
+ * `requiredAction: 'login'` (a logged-out Devvit player) until `completeAction`.
54
+ */
55
+ readonly actionRequired?: boolean;
56
+ }
57
+ export type NotificationConformancePromptResult = 'granted' | 'already-granted' | 'denied' | 'dismissed' | 'host-terms-declined';
58
+ export type NotificationConformanceProviderAnswer = 'accepted' | 'unregistered' | 'rate-limited' | 'rejected' | 'ambiguous';
59
+ /** Why a provider rejected a send: the project's setup, the recipient, or a missing agreement. */
60
+ export type NotificationConformanceRejectionReason = 'configuration' | 'recipient' | 'agreement';
61
+ export interface NotificationConformanceEndpointFixture {
62
+ readonly kind: 'fcm-token' | 'fcm-installation';
63
+ readonly value: NonEmptyString;
64
+ readonly environment?: 'production' | 'sandbox';
65
+ }
66
+ /** Another installation of the same player, registered directly with the backend. */
67
+ export interface NotificationConformanceInstallationFixture {
68
+ readonly installationId: NonEmptyString;
69
+ readonly kind: 'fcm-token' | 'fcm-installation';
70
+ readonly value: NonEmptyString;
71
+ readonly permission: NotificationPermissionState;
72
+ }
73
+ export interface NotificationConformanceTapInput {
74
+ readonly channel: NotificationChannel;
75
+ readonly topic?: NonEmptyString;
76
+ readonly route?: string;
77
+ readonly data?: Readonly<Record<string, string>>;
78
+ }
79
+ export interface NotificationConformanceThrows {
80
+ readonly code: NonEmptyString;
81
+ }
82
+ export interface NotificationConformanceDeliverInput {
83
+ readonly topic: NonEmptyString;
84
+ readonly idempotencyKey: NonEmptyString;
85
+ /** Defaults to `context.player`. */
86
+ readonly player?: NonEmptyString;
87
+ readonly provider?: NonEmptyString;
88
+ readonly templateData?: TemplateData;
89
+ /** Per-send routing context; strings only. */
90
+ readonly data?: Readonly<Record<string, string>>;
91
+ readonly locale?: NonEmptyString;
92
+ /** A post or comment id for providers whose sends need a link. */
93
+ readonly link?: NonEmptyString;
94
+ }
95
+ export interface NotificationConformanceSnapshotExpectation {
96
+ readonly app?: NotificationPermissionState;
97
+ readonly topics?: Readonly<Record<string, NotificationPermissionState>>;
98
+ readonly requiredAction?: NotificationRequiredAction;
99
+ }
100
+ export interface NotificationConformancePermissionExpectation extends NotificationConformanceSnapshotExpectation {
101
+ readonly sources?: Readonly<Record<string, 'host' | 'record'>>;
102
+ }
103
+ export interface NotificationConformanceEvidenceExpectation {
104
+ readonly schema?: NonEmptyString;
105
+ readonly payload?: Readonly<Record<string, string | number | boolean>>;
106
+ }
107
+ export interface NotificationConformanceRequestExpectation {
108
+ readonly outcomes?: Readonly<Record<string, NotificationPermissionOutcome>>;
109
+ readonly snapshot?: NotificationConformanceSnapshotExpectation;
110
+ readonly requiredAction?: NotificationRequiredAction;
111
+ readonly evidence?: NotificationConformanceEvidenceExpectation;
112
+ }
113
+ export interface NotificationConformanceReminderExpectation {
114
+ readonly route?: NotificationReminderRoute;
115
+ readonly status?: NotificationReminderStatus;
116
+ }
117
+ export interface NotificationConformanceOutcomeExpectation {
118
+ readonly status?: NotificationDeliveryStatus;
119
+ readonly alreadyProcessed?: boolean;
120
+ readonly reason?: NotificationSuppressionReason | NotificationDeferralReason | NotificationUnavailableReason;
121
+ readonly until?: IsoTimestamp;
122
+ }
123
+ export interface NotificationConformanceNoticeRecordExpectation {
124
+ readonly kind?: 'advertising' | 'night';
125
+ readonly state?: 'granted' | 'withdrawn';
126
+ readonly at?: IsoTimestamp;
127
+ readonly textVersion?: NonEmptyString;
128
+ }
129
+ export interface NotificationConformanceNoticeExpectation {
130
+ readonly kind?: NotificationNoticeKind;
131
+ readonly record?: NotificationConformanceNoticeRecordExpectation;
132
+ /** Asia/Seoul calendar date `YYYY-MM-DD`. */
133
+ readonly dueBy?: CalendarDate;
134
+ /** The variant of `notices.copy` the notice is listed with. */
135
+ readonly variant?: NotificationNoticeVariant;
136
+ }
137
+ export type NotificationConformanceStep = {
138
+ readonly actor: 'runtime';
139
+ readonly action: 'start' | 'restart';
140
+ } | {
141
+ readonly actor: 'clock';
142
+ readonly action: 'set';
143
+ readonly at: IsoTimestamp;
144
+ } | {
145
+ readonly actor: 'clock';
146
+ readonly action: 'advance';
147
+ readonly seconds: NonNegativeInteger;
148
+ } | {
149
+ readonly actor: 'host';
150
+ readonly action: 'queuePrompt';
151
+ readonly result: NotificationConformancePromptResult;
152
+ } | {
153
+ readonly actor: 'host';
154
+ readonly action: 'setPermission';
155
+ readonly state: NotificationPermissionState;
156
+ } | {
157
+ readonly actor: 'host';
158
+ readonly action: 'issueEndpoint';
159
+ readonly endpoint: NotificationConformanceEndpointFixture;
160
+ } | {
161
+ readonly actor: 'host';
162
+ readonly action: 'tap';
163
+ readonly input: NotificationConformanceTapInput;
164
+ } | {
165
+ readonly actor: 'host';
166
+ readonly action: 'completeAction';
167
+ } | {
168
+ readonly actor: 'game';
169
+ readonly action: 'getPermission';
170
+ readonly input: {
171
+ readonly topics: readonly NonEmptyString[];
172
+ };
173
+ readonly returns?: NotificationConformancePermissionExpectation;
174
+ readonly throws?: NotificationConformanceThrows;
175
+ } | {
176
+ readonly actor: 'game';
177
+ readonly action: 'requestPermission';
178
+ readonly input: {
179
+ readonly topics: readonly NonEmptyString[];
180
+ readonly gesture: boolean;
181
+ };
182
+ readonly returns?: NotificationConformanceRequestExpectation;
183
+ readonly throws?: NotificationConformanceThrows;
184
+ } | {
185
+ readonly actor: 'game';
186
+ readonly action: 'revokePermission';
187
+ readonly input: {
188
+ readonly topics: readonly NonEmptyString[];
189
+ };
190
+ readonly returns?: NotificationConformanceRequestExpectation;
191
+ readonly throws?: NotificationConformanceThrows;
192
+ } | {
193
+ readonly actor: 'game';
194
+ readonly action: 'register' | 'unregister';
195
+ readonly throws?: NotificationConformanceThrows;
196
+ } | {
197
+ readonly actor: 'game';
198
+ readonly action: 'setTopicPreference';
199
+ readonly input: {
200
+ readonly topic: NonEmptyString;
201
+ readonly enabled: boolean;
202
+ };
203
+ readonly throws?: NotificationConformanceThrows;
204
+ } | {
205
+ readonly actor: 'game';
206
+ readonly action: 'setAdvertisingConsent';
207
+ readonly input: {
208
+ readonly advertising?: 'granted' | 'withdrawn';
209
+ readonly night?: 'granted' | 'withdrawn';
210
+ readonly textVersion: NonEmptyString;
211
+ };
212
+ readonly throws?: NotificationConformanceThrows;
213
+ } | {
214
+ readonly actor: 'game';
215
+ readonly action: 'setReminder';
216
+ readonly input: {
217
+ readonly key: NonEmptyString;
218
+ readonly topic: NonEmptyString;
219
+ readonly fireAt: IsoTimestamp;
220
+ readonly templateData?: TemplateData;
221
+ readonly link?: NonEmptyString;
222
+ };
223
+ readonly returns?: NotificationConformanceReminderExpectation;
224
+ readonly throws?: NotificationConformanceThrows;
225
+ } | {
226
+ readonly actor: 'game';
227
+ readonly action: 'cancelReminder';
228
+ readonly input: {
229
+ readonly key: NonEmptyString;
230
+ };
231
+ readonly returns?: NotificationConformanceReminderExpectation;
232
+ readonly throws?: NotificationConformanceThrows;
233
+ } | {
234
+ readonly actor: 'game';
235
+ readonly action: 'listNotices';
236
+ readonly returns?: readonly NotificationConformanceNoticeExpectation[];
237
+ readonly throws?: NotificationConformanceThrows;
238
+ } | {
239
+ readonly actor: 'game';
240
+ readonly action: 'recordNotice';
241
+ /** A listed notice, or its kind, the kind and time of the record it reports on, and its `dueBy`. */
242
+ readonly input: RecordNotificationNoticeInput;
243
+ readonly throws?: NotificationConformanceThrows;
244
+ } | {
245
+ readonly actor: 'game';
246
+ readonly action: 'openSettings';
247
+ readonly returns?: 'opened' | 'unavailable';
248
+ readonly throws?: NotificationConformanceThrows;
249
+ } | {
250
+ readonly actor: 'game';
251
+ readonly action: 'subscribe';
252
+ readonly listener: NonEmptyString;
253
+ } | {
254
+ readonly actor: 'backend';
255
+ readonly action: 'authenticate';
256
+ readonly player: NonEmptyString;
257
+ } | {
258
+ readonly actor: 'backend';
259
+ readonly action: 'deliver';
260
+ readonly input: NotificationConformanceDeliverInput;
261
+ readonly returns?: NotificationConformanceOutcomeExpectation;
262
+ readonly throws?: NotificationConformanceThrows;
263
+ } | {
264
+ readonly actor: 'backend';
265
+ readonly action: 'queueProviderResult';
266
+ readonly result: NotificationConformanceProviderAnswer;
267
+ /** With `rate-limited`: the provider's retry delay (60 seconds when absent). */
268
+ readonly retryAfterSeconds?: PositiveInteger;
269
+ /** With `rejected`: why the provider rejected the send. */
270
+ readonly reason?: NotificationConformanceRejectionReason;
271
+ } | {
272
+ readonly actor: 'backend';
273
+ readonly action: 'registerEndpoint';
274
+ readonly input: NotificationConformanceInstallationFixture;
275
+ } | {
276
+ readonly actor: 'backend';
277
+ readonly action: 'revokeSession';
278
+ } | {
279
+ readonly actor: 'backend';
280
+ readonly action: 'runScheduler';
281
+ };
282
+ export type NotificationConformanceActor = NotificationConformanceStep['actor'];
283
+ export type NotificationConformanceAction = NotificationConformanceStep['action'];
284
+ export interface NotificationConformanceConsentExpectation {
285
+ readonly kind?: NotificationConsentKind;
286
+ readonly topic?: NonEmptyString;
287
+ readonly state?: NotificationConsentState;
288
+ readonly source?: NotificationConsentSource;
289
+ readonly player?: NonEmptyString;
290
+ readonly evidence?: NotificationConformanceEvidenceExpectation;
291
+ readonly textVersion?: NonEmptyString;
292
+ }
293
+ export interface NotificationConformanceEndpointExpectation {
294
+ readonly provider?: NonEmptyString;
295
+ readonly kind?: 'fcm-token' | 'fcm-installation';
296
+ readonly value?: NonEmptyString;
297
+ readonly installationId?: NonEmptyString;
298
+ readonly permission?: NotificationPermissionState;
299
+ readonly player?: NonEmptyString;
300
+ readonly status?: 'valid' | 'invalid';
301
+ }
302
+ export interface NotificationConformanceDeliveryExpectation extends NotificationConformanceOutcomeExpectation {
303
+ readonly idempotencyKey?: NonEmptyString;
304
+ readonly topic?: NonEmptyString;
305
+ readonly player?: NonEmptyString;
306
+ }
307
+ export interface NotificationConformanceActionExpectation {
308
+ readonly id?: NonEmptyString;
309
+ readonly title?: NonEmptyString;
310
+ readonly route?: NonEmptyString;
311
+ }
312
+ export interface NotificationConformanceContentExpectation {
313
+ readonly title?: string;
314
+ readonly body?: string;
315
+ readonly route?: string;
316
+ readonly actions?: readonly NotificationConformanceActionExpectation[];
317
+ }
318
+ export interface NotificationConformanceProviderCallExpectation {
319
+ readonly topic?: NonEmptyString;
320
+ /** The device address value the attempt went to. */
321
+ readonly address?: NonEmptyString;
322
+ readonly player?: NonEmptyString;
323
+ readonly idempotencyKey?: NonEmptyString;
324
+ readonly mapping?: Readonly<Record<string, string | number | boolean>>;
325
+ /** The send's link; `null` asserts that the call carried none. */
326
+ readonly link?: NonEmptyString | null;
327
+ readonly content?: NotificationConformanceContentExpectation;
328
+ /** The expiry the call carried; `null` asserts that it carried none. */
329
+ readonly expiresAt?: IsoTimestamp | null;
330
+ }
331
+ export interface NotificationConformanceLocalOperationExpectation {
332
+ readonly op?: 'schedule' | 'replace' | 'cancel';
333
+ readonly key?: NonEmptyString;
334
+ readonly topic?: NonEmptyString;
335
+ readonly fireAt?: IsoTimestamp;
336
+ readonly exact?: boolean;
337
+ }
338
+ export interface NotificationConformanceLocalEntryExpectation {
339
+ readonly key?: NonEmptyString;
340
+ readonly topic?: NonEmptyString;
341
+ readonly fireAt?: IsoTimestamp;
342
+ }
343
+ export interface NotificationConformanceLocalExpectation {
344
+ readonly operations?: readonly NotificationConformanceLocalOperationExpectation[];
345
+ readonly pending?: readonly NotificationConformanceLocalEntryExpectation[];
346
+ }
347
+ export interface NotificationConformanceReminderEntryExpectation {
348
+ readonly key?: NonEmptyString;
349
+ readonly topic?: NonEmptyString;
350
+ readonly fireAt?: IsoTimestamp;
351
+ readonly player?: NonEmptyString;
352
+ }
353
+ export interface NotificationConformanceEventExpectation {
354
+ readonly type?: 'opened' | 'received' | 'endpoint-changed' | 'permission-changed';
355
+ readonly channel?: NotificationChannel;
356
+ readonly source?: 'cold' | 'warm';
357
+ readonly topic?: NonEmptyString;
358
+ readonly route?: string;
359
+ readonly data?: Readonly<Record<string, string>>;
360
+ readonly referrer?: string;
361
+ }
362
+ export interface NotificationConformanceExpectation {
363
+ /** Calls that reached the host prompt API. */
364
+ readonly prompts?: NonNegativeInteger;
365
+ /** Consent records, without `at`. */
366
+ readonly consent?: readonly NotificationConformanceConsentExpectation[];
367
+ /** Final endpoints. */
368
+ readonly endpoints?: readonly NotificationConformanceEndpointExpectation[];
369
+ /** Every outcome answered or stored for a send key, in order. */
370
+ readonly deliveries?: readonly NotificationConformanceDeliveryExpectation[];
371
+ /** Every provider attempt, including one answered `rate-limited`. */
372
+ readonly providerCalls?: readonly NotificationConformanceProviderCallExpectation[];
373
+ readonly local?: NotificationConformanceLocalExpectation;
374
+ /** Pending server reminders. */
375
+ readonly reminders?: readonly NotificationConformanceReminderEntryExpectation[];
376
+ /** Events per listener name. */
377
+ readonly events?: Readonly<Record<string, readonly NotificationConformanceEventExpectation[]>>;
378
+ /** The backend's listed notices at the end, ordered by `dueBy` and then `kind`. */
379
+ readonly notices?: readonly NotificationConformanceNoticeExpectation[];
380
+ }
381
+ export interface NotificationConformanceVector {
382
+ /** Equals the file name without `.json`. */
383
+ readonly vector: string & tags.Pattern<'^[a-z0-9]+(?:-[a-z0-9]+)*$'>;
384
+ readonly protocol: NonEmptyString;
385
+ readonly summary: NonEmptyString;
386
+ /** The capability fields the scenario depends on; a provider runs the vector only when all hold. */
387
+ readonly appliesTo: readonly NotificationConformancePredicate[];
388
+ readonly profile: NotificationConformanceProfile;
389
+ /**
390
+ * Topic entries with this profile's mapping under `mapping`. Only the topics
391
+ * the scenario needs; the catalog validator does not check them.
392
+ */
393
+ readonly catalog: readonly NotificationTopicDeclaration[];
394
+ /**
395
+ * The catalog's `notices` block: the topic the scheduler's notice pushes go
396
+ * out through, which `catalog` declares with no copy of its own, and their
397
+ * copy per variant, which follows the catalog's rules.
398
+ */
399
+ readonly notices?: NotificationNoticeSettings;
400
+ readonly context: NotificationConformanceContextFixture;
401
+ readonly host?: NotificationConformanceHostFixture;
402
+ /** Backend deployment settings; Asia/Seoul is the zone when `timeZone` is absent. */
403
+ readonly deployment?: NotificationDeployment;
404
+ readonly steps: readonly NotificationConformanceStep[] & tags.MinItems<1>;
405
+ readonly expect: NotificationConformanceExpectation;
406
+ }
407
+ /**
408
+ * Validates one vector file: the shape, steps that expect either an answer or
409
+ * an error, provider answers whose fields fit them (`retryAfterSeconds` only
410
+ * with `rate-limited`, a `reason` exactly with `rejected`), time zones that
411
+ * `Intl` knows, every timestamp and date read strictly (a date that exists,
412
+ * `T`, and an offset), unique catalog topic ids that meet the topic
413
+ * definition rules, a `notices.copy` that follows the catalog's rules
414
+ * (`notificationNoticeCopyIssues`) in the default locale
415
+ * `notificationConformanceDefaultLocale`, and that the vector's own fixture
416
+ * profile satisfies its `appliesTo` predicates.
417
+ */
418
+ export declare function assertNotificationConformanceVector(input: unknown): NotificationConformanceVector;
419
+ export declare function isNotificationConformanceVector(input: unknown): input is NotificationConformanceVector;
420
+ /** The vector name a file at `path` must declare. */
421
+ export declare function notificationConformanceVectorNameOf(path: string): string;
422
+ /**
423
+ * Why `capabilities` do not satisfy `appliesTo`, or `undefined` when every
424
+ * predicate holds. A path that does not resolve to a declared value fails.
425
+ */
426
+ export declare function notificationConformanceAppliesToReason(appliesTo: readonly NotificationConformancePredicate[], capabilities: NotificationCapabilities): string | undefined;
427
+ export {};