@taico/taico 0.8.0 → 0.8.2

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 (98) hide show
  1. package/README.md +1 -1
  2. package/dist/ai-providers/enums/ai-model-job.enum.d.ts +2 -1
  3. package/dist/ai-providers/enums/ai-model-job.enum.js +5 -2
  4. package/dist/ai-providers/enums/ai-model-job.enum.js.map +1 -1
  5. package/dist/ai-providers/errors/ai-providers.errors.js +10 -4
  6. package/dist/ai-providers/errors/ai-providers.errors.js.map +1 -1
  7. package/dist/app.module.js +6 -0
  8. package/dist/app.module.js.map +1 -1
  9. package/dist/migrations/1761700000000-SplitSmallTasksSelection.d.ts +6 -0
  10. package/dist/migrations/1761700000000-SplitSmallTasksSelection.js +32 -0
  11. package/dist/migrations/1761700000000-SplitSmallTasksSelection.js.map +1 -0
  12. package/dist/migrations/1761800000000-ThreadTimers.d.ts +6 -0
  13. package/dist/migrations/1761800000000-ThreadTimers.js +22 -0
  14. package/dist/migrations/1761800000000-ThreadTimers.js.map +1 -0
  15. package/dist/migrations/1761900000000-DedupeArtefacts.d.ts +6 -0
  16. package/dist/migrations/1761900000000-DedupeArtefacts.js +24 -0
  17. package/dist/migrations/1761900000000-DedupeArtefacts.js.map +1 -0
  18. package/dist/tasks/tasks.service.add-artefact.spec.d.ts +1 -0
  19. package/dist/tasks/tasks.service.add-artefact.spec.js +63 -0
  20. package/dist/tasks/tasks.service.add-artefact.spec.js.map +1 -0
  21. package/dist/tasks/tasks.service.js +13 -0
  22. package/dist/tasks/tasks.service.js.map +1 -1
  23. package/dist/threads/agent/chat-model.factory.js +3 -1
  24. package/dist/threads/agent/chat-model.factory.js.map +1 -1
  25. package/dist/threads/agent/chat-model.factory.spec.js +49 -4
  26. package/dist/threads/agent/chat-model.factory.spec.js.map +1 -1
  27. package/dist/threads/agent/thread-agent-log.service.d.ts +10 -0
  28. package/dist/threads/agent/thread-agent-log.service.js +57 -1
  29. package/dist/threads/agent/thread-agent-log.service.js.map +1 -1
  30. package/dist/threads/agent/thread-agent-tools.factory.d.ts +3 -1
  31. package/dist/threads/agent/thread-agent-tools.factory.js +195 -3
  32. package/dist/threads/agent/thread-agent-tools.factory.js.map +1 -1
  33. package/dist/threads/agent/thread-agent.prompts.d.ts +15 -0
  34. package/dist/threads/agent/thread-agent.prompts.js +29 -1
  35. package/dist/threads/agent/thread-agent.prompts.js.map +1 -1
  36. package/dist/threads/agent/thread-agent.service.js +9 -3
  37. package/dist/threads/agent/thread-agent.service.js.map +1 -1
  38. package/dist/threads/agent/thread-agent.service.spec.js +23 -0
  39. package/dist/threads/agent/thread-agent.service.spec.js.map +1 -1
  40. package/dist/threads/chat.service.d.ts +10 -1
  41. package/dist/threads/chat.service.js +41 -13
  42. package/dist/threads/chat.service.js.map +1 -1
  43. package/dist/threads/chat.service.spec.d.ts +1 -0
  44. package/dist/threads/chat.service.spec.js +150 -0
  45. package/dist/threads/chat.service.spec.js.map +1 -0
  46. package/dist/threads/dto/service/threads.service.types.d.ts +12 -0
  47. package/dist/threads/dto/thread-response.dto.d.ts +2 -0
  48. package/dist/threads/dto/thread-response.dto.js +10 -0
  49. package/dist/threads/dto/thread-response.dto.js.map +1 -1
  50. package/dist/threads/dto/thread-task-subscription-params.dto.d.ts +4 -0
  51. package/dist/threads/dto/thread-task-subscription-params.dto.js +28 -0
  52. package/dist/threads/dto/thread-task-subscription-params.dto.js.map +1 -0
  53. package/dist/threads/dto/thread-timer-params.dto.d.ts +4 -0
  54. package/dist/threads/dto/thread-timer-params.dto.js +28 -0
  55. package/dist/threads/dto/thread-timer-params.dto.js.map +1 -0
  56. package/dist/threads/dto/thread-timer-response.dto.d.ts +13 -0
  57. package/dist/threads/dto/thread-timer-response.dto.js +92 -0
  58. package/dist/threads/dto/thread-timer-response.dto.js.map +1 -0
  59. package/dist/threads/task-subscriptions/thread-task-subscriptions.service.d.ts +8 -0
  60. package/dist/threads/task-subscriptions/thread-task-subscriptions.service.js +14 -0
  61. package/dist/threads/task-subscriptions/thread-task-subscriptions.service.js.map +1 -1
  62. package/dist/threads/thread-title.service.d.ts +8 -2
  63. package/dist/threads/thread-title.service.js +18 -3
  64. package/dist/threads/thread-title.service.js.map +1 -1
  65. package/dist/threads/thread-title.service.spec.d.ts +1 -0
  66. package/dist/threads/thread-title.service.spec.js +59 -0
  67. package/dist/threads/thread-title.service.spec.js.map +1 -0
  68. package/dist/threads/threads.controller.d.ts +4 -0
  69. package/dist/threads/threads.controller.js +40 -0
  70. package/dist/threads/threads.controller.js.map +1 -1
  71. package/dist/threads/threads.module.js +8 -0
  72. package/dist/threads/threads.module.js.map +1 -1
  73. package/dist/threads/threads.service.d.ts +8 -1
  74. package/dist/threads/threads.service.js +39 -10
  75. package/dist/threads/threads.service.js.map +1 -1
  76. package/dist/threads/timers/thread-timer-firing.entity.d.ts +9 -0
  77. package/dist/threads/timers/thread-timer-firing.entity.js +54 -0
  78. package/dist/threads/timers/thread-timer-firing.entity.js.map +1 -0
  79. package/dist/threads/timers/thread-timer-sweeper.service.d.ts +11 -0
  80. package/dist/threads/timers/thread-timer-sweeper.service.js +62 -0
  81. package/dist/threads/timers/thread-timer-sweeper.service.js.map +1 -0
  82. package/dist/threads/timers/thread-timer.entity.d.ts +17 -0
  83. package/dist/threads/timers/thread-timer.entity.js +87 -0
  84. package/dist/threads/timers/thread-timer.entity.js.map +1 -0
  85. package/dist/threads/timers/thread-timer.enums.d.ts +4 -0
  86. package/dist/threads/timers/thread-timer.enums.js +9 -0
  87. package/dist/threads/timers/thread-timer.enums.js.map +1 -0
  88. package/dist/threads/timers/thread-timers.service.d.ts +26 -0
  89. package/dist/threads/timers/thread-timers.service.js +130 -0
  90. package/dist/threads/timers/thread-timers.service.js.map +1 -0
  91. package/dist/threads/timers/timer-input.d.ts +13 -0
  92. package/dist/threads/timers/timer-input.js +159 -0
  93. package/dist/threads/timers/timer-input.js.map +1 -0
  94. package/dist/threads/timers/timer-input.spec.d.ts +1 -0
  95. package/dist/threads/timers/timer-input.spec.js +92 -0
  96. package/dist/threads/timers/timer-input.spec.js.map +1 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +5 -5
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ThreadTimersService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const event_emitter_1 = require("@nestjs/event-emitter");
15
+ const typeorm_1 = require("typeorm");
16
+ const node_crypto_1 = require("node:crypto");
17
+ const thread_entity_1 = require("../thread.entity");
18
+ const threads_events_1 = require("../events/threads.events");
19
+ const FIRE_BATCH_SIZE = 100;
20
+ let ThreadTimersService = class ThreadTimersService {
21
+ dataSource;
22
+ eventEmitter;
23
+ constructor(dataSource, eventEmitter) {
24
+ this.dataSource = dataSource;
25
+ this.eventEmitter = eventEmitter;
26
+ }
27
+ async create(input) {
28
+ const rows = await this.dataSource.query(`INSERT INTO thread_timers (id, thread_id, kind, reason, period_seconds, max_iterations, next_fire_at, created_for_actor_id)
29
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
30
+ RETURNING id, kind, reason, period_seconds, max_iterations, fired_count, next_fire_at, created_at`, [
31
+ (0, node_crypto_1.randomUUID)(),
32
+ input.threadId,
33
+ input.kind,
34
+ input.reason.trim(),
35
+ input.periodSeconds,
36
+ input.maxIterations,
37
+ input.firstFireAt,
38
+ input.forActorId,
39
+ ]);
40
+ await this.emitThreadUpdated(input.threadId, input.forActorId);
41
+ return toResult(rows[0]);
42
+ }
43
+ async cancel(input) {
44
+ if (!isUuid(input.timerId)) {
45
+ return false;
46
+ }
47
+ const removed = await this.dataSource.transaction(async (manager) => {
48
+ await manager.query(`DELETE FROM thread_timer_firings WHERE thread_id = $1 AND timer_id = $2`, [input.threadId, input.timerId]);
49
+ return manager.query(`WITH removed AS (
50
+ DELETE FROM thread_timers WHERE thread_id = $1 AND id = $2 RETURNING id
51
+ )
52
+ SELECT id FROM removed`, [input.threadId, input.timerId]);
53
+ });
54
+ if (removed.length === 0) {
55
+ return false;
56
+ }
57
+ await this.emitThreadUpdated(input.threadId, input.actorId);
58
+ return true;
59
+ }
60
+ async listActive(threadId) {
61
+ const rows = await this.dataSource.query(`SELECT id, kind, reason, period_seconds, max_iterations, fired_count, next_fire_at, created_at
62
+ FROM thread_timers
63
+ WHERE thread_id = $1 AND fired_count < max_iterations
64
+ ORDER BY next_fire_at ASC, id ASC`, [threadId]);
65
+ return rows.map(toResult);
66
+ }
67
+ async fireDue() {
68
+ const rows = await this.dataSource.query(`WITH due AS (
69
+ SELECT tt.id
70
+ FROM thread_timers tt
71
+ JOIN threads t ON t.id = tt.thread_id AND t.deleted_at IS NULL
72
+ WHERE tt.fired_count < tt.max_iterations AND tt.next_fire_at <= now()
73
+ ORDER BY tt.next_fire_at ASC
74
+ LIMIT $1
75
+ FOR UPDATE OF tt SKIP LOCKED
76
+ ),
77
+ fired AS (
78
+ UPDATE thread_timers tt
79
+ SET fired_count = tt.fired_count + 1,
80
+ next_fire_at = CASE
81
+ WHEN tt.period_seconds IS NULL THEN tt.next_fire_at
82
+ ELSE tt.next_fire_at + make_interval(secs => tt.period_seconds * (
83
+ floor(extract(epoch FROM (now() - tt.next_fire_at)) / tt.period_seconds) + 1
84
+ ))
85
+ END
86
+ FROM due
87
+ WHERE tt.id = due.id
88
+ RETURNING tt.id, tt.thread_id, tt.fired_count
89
+ ),
90
+ queued AS (
91
+ INSERT INTO thread_timer_firings (id, timer_id, thread_id, iteration)
92
+ SELECT gen_random_uuid(), fired.id, fired.thread_id, fired.fired_count FROM fired
93
+ ON CONFLICT ON CONSTRAINT "UQ_thread_timer_firings_iteration" DO NOTHING
94
+ RETURNING thread_id
95
+ )
96
+ SELECT DISTINCT thread_id FROM queued`, [FIRE_BATCH_SIZE]);
97
+ return rows.map((row) => row.thread_id);
98
+ }
99
+ async emitThreadUpdated(threadId, actorId) {
100
+ const thread = await this.dataSource
101
+ .getRepository(thread_entity_1.ThreadEntity)
102
+ .findOne({ where: { id: threadId } });
103
+ if (!thread) {
104
+ return;
105
+ }
106
+ this.eventEmitter.emit(threads_events_1.ThreadUpdatedEvent.INTERNAL, new threads_events_1.ThreadUpdatedEvent({ id: actorId }, thread));
107
+ }
108
+ };
109
+ exports.ThreadTimersService = ThreadTimersService;
110
+ exports.ThreadTimersService = ThreadTimersService = __decorate([
111
+ (0, common_1.Injectable)(),
112
+ __metadata("design:paramtypes", [typeorm_1.DataSource,
113
+ event_emitter_1.EventEmitter2])
114
+ ], ThreadTimersService);
115
+ function toResult(row) {
116
+ return {
117
+ id: row.id,
118
+ kind: row.kind,
119
+ reason: row.reason,
120
+ periodSeconds: row.period_seconds,
121
+ maxIterations: row.max_iterations,
122
+ firedCount: row.fired_count,
123
+ nextFireAt: new Date(row.next_fire_at),
124
+ createdAt: new Date(row.created_at),
125
+ };
126
+ }
127
+ function isUuid(value) {
128
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
129
+ }
130
+ //# sourceMappingURL=thread-timers.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-timers.service.js","sourceRoot":"","sources":["../../../src/threads/timers/thread-timers.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type Parsed<T> = {
2
+ ok: true;
3
+ value: T;
4
+ } | {
5
+ ok: false;
6
+ error: string;
7
+ };
8
+ export declare const MAX_DURATION_SECONDS: number;
9
+ export declare const MAX_ITERATIONS = 10;
10
+ export declare function parseDuration(input: string): Parsed<number>;
11
+ export declare function parseMaxIterations(input: unknown): Parsed<number>;
12
+ export declare function describeDuration(seconds: number): string;
13
+ export declare function parseInstant(at: string, timezone?: string): Parsed<Date>;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_ITERATIONS = exports.MAX_DURATION_SECONDS = void 0;
4
+ exports.parseDuration = parseDuration;
5
+ exports.parseMaxIterations = parseMaxIterations;
6
+ exports.describeDuration = describeDuration;
7
+ exports.parseInstant = parseInstant;
8
+ exports.MAX_DURATION_SECONDS = 366 * 24 * 60 * 60;
9
+ exports.MAX_ITERATIONS = 10;
10
+ const UNIT_SECONDS = { s: 1, m: 60, h: 3600 };
11
+ const UNIT_NAMES = { s: 'second', m: 'minute', h: 'hour' };
12
+ function parseDuration(input) {
13
+ const match = /^([1-9]\d*)([smh])$/.exec(input.trim());
14
+ if (!match) {
15
+ return {
16
+ ok: false,
17
+ error: `Invalid duration "${input}". Use a positive whole number followed by one unit: s, m or h (for example "30s", "5m", "2h"). Compound values such as "1h30m" are not supported; use "90m".`,
18
+ };
19
+ }
20
+ const seconds = Number(match[1]) * UNIT_SECONDS[match[2]];
21
+ if (seconds > exports.MAX_DURATION_SECONDS) {
22
+ return { ok: false, error: `Duration "${input}" is too long. The longest supported is 366 days.` };
23
+ }
24
+ return { ok: true, value: seconds };
25
+ }
26
+ function parseMaxIterations(input) {
27
+ if (typeof input !== 'number' || !Number.isInteger(input) || input < 1) {
28
+ return { ok: false, error: 'maxIterations must be a positive whole number.' };
29
+ }
30
+ if (input > exports.MAX_ITERATIONS) {
31
+ return { ok: false, error: `maxIterations can be at most ${exports.MAX_ITERATIONS}.` };
32
+ }
33
+ return { ok: true, value: input };
34
+ }
35
+ function describeDuration(seconds) {
36
+ for (const unit of ['h', 'm', 's']) {
37
+ if (seconds % UNIT_SECONDS[unit] === 0) {
38
+ const count = seconds / UNIT_SECONDS[unit];
39
+ return `${count} ${UNIT_NAMES[unit]}${count === 1 ? '' : 's'}`;
40
+ }
41
+ }
42
+ return `${seconds} seconds`;
43
+ }
44
+ const DATETIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
45
+ function parseInstant(at, timezone) {
46
+ const match = DATETIME_PATTERN.exec(at.trim());
47
+ if (!match) {
48
+ return {
49
+ ok: false,
50
+ error: `Invalid datetime "${at}". Use ISO 8601, for example "2026-10-15T12:00:00Z", "2026-10-15T12:00:00+11:00", or "2026-10-15T12:00:00" with a timezone such as "Australia/Sydney".`,
51
+ };
52
+ }
53
+ const [, year, month, day, hour, minute, second = '0', offset] = match;
54
+ const fields = {
55
+ year: Number(year),
56
+ month: Number(month),
57
+ day: Number(day),
58
+ hour: Number(hour),
59
+ minute: Number(minute),
60
+ second: Number(second),
61
+ };
62
+ const asUtc = Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second);
63
+ const check = new Date(asUtc);
64
+ if (check.getUTCFullYear() !== fields.year ||
65
+ check.getUTCMonth() !== fields.month - 1 ||
66
+ check.getUTCDate() !== fields.day ||
67
+ check.getUTCHours() !== fields.hour ||
68
+ check.getUTCMinutes() !== fields.minute ||
69
+ check.getUTCSeconds() !== fields.second) {
70
+ return { ok: false, error: `"${at}" is not a real date and time.` };
71
+ }
72
+ const zone = timezone?.trim() || undefined;
73
+ if (zone && !isValidTimeZone(zone)) {
74
+ return { ok: false, error: `Unknown timezone "${timezone}". Use an IANA name such as "Australia/Sydney" or "UTC".` };
75
+ }
76
+ if (offset) {
77
+ const offsetMinutes = offset === 'Z' ? 0 : parseOffset(offset);
78
+ if (offsetMinutes === null) {
79
+ return {
80
+ ok: false,
81
+ error: `"${at}" has offset ${offset}, which is not a real UTC offset. Offsets run from -12:00 to +14:00 with minutes 00-59.`,
82
+ };
83
+ }
84
+ const instant = new Date(asUtc - offsetMinutes * 60_000);
85
+ if (zone && zoneOffsetMinutes(zone, instant) !== offsetMinutes) {
86
+ return {
87
+ ok: false,
88
+ error: `"${at}" has offset ${offset}, which does not match timezone "${zone}" at that time. Give either the offset or the timezone, not conflicting ones.`,
89
+ };
90
+ }
91
+ return { ok: true, value: instant };
92
+ }
93
+ if (!zone) {
94
+ return {
95
+ ok: false,
96
+ error: `"${at}" has no offset. Add one ("Z" for UTC, or "+10:00") or pass a timezone such as "Australia/Sydney".`,
97
+ };
98
+ }
99
+ const candidateOffsets = new Set([-86_400_000, 0, 86_400_000].map((shift) => zoneOffsetMinutes(zone, new Date(asUtc + shift))));
100
+ const instants = [...candidateOffsets]
101
+ .map((offsetMinutes) => new Date(asUtc - offsetMinutes * 60_000))
102
+ .filter((instant) => zoneOffsetMinutes(zone, instant) * 60_000 === asUtc - instant.getTime());
103
+ const unique = [...new Set(instants.map((instant) => instant.getTime()))];
104
+ if (unique.length === 0) {
105
+ return { ok: false, error: `${at} does not exist in ${zone}: the clocks skip it for daylight saving. Pick another time.` };
106
+ }
107
+ if (unique.length > 1) {
108
+ return {
109
+ ok: false,
110
+ error: `${at} happens twice in ${zone} because of daylight saving. Give an offset to say which one (for example "${at}${formatOffset(zoneOffsetMinutes(zone, new Date(Math.min(...unique))))}").`,
111
+ };
112
+ }
113
+ return { ok: true, value: new Date(unique[0]) };
114
+ }
115
+ function isValidTimeZone(zone) {
116
+ try {
117
+ new Intl.DateTimeFormat('en-US', { timeZone: zone });
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ const MIN_OFFSET_MINUTES = -12 * 60;
125
+ const MAX_OFFSET_MINUTES = 14 * 60;
126
+ function parseOffset(offset) {
127
+ const sign = offset.startsWith('-') ? -1 : 1;
128
+ const [hours, minutes] = offset.slice(1).split(':').map(Number);
129
+ if (minutes > 59) {
130
+ return null;
131
+ }
132
+ const total = sign * (hours * 60 + minutes);
133
+ if (total < MIN_OFFSET_MINUTES || total > MAX_OFFSET_MINUTES) {
134
+ return null;
135
+ }
136
+ return total;
137
+ }
138
+ function formatOffset(minutes) {
139
+ const sign = minutes < 0 ? '-' : '+';
140
+ const abs = Math.abs(minutes);
141
+ return `${sign}${String(Math.floor(abs / 60)).padStart(2, '0')}:${String(abs % 60).padStart(2, '0')}`;
142
+ }
143
+ function zoneOffsetMinutes(zone, instant) {
144
+ const parts = new Intl.DateTimeFormat('en-US', {
145
+ timeZone: zone,
146
+ hourCycle: 'h23',
147
+ year: 'numeric',
148
+ month: '2-digit',
149
+ day: '2-digit',
150
+ hour: '2-digit',
151
+ minute: '2-digit',
152
+ second: '2-digit',
153
+ }).formatToParts(instant);
154
+ const get = (type) => Number(parts.find((part) => part.type === type)?.value);
155
+ const wallAsUtc = Date.UTC(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'), get('second'));
156
+ const wholeSeconds = Math.floor(instant.getTime() / 1000) * 1000;
157
+ return Math.round((wallAsUtc - wholeSeconds) / 60_000);
158
+ }
159
+ //# sourceMappingURL=timer-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer-input.js","sourceRoot":"","sources":["../../../src/threads/timers/timer-input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const timer_input_1 = require("./timer-input");
4
+ describe('timer input', () => {
5
+ describe('parseDuration', () => {
6
+ it.each([
7
+ ['5s', 5],
8
+ ['5m', 300],
9
+ ['2h', 7200],
10
+ [' 90m ', 5400],
11
+ ])('reads %p as %p seconds', (input, seconds) => {
12
+ expect((0, timer_input_1.parseDuration)(input)).toEqual({ ok: true, value: seconds });
13
+ });
14
+ it.each(['1h30m', '0m', '-5m', '5', 'm', '5d', '1.5h', '05m', ''])('rejects %p', (input) => {
15
+ expect((0, timer_input_1.parseDuration)(input).ok).toBe(false);
16
+ });
17
+ it('rejects durations longer than a year', () => {
18
+ expect((0, timer_input_1.parseDuration)('9000h').ok).toBe(false);
19
+ });
20
+ });
21
+ describe('parseMaxIterations', () => {
22
+ it('accepts positive whole numbers', () => {
23
+ expect((0, timer_input_1.parseMaxIterations)(5)).toEqual({ ok: true, value: 5 });
24
+ });
25
+ it('accepts up to 10', () => {
26
+ expect((0, timer_input_1.parseMaxIterations)(10)).toEqual({ ok: true, value: 10 });
27
+ });
28
+ it.each([0, -1, 1.5, '5', undefined, 11])('rejects %p', (input) => {
29
+ expect((0, timer_input_1.parseMaxIterations)(input).ok).toBe(false);
30
+ });
31
+ });
32
+ describe('describeDuration', () => {
33
+ it.each([
34
+ [300, '5 minutes'],
35
+ [3600, '1 hour'],
36
+ [90, '90 seconds'],
37
+ [5400, '90 minutes'],
38
+ ])('describes %p seconds as %p', (seconds, text) => {
39
+ expect((0, timer_input_1.describeDuration)(seconds)).toBe(text);
40
+ });
41
+ });
42
+ describe('parseInstant', () => {
43
+ const iso = (at, timezone) => {
44
+ const result = (0, timer_input_1.parseInstant)(at, timezone);
45
+ return result.ok ? result.value.toISOString() : result.error;
46
+ };
47
+ it('reads UTC and offset timestamps', () => {
48
+ expect(iso('2026-10-15T12:00:00Z')).toBe('2026-10-15T12:00:00.000Z');
49
+ expect(iso('2026-10-15T12:00:00+11:00')).toBe('2026-10-15T01:00:00.000Z');
50
+ expect(iso('2026-10-15T12:00-03:30')).toBe('2026-10-15T15:30:00.000Z');
51
+ });
52
+ it('reads a local time in a timezone', () => {
53
+ expect(iso('2026-10-15T12:00:00', 'Australia/Sydney')).toBe('2026-10-15T01:00:00.000Z');
54
+ expect(iso('2026-07-15T12:00:00', 'Australia/Sydney')).toBe('2026-07-15T02:00:00.000Z');
55
+ });
56
+ it('accepts an offset that agrees with the timezone', () => {
57
+ expect(iso('2026-10-15T12:00:00+11:00', 'Australia/Sydney')).toBe('2026-10-15T01:00:00.000Z');
58
+ });
59
+ it('rejects an offset that conflicts with the timezone', () => {
60
+ expect((0, timer_input_1.parseInstant)('2026-10-15T12:00:00+10:00', 'Australia/Sydney').ok).toBe(false);
61
+ });
62
+ it('rejects a local time with no timezone', () => {
63
+ expect((0, timer_input_1.parseInstant)('2026-10-15T12:00:00').ok).toBe(false);
64
+ });
65
+ it('rejects a local time that daylight saving skips', () => {
66
+ const result = (0, timer_input_1.parseInstant)('2026-10-04T02:30:00', 'Australia/Sydney');
67
+ expect(result.ok).toBe(false);
68
+ expect(!result.ok && result.error).toContain('does not exist');
69
+ });
70
+ it('rejects a local time that daylight saving repeats', () => {
71
+ const result = (0, timer_input_1.parseInstant)('2026-04-05T02:30:00', 'Australia/Sydney');
72
+ expect(result.ok).toBe(false);
73
+ expect(!result.ok && result.error).toContain('happens twice');
74
+ });
75
+ it('accepts the extreme real offsets', () => {
76
+ expect(iso('2026-10-15T12:00:00+14:00')).toBe('2026-10-14T22:00:00.000Z');
77
+ expect(iso('2026-10-15T12:00:00-12:00')).toBe('2026-10-16T00:00:00.000Z');
78
+ expect(iso('2026-10-15T12:00:00+05:45')).toBe('2026-10-15T06:15:00.000Z');
79
+ });
80
+ it.each(['+24:00', '+11:60', '-13:00', '+14:01', '+99:99'])('rejects the invalid offset %p', (offset) => {
81
+ const result = (0, timer_input_1.parseInstant)(`2026-10-15T12:00:00${offset}`);
82
+ expect(result.ok).toBe(false);
83
+ expect(!result.ok && result.error).toContain('not a real UTC offset');
84
+ });
85
+ it('rejects unknown timezones, impossible dates and other formats', () => {
86
+ expect((0, timer_input_1.parseInstant)('2026-10-15T12:00:00', 'Mars/Olympus').ok).toBe(false);
87
+ expect((0, timer_input_1.parseInstant)('2026-02-30T12:00:00Z').ok).toBe(false);
88
+ expect((0, timer_input_1.parseInstant)('tomorrow at noon').ok).toBe(false);
89
+ });
90
+ });
91
+ });
92
+ //# sourceMappingURL=timer-input.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer-input.spec.js","sourceRoot":"","sources":["../../../src/threads/timers/timer-input.spec.ts"],"names":[],"mappings":"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"}