@baukit/notifications-core 0.5.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/errors.d.ts +8 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +11 -0
  6. package/dist/errors.js.map +1 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +8 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/memory.d.ts +45 -0
  12. package/dist/memory.d.ts.map +1 -0
  13. package/dist/memory.js +104 -0
  14. package/dist/memory.js.map +1 -0
  15. package/dist/occurrences.d.ts +34 -0
  16. package/dist/occurrences.d.ts.map +1 -0
  17. package/dist/occurrences.js +84 -0
  18. package/dist/occurrences.js.map +1 -0
  19. package/dist/ownership.d.ts +11 -0
  20. package/dist/ownership.d.ts.map +1 -0
  21. package/dist/ownership.js +49 -0
  22. package/dist/ownership.js.map +1 -0
  23. package/dist/plan-vectors.d.ts +57 -0
  24. package/dist/plan-vectors.d.ts.map +1 -0
  25. package/dist/plan-vectors.js +108 -0
  26. package/dist/plan-vectors.js.map +1 -0
  27. package/dist/replacement-plan.d.ts +11 -0
  28. package/dist/replacement-plan.d.ts.map +1 -0
  29. package/dist/replacement-plan.js +28 -0
  30. package/dist/replacement-plan.js.map +1 -0
  31. package/dist/scheduler.d.ts +46 -0
  32. package/dist/scheduler.d.ts.map +1 -0
  33. package/dist/scheduler.js +143 -0
  34. package/dist/scheduler.js.map +1 -0
  35. package/dist/validation.d.ts +21 -0
  36. package/dist/validation.d.ts.map +1 -0
  37. package/dist/validation.js +62 -0
  38. package/dist/validation.js.map +1 -0
  39. package/dist/vitest.d.ts +13 -0
  40. package/dist/vitest.d.ts.map +1 -0
  41. package/dist/vitest.js +261 -0
  42. package/dist/vitest.js.map +1 -0
  43. package/package.json +61 -0
