@framers/agentos-ext-triggers 0.0.0-stage → 0.1.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 +98 -0
- package/README.md +198 -2
- package/dist/contract.d.ts +262 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/cron.d.ts +36 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +125 -0
- package/dist/cron.js.map +1 -0
- package/dist/housekeeping.d.ts +20 -0
- package/dist/housekeeping.d.ts.map +1 -0
- package/dist/housekeeping.js +33 -0
- package/dist/housekeeping.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/names.d.ts +9 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +9 -0
- package/dist/names.js.map +1 -0
- package/dist/report.d.ts +27 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +94 -0
- package/dist/report.js.map +1 -0
- package/dist/rows.d.ts +87 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +52 -0
- package/dist/rows.js.map +1 -0
- package/dist/scheduler.d.ts +44 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +743 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/store/ids.d.ts +3 -0
- package/dist/store/ids.d.ts.map +1 -0
- package/dist/store/ids.js +6 -0
- package/dist/store/ids.js.map +1 -0
- package/dist/store/migrations.d.ts +30 -0
- package/dist/store/migrations.d.ts.map +1 -0
- package/dist/store/migrations.js +26 -0
- package/dist/store/migrations.js.map +1 -0
- package/dist/store/prefix.d.ts +23 -0
- package/dist/store/prefix.d.ts.map +1 -0
- package/dist/store/prefix.js +37 -0
- package/dist/store/prefix.js.map +1 -0
- package/dist/store/sql/index.d.ts +21 -0
- package/dist/store/sql/index.d.ts.map +1 -0
- package/dist/store/sql/index.js +157 -0
- package/dist/store/sql/index.js.map +1 -0
- package/dist/watcher.d.ts +28 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +279 -0
- package/dist/watcher.js.map +1 -0
- package/dist/words.d.ts +12 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +57 -0
- package/dist/words.js.map +1 -0
- package/manifest.json +39 -0
- package/package.json +68 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
10
|
+
|
|
11
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
12
|
+
|
|
13
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
14
|
+
|
|
15
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
16
|
+
|
|
17
|
+
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
18
|
+
|
|
19
|
+
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
20
|
+
|
|
21
|
+
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
22
|
+
|
|
23
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
24
|
+
|
|
25
|
+
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
26
|
+
|
|
27
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
28
|
+
|
|
29
|
+
2. Grant of Copyright License.
|
|
30
|
+
|
|
31
|
+
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
32
|
+
|
|
33
|
+
3. Grant of Patent License.
|
|
34
|
+
|
|
35
|
+
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
36
|
+
|
|
37
|
+
4. Redistribution.
|
|
38
|
+
|
|
39
|
+
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
40
|
+
|
|
41
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
42
|
+
|
|
43
|
+
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
44
|
+
|
|
45
|
+
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
46
|
+
|
|
47
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
48
|
+
|
|
49
|
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
50
|
+
|
|
51
|
+
5. Submission of Contributions.
|
|
52
|
+
|
|
53
|
+
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
54
|
+
|
|
55
|
+
6. Trademarks.
|
|
56
|
+
|
|
57
|
+
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
58
|
+
|
|
59
|
+
7. Disclaimer of Warranty.
|
|
60
|
+
|
|
61
|
+
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
62
|
+
|
|
63
|
+
8. Limitation of Liability.
|
|
64
|
+
|
|
65
|
+
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
66
|
+
|
|
67
|
+
9. Accepting Warranty or Additional Liability.
|
|
68
|
+
|
|
69
|
+
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
70
|
+
|
|
71
|
+
END OF TERMS AND CONDITIONS
|
|
72
|
+
|
|
73
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
74
|
+
|
|
75
|
+
To apply the Apache License to your work, attach the following
|
|
76
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
77
|
+
replaced with your own identifying information. (Don't include
|
|
78
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
79
|
+
comment syntax for the file format. We also recommend that a
|
|
80
|
+
file or class name and description of purpose be included on the
|
|
81
|
+
same "printed page" as the copyright notice for easier
|
|
82
|
+
identification within third-party archives.
|
|
83
|
+
|
|
84
|
+
Copyright (c) 2025 Framers
|
|
85
|
+
|
|
86
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
87
|
+
you may not use this file except in compliance with the License.
|
|
88
|
+
You may obtain a copy of the License at
|
|
89
|
+
|
|
90
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
91
|
+
|
|
92
|
+
Unless required by applicable law or agreed to in writing, software
|
|
93
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
94
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
95
|
+
See the License for the specific language governing permissions and
|
|
96
|
+
limitations under the License.
|
|
97
|
+
|
|
98
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,199 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @framers/agentos-ext-triggers
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/framerslab/agentos-extensions/actions)
|
|
4
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
|
+
|
|
6
|
+
A scheduler of record in a product's own database, through the SQL storage adapter it already passes. Jobs are defined in code and registered when the process starts; their rows, their runs and the due times they missed live in three tables under a prefix of the product's choosing. A lease with a claim token gives each due time one run across processes, a missed-run rule decides what a job does with the due times it missed while its process was down or it was paused, and staff pause, resume and run a job by hand through the rows, with an audit hook the product writes its own record through. A stall watcher in a process that runs no jobs tells the product when a job goes quiet and when it runs again, and loops that run on their own timer report their runs here, so one page shows every job.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { computeNextRunAtMs, migrateTriggers, scheduleWords } from '@framers/agentos-ext-triggers';
|
|
10
|
+
|
|
11
|
+
await migrateTriggers(db, { prefix: 'trig', lockKey: 724313571 });
|
|
12
|
+
|
|
13
|
+
const next = computeNextRunAtMs({ kind: 'cron', expr: '20 0 * * *' }, Date.now()); // the next 00:20 UTC
|
|
14
|
+
const words = scheduleWords({ kind: 'cron', expr: '20 0 * * *' }); // "Daily at 00:20 UTC"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What the package holds
|
|
18
|
+
|
|
19
|
+
| Part | What it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `createScheduler` | Registration, the restore at start with the missed-run rules, the tick, the lease with a claim token, the fenced settlement, retries, pause, resume, run now, the reads, `forgetActor`, `report()` for reports-only jobs and the hourly retention. |
|
|
22
|
+
| `createSchedulerForTests` | The same scheduler with no interval armed: a host's tests drive `tick()` and `settled()` on their own clock. |
|
|
23
|
+
| `createWatcher` | The stall watcher for a process that runs no jobs: a stall told once, its recovery told once, a job that stays failed told again once a day. |
|
|
24
|
+
| `migrateTriggers` | Builds `<prefix>_jobs`, `<prefix>_runs`, `<prefix>_missed` and their migration ledger, `<prefix>_schema_migrations`, on PostgreSQL and SQLite. |
|
|
25
|
+
| `computeNextRunAtMs` | The next due time of a schedule strictly after a given time, or `undefined` when there is none. |
|
|
26
|
+
| `scheduleWords` | The sentence a schedule reads as on a jobs page. |
|
|
27
|
+
| The types | `Schedule`, `MissedRunRule`, `JobDefinition`, `RunContext`, `RunSummary`, `JobState`, `RunOutcome`, `JobView`, `RunView`, `MissedView`, `TriggersRefusal`, `TriggersAuditEntry`, `TriggersLogger`, `Keep`, `TriggersOptions`, `Scheduler`, `SchedulerInternals`, `Watcher`, `WatcherFailedAgain`, `WatcherOptions`. |
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @framers/agentos-ext-triggers @framers/sql-storage-adapter
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Node 22 or later. `@framers/sql-storage-adapter` 0.7.15 or later is an optional peer: the package loads it when `migrateTriggers` runs, so a process that only reads schedules never needs it.
|
|
36
|
+
|
|
37
|
+
## Adopting it
|
|
38
|
+
|
|
39
|
+
1. **Migrate.** The product's migration command calls `migrateTriggers` after the operations package's tables and before its own migrations, with the lock key its own runner takes:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
await migrateTriggers(db, { prefix: 'trig', lockKey });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It builds the three tables and the ledger under the prefix; run again, it applies nothing. A prefix is a lower-case letter followed by at most 32 lower-case letters, digits or underscores, `trig` when none is given; on SQLite, `sqlite` and a prefix beginning `sqlite_` are refused, since SQLite keeps those names for itself. The lock key is 724313571 when none is given, the adapter runner's own default, which the operations package takes too, so runners that share a database share a key.
|
|
46
|
+
|
|
47
|
+
2. **Build** one scheduler in each process that runs jobs, and one in each process that only reads and commands, which it never starts:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createScheduler } from '@framers/agentos-ext-triggers';
|
|
51
|
+
|
|
52
|
+
const scheduler = createScheduler({
|
|
53
|
+
db, // the product's StorageAdapter
|
|
54
|
+
prefix: 'trig',
|
|
55
|
+
process: 'sweeper', // the process word its leases carry
|
|
56
|
+
logger, // info, warn, error
|
|
57
|
+
audit: (trx, entry) => writeStaffEvent(trx, entry), // pause, resume and run now, inside the command's transaction
|
|
58
|
+
describe: (error) => ({ kind: errorKind(error) }), // a failed try's kind as a word
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`now` (the clock, `Date.now` by default), `tickMs` (10 s by default, the smallest `every`), `handRunsPerHour` (20 by default) and `keep` (the retention, below) are the other options.
|
|
63
|
+
|
|
64
|
+
3. **Register** each job before `start()`:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
scheduler.register({
|
|
68
|
+
name: 'ticket_routing', // a lower-case letter, then at most 63 lower-case letters, digits or underscores
|
|
69
|
+
title: 'Ticket routing',
|
|
70
|
+
process: 'sweeper',
|
|
71
|
+
schedule: { kind: 'every', everyMs: 300_000 },
|
|
72
|
+
rule: 'catch_up_once', // or 'skip', or 'required'
|
|
73
|
+
safeToRepeat: true, // offers run now; a lost run is run again
|
|
74
|
+
stallAfterMs: 900_000,
|
|
75
|
+
run: async ({ signal }) => {
|
|
76
|
+
const routed = await routeWaitingTickets({ signal });
|
|
77
|
+
return { did: `${routed} routed` };
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`register` throws on a name outside the rule, a duplicate name, an `every` below the tick, a cron expression the parser refuses, and a job marked `legalDuty` whose rule is not `required`. `retries` (5), `backoffMs` (30 s doubling to 10 minutes) and `timeoutMs` (10 minutes) take those defaults when left out.
|
|
83
|
+
|
|
84
|
+
4. **Start** after the product's other registrations, and stop inside the process's SIGTERM grace:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
await scheduler.start();
|
|
88
|
+
process.on('SIGTERM', () => void scheduler.stop({ waitMs: 5_000 }));
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`start()` writes each definition into its row, restores every job (below) and arms the tick. A second call while it runs waits for the same start and arms nothing more. `stop()` stops claiming (a tick in progress finishes first, and a claim it takes after `stop()` began starts no run and frees the job), holds the lease of each run in flight through `waitMs`, waits up to `waitMs` for those runs and then aborts their signals; a run that settles in the wait releases its lease, and one that does not keeps the held lease until it runs out two ticks after the wait, when the next claim marks it `lost`.
|
|
92
|
+
|
|
93
|
+
5. **Watch** from a process that runs no jobs, so a stall is seen while the process that runs them is down (below):
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { createWatcher } from '@framers/agentos-ext-triggers';
|
|
97
|
+
|
|
98
|
+
const watcher = createWatcher({ db, prefix: 'trig', process: 'web', logger });
|
|
99
|
+
watcher.onStalled(async (job, since) => openRequest(job.name, since)); // answers the request's id, stored on the job's row
|
|
100
|
+
watcher.onRecovered(async (job, since, ref) => resolveRequest(ref));
|
|
101
|
+
watcher.onFailedAgain(async (job, days) => alertAgain(job.name, days));
|
|
102
|
+
watcher.start();
|
|
103
|
+
process.on('SIGTERM', () => void watcher.stop());
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A process that only reads and commands calls `list`, `get`, `runs`, `missed`, `pause`, `resume` and `runNow` on its unstarted scheduler. `pause(name, actor, note?)` and `resume(name, actor)` answer the job's view or a refusal; `runNow(name, actor)` answers `{ queued: true }` or a refusal, and the process that runs the job runs it within one tick. A refusal is `{ refused, status }`: `not_found` (404), `already`, `in_flight`, `not_safe`, `reports_only` (409), and `hand_runs_used` (429, with `retryAt`, the next whole UTC hour), past `handRunsPerHour` hand runs in one UTC clock hour on the instance; two asks at once never both pass the bound.
|
|
107
|
+
|
|
108
|
+
## What the scheduler guarantees
|
|
109
|
+
|
|
110
|
+
- **One run per due time across processes.** A run starts only under a claim: a conditional update that writes the process's lease and a fresh 128-bit token on a job that is due and free. Two processes on one PostgreSQL database give one run per due time; SQLite is one process by the adapter's design.
|
|
111
|
+
- **A run that outlives its lease never settles over its successor.** Every write a settlement makes is conditional on its token, so a run whose lease another process took changes no job row; it is left open for that process's claim, which reads it `lost` whenever the first run's body ended. A job not marked `safeToRepeat` is paused by the system after a lost run, never run again on its own. A hand run's row is written under the job row's lock, so an account erased by `forgetActor` before the run row is written is on neither the row nor the run's context. A claim whose lease another process took before its run row was written runs nothing. Between a claim and its run row (one database round trip), a job's `lease.since` reads the claim's time; a command that writes the job row in that gap moves it to the command's time, never ahead of now, until the run row is written.
|
|
112
|
+
- **A restart restores every job and loses no row.** Due times that passed while no process ran a job are written to `<prefix>_missed` with cause `worker_down` and follow the job's rule: `skip` records each one skipped; `catch_up_once` records them skipped but the latest, which one run catches up; `required` catches up every one, the run's `covers` naming them. The rows and the move of the job's next due time land in one transaction, and a due time is recorded once whatever number of processes start at once. A catch-up runs on a due time the schedule had, never on "now". A next due time within the last two ticks is left to the claim, never recorded missed, since a process still running the job claims it within a tick. A one-shot first registered after its time is owed at once and follows its rule. A due time whose run's process died holding the lease is never recorded missed: the claim reads that run `lost` first, then runs the due time again or, for a job not safe to repeat, pauses it. Past 1,000 missed due times, `skip` and `catch_up_once` record the newest 1,000 and count the rest in the log line; `required` records the oldest 1,000 and each catch-up's settlement records the next batch.
|
|
113
|
+
- **Retries serve the due time their first try served.** A failed try below `retries` waits its backoff and runs again on the same due time, a restart past the backoff included; at the bound the job is paused by the system, reads `failed`, and `onFailed` fires once. `resume` clears it, and records the due times passed while it was paused (cause `paused`) by the job's rule. A resume before the next due time keeps it, and a run claimed before the pause keeps the due time it serves; a run that settles while its job is paused records the due times passed in the pause by the rule, so the resume owes the same catch-up whichever comes first.
|
|
114
|
+
- **A changed definition wins over a run in flight.** A run claimed under an older definition settles onto the rule stored by the newest registration and keeps the next due time that registration set, so a rolling restart never writes the old schedule back.
|
|
115
|
+
- **A hand run never doubles a run in flight and never runs a job not marked safe.** It leaves the schedule, the tries and a pause as they were.
|
|
116
|
+
- **Commands decide by their write.** Each of `pause`, `resume` and `runNow` is a conditional update; two at once on one job give one change and one `already`. The `audit` hook writes inside the command's transaction only after its row changed, and a hook that throws rolls the command back.
|
|
117
|
+
- **A timeout is seen at the first tick at or past it**, so a job's `signal` aborts (reason `timeout`) within one tick of its `timeoutMs`, and the try counts as failed when the body settles. The lease is renewed each tick until then.
|
|
118
|
+
- **A renewal the database refuses aborts the run's `signal` with `lease_lost` at once**, before another process can take the job.
|
|
119
|
+
|
|
120
|
+
## What a job body must do
|
|
121
|
+
|
|
122
|
+
- Honour `signal`: stop its work when it aborts (`timeout`, `lease_lost` or `stop`).
|
|
123
|
+
- When it is not safe to repeat, write `runId` and `dueAt` beside its effects, so a run after a lost one can tell what was done.
|
|
124
|
+
- Answer `{ outcome: 'failed' }`, or throw or reject (with any value, `undefined` included), for a failed try; `{ outcome: 'skipped' }` when there was nothing to do; `{ did }` for the words the jobs page shows. Words and integers only: never a person's name, address or message.
|
|
125
|
+
- Never outlive its timeout.
|
|
126
|
+
|
|
127
|
+
## The stall watcher
|
|
128
|
+
|
|
129
|
+
A job is stalled when it has gone quiet for longer than its `stallAfterMs` (15 minutes by default). A reports-only job is quiet since its last report. A scheduled job is quiet only while it is owed a run (its next due time, or its retry's readiness, has come), and since its last run (its registration when it never ran) or the time it became owed, whichever is later: a job due less often than its gap, a one-shot registered days ahead and a retry waiting out its backoff read stalled only once they have stayed owed for longer than their gap with no run, never while a due time that came less than a gap ago waits for its run. `createWatcher` judges every job, scheduled and reports-only, from a process that runs none of them, so a stall is seen while the process that runs them is down. Several processes may run a watcher; a pass runs under the watch lease, a row of its own named `__watch` (never listed, never commanded), so one pass at a time judges, across processes: the pass renews the lease every half interval and before each job, a host's slow handler included, and a pass whose lease another holder took judges no further job. Each handler call is bounded by `handlerTimeoutMs` (30 seconds by default): a call that has not settled by then is given up as a throw (one `watch_handler_timed_out` line), so a handler that never settles never holds the pass or its lease, and the next pass calls it again. It passes at `start()` and then every `everyMs` (a minute by default).
|
|
130
|
+
|
|
131
|
+
- **A stall is told once.** The pass marks the stall on the job's row (`stall_since`, the stall's start: the last run's time, or the registration time of a job never run), then calls `onStalled(job, since)` and stores the reference the handler answers (the host's request id, say) as `stall_ref`. The reference is stored only once every handler settled: a handler that throws leaves the stall marked and unacknowledged, and the next pass calls it again, so the host's handler is idempotent by the job's name and `since` (it finds the record it made before opening one). A handler that answers `null` acknowledges the stall with no reference. A second watcher and a restarted one find the stall stored and tell nothing.
|
|
132
|
+
- **A recovery is told once.** When the job runs again, `onRecovered(job, since, ref)` is called with the stored reference (`null` when the stall was acknowledged with none), and the stall is cleared only once every handler settled; a handler that throws is called again on the next pass with the same reference. A job that ran again before its stall was acknowledged is told the stall first, so the recovery carries the reference the host made; while that stays unacknowledged the stall is kept.
|
|
133
|
+
- **What is never judged stalled.** A paused job and a failed job, whose state already says what is wrong; a scheduled job with nothing owed: a one-shot that ran, or a job whose next due time or retry is still ahead; and a job whose row changed after the pass read it (a run settled, a pause, a failure, or a registration that moved its next due time ahead or changed its gap): the mark is one conditional update on the row as the pass judged it.
|
|
134
|
+
- **A job that stays failed is told again once a day.** `onFailedAgain(job, days)` is called when the job's `alerted_at` (the time of its last alert) is a day old, with the whole days since it failed; `alerted_at` moves to now only once every handler settled, so a handler that throws is called again on the next pass. The handler keys its alert by the day count. The row is read again at the job's turn, so a job resumed since the pass began is not told.
|
|
135
|
+
|
|
136
|
+
A pass that fails (the database cannot be reached) writes one `watch_failed` line through the logger, and the next pass tries again; no handler's throw stops the pass. `stop()` clears the interval and waits for a pass in progress, which frees the watch lease; a watcher that dies inside a pass leaves the lease to run out two intervals later.
|
|
137
|
+
|
|
138
|
+
## Reports-only jobs
|
|
139
|
+
|
|
140
|
+
A loop that runs on its own timer (a box's cron line, a loop inside the product's own process) reports each run, so the jobs page shows it and the watcher judges it:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
scheduler.report('backup_dump', { title: 'Backup', process: 'box', everyMs: 86_400_000 }, 'ok');
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The scheduler keeps a row of kind `reports_only` and a run row for it, written once a minute at most a job: reports in between are coalesced in memory, so a loop may report each second. A `failed` report after a written `ok` or `skipped` is written at once, so the row never reads ok over a failure its loop reported (two writes a minute at most), and a job's writes land in the order they began. The row's stall gap is the facts' `stallAfterMs`, else twice `everyMs`, and never under three minutes, since a healthy loop's row can read up to a minute old. `report()` never throws and never waits: a name outside the job-name rule, facts no schedule can have (an interval or a gap that is not a whole number of milliseconds above 0), an outcome other than `ok`, `failed` or `skipped`, and a store that refuses the write log one warning (`report_refused` or `report_failed`); a report refused before the store holds no minute, and a report under the name of a scheduled job changes nothing of that job. `settled()` on a scheduler from `createSchedulerForTests` waits for the writes a report began. A reports-only job has no lease, no pause and no hand run: `pause`, `resume` and `runNow` answer `reports_only` (409), and a tick never claims it.
|
|
147
|
+
|
|
148
|
+
## Retention
|
|
149
|
+
|
|
150
|
+
| Rows | Kept | Who deletes |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `<prefix>_runs` | the newest 100 a job, and those of the last 14 days | the scheduler's tick, once an hour at most |
|
|
153
|
+
| `<prefix>_missed` | the newest 50 a job, and those of the last 7 days | the same |
|
|
154
|
+
| a staff account's id in `<prefix>_jobs` and `<prefix>_runs` | until the product deletes that account | the product's deletion step calls `scheduler.forgetActor(trx, accountId)` |
|
|
155
|
+
|
|
156
|
+
A run still running and a missed due time still owed to a catch-up are kept whatever their age and number, so a `required` catch-up that fails and retries still covers every due time it owes. `keep` (`runsPerJob`, `runDays`, `missedPerJob`, `missedDays`) replaces each default it gives; a field that is not a whole number above 0 throws a `RangeError` when the scheduler is built.
|
|
157
|
+
|
|
158
|
+
## Privacy
|
|
159
|
+
|
|
160
|
+
Nothing in these tables is a person's data beyond a staff account id in three columns: `paused_by` and `hand_run_by` on a job's row and `by_hand` on a run. `forgetActor` nulls them, or makes a pause's actor `system`, when the product deletes that staff account. Everything else is a job's name, title and process word, its schedule, times, counts, a run's outcome and error kind, the `did` words a job answers (words and integers, never a person's name, address or message) and the reference a host's stall handler answers. No customer's deletion touches these tables.
|
|
161
|
+
|
|
162
|
+
## Schedules
|
|
163
|
+
|
|
164
|
+
Three kinds. Every time is milliseconds since the epoch, and every clock is UTC.
|
|
165
|
+
|
|
166
|
+
| Kind | Shape | The next due time after `afterMs` |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `at` | `{ kind: 'at', at }` | `at`, when it is later than `afterMs`; otherwise none. |
|
|
169
|
+
| `every` | `{ kind: 'every', everyMs, anchorMs? }` | `afterMs + everyMs`; with an anchor, the next step of the anchor's grid, or the anchor itself while it is ahead. An interval that is not a positive integer has none. |
|
|
170
|
+
| `cron` | `{ kind: 'cron', expr }` | The next whole minute whose UTC fields match. |
|
|
171
|
+
|
|
172
|
+
A cron expression has five fields: the minute (0 to 59), the hour (0 to 23), the day of the month (1 to 31), the month (1 to 12) and the day of the week (0 to 6, 0 for Sunday). A field is `*`, a number, a range `a-b`, a step `*/n`, `a-b/n` or `a/n` (from `a` to the field's end), or a comma list of these. Every value, range and step is checked when the expression is parsed: a step of 0, a value outside its field, a reversed range, an empty part, a word or a count of fields other than five throws a `RangeError` named `CronRefused`, which says the reason and the expression. A restricted day of the month and a restricted day of the week must both match (`0 0 13 * 5` is a Friday the 13th), where [crontab(5)](https://man7.org/linux/man-pages/man5/crontab.5.html) runs a line when either matches. The search steps a day at a time over one 400-year Gregorian cycle, after which the calendar repeats, so a match years away is found (`30 6 29 2 1`, 29 February on a Monday, comes 40 years apart across 2100) and an expression no day can match answers `undefined` (`0 0 31 2 *`). It reads the UTC fields only, so neither the host's zone nor its daylight-saving changes move a due time.
|
|
173
|
+
|
|
174
|
+
`scheduleWords` reads a schedule as one of these forms:
|
|
175
|
+
|
|
176
|
+
| Schedule | Words |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `{ kind: 'every', everyMs: 300_000 }` | Every 5 minutes |
|
|
179
|
+
| `{ kind: 'every', everyMs: 8 * 86_400_000 }` | Every 8 days |
|
|
180
|
+
| `{ kind: 'cron', expr: '*/15 * * * *' }` | Every 15 minutes |
|
|
181
|
+
| `{ kind: 'cron', expr: '40 * * * *' }` | Every hour at :40 |
|
|
182
|
+
| `{ kind: 'cron', expr: '20 0 * * *' }` | Daily at 00:20 UTC |
|
|
183
|
+
| `{ kind: 'cron', expr: '23 8 * * 1' }` | Weekly on Monday at 08:23 UTC |
|
|
184
|
+
| `{ kind: 'cron', expr: '0 6 1 * *' }` | Monthly on day 1 at 06:00 UTC |
|
|
185
|
+
| `{ kind: 'at', at: Date.UTC(2026, 10, 4, 9, 0) }` | Once, 4 Nov 2026 09:00 UTC |
|
|
186
|
+
|
|
187
|
+
Any other cron expression reads `Cron <expr> UTC`. A minute step reads as an interval only when it divides the hour: `*/7` runs at :56 and again at :00, so it reads `Cron */7 * * * * UTC`. `scheduleWords` throws a `RangeError` for an interval that is not a whole number of milliseconds above 0 or a time that is not finite, and `CronRefused` for a cron expression the parser refuses, so it never reads them as words. `register` checks more (`requireSchedule`: an interval of at least `tickMs`, a finite anchor), so a schedule these words describe, such as `{ kind: 'every', everyMs: 300_000, anchorMs: NaN }` ("Every 5 minutes"), can still be refused there.
|
|
188
|
+
|
|
189
|
+
## Tests
|
|
190
|
+
|
|
191
|
+
The suite runs every table case on SQLite through sql.js in memory and, when `AGENTOS_TEST_POSTGRES_URL` names a server, on PostgreSQL; without it the PostgreSQL half shows as skipped under its reason. The repository's CI runs both, PostgreSQL 16 among its services. The cron cases run under UTC and, in `test/cron-tz.test.ts`, under `America/New_York` across its fall-back on 1 November 2026. `test/scheduler.test.ts` drives the tick by hand on a fake clock (`createSchedulerForTests` gives a host the same); its cases that need two processes at once run their calls at once on PostgreSQL and one after the other on SQLite. `test/watcher.test.ts`, `test/report.test.ts` and `test/housekeeping.test.ts` drive the watcher's passes and the tick by hand on the same clock.
|
|
192
|
+
|
|
193
|
+
## Releases
|
|
194
|
+
|
|
195
|
+
New versions are published from [framerslab/agentos-extensions](https://github.com/framerslab/agentos-extensions) by GitHub Actions. The first version publishes through the release workflow's npm token, since npm configures a trusted publisher only for a package it holds; every later version publishes through npm trusted publishing, with a provenance statement, once that publisher is configured ([RELEASING.md](https://github.com/framerslab/agentos-extensions/blob/master/RELEASING.md)).
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
Apache-2.0
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The package's types: a job's definition, its schedule and missed-run rule, the
|
|
3
|
+
* context a run is given and the summary it answers, the views the reads answer, the refusals the
|
|
4
|
+
* commands answer, the options `createScheduler` and `createWatcher` take, and the `Scheduler` and
|
|
5
|
+
* `Watcher` surfaces. Every shape is `readonly`. Times are milliseconds since the epoch; every
|
|
6
|
+
* schedule is UTC.
|
|
7
|
+
*/
|
|
8
|
+
import type { SchemaMigrationReport, StorageAdapter } from '@framers/sql-storage-adapter';
|
|
9
|
+
/** When a job runs. Times are milliseconds since the epoch; cron is five fields, UTC. `every` is the scheduler's tick at least, 10 s by default. */
|
|
10
|
+
export type Schedule = {
|
|
11
|
+
readonly kind: 'at';
|
|
12
|
+
readonly at: number;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'every';
|
|
15
|
+
readonly everyMs: number;
|
|
16
|
+
readonly anchorMs?: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'cron';
|
|
19
|
+
readonly expr: string;
|
|
20
|
+
};
|
|
21
|
+
/** What a job does with the runs it missed while its process was down or it was paused. */
|
|
22
|
+
export type MissedRunRule = 'catch_up_once' | 'skip' | 'required';
|
|
23
|
+
/** A job as the host defines it in code and registers before `start()`: its name, title, process, schedule and missed-run rule, its bounds, and the function a run calls. */
|
|
24
|
+
export interface JobDefinition {
|
|
25
|
+
/** The row's key and the page's address: `[a-z][a-z0-9_]{0,63}`. */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/** The title the page shows: "Ticket routing". */
|
|
28
|
+
readonly title: string;
|
|
29
|
+
/** The process word of the definition's host: `sweeper`, `web`, `ops-worker`. */
|
|
30
|
+
readonly process: string;
|
|
31
|
+
readonly schedule: Schedule;
|
|
32
|
+
readonly rule: MissedRunRule;
|
|
33
|
+
/** The gap after which the job counts as stalled; 900_000 by default. */
|
|
34
|
+
readonly stallAfterMs?: number;
|
|
35
|
+
/** Safe to run twice, two runs at once included (a lease lost across a cut can overlap a run). Run now is offered only when true. */
|
|
36
|
+
readonly safeToRepeat: boolean;
|
|
37
|
+
/** Tries before the job pauses itself; 5 by default. */
|
|
38
|
+
readonly retries?: number;
|
|
39
|
+
/** The wait before try n; 30 s doubling to 10 minutes by default. */
|
|
40
|
+
readonly backoffMs?: (attempt: number) => number;
|
|
41
|
+
/** A run's bound; 600_000 by default. At it the signal is aborted and the try counts as failed (`timeout`); the lease is renewed each tick until the promise settles. */
|
|
42
|
+
readonly timeoutMs?: number;
|
|
43
|
+
/** A timed deletion, a retention or required mail: `rule` must be `required`, and `register` refuses otherwise. */
|
|
44
|
+
readonly legalDuty?: true;
|
|
45
|
+
run(context: RunContext): Promise<RunSummary | void>;
|
|
46
|
+
}
|
|
47
|
+
/** What a run is given: the time, the job's view, the try's number, who asked for a hand run, the due times it covers, its id and due time, the signal the timeout aborts and the logger. */
|
|
48
|
+
export interface RunContext {
|
|
49
|
+
readonly now: number;
|
|
50
|
+
readonly job: JobView;
|
|
51
|
+
readonly attempt: number;
|
|
52
|
+
/** The staff account that asked for a hand run, or null on a scheduled run. */
|
|
53
|
+
readonly byHand: {
|
|
54
|
+
readonly accountId: string;
|
|
55
|
+
} | null;
|
|
56
|
+
/** The due times this run covers: one on a scheduled run; every missed one on a `required` catch-up. */
|
|
57
|
+
readonly covers: readonly number[];
|
|
58
|
+
/** The run's id and the due time it serves (a hand run's: the time it was asked for): the idempotency key a job not safe to repeat writes beside its effects. */
|
|
59
|
+
readonly runId: string;
|
|
60
|
+
readonly dueAt: number;
|
|
61
|
+
readonly signal: AbortSignal;
|
|
62
|
+
readonly logger: TriggersLogger;
|
|
63
|
+
}
|
|
64
|
+
/** What the page's "Did" column reads: words and integers, never a person's anything. */
|
|
65
|
+
export interface RunSummary {
|
|
66
|
+
readonly did?: string;
|
|
67
|
+
readonly counts?: Readonly<Record<string, number>>;
|
|
68
|
+
/** `failed` counts as a try exactly as a throw does; `skipped` records the run as skipped (another holder, nothing to do); `ok` when left out. */
|
|
69
|
+
readonly outcome?: 'ok' | 'skipped' | 'failed';
|
|
70
|
+
}
|
|
71
|
+
/** A job's state as the page shows it, read from its row and its newest runs. */
|
|
72
|
+
export type JobState = 'ok' | 'running' | 'failed' | 'missed' | 'stalled' | 'paused' | 'no_run';
|
|
73
|
+
/** A run's outcome: `running` until it settles. */
|
|
74
|
+
export type RunOutcome = 'ok' | 'failed' | 'skipped' | 'running';
|
|
75
|
+
/** A job as the reads answer it: its definition's facts, its state, its pause, its last run, its next due time, its lease, its retries and failure, a hand run asked for, and its missed runs of the last 24 hours. */
|
|
76
|
+
export interface JobView {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly title: string;
|
|
79
|
+
readonly process: string;
|
|
80
|
+
readonly kind: 'scheduled' | 'reports_only';
|
|
81
|
+
readonly schedule: Schedule | null;
|
|
82
|
+
/** "Every 5 minutes", "Every hour at :40", "Daily at 00:20 UTC", "Once, 4 Nov 2026 09:00 UTC": the package's words, which the product may replace. */
|
|
83
|
+
readonly scheduleWords: string;
|
|
84
|
+
readonly rule: MissedRunRule;
|
|
85
|
+
readonly stallAfterMs: number;
|
|
86
|
+
readonly safeToRepeat: boolean;
|
|
87
|
+
readonly retries: number;
|
|
88
|
+
readonly legalDuty: boolean;
|
|
89
|
+
readonly state: JobState;
|
|
90
|
+
readonly paused: {
|
|
91
|
+
readonly by: string;
|
|
92
|
+
readonly at: number;
|
|
93
|
+
readonly note: string | null;
|
|
94
|
+
} | null;
|
|
95
|
+
readonly lastRun: {
|
|
96
|
+
readonly at: number;
|
|
97
|
+
readonly outcome: RunOutcome;
|
|
98
|
+
readonly ms: number | null;
|
|
99
|
+
readonly did: string | null;
|
|
100
|
+
} | null;
|
|
101
|
+
readonly nextDueAt: number | null;
|
|
102
|
+
readonly lease: {
|
|
103
|
+
readonly holder: string;
|
|
104
|
+
readonly since: number;
|
|
105
|
+
} | null;
|
|
106
|
+
readonly failed: {
|
|
107
|
+
readonly tries: number;
|
|
108
|
+
readonly lastAt: number;
|
|
109
|
+
readonly kind: string;
|
|
110
|
+
readonly alertedAt: number | null;
|
|
111
|
+
} | null;
|
|
112
|
+
/** A failed try below the job's retry bound: the tries used, the last try's error kind as a word, the time of the next try. `failed` stays null until the tries are used. */
|
|
113
|
+
readonly retrying: {
|
|
114
|
+
readonly tries: number;
|
|
115
|
+
readonly kind: string;
|
|
116
|
+
readonly nextAt: number;
|
|
117
|
+
} | null;
|
|
118
|
+
/** A hand run asked for and not yet claimed: the staff account that asked and when. */
|
|
119
|
+
readonly handRun: {
|
|
120
|
+
readonly by: string;
|
|
121
|
+
readonly at: number;
|
|
122
|
+
} | null;
|
|
123
|
+
readonly missed24h: number;
|
|
124
|
+
}
|
|
125
|
+
/** One run as the reads answer it, newest first. */
|
|
126
|
+
export interface RunView {
|
|
127
|
+
readonly id: string;
|
|
128
|
+
readonly job: string;
|
|
129
|
+
readonly startedAt: number;
|
|
130
|
+
readonly endedAt: number | null;
|
|
131
|
+
readonly outcome: RunOutcome;
|
|
132
|
+
readonly ms: number | null;
|
|
133
|
+
readonly tries: number;
|
|
134
|
+
/** The error's kind as the host's `describe` names it (`QueryFailed`, `timeout`, `refused`, `lost`), else `error`. */
|
|
135
|
+
readonly kind: string | null;
|
|
136
|
+
readonly did: string | null;
|
|
137
|
+
readonly byHand: string | null;
|
|
138
|
+
}
|
|
139
|
+
/** One missed due time: why it was missed, whether it was caught up or skipped, and the run that caught it up. */
|
|
140
|
+
export interface MissedView {
|
|
141
|
+
readonly dueAt: number;
|
|
142
|
+
readonly cause: 'worker_down' | 'paused';
|
|
143
|
+
readonly action: 'caught_up' | 'skipped';
|
|
144
|
+
readonly actedAt: number | null;
|
|
145
|
+
readonly runId: string | null;
|
|
146
|
+
}
|
|
147
|
+
/** A command's refusal: the reason as a word, the HTTP status a host answers with, and, for `hand_runs_used`, when the next hand run is allowed. */
|
|
148
|
+
export type TriggersRefusal = {
|
|
149
|
+
readonly refused: 'not_found' | 'not_safe' | 'in_flight' | 'hand_runs_used' | 'reports_only' | 'already';
|
|
150
|
+
readonly status: number;
|
|
151
|
+
readonly retryAt?: number;
|
|
152
|
+
};
|
|
153
|
+
/** The record of a staff command, written through the host's hook inside the command's transaction. */
|
|
154
|
+
export interface TriggersAuditEntry {
|
|
155
|
+
readonly kind: 'job_paused' | 'job_resumed' | 'job_run';
|
|
156
|
+
readonly staffAccountId: string;
|
|
157
|
+
/** The job's name. */
|
|
158
|
+
readonly subject: string;
|
|
159
|
+
readonly detail: Readonly<Record<string, string | number>>;
|
|
160
|
+
}
|
|
161
|
+
/** The host's logger: three levels, a message and fields. */
|
|
162
|
+
export interface TriggersLogger {
|
|
163
|
+
info(message: string, fields?: Record<string, unknown>): void;
|
|
164
|
+
warn(message: string, fields?: Record<string, unknown>): void;
|
|
165
|
+
error(message: string, fields?: Record<string, unknown>): void;
|
|
166
|
+
}
|
|
167
|
+
/** How long the runs and missed runs are kept: the newest `runsPerJob` runs a job and those of the last `runDays` days, the same for missed runs. */
|
|
168
|
+
export interface Keep {
|
|
169
|
+
readonly runsPerJob: number;
|
|
170
|
+
readonly runDays: number;
|
|
171
|
+
readonly missedPerJob: number;
|
|
172
|
+
readonly missedDays: number;
|
|
173
|
+
}
|
|
174
|
+
/** The options `createScheduler` takes: the adapter, the table prefix, the process word, the logger and clock, the tick, the retention, the hand-run bound, and the host's error and audit hooks. */
|
|
175
|
+
export interface TriggersOptions {
|
|
176
|
+
readonly db: StorageAdapter;
|
|
177
|
+
/** The table prefix, `trig` by default; the rule the operations package's `requirePrefix` applies. */
|
|
178
|
+
readonly prefix?: string;
|
|
179
|
+
readonly process: string;
|
|
180
|
+
readonly logger: TriggersLogger;
|
|
181
|
+
readonly now?: () => number;
|
|
182
|
+
/** 10_000 by default; the smallest `every`. */
|
|
183
|
+
readonly tickMs?: number;
|
|
184
|
+
/** The retention of runs and missed runs; each field left out takes its default. */
|
|
185
|
+
readonly keep?: Partial<Keep>;
|
|
186
|
+
/** Hand runs an hour on the instance; 20 by default. */
|
|
187
|
+
readonly handRunsPerHour?: number;
|
|
188
|
+
/** The error's kind, as the operations `describe` hook answers it. */
|
|
189
|
+
describe?(error: unknown): {
|
|
190
|
+
readonly kind: string;
|
|
191
|
+
} | null;
|
|
192
|
+
audit?(trx: StorageAdapter, entry: TriggersAuditEntry): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
/** What `createScheduler` answers: registration and start for the process that runs the jobs, the commands and reads every process may call, the run hooks and the actor's erasure. */
|
|
195
|
+
export interface Scheduler {
|
|
196
|
+
/** Before `start()`; throws on a duplicate name, a schedule below the tick, or a legal-duty job whose rule is not `required`. */
|
|
197
|
+
register(definition: JobDefinition): void;
|
|
198
|
+
/** A job that runs on its own timer and reports its runs here: upserts its `reports_only` row and a run a minute at most. Never throws, never waits. */
|
|
199
|
+
report(name: string, facts: {
|
|
200
|
+
readonly title: string;
|
|
201
|
+
readonly process: string;
|
|
202
|
+
readonly everyMs: number;
|
|
203
|
+
readonly stallAfterMs?: number;
|
|
204
|
+
readonly legalDuty?: true;
|
|
205
|
+
}, outcome: 'ok' | 'failed' | 'skipped'): void;
|
|
206
|
+
/** Restores the rows, applies the missed-run rules, arms the tick. */
|
|
207
|
+
start(): Promise<void>;
|
|
208
|
+
/** Stops claiming and the tick, waits up to `waitMs` (5 s by default, inside the host's SIGTERM grace) for the runs in flight, then aborts their signals; a run that settles in the wait releases its lease through the fenced settlement, and one that does not stops being renewed when the process exits, so its lease expires within two ticks and the run reads `lost` at the next claim. */
|
|
209
|
+
stop(options?: {
|
|
210
|
+
readonly waitMs?: number;
|
|
211
|
+
}): Promise<void>;
|
|
212
|
+
pause(name: string, actor: {
|
|
213
|
+
readonly accountId: string;
|
|
214
|
+
}, note?: string): Promise<JobView | TriggersRefusal>;
|
|
215
|
+
resume(name: string, actor: {
|
|
216
|
+
readonly accountId: string;
|
|
217
|
+
}): Promise<JobView | TriggersRefusal>;
|
|
218
|
+
runNow(name: string, actor: {
|
|
219
|
+
readonly accountId: string;
|
|
220
|
+
}): Promise<{
|
|
221
|
+
readonly queued: true;
|
|
222
|
+
} | TriggersRefusal>;
|
|
223
|
+
list(filter?: {
|
|
224
|
+
readonly state?: JobState | 'attention';
|
|
225
|
+
readonly process?: string;
|
|
226
|
+
readonly cursor?: string;
|
|
227
|
+
}): Promise<{
|
|
228
|
+
readonly jobs: readonly JobView[];
|
|
229
|
+
readonly cursor: string | null;
|
|
230
|
+
}>;
|
|
231
|
+
get(name: string): Promise<JobView | null>;
|
|
232
|
+
runs(name: string, options?: {
|
|
233
|
+
readonly limit?: number;
|
|
234
|
+
}): Promise<readonly RunView[]>;
|
|
235
|
+
missed(name: string, options?: {
|
|
236
|
+
readonly sinceMs?: number;
|
|
237
|
+
}): Promise<readonly MissedView[]>;
|
|
238
|
+
/** After each run has settled; the operations package writes `ops_beats` here. */
|
|
239
|
+
onRun(handler: (job: JobView, run: RunView) => void): () => void;
|
|
240
|
+
/** Once, when a job's tries are used. */
|
|
241
|
+
onFailed(handler: (job: JobView, run: RunView) => void): () => void;
|
|
242
|
+
/** Nulls the staff account id on every row that names it (a staff account's deletion). */
|
|
243
|
+
forgetActor(trx: StorageAdapter, accountId: string): Promise<void>;
|
|
244
|
+
}
|
|
245
|
+
/** The stall watcher's extra duty: a failed job whose `alerted_at` is 24 hours old gets one more alert; the handler is given the day count since it failed. */
|
|
246
|
+
export interface WatcherFailedAgain {
|
|
247
|
+
onFailedAgain(handler: (job: JobView, days: number) => Promise<void>): () => void;
|
|
248
|
+
}
|
|
249
|
+
/** The stall watcher for a process that runs no jobs (the web): under its own lease row it finds stalled jobs and calls `onStalled` once per stall, and `onRecovered` when the job runs again. */
|
|
250
|
+
export interface Watcher extends WatcherFailedAgain {
|
|
251
|
+
start(): void;
|
|
252
|
+
stop(): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* The handler answers the host's reference for the stall (the request's id), which the watcher stores on the job's row (`stall_ref`). Paused and failed jobs are never judged stalled.
|
|
255
|
+
* `since` is the stall's start: the last run's time, or the registration time of a job never run; the host keys its record by `(name, since)`, so a handler called again for one stall finds the record it made.
|
|
256
|
+
*/
|
|
257
|
+
onStalled(handler: (job: JobView, since: number) => Promise<string | null>): () => void;
|
|
258
|
+
onRecovered(handler: (job: JobView, stalledSince: number, ref: string | null) => Promise<void>): () => void;
|
|
259
|
+
}
|
|
260
|
+
/** What `migrateTriggers` answers: the adapter runner's own report. */
|
|
261
|
+
export type TriggersMigrationReport = SchemaMigrationReport;
|
|
262
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE1F,oJAAoJ;AACpJ,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElE,6KAA6K;AAC7K,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,qIAAqI;IACrI,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,yKAAyK;IACzK,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,mHAAmH;IACnH,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC;IAC1B,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACtD;AAED,6LAA6L;AAC7L,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvD,wGAAwG;IACxG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,iKAAiK;IACjK,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,kJAAkJ;IAClJ,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,QAAQ,CAAC;CAChD;AAED,iFAAiF;AACjF,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAChG,mDAAmD;AACnD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAEjE,uNAAuN;AACvN,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,cAAc,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC,sJAAsJ;IACtJ,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACnG,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACxI,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACtI,6KAA6K;IAC7K,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrG,uFAAuF;IACvF,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,oDAAoD;AACpD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sHAAsH;IACtH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,kHAAkH;AAClH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,oJAAoJ;AACpJ,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/L,uGAAuG;AACvG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAChE;AAED,qJAAqJ;AACrJ,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,qMAAqM;AACrM,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,sGAAsG;IACtG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,uLAAuL;AACvL,MAAM,WAAW,SAAS;IACxB,iIAAiI;IACjI,QAAQ,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1C,wJAAwJ;IACxJ,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;KAAE,EAAE,OAAO,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;IACnN,sEAAsE;IACtE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,kYAAkY;IAClY,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,eAAe,CAAC,CAAC;IAC9G,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,GAAG,eAAe,CAAC,CAAC;IAChG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,GAAG,eAAe,CAAC,CAAC;IAClH,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAChM,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IACvF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;IAC9F,kFAAkF;IAClF,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACpE,0FAA0F;IAC1F,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED,+JAA+J;AAC/J,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;CACnF;AAED,kMAAkM;AAClM,MAAM,WAAW,OAAQ,SAAQ,kBAAkB;IACjD,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;IACxF,WAAW,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;CAC7G;AAED,uEAAuE;AACvE,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,CAAC"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":""}
|