@upnextapp/core 0.17.2 → 0.19.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/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/scheduling.d.ts +0 -1
- package/dist/scheduling.js +11 -4
- package/dist/types.d.ts +4 -0
- package/dist/writeAuthority.d.ts +9 -0
- package/dist/writeAuthority.js +42 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export type { ReminderStatus, ScheduledReminder, RecurrenceType, ResponsibilityDef, AppSettings, StoreSchema } from './types.js';
|
|
2
|
-
export {
|
|
1
|
+
export type { ReminderStatus, ScheduledReminder, RecurrenceType, ResponsibilityDef, AppSettings, StoreSchema, WriteKind } from './types.js';
|
|
2
|
+
export { authorityOverride } from './writeAuthority.js';
|
|
3
|
+
export type { AuthorityRow } from './writeAuthority.js';
|
|
4
|
+
export { calculateNextTrigger, calculateNextTriggerFromNow, firstSlotForDay, isWithinActiveHours, matchesRecurrenceInterval, shouldTriggerOnDay, isStaleActive, isOccurrenceOverdue, STALE_ACTIVE_THRESHOLD_MS } from './scheduling.js';
|
|
3
5
|
export { isTimeOfDayOutsideActiveHours } from './activeHours.js';
|
|
4
6
|
export { coarsenIdleTime, IDLE_TIME_BUCKET_SECONDS } from './coarsenIdleTime.js';
|
|
5
7
|
export { formatNextTrigger } from './formatNextTrigger.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { authorityOverride } from './writeAuthority.js';
|
|
2
|
+
export { calculateNextTrigger, calculateNextTriggerFromNow, firstSlotForDay, isWithinActiveHours, matchesRecurrenceInterval, shouldTriggerOnDay, isStaleActive, isOccurrenceOverdue, STALE_ACTIVE_THRESHOLD_MS } from './scheduling.js';
|
|
2
3
|
export { isTimeOfDayOutsideActiveHours } from './activeHours.js';
|
|
3
4
|
export { coarsenIdleTime, IDLE_TIME_BUCKET_SECONDS } from './coarsenIdleTime.js';
|
|
4
5
|
export { formatNextTrigger } from './formatNextTrigger.js';
|
package/dist/scheduling.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ResponsibilityDef, type ScheduledReminder } from './types.js';
|
|
2
2
|
export declare const STALE_ACTIVE_THRESHOLD_MS: number;
|
|
3
|
-
export declare const HOURLY_STALE_THRESHOLD_MS: number;
|
|
4
3
|
export declare function shouldTriggerOnDay(def: ResponsibilityDef, date: Date): boolean;
|
|
5
4
|
export declare function isWithinActiveHours(now: number, startTime: string, endTime: string): boolean;
|
|
6
5
|
export declare function matchesRecurrenceInterval(def: ResponsibilityDef, day: Date, startDate: Date, anchorDayStartMs: number): boolean;
|
package/dist/scheduling.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export const STALE_ACTIVE_THRESHOLD_MS = 48 * 60 * 60 * 1000;
|
|
2
|
-
export const HOURLY_STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
3
2
|
export function shouldTriggerOnDay(def, date) {
|
|
4
3
|
if (!def.daysOfWeek || def.daysOfWeek.length === 0)
|
|
5
4
|
return true;
|
|
@@ -173,10 +172,18 @@ export function calculateNextTriggerFromNow(def, fromTime, activeStart, activeEn
|
|
|
173
172
|
return calculateNextTrigger(def, fromTime, activeStart, activeEnd);
|
|
174
173
|
}
|
|
175
174
|
export function isStaleActive(reminder, now, def) {
|
|
176
|
-
|
|
175
|
+
// Single 48h threshold for every recurrence type, aligned with the server's P3
|
|
176
|
+
// progression floor (sync_lww_guard) and writeAuthority.ts — the sweep fires only
|
|
177
|
+
// when the server would accept the dismissal, so attempts succeed first try.
|
|
178
|
+
// Keyed to activated_at — the alive-as-active clock — because updated_at is
|
|
179
|
+
// re-stamped by every plugin tick, which starved the sweep on ticking rows (Obs 3).
|
|
180
|
+
// Legacy rows with a null activated_at fall back to updated_at (server COALESCE).
|
|
181
|
+
// def is retained for signature compatibility.
|
|
182
|
+
void def;
|
|
183
|
+
const aliveAsActiveAt = reminder.activatedAt ?? reminder.updatedAt;
|
|
184
|
+
if (aliveAsActiveAt == null)
|
|
177
185
|
return true;
|
|
178
|
-
|
|
179
|
-
return (now - reminder.activatedAt) > threshold;
|
|
186
|
+
return (now - aliveAsActiveAt) > STALE_ACTIVE_THRESHOLD_MS;
|
|
180
187
|
}
|
|
181
188
|
export function isOccurrenceOverdue(def, reminder, now, activeStart, activeEnd) {
|
|
182
189
|
// Auto-advance only when the next due slot is on a NEW calendar day than the
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type ReminderStatus = 'scheduled' | 'active' | 'delayed' | 'completed';
|
|
2
|
+
export type WriteKind = 'user' | 'auto';
|
|
2
3
|
export interface ScheduledReminder {
|
|
3
4
|
defId: string;
|
|
4
5
|
status: ReminderStatus;
|
|
@@ -7,6 +8,8 @@ export interface ScheduledReminder {
|
|
|
7
8
|
delayUntil?: number;
|
|
8
9
|
lastCompletedAt?: number;
|
|
9
10
|
pluginData?: Record<string, unknown>;
|
|
11
|
+
writeKind?: WriteKind;
|
|
12
|
+
updatedAt?: number;
|
|
10
13
|
}
|
|
11
14
|
export type RecurrenceType = 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
12
15
|
export interface ResponsibilityDef {
|
|
@@ -21,6 +24,7 @@ export interface ResponsibilityDef {
|
|
|
21
24
|
pluginId?: string;
|
|
22
25
|
reminderType?: string;
|
|
23
26
|
pluginData?: Record<string, unknown>;
|
|
27
|
+
writeKind?: WriteKind;
|
|
24
28
|
}
|
|
25
29
|
export interface AppSettings {
|
|
26
30
|
activeStartTime: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AuthorityRow {
|
|
2
|
+
writeKind?: string | null;
|
|
3
|
+
status?: string | null;
|
|
4
|
+
delayUntil?: number | string | null;
|
|
5
|
+
nextTriggerTime?: number | string | null;
|
|
6
|
+
activatedAt?: number | string | null;
|
|
7
|
+
updatedAt?: number | string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function authorityOverride(local: AuthorityRow, incoming: AuthorityRow, now: number): boolean | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { STALE_ACTIVE_THRESHOLD_MS } from './scheduling.js';
|
|
2
|
+
// Matches the server's jsonb BIGINT casts: NULL/missing coerces to 0.
|
|
3
|
+
function stamp(value) {
|
|
4
|
+
return Number(value) || 0;
|
|
5
|
+
}
|
|
6
|
+
// Client mirror of sync_lww_guard (supabase/migrations/20260920200000_p3_staleness_activation.sql):
|
|
7
|
+
// incoming user over local auto always wins; incoming auto over local user wins only via a
|
|
8
|
+
// progression rule — the user's own data says it's time:
|
|
9
|
+
// P1 delayed -> active once the user-set delay_until has passed
|
|
10
|
+
// P2 completed -> scheduled/active with a strictly LATER next_trigger_time (forward rebook only)
|
|
11
|
+
// P3 active -> scheduled/completed once the row is 48h stale by activated_at — the
|
|
12
|
+
// alive-as-active clock, falling back to updated_at when activated_at is NULL (legacy
|
|
13
|
+
// rows). updated_at alone is wrong: ticking plugins re-stamp it every tick, which
|
|
14
|
+
// starved the sweep on actively-ticking rows (Obs 3).
|
|
15
|
+
// P4 scheduled -> active once the user-scheduled next_trigger_time has arrived (the fire)
|
|
16
|
+
// Returns null when kinds match so the caller falls back to updated_at comparison.
|
|
17
|
+
// Missing kinds default to 'auto' (S9: pre-migration rows, old clients).
|
|
18
|
+
export function authorityOverride(local, incoming, now) {
|
|
19
|
+
const localKind = local.writeKind ?? 'auto';
|
|
20
|
+
const incomingKind = incoming.writeKind ?? 'auto';
|
|
21
|
+
if (localKind === incomingKind)
|
|
22
|
+
return null;
|
|
23
|
+
if (incomingKind === 'user')
|
|
24
|
+
return true;
|
|
25
|
+
if (local.status === 'delayed'
|
|
26
|
+
&& incoming.status === 'active'
|
|
27
|
+
&& stamp(local.delayUntil) <= now)
|
|
28
|
+
return true;
|
|
29
|
+
if (local.status === 'completed'
|
|
30
|
+
&& (incoming.status === 'scheduled' || incoming.status === 'active')
|
|
31
|
+
&& stamp(incoming.nextTriggerTime) > stamp(local.nextTriggerTime))
|
|
32
|
+
return true;
|
|
33
|
+
if (local.status === 'active'
|
|
34
|
+
&& (incoming.status === 'scheduled' || incoming.status === 'completed')
|
|
35
|
+
&& stamp(local.activatedAt ?? local.updatedAt) <= now - STALE_ACTIVE_THRESHOLD_MS)
|
|
36
|
+
return true;
|
|
37
|
+
if (local.status === 'scheduled'
|
|
38
|
+
&& incoming.status === 'active'
|
|
39
|
+
&& stamp(local.nextTriggerTime) <= now)
|
|
40
|
+
return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|