@@ -0,0 +1,108 @@
1
+ import { NotificationPlanError } from './errors.js';
2
+ import { resolveNotificationOccurrences, } from './occurrences.js';
3
+ import { planNotificationReplacement } from './replacement-plan.js';
4
+ /**
5
+ * Expands `fixtures/notifications/plan-vectors-v1.json` into checks. Each success case yields
6
+ * three: the expected plan, convergence once the plan is applied, and independence from input
7
+ * order. Each error case yields one check of the thrown `NotificationPlanError` code and ID.
8
+ */
9
+ export function notificationPlanVectorChecks(fixture) {
10
+ return fixture.cases.flatMap((entry) => isErrorCase(entry) ? [errorCheck(entry)] : successChecks(entry));
11
+ }
12
+ function isErrorCase(entry) {
13
+ return 'error' in entry.expected;
14
+ }
15
+ function successChecks(entry) {
16
+ const { input } = entry;
17
+ return [
18
+ {
19
+ label: entry.name,
20
+ expected: entry.expected,
21
+ actual: () => summary(input, toCurrent(input.current)),
22
+ },
23
+ {
24
+ label: `${entry.name} converges after the plan is applied`,
25
+ expected: { schedule: [], cancel: [], notKept: 0 },
26
+ actual: () => convergence(input),
27
+ },
28
+ {
29
+ label: `${entry.name} does not depend on input order`,
30
+ expected: entry.expected,
31
+ actual: () => summary({ ...input, occurrences: [...input.occurrences].reverse() }, toCurrent(input.current).reverse()),
32
+ },
33
+ ];
34
+ }
35
+ function errorCheck(entry) {
36
+ const { code, logicalId } = entry.expected.error;
37
+ return {
38
+ label: entry.name,
39
+ expected: { error: { code, logicalId } },
40
+ actual: () => thrownPlanError(() => run(entry.input, toCurrent(entry.input.current))),
41
+ };
42
+ }
43
+ function toCurrent(entries) {
44
+ return entries.map(({ logicalId, instant, contentDigest }) => ({
45
+ logicalId,
46
+ epochMilliseconds: Date.parse(instant),
47
+ contentDigest,
48
+ }));
49
+ }
50
+ function run(input, current) {
51
+ const resolution = resolveNotificationOccurrences({
52
+ occurrences: input.occurrences,
53
+ timeZone: input.timeZone,
54
+ gap: input.gap,
55
+ fold: input.fold,
56
+ clock: () => Date.parse(input.now),
57
+ horizonDays: input.horizonDays,
58
+ });
59
+ const plan = planNotificationReplacement(current, resolution.desired, {
60
+ replaceAll: input.replaceAll ?? false,
61
+ });
62
+ return { resolution, plan };
63
+ }
64
+ function summary(input, current) {
65
+ const { resolution, plan } = run(input, current);
66
+ return {
67
+ firstCivilDate: resolution.firstCivilDate,
68
+ lastCivilDate: resolution.lastCivilDate,
69
+ schedule: plan.schedule.map((entry) => ({
70
+ logicalId: entry.logicalId,
71
+ instant: new Date(entry.epochMilliseconds).toISOString().replace('.000Z', 'Z'),
72
+ transition: entry.transition,
73
+ })),
74
+ keep: plan.keep.map((entry) => entry.logicalId),
75
+ cancel: plan.cancel.map((entry) => entry.logicalId),
76
+ skipped: resolution.skipped,
77
+ };
78
+ }
79
+ function appliedPlan(input) {
80
+ const { plan } = run(input, toCurrent(input.current));
81
+ return [...plan.keep, ...plan.schedule].map(({ logicalId, epochMilliseconds, contentDigest }) => ({
82
+ logicalId,
83
+ epochMilliseconds,
84
+ contentDigest,
85
+ }));
86
+ }
87
+ function convergence(input) {
88
+ const applied = appliedPlan(input);
89
+ const settled = summary({ ...input, replaceAll: false }, [...applied].reverse());
90
+ return {
91
+ schedule: settled.schedule,
92
+ cancel: settled.cancel,
93
+ notKept: applied.length - settled.keep.length,
94
+ };
95
+ }
96
+ function thrownPlanError(action) {
97
+ try {
98
+ action();
99
+ }
100
+ catch (error) {
101
+ if (error instanceof NotificationPlanError) {
102
+ return { error: { code: error.code, logicalId: error.logicalId } };
103
+ }
104
+ return { unexpected: String(error) };
105
+ }
106
+ return { unexpected: 'no error thrown' };
107
+ }
108
+ //# sourceMappingURL=plan-vectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-vectors.js","sourceRoot":"","sources":["../src/plan-vectors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type PlannedNotification } from './validation.js';
2
+ export interface NotificationReplacementOptions {
3
+ readonly replaceAll?: boolean;
4
+ }
5
+ export interface NotificationReplacementPlan<TCurrent extends PlannedNotification, TDesired extends PlannedNotification> {
6
+ readonly keep: readonly TCurrent[];
7
+ readonly cancel: readonly TCurrent[];
8
+ readonly schedule: readonly TDesired[];
9
+ }
10
+ export declare function planNotificationReplacement<TCurrent extends PlannedNotification, TDesired extends PlannedNotification>(current: readonly TCurrent[], desired: readonly TDesired[], options?: NotificationReplacementOptions): NotificationReplacementPlan<TCurrent, TDesired>;
11
+ //# sourceMappingURL=replacement-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replacement-plan.d.ts","sourceRoot":"","sources":["../src/replacement-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,2BAA2B,CAC1C,QAAQ,SAAS,mBAAmB,EACpC,QAAQ,SAAS,mBAAmB;IAEpC,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;CACxC;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,SAAS,mBAAmB,EACpC,QAAQ,SAAS,mBAAmB,EAEpC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAC5B,OAAO,EAAE,SAAS,QAAQ,EAAE,EAC5B,OAAO,GAAE,8BAAmC,GAC3C,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAwBjD"}
@@ -0,0 +1,28 @@
1
+ import { assertPlannedNotifications, compareByInstant, } from './validation.js';
2
+ export function planNotificationReplacement(current, desired, options = {}) {
3
+ assertPlannedNotifications(current);
4
+ assertPlannedNotifications(desired);
5
+ const desiredById = new Map(desired.map((entry) => [entry.logicalId, entry]));
6
+ const keep = [];
7
+ const cancel = [];
8
+ const kept = new Set();
9
+ for (const entry of current) {
10
+ const wanted = desiredById.get(entry.logicalId);
11
+ if (options.replaceAll !== true && wanted !== undefined && samePlacement(entry, wanted)) {
12
+ keep.push(entry);
13
+ kept.add(entry.logicalId);
14
+ }
15
+ else {
16
+ cancel.push(entry);
17
+ }
18
+ }
19
+ return {
20
+ keep: keep.sort(compareByInstant),
21
+ cancel: cancel.sort(compareByInstant),
22
+ schedule: desired.filter((entry) => !kept.has(entry.logicalId)).sort(compareByInstant),
23
+ };
24
+ }
25
+ function samePlacement(left, right) {
26
+ return (left.epochMilliseconds === right.epochMilliseconds && left.contentDigest === right.contentDigest);
27
+ }
28
+ //# sourceMappingURL=replacement-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replacement-plan.js","sourceRoot":"","sources":["../src/replacement-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC;AAezB,MAAM,UAAU,2BAA2B,CAIzC,OAA4B,EAC5B,OAA4B,EAC5B,UAA0C,EAAE;IAE5C,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACpC,0BAA0B,CAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;QACjC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC;QACrC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;KACvF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,IAAyB,EAAE,KAA0B;IAC1E,OAAO,CACL,IAAI,CAAC,iBAAiB,KAAK,KAAK,CAAC,iBAAiB,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,aAAa,CACjG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type OwnedNotificationMarker } from './ownership.js';
2
+ import { type NotificationReplacementOptions } from './replacement-plan.js';
3
+ import { type PlannedNotification } from './validation.js';
4
+ export type NotificationPermission = 'granted' | 'denied' | 'undetermined';
5
+ export interface PendingNotification {
6
+ readonly identifier: string;
7
+ readonly marker: OwnedNotificationMarker | null;
8
+ }
9
+ export interface OwnedNotificationScheduleRequest<TContent> {
10
+ readonly identifier: string;
11
+ readonly marker: OwnedNotificationMarker;
12
+ readonly content: TContent;
13
+ }
14
+ export interface NotificationPlatform<TContent> {
15
+ list(): Promise<readonly PendingNotification[]>;
16
+ cancel(identifier: string): Promise<void>;
17
+ permission(): Promise<NotificationPermission>;
18
+ schedule(request: OwnedNotificationScheduleRequest<TContent>): Promise<void>;
19
+ }
20
+ export interface NotificationOwner {
21
+ readonly namespace: string;
22
+ }
23
+ export interface OwnedNotification<TContent> extends PlannedNotification {
24
+ readonly content: TContent;
25
+ }
26
+ export type OwnedNotificationFailureCode = 'list_failed' | 'cancel_failed' | 'permission_denied' | 'permission_failed' | 'schedule_failed' | 'schedule_limit';
27
+ export interface OwnedNotificationFailure {
28
+ readonly code: OwnedNotificationFailureCode;
29
+ readonly logicalId?: string;
30
+ }
31
+ export type OwnedNotificationReplacementStatus = 'complete' | 'incomplete' | 'superseded';
32
+ export interface OwnedNotificationReplacementOutcome {
33
+ readonly status: OwnedNotificationReplacementStatus;
34
+ readonly kept: readonly string[];
35
+ readonly cancelled: readonly string[];
36
+ readonly scheduled: readonly string[];
37
+ readonly failures: readonly OwnedNotificationFailure[];
38
+ }
39
+ export interface OwnedNotificationScheduler<TContent> {
40
+ replaceOwned(owner: NotificationOwner, desired: readonly OwnedNotification<TContent>[], options?: NotificationReplacementOptions): Promise<OwnedNotificationReplacementOutcome>;
41
+ }
42
+ export interface OwnedNotificationSchedulerOptions {
43
+ readonly pendingLimit?: number;
44
+ }
45
+ export declare function createOwnedNotificationScheduler<TContent>(platform: NotificationPlatform<TContent>, options?: OwnedNotificationSchedulerOptions): OwnedNotificationScheduler<TContent>;
46
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,gCAAgC,CAAC,QAAQ;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB,CAAC,QAAQ;IAC5C,IAAI,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;IAChD,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,UAAU,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,gCAAgC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB,CAAC,QAAQ,CAAE,SAAQ,mBAAmB;IACtE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC5B;AAED,MAAM,MAAM,4BAA4B,GACpC,aAAa,GACb,eAAe,GACf,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,CAAC;AAErB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,4BAA4B,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,kCAAkC,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC;AAE1F,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,MAAM,EAAE,kCAAkC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,0BAA0B,CAAC,QAAQ;IAClD,YAAY,CACV,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,SAAS,iBAAiB,CAAC,QAAQ,CAAC,EAAE,EAC/C,OAAO,CAAC,EAAE,8BAA8B,GACvC,OAAO,CAAC,mCAAmC,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AA2BD,wBAAgB,gCAAgC,CAAC,QAAQ,EACvD,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EACxC,OAAO,GAAE,iCAAsC,GAC9C,0BAA0B,CAAC,QAAQ,CAAC,CAkBtC"}
@@ -0,0 +1,143 @@
1
+ import { NotificationPlanError } from './errors.js';
2
+ import { isOwnedBy, ownedNotificationIdentifier, } from './ownership.js';
3
+ import { planNotificationReplacement, } from './replacement-plan.js';
4
+ import { assertNamespace, assertPlannedNotifications, } from './validation.js';
5
+ const SUPERSEDED = Object.freeze({
6
+ status: 'superseded',
7
+ kept: [],
8
+ cancelled: [],
9
+ scheduled: [],
10
+ failures: [],
11
+ });
12
+ export function createOwnedNotificationScheduler(platform, options = {}) {
13
+ const pendingLimit = options.pendingLimit ?? Number.POSITIVE_INFINITY;
14
+ if (pendingLimit !== Number.POSITIVE_INFINITY && !isPositiveInteger(pendingLimit)) {
15
+ throw new NotificationPlanError('invalid_pending_limit');
16
+ }
17
+ const queues = new Map();
18
+ return {
19
+ async replaceOwned(owner, desired, replaceOptions = {}) {
20
+ assertNamespace(owner.namespace);
21
+ assertPlannedNotifications(desired);
22
+ const namespace = owner.namespace;
23
+ const snapshot = [...desired];
24
+ return enqueue(queues, namespace, () => replaceNow(platform, namespace, snapshot, replaceOptions, pendingLimit));
25
+ },
26
+ };
27
+ }
28
+ function enqueue(queues, namespace, run) {
29
+ return new Promise((resolve, reject) => {
30
+ const replacement = { run, resolve, reject };
31
+ const queue = queues.get(namespace);
32
+ if (queue === undefined) {
33
+ const created = { waiting: null };
34
+ queues.set(namespace, created);
35
+ start(queues, namespace, created, replacement);
36
+ return;
37
+ }
38
+ queue.waiting?.resolve(SUPERSEDED);
39
+ queue.waiting = replacement;
40
+ });
41
+ }
42
+ function start(queues, namespace, queue, replacement) {
43
+ void replacement
44
+ .run()
45
+ .then(replacement.resolve, replacement.reject)
46
+ .finally(() => {
47
+ const next = queue.waiting;
48
+ queue.waiting = null;
49
+ if (next === null) {
50
+ queues.delete(namespace);
51
+ return;
52
+ }
53
+ start(queues, namespace, queue, next);
54
+ });
55
+ }
56
+ async function replaceNow(platform, namespace, desired, options, pendingLimit) {
57
+ let pending;
58
+ try {
59
+ pending = await platform.list();
60
+ }
61
+ catch {
62
+ return outcome([], [], [], [{ code: 'list_failed' }]);
63
+ }
64
+ const current = pending.flatMap(({ identifier, marker }) => isOwnedBy(namespace, identifier, marker) ? [marker] : []);
65
+ const plan = planNotificationReplacement(current, desired, options);
66
+ const cancelFailures = [];
67
+ const cancelled = [];
68
+ for (const entry of plan.cancel) {
69
+ try {
70
+ await platform.cancel(ownedNotificationIdentifier(namespace, entry.logicalId));
71
+ cancelled.push(entry.logicalId);
72
+ }
73
+ catch {
74
+ cancelFailures.push({ code: 'cancel_failed', logicalId: entry.logicalId });
75
+ }
76
+ }
77
+ const blocked = new Set(cancelFailures.map((failure) => failure.logicalId));
78
+ const toSchedule = plan.schedule.filter((entry) => !blocked.has(entry.logicalId));
79
+ const capacity = pendingLimit - (pending.length - cancelled.length);
80
+ const scheduling = await scheduleAll(platform, namespace, toSchedule, capacity);
81
+ return outcome(plan.keep.map((entry) => entry.logicalId), cancelled, scheduling.scheduled, [...cancelFailures, ...scheduling.failures]);
82
+ }
83
+ async function scheduleAll(platform, namespace, entries, capacity) {
84
+ if (entries.length === 0) {
85
+ return { scheduled: [], failures: [] };
86
+ }
87
+ const blockedCode = await permissionFailure(platform);
88
+ if (blockedCode !== null) {
89
+ return {
90
+ scheduled: [],
91
+ failures: entries.map((entry) => ({ code: blockedCode, logicalId: entry.logicalId })),
92
+ };
93
+ }
94
+ const scheduled = [];
95
+ const failures = [];
96
+ for (const entry of entries) {
97
+ if (scheduled.length >= capacity) {
98
+ failures.push({ code: 'schedule_limit', logicalId: entry.logicalId });
99
+ continue;
100
+ }
101
+ try {
102
+ await platform.schedule(scheduleRequest(namespace, entry));
103
+ scheduled.push(entry.logicalId);
104
+ }
105
+ catch {
106
+ failures.push({ code: 'schedule_failed', logicalId: entry.logicalId });
107
+ }
108
+ }
109
+ return { scheduled, failures };
110
+ }
111
+ async function permissionFailure(platform) {
112
+ try {
113
+ return (await platform.permission()) === 'granted' ? null : 'permission_denied';
114
+ }
115
+ catch {
116
+ return 'permission_failed';
117
+ }
118
+ }
119
+ function scheduleRequest(namespace, entry) {
120
+ return {
121
+ identifier: ownedNotificationIdentifier(namespace, entry.logicalId),
122
+ marker: {
123
+ namespace,
124
+ logicalId: entry.logicalId,
125
+ epochMilliseconds: entry.epochMilliseconds,
126
+ contentDigest: entry.contentDigest,
127
+ },
128
+ content: entry.content,
129
+ };
130
+ }
131
+ function outcome(kept, cancelled, scheduled, failures) {
132
+ return {
133
+ status: failures.length === 0 ? 'complete' : 'incomplete',
134
+ kept,
135
+ cancelled,
136
+ scheduled,
137
+ failures,
138
+ };
139
+ }
140
+ function isPositiveInteger(value) {
141
+ return Number.isSafeInteger(value) && value > 0;
142
+ }
143
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare const MAX_NAMESPACE_LENGTH = 64;
2
+ export declare const MAX_LOGICAL_ID_LENGTH = 128;
3
+ export declare const MAX_CONTENT_DIGEST_LENGTH = 128;
4
+ export interface PlannedNotification {
5
+ readonly logicalId: string;
6
+ readonly epochMilliseconds: number;
7
+ readonly contentDigest: string;
8
+ }
9
+ export declare function isValidNamespace(namespace: unknown): namespace is string;
10
+ export declare function isValidLogicalId(logicalId: unknown): logicalId is string;
11
+ export declare function isValidContentDigest(contentDigest: unknown): contentDigest is string;
12
+ export declare function isValidInstant(epochMilliseconds: unknown): epochMilliseconds is number;
13
+ export declare function assertNamespace(namespace: unknown): asserts namespace is string;
14
+ export declare function assertIdentity(entry: {
15
+ readonly logicalId: unknown;
16
+ readonly contentDigest: unknown;
17
+ }, seen: Set<string>): void;
18
+ export declare function assertPlannedNotifications(entries: readonly PlannedNotification[]): void;
19
+ export declare function compareByInstant(left: PlannedNotification, right: PlannedNotification): number;
20
+ export declare function compareCodeUnits(left: string, right: string): number;
21
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAK7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,IAAI,MAAM,CAMxE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,IAAI,MAAM,CAExE;AAED,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,OAAO,GAAG,aAAa,IAAI,MAAM,CAEpF;AAED,wBAAgB,cAAc,CAAC,iBAAiB,EAAE,OAAO,GAAG,iBAAiB,IAAI,MAAM,CAMtF;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAI/E;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE;IAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;CAAE,EACvE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAChB,IAAI,CAWN;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI,CAQxF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAK9F;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKpE"}
@@ -0,0 +1,62 @@
1
+ import { NotificationPlanError } from './errors.js';
2
+ export const MAX_NAMESPACE_LENGTH = 64;
3
+ export const MAX_LOGICAL_ID_LENGTH = 128;
4
+ export const MAX_CONTENT_DIGEST_LENGTH = 128;
5
+ const NAMESPACE_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
6
+ const VISIBLE_ASCII_PATTERN = /^[!-~]+$/;
7
+ export function isValidNamespace(namespace) {
8
+ return (typeof namespace === 'string' &&
9
+ namespace.length <= MAX_NAMESPACE_LENGTH &&
10
+ NAMESPACE_PATTERN.test(namespace));
11
+ }
12
+ export function isValidLogicalId(logicalId) {
13
+ return isVisibleAscii(logicalId, MAX_LOGICAL_ID_LENGTH);
14
+ }
15
+ export function isValidContentDigest(contentDigest) {
16
+ return isVisibleAscii(contentDigest, MAX_CONTENT_DIGEST_LENGTH);
17
+ }
18
+ export function isValidInstant(epochMilliseconds) {
19
+ return (typeof epochMilliseconds === 'number' &&
20
+ Number.isSafeInteger(epochMilliseconds) &&
21
+ !Number.isNaN(new Date(epochMilliseconds).getTime()));
22
+ }
23
+ export function assertNamespace(namespace) {
24
+ if (!isValidNamespace(namespace)) {
25
+ throw new NotificationPlanError('invalid_namespace');
26
+ }
27
+ }
28
+ export function assertIdentity(entry, seen) {
29
+ if (!isValidLogicalId(entry.logicalId)) {
30
+ throw new NotificationPlanError('invalid_logical_id');
31
+ }
32
+ if (!isValidContentDigest(entry.contentDigest)) {
33
+ throw new NotificationPlanError('invalid_content_digest', entry.logicalId);
34
+ }
35
+ if (seen.has(entry.logicalId)) {
36
+ throw new NotificationPlanError('duplicate_logical_id', entry.logicalId);
37
+ }
38
+ seen.add(entry.logicalId);
39
+ }
40
+ export function assertPlannedNotifications(entries) {
41
+ const seen = new Set();
42
+ for (const entry of entries) {
43
+ assertIdentity(entry, seen);
44
+ if (!isValidInstant(entry.epochMilliseconds)) {
45
+ throw new NotificationPlanError('invalid_instant', entry.logicalId);
46
+ }
47
+ }
48
+ }
49
+ export function compareByInstant(left, right) {
50
+ return (left.epochMilliseconds - right.epochMilliseconds ||
51
+ compareCodeUnits(left.logicalId, right.logicalId));
52
+ }
53
+ export function compareCodeUnits(left, right) {
54
+ if (left === right) {
55
+ return 0;
56
+ }
57
+ return left < right ? -1 : 1;
58
+ }
59
+ function isVisibleAscii(value, maxLength) {
60
+ return (typeof value === 'string' && value.length <= maxLength && VISIBLE_ASCII_PATTERN.test(value));
61
+ }
62
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { NotificationPlatformFaults } from './memory.js';
2
+ import type { OwnedNotificationScheduler, OwnedNotificationSchedulerOptions, PendingNotification } from './scheduler.js';
3
+ export interface OwnedNotificationSchedulerHarness<TContent> {
4
+ readonly scheduler: OwnedNotificationScheduler<TContent>;
5
+ readonly faults: NotificationPlatformFaults;
6
+ content(logicalId: string, text: string): TContent;
7
+ addUnrelated(identifier: string): Promise<void> | void;
8
+ pending(): Promise<readonly PendingNotification[]> | readonly PendingNotification[];
9
+ }
10
+ export type OwnedNotificationSchedulerHarnessFactory<TContent> = (options: OwnedNotificationSchedulerOptions) => Promise<OwnedNotificationSchedulerHarness<TContent>> | OwnedNotificationSchedulerHarness<TContent>;
11
+ /** Registers the owned-notification replacement contract in the current Vitest suite. */
12
+ export declare function describeOwnedNotificationSchedulerContract<TContent>(makeHarness: OwnedNotificationSchedulerHarnessFactory<TContent>): void;
13
+ //# sourceMappingURL=vitest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAIV,0BAA0B,EAC1B,iCAAiC,EACjC,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,iCAAiC,CAAC,QAAQ;IACzD,QAAQ,CAAC,SAAS,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IACzD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,OAAO,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,GAAG,SAAS,mBAAmB,EAAE,CAAC;CACrF;AAED,MAAM,MAAM,wCAAwC,CAAC,QAAQ,IAAI,CAC/D,OAAO,EAAE,iCAAiC,KAExC,OAAO,CAAC,iCAAiC,CAAC,QAAQ,CAAC,CAAC,GACpD,iCAAiC,CAAC,QAAQ,CAAC,CAAC;AAYhD,yFAAyF;AACzF,wBAAgB,0CAA0C,CAAC,QAAQ,EACjE,WAAW,EAAE,wCAAwC,CAAC,QAAQ,CAAC,GAC9D,IAAI,CA4SN"}