@ryuzaki13/react-foundation-lib 2.14.1 → 2.15.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/async-task-runner/createDeadlineTimer.d.ts +8 -0
- package/dist/async-task-runner/createDeadlineTimer.d.ts.map +1 -0
- package/dist/async-task-runner/deadlineTimerTypes.d.ts +12 -0
- package/dist/async-task-runner/deadlineTimerTypes.d.ts.map +1 -0
- package/dist/async-task-runner/index.d.ts +2 -0
- package/dist/async-task-runner/index.d.ts.map +1 -1
- package/dist/async-task-runner/index.js +35 -1
- package/dist/async-task-runner/index.js.map +1 -1
- package/package.json +1 -1
- package/src/async-task-runner/README.mdx +53 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DeadlineTimer, type DeadlineTimerOptions } from "./deadlineTimerTypes";
|
|
2
|
+
/**
|
|
3
|
+
* Ожидает один абсолютный срок. Длинное ожидание разбивается только на native
|
|
4
|
+
* timers: промежуточные части не вызывают предметный callback. Создание не
|
|
5
|
+
* запускает таймер, а отменённое поколение не оживает от позднего callback.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createDeadlineTimer({ onDue }: DeadlineTimerOptions): DeadlineTimer;
|
|
8
|
+
//# sourceMappingURL=createDeadlineTimer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDeadlineTimer.d.ts","sourceRoot":"","sources":["../../src/async-task-runner/createDeadlineTimer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AASrF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE,oBAAoB,GAAG,aAAa,CAgDlF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Одноразовое пробуждение: предметную работу и обработку её отказа выполняет caller. */
|
|
2
|
+
export type DeadlineTimerOptions = Readonly<{
|
|
3
|
+
onDue: () => void;
|
|
4
|
+
}>;
|
|
5
|
+
/** Абсолютный срок в Unix milliseconds не зависит от длительности предыдущей задачи. */
|
|
6
|
+
export type DeadlineTimer = Readonly<{
|
|
7
|
+
/** Заменяет срок; null отменяет его. Некорректный срок не отменяет уже назначенный. */
|
|
8
|
+
schedule: (deadlineAtMs: number | null) => void;
|
|
9
|
+
/** Отзывает только ещё не начавшееся пробуждение, а не работу внутри onDue. */
|
|
10
|
+
cancel: () => void;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=deadlineTimerTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadlineTimerTypes.d.ts","sourceRoot":"","sources":["../../src/async-task-runner/deadlineTimerTypes.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC,CAAC;AAEH,wFAAwF;AACxF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACpC,uFAAuF;IACvF,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,+EAA+E;IAC/E,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { createCoalescingTaskRunner } from "./createCoalescingTaskRunner";
|
|
2
2
|
export { type CoalescingTaskRunner, type CoalescingTaskRunnerOptions } from "./asyncTaskRunnerTypes";
|
|
3
|
+
export { createDeadlineTimer } from "./createDeadlineTimer";
|
|
4
|
+
export { type DeadlineTimer, type DeadlineTimerOptions } from "./deadlineTimerTypes";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/async-task-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/async-task-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -59,7 +59,41 @@ function e(e) {
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
function t({ onDue: e }) {
|
|
63
|
+
let t = null, n = () => {
|
|
64
|
+
let e = t;
|
|
65
|
+
t = null, e && e.timer !== null && (clearTimeout(e.timer), e.timer = null);
|
|
66
|
+
}, r = (n) => {
|
|
67
|
+
let i = Math.max(1, Math.min(2147483647, n.deadlineAtMs - Date.now())), a = setTimeout(() => {
|
|
68
|
+
if (t === n && n.timer === a) {
|
|
69
|
+
if (n.timer = null, Date.now() < n.deadlineAtMs) {
|
|
70
|
+
r(n);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
t = null, e();
|
|
74
|
+
}
|
|
75
|
+
}, i);
|
|
76
|
+
n.timer = a, a.unref?.();
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
schedule(e) {
|
|
80
|
+
if (e === null) {
|
|
81
|
+
n();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!Number.isSafeInteger(e)) throw RangeError("Абсолютный срок должен быть безопасным целым числом Unix milliseconds.");
|
|
85
|
+
if (t?.deadlineAtMs === e) return;
|
|
86
|
+
n();
|
|
87
|
+
let i = {
|
|
88
|
+
deadlineAtMs: e,
|
|
89
|
+
timer: null
|
|
90
|
+
};
|
|
91
|
+
t = i, r(i);
|
|
92
|
+
},
|
|
93
|
+
cancel: n
|
|
94
|
+
};
|
|
95
|
+
}
|
|
62
96
|
//#endregion
|
|
63
|
-
export { e as createCoalescingTaskRunner };
|
|
97
|
+
export { e as createCoalescingTaskRunner, t as createDeadlineTimer };
|
|
64
98
|
|
|
65
99
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/async-task-runner/createCoalescingTaskRunner.ts"],"sourcesContent":["import { type CoalescingTaskRunner, type CoalescingTaskRunnerOptions } from \"./asyncTaskRunnerTypes\";\n\ntype ScheduledWake = { kind: \"retry\" | \"tail\"; timer: ReturnType<typeof setTimeout> };\ntype RunnerGeneration = {\n\tpending: Promise<void> | null;\n\trerun: boolean;\n\tpollTimer: ReturnType<typeof setInterval> | null;\n\tscheduledWake: ScheduledWake | null;\n\treportingFailure: Readonly<{ error: unknown }> | null;\n};\n\n/**\n * Создаёт неактивный runner. Поколение защищает от поздних таймеров, single-flight\n * объединяет сигналы в один следующий проход, stop не имитирует отмену уже\n * выполняющейся атомарной задачи. Предметная запись и диагностика принадлежат caller.\n */\nexport function createCoalescingTaskRunner(options: CoalescingTaskRunnerOptions): CoalescingTaskRunner {\n\tfor (const delay of [options.retryDelayMs, ...(options.pollIntervalMs === undefined ? [] : [options.pollIntervalMs])]) {\n\t\t// Более длинную задержку Node превращает в 1 ms: молчаливое ограничение здесь опасно.\n\t\tif (!Number.isInteger(delay) || delay < 1 || delay > 2_147_483_647) {\n\t\t\tthrow new RangeError(\"Задержки runner должны быть целыми числами от 1 до 2147483647 ms.\");\n\t\t}\n\t}\n\tlet active: RunnerGeneration | null = null;\n\tlet stopping: Promise<void> | null = null;\n\tlet stopRevision = 0;\n\n\tconst clearScheduledWake = (generation: RunnerGeneration) => {\n\t\tif (generation.scheduledWake) clearTimeout(generation.scheduledWake.timer);\n\t\tgeneration.scheduledWake = null;\n\t};\n\tconst clearTimers = (generation: RunnerGeneration) => {\n\t\tclearScheduledWake(generation);\n\t\tif (generation.pollTimer !== null) clearInterval(generation.pollTimer);\n\t\tgeneration.pollTimer = null;\n\t};\n\tconst backgroundWake = (generation: RunnerGeneration) => {\n\t\tif (active !== generation || generation.reportingFailure || generation.scheduledWake?.kind === \"retry\") return;\n\t\t// Обычный отказ уже передан onError; отказ самого onError сохранён для\n\t\t// admission/stop. Timer не создаёт unhandled rejection и не теряет reporter error.\n\t\tvoid wake().catch(() => {});\n\t};\n\tconst schedule = (generation: RunnerGeneration, kind: ScheduledWake[\"kind\"], delay: number) => {\n\t\tif (active !== generation || generation.reportingFailure || generation.scheduledWake) return;\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (active !== generation || generation.scheduledWake?.timer !== timer) return;\n\t\t\tgeneration.scheduledWake = null;\n\t\t\tbackgroundWake(generation);\n\t\t}, delay);\n\t\tgeneration.scheduledWake = { kind, timer };\n\t\ttimer.unref?.();\n\t};\n\tconst wake = (): Promise<void> => {\n\t\tconst generation = active;\n\t\tif (!generation) return Promise.resolve();\n\t\tif (generation.reportingFailure) return Promise.reject(generation.reportingFailure.error);\n\t\tif (generation.pending) {\n\t\t\tgeneration.rerun = true;\n\t\t\treturn generation.pending;\n\t\t}\n\t\t// Явный wake ускоряет retry. Только caller, не poll, может принять это решение.\n\t\tclearScheduledWake(generation);\n\t\tgeneration.rerun = false;\n\t\tgeneration.pending = Promise.resolve()\n\t\t\t.then(async () => {\n\t\t\t\t// stop до первой microtask не разрешает позднее выполнение callback.\n\t\t\t\tif (active === generation) await options.run();\n\t\t\t})\n\t\t\t.catch(async (error: unknown) => {\n\t\t\t\t// Уже начатый run сообщает отказ даже после stop; shutdown не скрывает ошибку.\n\t\t\t\ttry {\n\t\t\t\t\tawait options.onError(error);\n\t\t\t\t} catch (reporterError: unknown) {\n\t\t\t\t\t// Wrapper различает отсутствие отказа и реальные throw undefined/null.\n\t\t\t\t\tgeneration.reportingFailure = { error: reporterError };\n\t\t\t\t\tgeneration.rerun = false;\n\t\t\t\t\tclearTimers(generation);\n\t\t\t\t}\n\t\t\t\tschedule(generation, \"retry\", options.retryDelayMs);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tgeneration.pending = null;\n\t\t\t\t// При отказе retry timer уже существует и не заменяется немедленным хвостом.\n\t\t\t\tif (generation.rerun) schedule(generation, \"tail\", 0);\n\t\t\t});\n\t\treturn generation.pending;\n\t};\n\tconst startGeneration = (requestedBeforeStop: number): Promise<void> => {\n\t\tif (requestedBeforeStop !== stopRevision) return Promise.reject(new Error(\"Запуск runner отменён более поздней остановкой.\"));\n\t\tif (active) {\n\t\t\tif (active.reportingFailure) return Promise.reject(active.reportingFailure.error);\n\t\t\t// Повторный start не является wake и не создаёт лишний хвост или обход retry.\n\t\t\treturn active.pending ?? Promise.resolve();\n\t\t}\n\t\tconst generation: RunnerGeneration = { pending: null, rerun: false, pollTimer: null, scheduledWake: null, reportingFailure: null };\n\t\tactive = generation;\n\t\tif (options.pollIntervalMs !== undefined) {\n\t\t\tgeneration.pollTimer = setInterval(() => backgroundWake(generation), options.pollIntervalMs);\n\t\t\tgeneration.pollTimer.unref?.();\n\t\t}\n\t\treturn wake();\n\t};\n\treturn {\n\t\tstart() {\n\t\t\tconst requestedBeforeStop = stopRevision;\n\t\t\treturn stopping ? stopping.then(() => startGeneration(requestedBeforeStop)) : startGeneration(requestedBeforeStop);\n\t\t},\n\t\twake,\n\t\tstop() {\n\t\t\tstopRevision++;\n\t\t\tif (stopping) return stopping;\n\t\t\tconst generation = active;\n\t\t\tif (!generation) return Promise.resolve();\n\t\t\tactive = null;\n\t\t\tgeneration.rerun = false;\n\t\t\tclearTimers(generation);\n\t\t\tstopping = (async () => {\n\t\t\t\t// Task failure уже обработан onError и остаётся у start/wake caller;\n\t\t\t\t// drain отдельно передаёт точный отказ reporter, если тот не сработал.\n\t\t\t\tawait generation.pending?.catch(() => {});\n\t\t\t\tif (generation.reportingFailure) throw generation.reportingFailure.error;\n\t\t\t})().finally(() => {\n\t\t\t\tstopping = null;\n\t\t\t});\n\t\t\treturn stopping;\n\t\t}\n\t};\n}\n"],"mappings":";AAgBA,SAAgB,EAA2B,GAA4D;CACtG,KAAK,IAAM,KAAS,CAAC,EAAQ,cAAc,GAAI,EAAQ,mBAAmB,KAAA,IAAY,CAAC,IAAI,CAAC,EAAQ,cAAc,CAAE,GAEnH,IAAI,CAAC,OAAO,UAAU,CAAK,KAAK,IAAQ,KAAK,IAAQ,YACpD,MAAU,WAAW,mEAAmE;CAG1F,IAAI,IAAkC,MAClC,IAAiC,MACjC,IAAe,GAEb,KAAsB,MAAiC;EAE5D,AADI,EAAW,iBAAe,aAAa,EAAW,cAAc,KAAK,GACzE,EAAW,gBAAgB;CAC5B,GACM,KAAe,MAAiC;EAGrD,AAFA,EAAmB,CAAU,GACzB,EAAW,cAAc,QAAM,cAAc,EAAW,SAAS,GACrE,EAAW,YAAY;CACxB,GACM,KAAkB,MAAiC;EACpD,MAAW,KAAc,EAAW,oBAAoB,EAAW,eAAe,SAAS,WAG/F,EAAU,CAAC,CAAC,YAAY,CAAC,CAAC;CAC3B,GACM,KAAY,GAA8B,GAA6B,MAAkB;EAC9F,IAAI,MAAW,KAAc,EAAW,oBAAoB,EAAW,eAAe;EACtF,IAAM,IAAQ,iBAAiB;GAC1B,MAAW,KAAc,EAAW,eAAe,UAAU,MACjE,EAAW,gBAAgB,MAC3B,EAAe,CAAU;EAC1B,GAAG,CAAK;EAER,AADA,EAAW,gBAAgB;GAAE;GAAM;EAAM,GACzC,EAAM,QAAQ;CACf,GACM,UAA4B;EACjC,IAAM,IAAa;EAiCnB,OAhCK,IACD,EAAW,mBAAyB,QAAQ,OAAO,EAAW,iBAAiB,KAAK,IACpF,EAAW,WACd,EAAW,QAAQ,IACZ,EAAW,YAGnB,EAAmB,CAAU,GAC7B,EAAW,QAAQ,IACnB,EAAW,UAAU,QAAQ,QAAQ,CAAC,CACpC,KAAK,YAAY;GAEjB,AAAI,MAAW,KAAY,MAAM,EAAQ,IAAI;EAC9C,CAAC,CAAC,CACD,MAAM,OAAO,MAAmB;GAEhC,IAAI;IACH,MAAM,EAAQ,QAAQ,CAAK;GAC5B,SAAS,GAAwB;IAIhC,AAFA,EAAW,mBAAmB,EAAE,OAAO,EAAc,GACrD,EAAW,QAAQ,IACnB,EAAY,CAAU;GACvB;GAEA,MADA,EAAS,GAAY,SAAS,EAAQ,YAAY,GAC5C;EACP,CAAC,CAAC,CACD,cAAc;GAGd,AAFA,EAAW,UAAU,MAEjB,EAAW,SAAO,EAAS,GAAY,QAAQ,CAAC;EACrD,CAAC,GACK,EAAW,WAhCM,QAAQ,QAAQ;CAiCzC,GACM,KAAmB,MAA+C;EACvE,IAAI,MAAwB,GAAc,OAAO,QAAQ,OAAO,gBAAI,MAAM,iDAAiD,CAAC;EAC5H,IAAI,GAGH,OAFI,EAAO,mBAAyB,QAAQ,OAAO,EAAO,iBAAiB,KAAK,IAEzE,EAAO,WAAW,QAAQ,QAAQ;EAE1C,IAAM,IAA+B;GAAE,SAAS;GAAM,OAAO;GAAO,WAAW;GAAM,eAAe;GAAM,kBAAkB;EAAK;EAMjI,OALA,IAAS,GACL,EAAQ,mBAAmB,KAAA,MAC9B,EAAW,YAAY,kBAAkB,EAAe,CAAU,GAAG,EAAQ,cAAc,GAC3F,EAAW,UAAU,QAAQ,IAEvB,EAAK;CACb;CACA,OAAO;EACN,QAAQ;GACP,IAAM,IAAsB;GAC5B,OAAO,IAAW,EAAS,WAAW,EAAgB,CAAmB,CAAC,IAAI,EAAgB,CAAmB;EAClH;EACA;EACA,OAAO;GAEN,IADA,KACI,GAAU,OAAO;GACrB,IAAM,IAAa;GAanB,OAZK,KACL,IAAS,MACT,EAAW,QAAQ,IACnB,EAAY,CAAU,GACtB,KAAY,YAAY;IAIvB,IADA,MAAM,EAAW,SAAS,YAAY,CAAC,CAAC,GACpC,EAAW,kBAAkB,MAAM,EAAW,iBAAiB;GACpE,EAAA,CAAG,CAAC,CAAC,cAAc;IAClB,IAAW;GACZ,CAAC,GACM,KAZiB,QAAQ,QAAQ;EAazC;CACD;AACD"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/async-task-runner/createCoalescingTaskRunner.ts","../../src/async-task-runner/createDeadlineTimer.ts"],"sourcesContent":["import { type CoalescingTaskRunner, type CoalescingTaskRunnerOptions } from \"./asyncTaskRunnerTypes\";\n\ntype ScheduledWake = { kind: \"retry\" | \"tail\"; timer: ReturnType<typeof setTimeout> };\ntype RunnerGeneration = {\n\tpending: Promise<void> | null;\n\trerun: boolean;\n\tpollTimer: ReturnType<typeof setInterval> | null;\n\tscheduledWake: ScheduledWake | null;\n\treportingFailure: Readonly<{ error: unknown }> | null;\n};\n\n/**\n * Создаёт неактивный runner. Поколение защищает от поздних таймеров, single-flight\n * объединяет сигналы в один следующий проход, stop не имитирует отмену уже\n * выполняющейся атомарной задачи. Предметная запись и диагностика принадлежат caller.\n */\nexport function createCoalescingTaskRunner(options: CoalescingTaskRunnerOptions): CoalescingTaskRunner {\n\tfor (const delay of [options.retryDelayMs, ...(options.pollIntervalMs === undefined ? [] : [options.pollIntervalMs])]) {\n\t\t// Более длинную задержку Node превращает в 1 ms: молчаливое ограничение здесь опасно.\n\t\tif (!Number.isInteger(delay) || delay < 1 || delay > 2_147_483_647) {\n\t\t\tthrow new RangeError(\"Задержки runner должны быть целыми числами от 1 до 2147483647 ms.\");\n\t\t}\n\t}\n\tlet active: RunnerGeneration | null = null;\n\tlet stopping: Promise<void> | null = null;\n\tlet stopRevision = 0;\n\n\tconst clearScheduledWake = (generation: RunnerGeneration) => {\n\t\tif (generation.scheduledWake) clearTimeout(generation.scheduledWake.timer);\n\t\tgeneration.scheduledWake = null;\n\t};\n\tconst clearTimers = (generation: RunnerGeneration) => {\n\t\tclearScheduledWake(generation);\n\t\tif (generation.pollTimer !== null) clearInterval(generation.pollTimer);\n\t\tgeneration.pollTimer = null;\n\t};\n\tconst backgroundWake = (generation: RunnerGeneration) => {\n\t\tif (active !== generation || generation.reportingFailure || generation.scheduledWake?.kind === \"retry\") return;\n\t\t// Обычный отказ уже передан onError; отказ самого onError сохранён для\n\t\t// admission/stop. Timer не создаёт unhandled rejection и не теряет reporter error.\n\t\tvoid wake().catch(() => {});\n\t};\n\tconst schedule = (generation: RunnerGeneration, kind: ScheduledWake[\"kind\"], delay: number) => {\n\t\tif (active !== generation || generation.reportingFailure || generation.scheduledWake) return;\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (active !== generation || generation.scheduledWake?.timer !== timer) return;\n\t\t\tgeneration.scheduledWake = null;\n\t\t\tbackgroundWake(generation);\n\t\t}, delay);\n\t\tgeneration.scheduledWake = { kind, timer };\n\t\ttimer.unref?.();\n\t};\n\tconst wake = (): Promise<void> => {\n\t\tconst generation = active;\n\t\tif (!generation) return Promise.resolve();\n\t\tif (generation.reportingFailure) return Promise.reject(generation.reportingFailure.error);\n\t\tif (generation.pending) {\n\t\t\tgeneration.rerun = true;\n\t\t\treturn generation.pending;\n\t\t}\n\t\t// Явный wake ускоряет retry. Только caller, не poll, может принять это решение.\n\t\tclearScheduledWake(generation);\n\t\tgeneration.rerun = false;\n\t\tgeneration.pending = Promise.resolve()\n\t\t\t.then(async () => {\n\t\t\t\t// stop до первой microtask не разрешает позднее выполнение callback.\n\t\t\t\tif (active === generation) await options.run();\n\t\t\t})\n\t\t\t.catch(async (error: unknown) => {\n\t\t\t\t// Уже начатый run сообщает отказ даже после stop; shutdown не скрывает ошибку.\n\t\t\t\ttry {\n\t\t\t\t\tawait options.onError(error);\n\t\t\t\t} catch (reporterError: unknown) {\n\t\t\t\t\t// Wrapper различает отсутствие отказа и реальные throw undefined/null.\n\t\t\t\t\tgeneration.reportingFailure = { error: reporterError };\n\t\t\t\t\tgeneration.rerun = false;\n\t\t\t\t\tclearTimers(generation);\n\t\t\t\t}\n\t\t\t\tschedule(generation, \"retry\", options.retryDelayMs);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tgeneration.pending = null;\n\t\t\t\t// При отказе retry timer уже существует и не заменяется немедленным хвостом.\n\t\t\t\tif (generation.rerun) schedule(generation, \"tail\", 0);\n\t\t\t});\n\t\treturn generation.pending;\n\t};\n\tconst startGeneration = (requestedBeforeStop: number): Promise<void> => {\n\t\tif (requestedBeforeStop !== stopRevision) return Promise.reject(new Error(\"Запуск runner отменён более поздней остановкой.\"));\n\t\tif (active) {\n\t\t\tif (active.reportingFailure) return Promise.reject(active.reportingFailure.error);\n\t\t\t// Повторный start не является wake и не создаёт лишний хвост или обход retry.\n\t\t\treturn active.pending ?? Promise.resolve();\n\t\t}\n\t\tconst generation: RunnerGeneration = { pending: null, rerun: false, pollTimer: null, scheduledWake: null, reportingFailure: null };\n\t\tactive = generation;\n\t\tif (options.pollIntervalMs !== undefined) {\n\t\t\tgeneration.pollTimer = setInterval(() => backgroundWake(generation), options.pollIntervalMs);\n\t\t\tgeneration.pollTimer.unref?.();\n\t\t}\n\t\treturn wake();\n\t};\n\treturn {\n\t\tstart() {\n\t\t\tconst requestedBeforeStop = stopRevision;\n\t\t\treturn stopping ? stopping.then(() => startGeneration(requestedBeforeStop)) : startGeneration(requestedBeforeStop);\n\t\t},\n\t\twake,\n\t\tstop() {\n\t\t\tstopRevision++;\n\t\t\tif (stopping) return stopping;\n\t\t\tconst generation = active;\n\t\t\tif (!generation) return Promise.resolve();\n\t\t\tactive = null;\n\t\t\tgeneration.rerun = false;\n\t\t\tclearTimers(generation);\n\t\t\tstopping = (async () => {\n\t\t\t\t// Task failure уже обработан onError и остаётся у start/wake caller;\n\t\t\t\t// drain отдельно передаёт точный отказ reporter, если тот не сработал.\n\t\t\t\tawait generation.pending?.catch(() => {});\n\t\t\t\tif (generation.reportingFailure) throw generation.reportingFailure.error;\n\t\t\t})().finally(() => {\n\t\t\t\tstopping = null;\n\t\t\t});\n\t\t\treturn stopping;\n\t\t}\n\t};\n}\n","import { type DeadlineTimer, type DeadlineTimerOptions } from \"./deadlineTimerTypes\";\n\nconst MAX_NATIVE_DELAY_MS = 2_147_483_647;\n\ntype DeadlineGeneration = {\n\tdeadlineAtMs: number;\n\ttimer: ReturnType<typeof setTimeout> | null;\n};\n\n/**\n * Ожидает один абсолютный срок. Длинное ожидание разбивается только на native\n * timers: промежуточные части не вызывают предметный callback. Создание не\n * запускает таймер, а отменённое поколение не оживает от позднего callback.\n */\nexport function createDeadlineTimer({ onDue }: DeadlineTimerOptions): DeadlineTimer {\n\tlet active: DeadlineGeneration | null = null;\n\n\tconst cancel = (): void => {\n\t\tconst generation = active;\n\t\tactive = null;\n\t\tif (generation && generation.timer !== null) {\n\t\t\tclearTimeout(generation.timer);\n\t\t\tgeneration.timer = null;\n\t\t}\n\t};\n\tconst arm = (generation: DeadlineGeneration): void => {\n\t\t// Node превращает переполнение delay в 1 ms. Даже годовой deadline нельзя\n\t\t// передавать напрямую; оставшийся срок перепроверяется после каждой части.\n\t\tconst delay = Math.max(1, Math.min(MAX_NATIVE_DELAY_MS, generation.deadlineAtMs - Date.now()));\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (active !== generation || generation.timer !== timer) return;\n\t\t\tgeneration.timer = null;\n\t\t\tif (Date.now() < generation.deadlineAtMs) {\n\t\t\t\tarm(generation);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Срок отзывается до callback: onDue может безопасно назначить следующий.\n\t\t\t// Ошибки и async rejection принадлежат caller, а не timer primitive.\n\t\t\tactive = null;\n\t\t\tonDue();\n\t\t}, delay);\n\t\tgeneration.timer = timer;\n\t\ttimer.unref?.();\n\t};\n\n\treturn {\n\t\tschedule(deadlineAtMs) {\n\t\t\tif (deadlineAtMs === null) {\n\t\t\t\tcancel();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!Number.isSafeInteger(deadlineAtMs)) {\n\t\t\t\tthrow new RangeError(\"Абсолютный срок должен быть безопасным целым числом Unix milliseconds.\");\n\t\t\t}\n\t\t\tif (active?.deadlineAtMs === deadlineAtMs) return;\n\t\t\tcancel();\n\t\t\tconst generation: DeadlineGeneration = { deadlineAtMs, timer: null };\n\t\t\tactive = generation;\n\t\t\tarm(generation);\n\t\t},\n\t\tcancel\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
# Последовательный async runner
|
|
1
|
+
# Последовательный async runner и абсолютный deadline
|
|
2
2
|
|
|
3
3
|
Публичный импорт: `@ryuzaki13/react-foundation-lib/async-task-runner`.
|
|
4
4
|
Runner объединяет повторные сигналы, повторяет отказавшую задачу и корректно
|
|
5
5
|
завершает lifecycle. Он не знает о HTTP, БД, React или предметном составе.
|
|
6
6
|
Создание и импорт не вызывают `run` и не создают таймеры.
|
|
7
7
|
|
|
8
|
+
`createDeadlineTimer` дополняет runner независимым одноразовым пробуждением в
|
|
9
|
+
абсолютный момент. Он не задаёт polling или retry policy и не вызывает
|
|
10
|
+
предметную работу при промежуточных частях длинного ожидания.
|
|
11
|
+
|
|
8
12
|
```ts
|
|
9
13
|
import { createCoalescingTaskRunner } from "@ryuzaki13/react-foundation-lib/async-task-runner";
|
|
10
14
|
|
|
@@ -77,3 +81,51 @@ stop отменяет такой отложенный start, не позволя
|
|
|
77
81
|
создаёт цикл самодренирования. Остановка принадлежит внешнему lifecycle.
|
|
78
82
|
Callback, который никогда не завершается, удерживает drain; timeout и реальная
|
|
79
83
|
отмена внешней операции остаются ответственностью приложения.
|
|
84
|
+
|
|
85
|
+
## Абсолютный одноразовый срок
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createDeadlineTimer } from "@ryuzaki13/react-foundation-lib/async-task-runner";
|
|
89
|
+
|
|
90
|
+
const deadline = createDeadlineTimer({
|
|
91
|
+
onDue: () => {
|
|
92
|
+
// Async callback имеет собственную обработку отказа у владельца работы.
|
|
93
|
+
void runner.wake().catch(reportTaskFailure);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
deadline.schedule(nextEffectiveAtMs);
|
|
97
|
+
// При остановке сначала отменяется допуск timer, затем ожидается работа runner.
|
|
98
|
+
deadline.cancel();
|
|
99
|
+
await runner.stop();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Создание timer не запускает callback и не создаёт native timer.
|
|
103
|
+
`schedule(deadlineAtMs: number | null): void` заменяет прежний срок, а `null`
|
|
104
|
+
отменяет его. Срок — безопасное целое число абсолютных Unix milliseconds;
|
|
105
|
+
`NaN`, бесконечность, дробное или небезопасное число дают `RangeError` до
|
|
106
|
+
побочных эффектов и не отменяют уже назначенный корректный срок. Повтор того
|
|
107
|
+
же pending срока идемпотентен. Просроченный срок, включая отрицательное число,
|
|
108
|
+
выполняется отдельной timer-задачей, а не синхронно внутри `schedule`.
|
|
109
|
+
|
|
110
|
+
Задержка каждого native timer не превышает 2147483647 ms. Если абсолютный срок
|
|
111
|
+
дальше, ожидание разбивается на части с повторной проверкой `Date.now()`.
|
|
112
|
+
Промежуточные части не вызывают `onDue`, не читают внешние источники и не
|
|
113
|
+
выполняют фоновые предметные проходы. Одновременно существует не более одного
|
|
114
|
+
native timer. В Node используется `unref`: само ожидание не удерживает процесс.
|
|
115
|
+
|
|
116
|
+
`onDue` вызывается один раз за назначенный срок. До вызова срок снимается, поэтому
|
|
117
|
+
callback может назначить следующий срок. `cancel(): void` удаляет pending timer
|
|
118
|
+
и отзывает его поколение; поздние callbacks отменённого или заменённого срока
|
|
119
|
+
игнорируются. Cancel не отменяет уже начавшуюся работу внутри `onDue`. Новая
|
|
120
|
+
`schedule` после cancel разрешена; окончательный lifecycle принадлежит caller.
|
|
121
|
+
|
|
122
|
+
Timer не знает timezone, календарных правил, БД, SQL, transport или React.
|
|
123
|
+
Вычисление абсолютного срока, обработка синхронных ошибок и async rejection
|
|
124
|
+
в callback, retry и остановка атомарной работы остаются у caller. Он не ожидает
|
|
125
|
+
Promise, возвращённый callback, и не добавляет собственной диагностики.
|
|
126
|
+
|
|
127
|
+
Точность зависит от event loop и системных часов. После перевода часов назад
|
|
128
|
+
ранний callback перевзводит оставшееся ожидание; после перевода часов вперёд
|
|
129
|
+
просроченность обнаруживается при ближайшем уже назначенном timer callback,
|
|
130
|
+
не через скрытый interval. Этот primitive не гарантирует немедленную реакцию
|
|
131
|
+
на изменение часов и не заменяет внешний startup/reconnect catch-up.
|