@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.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +45 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +104 -0
- package/dist/memory.js.map +1 -0
- package/dist/occurrences.d.ts +34 -0
- package/dist/occurrences.d.ts.map +1 -0
- package/dist/occurrences.js +84 -0
- package/dist/occurrences.js.map +1 -0
- package/dist/ownership.d.ts +11 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/ownership.js +49 -0
- package/dist/ownership.js.map +1 -0
- package/dist/plan-vectors.d.ts +57 -0
- package/dist/plan-vectors.d.ts.map +1 -0
- package/dist/plan-vectors.js +108 -0
- package/dist/plan-vectors.js.map +1 -0
- package/dist/replacement-plan.d.ts +11 -0
- package/dist/replacement-plan.d.ts.map +1 -0
- package/dist/replacement-plan.js +28 -0
- package/dist/replacement-plan.js.map +1 -0
- package/dist/scheduler.d.ts +46 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +143 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/validation.d.ts +21 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +62 -0
- package/dist/validation.js.map +1 -0
- package/dist/vitest.d.ts +13 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +261 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Koss
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# `@baukit/notifications-core`
|
|
2
|
+
|
|
3
|
+
`@baukit/notifications-core` plans local notifications and replaces only the ones a product owns.
|
|
4
|
+
It has no runtime dependencies besides `@baukit/localization-core` and never imports a
|
|
5
|
+
notification library. The product decides which occurrences are eligible and what the copy says.
|
|
6
|
+
This package turns them into instants, drops the ones outside the horizon, and computes what to
|
|
7
|
+
keep, cancel, and schedule.
|
|
8
|
+
|
|
9
|
+
## Planning
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
planNotificationReplacement,
|
|
14
|
+
resolveNotificationOccurrences,
|
|
15
|
+
} from '@baukit/notifications-core';
|
|
16
|
+
|
|
17
|
+
const resolution = resolveNotificationOccurrences({
|
|
18
|
+
occurrences: [
|
|
19
|
+
{
|
|
20
|
+
logicalId: 'plan:2026-03-29',
|
|
21
|
+
civilDate: '2026-03-29',
|
|
22
|
+
civilTime: '02:30',
|
|
23
|
+
contentDigest: 'v3',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
logicalId: 'plan:2026-03-30',
|
|
27
|
+
civilDate: '2026-03-30',
|
|
28
|
+
civilTime: '02:30',
|
|
29
|
+
contentDigest: 'v3',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
timeZone: 'Europe/Berlin',
|
|
33
|
+
gap: 'shiftForward',
|
|
34
|
+
fold: 'earlier',
|
|
35
|
+
clock: () => Date.now(),
|
|
36
|
+
horizonDays: 14,
|
|
37
|
+
});
|
|
38
|
+
// resolution.desired: both entries with epochMilliseconds and transition ('gap' for the first)
|
|
39
|
+
|
|
40
|
+
const plan = planNotificationReplacement(current, resolution.desired);
|
|
41
|
+
// plan.keep, plan.cancel, plan.schedule, each sorted by instant, then logical ID
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Each occurrence resolves through `resolveZonedLocalTime` from `@baukit/localization-core`. The
|
|
45
|
+
caller picks the gap and fold policies; neither has a default.
|
|
46
|
+
- The horizon is the civil dates from today in `timeZone` through today plus `horizonDays - 1`.
|
|
47
|
+
`resolution.firstCivilDate` and `resolution.lastCivilDate` report it.
|
|
48
|
+
- `resolution.skipped` lists occurrences with a reason: `past` (the instant is at or before the
|
|
49
|
+
clock), `outside_horizon`, or `nonexistent_local_time` (a gap under `gap: 'reject'`).
|
|
50
|
+
- Extra fields on an occurrence, such as the notification copy, pass through to `desired`.
|
|
51
|
+
- An entry is kept only when its logical ID, instant, and content digest all match. A text change
|
|
52
|
+
under the same digest is kept on purpose. Change the digest, or pass `{ replaceAll: true }`, to
|
|
53
|
+
replace the text.
|
|
54
|
+
- Invalid input throws `NotificationPlanError` with a `code` and, when it applies, the `logicalId`:
|
|
55
|
+
`invalid_civil_date`, `invalid_civil_time`, `invalid_time_zone`, `invalid_horizon`,
|
|
56
|
+
`invalid_clock`, `invalid_logical_id`, `invalid_content_digest`, `invalid_instant`, or
|
|
57
|
+
`duplicate_logical_id`. An invalid date throws even when it falls outside the horizon. An
|
|
58
|
+
unknown policy throws `RangeError`, as in `resolveZonedLocalTime`.
|
|
59
|
+
|
|
60
|
+
The same input and clock give the same result, whatever order the occurrences arrive in. The shared
|
|
61
|
+
vectors in `fixtures/notifications/plan-vectors-v1.json` cover DST gaps and folds, month and year
|
|
62
|
+
changes, travel between zones, duplicates, invalid input, changed content, and horizon boundaries.
|
|
63
|
+
`@baukit/notifications-core/vectors` exports `notificationPlanVectorChecks(fixture)`, the checks the
|
|
64
|
+
Vitest suite runs: each success case yields the expected plan, convergence once the plan is applied,
|
|
65
|
+
and independence from input order. `examples/expo-notifications-conformance` runs them inside
|
|
66
|
+
Hermes.
|
|
67
|
+
|
|
68
|
+
## Owned replacement
|
|
69
|
+
|
|
70
|
+
`createOwnedNotificationScheduler(platform, { pendingLimit })` runs a plan against a
|
|
71
|
+
`NotificationPlatform<TContent>` port with four calls: `list`, `cancel`, `permission`, and
|
|
72
|
+
`schedule`. `@baukit/notifications-expo` is the Expo implementation.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const outcome = await scheduler.replaceOwned({ namespace: 'reminders' }, desired);
|
|
76
|
+
// { status: 'complete' | 'incomplete' | 'superseded', kept, cancelled, scheduled, failures }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- A notification belongs to a namespace when it carries a valid marker for that namespace and its
|
|
80
|
+
identifier is `baukit:<namespace>:<logicalId>`. Everything else is left alone, including other
|
|
81
|
+
Baukit namespaces and requests with a copied marker.
|
|
82
|
+
- The scheduler cancels one request at a time and never cancels everything. An item whose cancel
|
|
83
|
+
failed is not rescheduled in the same run.
|
|
84
|
+
- It checks permission only when something needs scheduling. Without permission it still cancels
|
|
85
|
+
stale entries.
|
|
86
|
+
- It schedules in instant order and stops at `pendingLimit`, which counts every pending request on
|
|
87
|
+
the device, not only this owner's.
|
|
88
|
+
- Failures carry a code and a logical ID, never content: `list_failed`, `cancel_failed`,
|
|
89
|
+
`permission_denied`, `permission_failed`, `schedule_failed`, `schedule_limit`. Any failure makes
|
|
90
|
+
the outcome `incomplete`. Calling `replaceOwned` again with the same set converges.
|
|
91
|
+
- One replacement runs per namespace at a time. While it runs, later calls for that namespace
|
|
92
|
+
collapse to the newest. The ones that never ran resolve as `superseded`. Namespaces do not wait
|
|
93
|
+
for each other. Share one scheduler per device so this works.
|
|
94
|
+
- An invalid namespace (lowercase letters and digits separated by `.` or `-`, at most 64
|
|
95
|
+
characters) or an invalid desired set rejects with `NotificationPlanError` before the platform is
|
|
96
|
+
touched.
|
|
97
|
+
|
|
98
|
+
## Conformance suite
|
|
99
|
+
|
|
100
|
+
`@baukit/notifications-core/vitest` exports `describeOwnedNotificationSchedulerContract`. Run it
|
|
101
|
+
against any platform implementation; it needs the consumer's Vitest installation.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import {
|
|
105
|
+
InMemoryNotificationPlatform,
|
|
106
|
+
createOwnedNotificationScheduler,
|
|
107
|
+
} from '@baukit/notifications-core';
|
|
108
|
+
import { describeOwnedNotificationSchedulerContract } from '@baukit/notifications-core/vitest';
|
|
109
|
+
|
|
110
|
+
describeOwnedNotificationSchedulerContract<string>((options) => {
|
|
111
|
+
const platform = new InMemoryNotificationPlatform<string>();
|
|
112
|
+
return {
|
|
113
|
+
scheduler: createOwnedNotificationScheduler(platform, options),
|
|
114
|
+
faults: platform.faults,
|
|
115
|
+
content: (_logicalId, text) => text,
|
|
116
|
+
addUnrelated: (identifier) => platform.addUnrelated(identifier),
|
|
117
|
+
pending: () => platform.pending(),
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`NotificationPlatformFaultState` holds the injected faults (failed list, cancel, schedule and
|
|
123
|
+
permission calls, a revoked permission, a held list), so a mocked native module can reuse it.
|
|
124
|
+
|
|
125
|
+
## Boundaries
|
|
126
|
+
|
|
127
|
+
The package does not pick reminder times, quiet hours, or copy, and it does not request permission.
|
|
128
|
+
Products compute eligible occurrences and the content digest, ask for permission in their own UX, and
|
|
129
|
+
choose the pending limit for their platform. It plans one time zone per call.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type NotificationPlanErrorCode = 'invalid_civil_date' | 'invalid_civil_time' | 'invalid_time_zone' | 'invalid_horizon' | 'invalid_clock' | 'invalid_namespace' | 'invalid_logical_id' | 'invalid_content_digest' | 'invalid_instant' | 'invalid_pending_limit' | 'duplicate_logical_id' | 'reserved_data_key';
|
|
2
|
+
export declare class NotificationPlanError extends Error {
|
|
3
|
+
readonly name = "NotificationPlanError";
|
|
4
|
+
readonly code: NotificationPlanErrorCode;
|
|
5
|
+
readonly logicalId: string | undefined;
|
|
6
|
+
constructor(code: NotificationPlanErrorCode, logicalId?: string);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,yBAAyB,GACjC,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,eAAe,GACf,mBAAmB,GACnB,oBAAoB,GACpB,wBAAwB,GACxB,iBAAiB,GACjB,uBAAuB,GACvB,sBAAsB,GACtB,mBAAmB,CAAC;AAExB,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;IACjD,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE3B,IAAI,EAAE,yBAAyB,EAAE,SAAS,CAAC,EAAE,MAAM;CAKhE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class NotificationPlanError extends Error {
|
|
2
|
+
name = 'NotificationPlanError';
|
|
3
|
+
code;
|
|
4
|
+
logicalId;
|
|
5
|
+
constructor(code, logicalId) {
|
|
6
|
+
super(logicalId === undefined ? code : `${code}: ${logicalId}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.logicalId = logicalId;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAcA,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5B,IAAI,GAAG,uBAAuB,CAAC;IACxC,IAAI,CAA4B;IAChC,SAAS,CAAqB;IAEvC,YAAY,IAA+B,EAAE,SAAkB;QAC7D,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,SAAS,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NotificationPlanError, type NotificationPlanErrorCode } from './errors.js';
|
|
2
|
+
export { InMemoryNotificationPlatform, NotificationPlatformFaultState, type NotificationPlatformFaults, type StoredNotification, } from './memory.js';
|
|
3
|
+
export { resolveNotificationOccurrences, type NotificationClock, type NotificationOccurrence, type NotificationOccurrenceInput, type NotificationOccurrenceResolution, type ResolvedNotification, type SkippedOccurrence, type SkippedOccurrenceReason, } from './occurrences.js';
|
|
4
|
+
export { decodeOwnedNotificationMarker, encodeOwnedNotificationMarker, isOwnedBy, OWNED_NOTIFICATION_DATA_KEY, OWNED_NOTIFICATION_MARKER_VERSION, ownedNotificationIdentifier, type OwnedNotificationMarker, } from './ownership.js';
|
|
5
|
+
export { planNotificationReplacement, type NotificationReplacementOptions, type NotificationReplacementPlan, } from './replacement-plan.js';
|
|
6
|
+
export { createOwnedNotificationScheduler, type NotificationOwner, type NotificationPermission, type NotificationPlatform, type OwnedNotification, type OwnedNotificationFailure, type OwnedNotificationFailureCode, type OwnedNotificationReplacementOutcome, type OwnedNotificationReplacementStatus, type OwnedNotificationScheduler, type OwnedNotificationSchedulerOptions, type OwnedNotificationScheduleRequest, type PendingNotification, } from './scheduler.js';
|
|
7
|
+
export { isValidContentDigest, isValidLogicalId, isValidNamespace, MAX_CONTENT_DIGEST_LENGTH, MAX_LOGICAL_ID_LENGTH, MAX_NAMESPACE_LENGTH, type PlannedNotification, } from './validation.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,KAAK,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,EAC9B,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,8BAA8B,EAC9B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAC7B,SAAS,EACT,2BAA2B,EAC3B,iCAAiC,EACjC,2BAA2B,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,2BAA2B,EAC3B,KAAK,8BAA8B,EACnC,KAAK,2BAA2B,GACjC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,gCAAgC,EAChC,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,mCAAmC,EACxC,KAAK,kCAAkC,EACvC,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACtC,KAAK,gCAAgC,EACrC,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NotificationPlanError } from './errors.js';
|
|
2
|
+
export { InMemoryNotificationPlatform, NotificationPlatformFaultState, } from './memory.js';
|
|
3
|
+
export { resolveNotificationOccurrences, } from './occurrences.js';
|
|
4
|
+
export { decodeOwnedNotificationMarker, encodeOwnedNotificationMarker, isOwnedBy, OWNED_NOTIFICATION_DATA_KEY, OWNED_NOTIFICATION_MARKER_VERSION, ownedNotificationIdentifier, } from './ownership.js';
|
|
5
|
+
export { planNotificationReplacement, } from './replacement-plan.js';
|
|
6
|
+
export { createOwnedNotificationScheduler, } from './scheduler.js';
|
|
7
|
+
export { isValidContentDigest, isValidLogicalId, isValidNamespace, MAX_CONTENT_DIGEST_LENGTH, MAX_LOGICAL_ID_LENGTH, MAX_NAMESPACE_LENGTH, } from './validation.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAkC,MAAM,aAAa,CAAC;AACpF,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,GAG/B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,8BAA8B,GAQ/B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAC7B,SAAS,EACT,2BAA2B,EAC3B,iCAAiC,EACjC,2BAA2B,GAE5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,2BAA2B,GAG5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,gCAAgC,GAajC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC"}
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { NotificationPermission, NotificationPlatform, OwnedNotificationScheduleRequest, PendingNotification } from './scheduler.js';
|
|
2
|
+
export interface NotificationPlatformFaults {
|
|
3
|
+
setPermission(permission: NotificationPermission): void;
|
|
4
|
+
failNextList(): void;
|
|
5
|
+
failNextPermission(): void;
|
|
6
|
+
failCancel(identifier: string): void;
|
|
7
|
+
failSchedule(identifier: string): void;
|
|
8
|
+
holdNextList(): () => void;
|
|
9
|
+
afterNextList(action: () => void): void;
|
|
10
|
+
}
|
|
11
|
+
export interface StoredNotification<TContent> extends PendingNotification {
|
|
12
|
+
readonly content: TContent | null;
|
|
13
|
+
}
|
|
14
|
+
export declare class NotificationPlatformFaultState implements NotificationPlatformFaults {
|
|
15
|
+
permission: NotificationPermission;
|
|
16
|
+
private listFailures;
|
|
17
|
+
private permissionFailures;
|
|
18
|
+
private readonly cancelFailures;
|
|
19
|
+
private readonly scheduleFailures;
|
|
20
|
+
private readonly listHolds;
|
|
21
|
+
private readonly listActions;
|
|
22
|
+
setPermission(permission: NotificationPermission): void;
|
|
23
|
+
failNextList(): void;
|
|
24
|
+
failNextPermission(): void;
|
|
25
|
+
failCancel(identifier: string): void;
|
|
26
|
+
failSchedule(identifier: string): void;
|
|
27
|
+
holdNextList(): () => void;
|
|
28
|
+
afterNextList(action: () => void): void;
|
|
29
|
+
beforeList(): Promise<void>;
|
|
30
|
+
afterList(): void;
|
|
31
|
+
beforePermission(): void;
|
|
32
|
+
beforeCancel(identifier: string): void;
|
|
33
|
+
beforeSchedule(identifier: string): void;
|
|
34
|
+
}
|
|
35
|
+
export declare class InMemoryNotificationPlatform<TContent> implements NotificationPlatform<TContent> {
|
|
36
|
+
readonly faults: NotificationPlatformFaultState;
|
|
37
|
+
private readonly requests;
|
|
38
|
+
addUnrelated(identifier: string): void;
|
|
39
|
+
pending(): readonly StoredNotification<TContent>[];
|
|
40
|
+
list(): Promise<readonly PendingNotification[]>;
|
|
41
|
+
cancel(identifier: string): Promise<void>;
|
|
42
|
+
permission(): Promise<NotificationPermission>;
|
|
43
|
+
schedule(request: OwnedNotificationScheduleRequest<TContent>): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,gCAAgC,EAChC,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACxD,YAAY,IAAI,IAAI,CAAC;IACrB,kBAAkB,IAAI,IAAI,CAAC;IAC3B,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,YAAY,IAAI,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB,CAAC,QAAQ,CAAE,SAAQ,mBAAmB;IACvE,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CACnC;AAED,qBAAa,8BAA+B,YAAW,0BAA0B;IAC/E,UAAU,EAAE,sBAAsB,CAAa;IAC/C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAElD,aAAa,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI;IAIvD,YAAY,IAAI,IAAI;IAIpB,kBAAkB,IAAI,IAAI;IAI1B,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIpC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAItC,YAAY,IAAI,MAAM,IAAI;IAU1B,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI;IAIjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,SAAS,IAAI,IAAI;IAIjB,gBAAgB,IAAI,IAAI;IAOxB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMtC,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAKzC;AAED,qBAAa,4BAA4B,CAAC,QAAQ,CAAE,YAAW,oBAAoB,CAAC,QAAQ,CAAC;IAC3F,QAAQ,CAAC,MAAM,iCAAwC;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmD;IAE5E,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAItC,OAAO,IAAI,SAAS,kBAAkB,CAAC,QAAQ,CAAC,EAAE;IAM5C,IAAI,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC;IAOrD,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzC,UAAU,IAAI,OAAO,CAAC,sBAAsB,CAAC;IAO7C,QAAQ,CAAC,OAAO,EAAE,gCAAgC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAU7E"}
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export class NotificationPlatformFaultState {
|
|
2
|
+
permission = 'granted';
|
|
3
|
+
listFailures = 0;
|
|
4
|
+
permissionFailures = 0;
|
|
5
|
+
cancelFailures = new Set();
|
|
6
|
+
scheduleFailures = new Set();
|
|
7
|
+
listHolds = [];
|
|
8
|
+
listActions = [];
|
|
9
|
+
setPermission(permission) {
|
|
10
|
+
this.permission = permission;
|
|
11
|
+
}
|
|
12
|
+
failNextList() {
|
|
13
|
+
this.listFailures += 1;
|
|
14
|
+
}
|
|
15
|
+
failNextPermission() {
|
|
16
|
+
this.permissionFailures += 1;
|
|
17
|
+
}
|
|
18
|
+
failCancel(identifier) {
|
|
19
|
+
this.cancelFailures.add(identifier);
|
|
20
|
+
}
|
|
21
|
+
failSchedule(identifier) {
|
|
22
|
+
this.scheduleFailures.add(identifier);
|
|
23
|
+
}
|
|
24
|
+
holdNextList() {
|
|
25
|
+
let release = () => undefined;
|
|
26
|
+
this.listHolds.push(new Promise((resolve) => {
|
|
27
|
+
release = resolve;
|
|
28
|
+
}));
|
|
29
|
+
return release;
|
|
30
|
+
}
|
|
31
|
+
afterNextList(action) {
|
|
32
|
+
this.listActions.push(action);
|
|
33
|
+
}
|
|
34
|
+
async beforeList() {
|
|
35
|
+
await this.listHolds.shift();
|
|
36
|
+
if (this.listFailures > 0) {
|
|
37
|
+
this.listFailures -= 1;
|
|
38
|
+
throw new Error('list failed');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
afterList() {
|
|
42
|
+
this.listActions.shift()?.();
|
|
43
|
+
}
|
|
44
|
+
beforePermission() {
|
|
45
|
+
if (this.permissionFailures > 0) {
|
|
46
|
+
this.permissionFailures -= 1;
|
|
47
|
+
throw new Error('permission check failed');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
beforeCancel(identifier) {
|
|
51
|
+
if (this.cancelFailures.delete(identifier)) {
|
|
52
|
+
throw new Error('cancel failed');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
beforeSchedule(identifier) {
|
|
56
|
+
if (this.scheduleFailures.delete(identifier)) {
|
|
57
|
+
throw new Error('schedule failed');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class InMemoryNotificationPlatform {
|
|
62
|
+
faults = new NotificationPlatformFaultState();
|
|
63
|
+
requests = new Map();
|
|
64
|
+
addUnrelated(identifier) {
|
|
65
|
+
this.requests.set(identifier, { identifier, marker: null, content: null });
|
|
66
|
+
}
|
|
67
|
+
pending() {
|
|
68
|
+
return [...this.requests.values()].sort((left, right) => left.identifier < right.identifier ? -1 : 1);
|
|
69
|
+
}
|
|
70
|
+
async list() {
|
|
71
|
+
await this.faults.beforeList();
|
|
72
|
+
const snapshot = this.pending().map(({ identifier, marker }) => ({ identifier, marker }));
|
|
73
|
+
this.faults.afterList();
|
|
74
|
+
return snapshot;
|
|
75
|
+
}
|
|
76
|
+
cancel(identifier) {
|
|
77
|
+
return settle(() => {
|
|
78
|
+
this.faults.beforeCancel(identifier);
|
|
79
|
+
this.requests.delete(identifier);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
permission() {
|
|
83
|
+
return settle(() => {
|
|
84
|
+
this.faults.beforePermission();
|
|
85
|
+
return this.faults.permission;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
schedule(request) {
|
|
89
|
+
return settle(() => {
|
|
90
|
+
this.faults.beforeSchedule(request.identifier);
|
|
91
|
+
this.requests.set(request.identifier, {
|
|
92
|
+
identifier: request.identifier,
|
|
93
|
+
marker: request.marker,
|
|
94
|
+
content: request.content,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function settle(action) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
resolve(action());
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type FoldPolicy, type GapPolicy, type LocalTimeTransition } from '@baukit/localization-core';
|
|
2
|
+
export interface NotificationOccurrence {
|
|
3
|
+
readonly logicalId: string;
|
|
4
|
+
readonly civilDate: string;
|
|
5
|
+
readonly civilTime: string;
|
|
6
|
+
readonly contentDigest: string;
|
|
7
|
+
}
|
|
8
|
+
export type NotificationClock = () => number;
|
|
9
|
+
export interface NotificationOccurrenceInput<T extends NotificationOccurrence> {
|
|
10
|
+
readonly occurrences: readonly T[];
|
|
11
|
+
readonly timeZone: string;
|
|
12
|
+
readonly gap: GapPolicy;
|
|
13
|
+
readonly fold: FoldPolicy;
|
|
14
|
+
readonly clock: NotificationClock;
|
|
15
|
+
readonly horizonDays: number;
|
|
16
|
+
}
|
|
17
|
+
export type ResolvedNotification<T extends NotificationOccurrence = NotificationOccurrence> = T & {
|
|
18
|
+
readonly epochMilliseconds: number;
|
|
19
|
+
readonly transition: LocalTimeTransition;
|
|
20
|
+
};
|
|
21
|
+
export type SkippedOccurrenceReason = 'past' | 'outside_horizon' | 'nonexistent_local_time';
|
|
22
|
+
export interface SkippedOccurrence {
|
|
23
|
+
readonly logicalId: string;
|
|
24
|
+
readonly reason: SkippedOccurrenceReason;
|
|
25
|
+
}
|
|
26
|
+
export interface NotificationOccurrenceResolution<T extends NotificationOccurrence> {
|
|
27
|
+
readonly now: number;
|
|
28
|
+
readonly firstCivilDate: string;
|
|
29
|
+
readonly lastCivilDate: string;
|
|
30
|
+
readonly desired: readonly ResolvedNotification<T>[];
|
|
31
|
+
readonly skipped: readonly SkippedOccurrence[];
|
|
32
|
+
}
|
|
33
|
+
export declare function resolveNotificationOccurrences<T extends NotificationOccurrence>(input: NotificationOccurrenceInput<T>): NotificationOccurrenceResolution<T>;
|
|
34
|
+
//# sourceMappingURL=occurrences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"occurrences.d.ts","sourceRoot":"","sources":["../src/occurrences.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,mBAAmB,EAEzB,MAAM,2BAA2B,CAAC;AAKnC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,MAAM,CAAC;AAE7C,MAAM,WAAW,2BAA2B,CAAC,CAAC,SAAS,sBAAsB;IAC3E,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,sBAAsB,GAAG,sBAAsB,IAAI,CAAC,GAAG;IAChG,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,iBAAiB,GAAG,wBAAwB,CAAC;AAE5F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;CAC1C;AAED,MAAM,WAAW,gCAAgC,CAAC,CAAC,SAAS,sBAAsB;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAWD,wBAAgB,8BAA8B,CAAC,CAAC,SAAS,sBAAsB,EAC7E,KAAK,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACpC,gCAAgC,CAAC,CAAC,CAAC,CAgCrC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { addCivilDays, civilDateForInstant, compareCivilDates, INVALID_TIME_ZONE_CODE, NONEXISTENT_LOCAL_TIME_CODE, resolveZonedLocalTime, } from '@baukit/localization-core';
|
|
2
|
+
import { NotificationPlanError } from './errors.js';
|
|
3
|
+
import { assertIdentity, compareByInstant, compareCodeUnits } from './validation.js';
|
|
4
|
+
const TIME_ZONE_PROBE_DATE = '2000-01-01';
|
|
5
|
+
const TIME_ZONE_PROBE_TIME = '12:00';
|
|
6
|
+
export function resolveNotificationOccurrences(input) {
|
|
7
|
+
const window = horizonWindow(input);
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const desired = [];
|
|
10
|
+
const skipped = [];
|
|
11
|
+
for (const occurrence of input.occurrences) {
|
|
12
|
+
assertIdentity(occurrence, seen);
|
|
13
|
+
const result = resolveZonedLocalTime({
|
|
14
|
+
civilDate: occurrence.civilDate,
|
|
15
|
+
civilTime: occurrence.civilTime,
|
|
16
|
+
timeZone: input.timeZone,
|
|
17
|
+
gap: input.gap,
|
|
18
|
+
fold: input.fold,
|
|
19
|
+
});
|
|
20
|
+
const reason = skipReason(occurrence, result, window);
|
|
21
|
+
if (reason !== null) {
|
|
22
|
+
skipped.push({ logicalId: occurrence.logicalId, reason });
|
|
23
|
+
}
|
|
24
|
+
else if (result.ok) {
|
|
25
|
+
desired.push({
|
|
26
|
+
...occurrence,
|
|
27
|
+
epochMilliseconds: result.epochMilliseconds,
|
|
28
|
+
transition: result.transition,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...window,
|
|
34
|
+
desired: desired.sort(compareByInstant),
|
|
35
|
+
skipped: skipped.sort((left, right) => compareCodeUnits(left.logicalId, right.logicalId)),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function horizonWindow(input) {
|
|
39
|
+
assertTimeZoneAndPolicies(input);
|
|
40
|
+
if (!Number.isSafeInteger(input.horizonDays) || input.horizonDays < 1) {
|
|
41
|
+
throw new NotificationPlanError('invalid_horizon');
|
|
42
|
+
}
|
|
43
|
+
const now = input.clock();
|
|
44
|
+
if (!Number.isFinite(now) || Number.isNaN(new Date(now).getTime())) {
|
|
45
|
+
throw new NotificationPlanError('invalid_clock');
|
|
46
|
+
}
|
|
47
|
+
const firstCivilDate = civilDateForInstant(now, input.timeZone);
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
now,
|
|
51
|
+
firstCivilDate,
|
|
52
|
+
lastCivilDate: addCivilDays(firstCivilDate, input.horizonDays - 1),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new NotificationPlanError('invalid_horizon');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function assertTimeZoneAndPolicies(input) {
|
|
60
|
+
const probe = resolveZonedLocalTime({
|
|
61
|
+
civilDate: TIME_ZONE_PROBE_DATE,
|
|
62
|
+
civilTime: TIME_ZONE_PROBE_TIME,
|
|
63
|
+
timeZone: input.timeZone,
|
|
64
|
+
gap: input.gap,
|
|
65
|
+
fold: input.fold,
|
|
66
|
+
});
|
|
67
|
+
if (!probe.ok && probe.code === INVALID_TIME_ZONE_CODE) {
|
|
68
|
+
throw new NotificationPlanError('invalid_time_zone');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function skipReason(occurrence, result, window) {
|
|
72
|
+
if (!result.ok && result.code !== NONEXISTENT_LOCAL_TIME_CODE) {
|
|
73
|
+
throw new NotificationPlanError(result.code, occurrence.logicalId);
|
|
74
|
+
}
|
|
75
|
+
if (compareCivilDates(occurrence.civilDate, window.firstCivilDate) < 0 ||
|
|
76
|
+
compareCivilDates(occurrence.civilDate, window.lastCivilDate) > 0) {
|
|
77
|
+
return 'outside_horizon';
|
|
78
|
+
}
|
|
79
|
+
if (!result.ok) {
|
|
80
|
+
return 'nonexistent_local_time';
|
|
81
|
+
}
|
|
82
|
+
return result.epochMilliseconds <= window.now ? 'past' : null;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=occurrences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"occurrences.js","sourceRoot":"","sources":["../src/occurrences.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PlannedNotification } from './validation.js';
|
|
2
|
+
export declare const OWNED_NOTIFICATION_DATA_KEY = "baukitNotification";
|
|
3
|
+
export declare const OWNED_NOTIFICATION_MARKER_VERSION = 1;
|
|
4
|
+
export interface OwnedNotificationMarker extends PlannedNotification {
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function ownedNotificationIdentifier(namespace: string, logicalId: string): string;
|
|
8
|
+
export declare function encodeOwnedNotificationMarker(marker: OwnedNotificationMarker): string;
|
|
9
|
+
export declare function decodeOwnedNotificationMarker(value: unknown): OwnedNotificationMarker | null;
|
|
10
|
+
export declare function isOwnedBy(namespace: string, identifier: string, marker: OwnedNotificationMarker | null): marker is OwnedNotificationMarker;
|
|
11
|
+
//# sourceMappingURL=ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../src/ownership.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,eAAO,MAAM,2BAA2B,uBAAuB,CAAC;AAChE,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAKnD,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,uBAAuB,GAAG,MAAM,CAQrF;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAyB5F;AAED,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,uBAAuB,GAAG,IAAI,GACrC,MAAM,IAAI,uBAAuB,CAMnC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { assertNamespace, isValidContentDigest, isValidInstant, isValidLogicalId, isValidNamespace, } from './validation.js';
|
|
2
|
+
export const OWNED_NOTIFICATION_DATA_KEY = 'baukitNotification';
|
|
3
|
+
export const OWNED_NOTIFICATION_MARKER_VERSION = 1;
|
|
4
|
+
const IDENTIFIER_PREFIX = 'baukit';
|
|
5
|
+
const IDENTIFIER_SEPARATOR = ':';
|
|
6
|
+
export function ownedNotificationIdentifier(namespace, logicalId) {
|
|
7
|
+
assertNamespace(namespace);
|
|
8
|
+
return [IDENTIFIER_PREFIX, namespace, logicalId].join(IDENTIFIER_SEPARATOR);
|
|
9
|
+
}
|
|
10
|
+
export function encodeOwnedNotificationMarker(marker) {
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
version: OWNED_NOTIFICATION_MARKER_VERSION,
|
|
13
|
+
namespace: marker.namespace,
|
|
14
|
+
logicalId: marker.logicalId,
|
|
15
|
+
epochMilliseconds: marker.epochMilliseconds,
|
|
16
|
+
contentDigest: marker.contentDigest,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function decodeOwnedNotificationMarker(value) {
|
|
20
|
+
if (typeof value !== 'string') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(value);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const record = parsed;
|
|
34
|
+
const { namespace, logicalId, epochMilliseconds, contentDigest } = record;
|
|
35
|
+
if (record['version'] !== OWNED_NOTIFICATION_MARKER_VERSION ||
|
|
36
|
+
!isValidNamespace(namespace) ||
|
|
37
|
+
!isValidLogicalId(logicalId) ||
|
|
38
|
+
!isValidInstant(epochMilliseconds) ||
|
|
39
|
+
!isValidContentDigest(contentDigest)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return { namespace, logicalId, epochMilliseconds, contentDigest };
|
|
43
|
+
}
|
|
44
|
+
export function isOwnedBy(namespace, identifier, marker) {
|
|
45
|
+
return (marker !== null &&
|
|
46
|
+
marker.namespace === namespace &&
|
|
47
|
+
identifier === ownedNotificationIdentifier(namespace, marker.logicalId));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.js","sourceRoot":"","sources":["../src/ownership.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAChE,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AACnC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAMjC,MAAM,UAAU,2BAA2B,CAAC,SAAiB,EAAE,SAAiB;IAC9E,eAAe,CAAC,SAAS,CAAC,CAAC;IAC3B,OAAO,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,MAA+B;IAC3E,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,OAAO,EAAE,iCAAiC;QAC1C,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,KAAc;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAC1E,IACE,MAAM,CAAC,SAAS,CAAC,KAAK,iCAAiC;QACvD,CAAC,gBAAgB,CAAC,SAAS,CAAC;QAC5B,CAAC,gBAAgB,CAAC,SAAS,CAAC;QAC5B,CAAC,cAAc,CAAC,iBAAiB,CAAC;QAClC,CAAC,oBAAoB,CAAC,aAAa,CAAC,EACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,SAAiB,EACjB,UAAkB,EAClB,MAAsC;IAEtC,OAAO,CACL,MAAM,KAAK,IAAI;QACf,MAAM,CAAC,SAAS,KAAK,SAAS;QAC9B,UAAU,KAAK,2BAA2B,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,CACxE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { FoldPolicy, GapPolicy, LocalTimeTransition } from '@baukit/localization-core';
|
|
2
|
+
import { type NotificationOccurrence, type SkippedOccurrence } from './occurrences.js';
|
|
3
|
+
export interface NotificationPlanVectorCurrent {
|
|
4
|
+
readonly logicalId: string;
|
|
5
|
+
readonly instant: string;
|
|
6
|
+
readonly contentDigest: string;
|
|
7
|
+
}
|
|
8
|
+
export interface NotificationPlanVectorInput {
|
|
9
|
+
readonly timeZone: string;
|
|
10
|
+
readonly gap: GapPolicy;
|
|
11
|
+
readonly fold: FoldPolicy;
|
|
12
|
+
readonly now: string;
|
|
13
|
+
readonly horizonDays: number;
|
|
14
|
+
readonly replaceAll?: boolean;
|
|
15
|
+
readonly occurrences: readonly NotificationOccurrence[];
|
|
16
|
+
readonly current: readonly NotificationPlanVectorCurrent[];
|
|
17
|
+
}
|
|
18
|
+
export interface NotificationPlanVectorResult {
|
|
19
|
+
readonly firstCivilDate: string;
|
|
20
|
+
readonly lastCivilDate: string;
|
|
21
|
+
readonly schedule: readonly {
|
|
22
|
+
readonly logicalId: string;
|
|
23
|
+
readonly instant: string;
|
|
24
|
+
readonly transition: LocalTimeTransition;
|
|
25
|
+
}[];
|
|
26
|
+
readonly keep: readonly string[];
|
|
27
|
+
readonly cancel: readonly string[];
|
|
28
|
+
readonly skipped: readonly SkippedOccurrence[];
|
|
29
|
+
}
|
|
30
|
+
export interface NotificationPlanVectorError {
|
|
31
|
+
readonly error: {
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly logicalId?: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface NotificationPlanVectorCase {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly input: NotificationPlanVectorInput;
|
|
39
|
+
readonly expected: NotificationPlanVectorResult | NotificationPlanVectorError;
|
|
40
|
+
}
|
|
41
|
+
export interface NotificationPlanVectorFixture {
|
|
42
|
+
readonly version: number;
|
|
43
|
+
readonly cases: readonly NotificationPlanVectorCase[];
|
|
44
|
+
}
|
|
45
|
+
/** One vector: `actual()` must deep-equal `expected` (`undefined` fields count as absent). */
|
|
46
|
+
export interface NotificationPlanVectorCheck {
|
|
47
|
+
readonly label: string;
|
|
48
|
+
readonly expected: unknown;
|
|
49
|
+
readonly actual: () => unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Expands `fixtures/notifications/plan-vectors-v1.json` into checks. Each success case yields
|
|
53
|
+
* three: the expected plan, convergence once the plan is applied, and independence from input
|
|
54
|
+
* order. Each error case yields one check of the thrown `NotificationPlanError` code and ID.
|
|
55
|
+
*/
|
|
56
|
+
export declare function notificationPlanVectorChecks(fixture: NotificationPlanVectorFixture): NotificationPlanVectorCheck[];
|
|
57
|
+
//# sourceMappingURL=plan-vectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-vectors.d.ts","sourceRoot":"","sources":["../src/plan-vectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAG5F,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAI1B,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,SAAS,6BAA6B,EAAE,CAAC;CAC5D;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;KAC1C,EAAE,CAAC;IACJ,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,2BAA2B,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,4BAA4B,GAAG,2BAA2B,CAAC;CAC/E;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,0BAA0B,EAAE,CAAC;CACvD;AAED,8FAA8F;AAC9F,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC;CAChC;AAQD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,2BAA2B,EAAE,CAI/B"}
|