@phis/contracts 0.7.3 → 0.7.4
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/README.md +8 -1
- package/dist/addon.d.ts +57 -6
- package/dist/addon.js +10 -0
- package/dist/capabilities.d.ts +54 -4
- package/dist/core.d.ts +23 -2
- package/dist/core.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,14 @@ erased at compile time apart from a handful of constants.
|
|
|
61
61
|
What it covers:
|
|
62
62
|
|
|
63
63
|
- **Manifest** -- `PhisAddonManifestV1`: identity, version, required Core capabilities, the routes an
|
|
64
|
-
Add-on claims, the services it provides.
|
|
64
|
+
Add-on claims, the services it provides, the jobs it runs and the events it listens to or emits.
|
|
65
|
+
- **Jobs and events** -- a job carries a `PhisAddonJobSchedule` (`every` one of
|
|
66
|
+
`PHIS_ADDON_JOB_INTERVALS`, five minutes at the shortest, with `at` and `weekday` for the daily and
|
|
67
|
+
weekly steps) that Core keeps: each moment of a schedule is run by exactly one instance. An event an
|
|
68
|
+
Add-on emits is a `PhisAddonEmittedEventId`, its own id in front (`@scope/name:what.happened`),
|
|
69
|
+
declared under `emits` and sent through the `events` capability (`PhisEventsCapabilityV1`); a listener
|
|
70
|
+
receives a `PhisAddonEmittedEventSubject` with emitter, time, hop count, dedupe key and facts. Core's
|
|
71
|
+
own events keep their bare names. `PHIS_ADDON_EVENT_MAX_HOPS` bounds a chain of handlers emitting.
|
|
65
72
|
- **Runtime** -- `PhisAddonRuntimeV1`, `PhisAddonHandler`, `PhisAddonRequestContext`. The artifact
|
|
66
73
|
exports it under the name `phisAddon` (`PhisAddonRuntimeModuleV1`).
|
|
67
74
|
- **Schema** -- `PhisAddonSchemaDescriptor` and its column, index and constraint descriptors. Tables are
|
package/dist/addon.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* back a constant, it is in the wrong package.
|
|
13
13
|
*/
|
|
14
14
|
import type { PhisAddonCapabilities, PhisAddonEventHandler, PhisAddonJobHandler, PhisSiteAccess } from "./capabilities.js";
|
|
15
|
-
import type { PhisAddonEventId, PhisCoreCapabilityId, PhisServiceKind } from "./core.js";
|
|
15
|
+
import type { PhisAddonEmittedEventId, PhisAddonEventId, PhisCoreCapabilityId, PhisServiceKind } from "./core.js";
|
|
16
16
|
import type { PhisAddonQueryCatalog } from "./queries.js";
|
|
17
17
|
import type { PhisAddonSchemaDescriptor } from "./schema.js";
|
|
18
18
|
export declare const PHIS_ADDON_MANIFEST_VERSION: 1;
|
|
@@ -101,17 +101,61 @@ export type PhisAddonServiceProviderDescriptor = {
|
|
|
101
101
|
factoryExport: string;
|
|
102
102
|
requiredCoreCapabilities: PhisCoreCapabilityId[];
|
|
103
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* The steps a job may run at. Every step divides the hour or the day, so the moments a schedule names
|
|
106
|
+
* fall on the clock -- `5m` is :00, :05, :10 -- and every instance of Core computes the same moment
|
|
107
|
+
* from the same clock without asking another.
|
|
108
|
+
*
|
|
109
|
+
* Nothing shorter than five minutes: a job that has to look more often than that is polling for an
|
|
110
|
+
* event, and an event is what it should be given. Jobs run per Site, so a short step multiplies by
|
|
111
|
+
* the Sites of a stage, and the services a job reads from have quotas of their own.
|
|
112
|
+
*/
|
|
113
|
+
export declare const PHIS_ADDON_JOB_INTERVALS: readonly ["5m", "15m", "30m", "hourly", "daily", "weekly"];
|
|
114
|
+
export type PhisAddonJobInterval = (typeof PHIS_ADDON_JOB_INTERVALS)[number];
|
|
115
|
+
/**
|
|
116
|
+
* When a job wants to run. Core keeps it: the schedule is a promise, not a note for the operator.
|
|
117
|
+
*
|
|
118
|
+
* A schedule is a series of moments, and each moment is claimed by exactly one instance of Core --
|
|
119
|
+
* the claim is a row whose key is the moment, so the database, not a leader, decides who runs. A
|
|
120
|
+
* moment nobody claimed while every instance was down is run once at the next start, the latest one
|
|
121
|
+
* only; a job does not receive a week of missed runs after a week of downtime. A job still running
|
|
122
|
+
* when its next moment comes has that moment recorded as skipped rather than run beside itself, which
|
|
123
|
+
* is why a handler must finish well inside its own step.
|
|
124
|
+
*/
|
|
125
|
+
export type PhisAddonJobSchedule = {
|
|
126
|
+
every: PhisAddonJobInterval;
|
|
127
|
+
/**
|
|
128
|
+
* The time of day, `HH:MM` in UTC, for `daily` and `weekly`; absent means `00:00`. Not for the shorter
|
|
129
|
+
* steps, which fall on the clock by themselves.
|
|
130
|
+
*/
|
|
131
|
+
at?: string;
|
|
132
|
+
/** The ISO weekday for `weekly`, 1 (Monday) to 7 (Sunday); absent means Monday. */
|
|
133
|
+
weekday?: number;
|
|
134
|
+
};
|
|
104
135
|
export type PhisAddonJobDescriptor = {
|
|
105
136
|
id: string;
|
|
106
137
|
handler: string;
|
|
107
138
|
/**
|
|
108
|
-
*
|
|
139
|
+
* When Core runs this job. Absent means it runs only when an operator starts it with `phis addon
|
|
140
|
+
* job`, which also runs a scheduled job out of turn.
|
|
109
141
|
*
|
|
110
|
-
*
|
|
111
|
-
* one intended run. The operator schedules `phis addon job run`, which is one runner by construction,
|
|
112
|
-
* and this field tells them what the Add-on had in mind.
|
|
142
|
+
* Optional like every field added after this manifest version was in the wild.
|
|
113
143
|
*/
|
|
114
|
-
|
|
144
|
+
schedule?: PhisAddonJobSchedule;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* One event this Add-on announces under its own name.
|
|
148
|
+
*
|
|
149
|
+
* Declared like a job or a route: the id, its emitter's own id in front, is part of the manifest and
|
|
150
|
+
* therefore of the digest, so what an installed Add-on can say to the others is readable at install.
|
|
151
|
+
* Emitting an undeclared event is refused. `audience` says who may listen: every Add-on of the Site
|
|
152
|
+
* that declares a handler for it, which is the default, because the operator installed each of them;
|
|
153
|
+
* or `self`, for an Add-on that uses the outbox to hand work to its own handler after the fact.
|
|
154
|
+
*/
|
|
155
|
+
export type PhisAddonEmittedEventDescriptor = {
|
|
156
|
+
id: PhisAddonEmittedEventId;
|
|
157
|
+
description?: string;
|
|
158
|
+
audience?: "stage" | "self";
|
|
115
159
|
};
|
|
116
160
|
/**
|
|
117
161
|
* One setting an operator may configure for this Add-on.
|
|
@@ -259,6 +303,13 @@ export type PhisAddonManifestV1 = {
|
|
|
259
303
|
* Add-on that declares them and links nothing is told nothing.
|
|
260
304
|
*/
|
|
261
305
|
eventHandlers?: PhisAddonEventHandlerDescriptor[];
|
|
306
|
+
/**
|
|
307
|
+
* The events this Add-on emits. Absent means none, and an emit of anything is refused.
|
|
308
|
+
*
|
|
309
|
+
* Optional like `eventHandlers`. Declaring an event is not delivering it: a row is written only for
|
|
310
|
+
* an Add-on of the Site that declares a handler for the id, and none at all where nobody listens.
|
|
311
|
+
*/
|
|
312
|
+
emits?: PhisAddonEmittedEventDescriptor[];
|
|
262
313
|
/**
|
|
263
314
|
* What an operator may configure. Absent means nothing is.
|
|
264
315
|
*
|
package/dist/addon.js
CHANGED
|
@@ -13,3 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export const PHIS_ADDON_MANIFEST_VERSION = 1;
|
|
15
15
|
export const PHIS_ADDON_ABI_VERSION = 1;
|
|
16
|
+
/**
|
|
17
|
+
* The steps a job may run at. Every step divides the hour or the day, so the moments a schedule names
|
|
18
|
+
* fall on the clock -- `5m` is :00, :05, :10 -- and every instance of Core computes the same moment
|
|
19
|
+
* from the same clock without asking another.
|
|
20
|
+
*
|
|
21
|
+
* Nothing shorter than five minutes: a job that has to look more often than that is polling for an
|
|
22
|
+
* event, and an event is what it should be given. Jobs run per Site, so a short step multiplies by
|
|
23
|
+
* the Sites of a stage, and the services a job reads from have quotas of their own.
|
|
24
|
+
*/
|
|
25
|
+
export const PHIS_ADDON_JOB_INTERVALS = ["5m", "15m", "30m", "hourly", "daily", "weekly"];
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* at the point where it made the mistake.
|
|
13
13
|
*/
|
|
14
14
|
import type { PhisAssetsCapabilityV1 } from "./assets.js";
|
|
15
|
-
import type { PhisAddonThreadEventId } from "./core.js";
|
|
15
|
+
import type { PhisAddonEmittedEventId, PhisAddonThreadEventId } from "./core.js";
|
|
16
16
|
import type { PhisPaymentChangedEventFacts, PhisPaymentNotificationsCapabilityV1, PhisPaymentsCapabilityV1 } from "./payments.js";
|
|
17
17
|
import type { PhisDataCapabilityV1 } from "./queries.js";
|
|
18
18
|
import type { PhisResourceLinksCapabilityV1 } from "./resource-links.js";
|
|
@@ -109,6 +109,52 @@ export type PhisAddonCapabilities = {
|
|
|
109
109
|
paymentNotifications?: PhisPaymentNotificationsCapabilityV1;
|
|
110
110
|
/** Writing to a person about an occasion this Add-on declared; Core owns the frame and the sending. */
|
|
111
111
|
notifications?: PhisNotificationsCapabilityV1;
|
|
112
|
+
/** Announcing an event this Add-on declared under its own name; Core owns the outbox and the delivery. */
|
|
113
|
+
events?: PhisEventsCapabilityV1;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* The events capability: `@phis/server/events:v1`.
|
|
117
|
+
*
|
|
118
|
+
* An Add-on says that something happened; Core writes one outbox row per Add-on of this Site that
|
|
119
|
+
* declares a handler for the event, and delivers them the way it delivers its own events -- after the
|
|
120
|
+
* fact, in order, retried until they arrive. Emitting is only a write to the outbox, so it is cheap and
|
|
121
|
+
* never waits for a listener.
|
|
122
|
+
*
|
|
123
|
+
* `facts` are values about what happened, as JSON, bounded in size, and never references into another
|
|
124
|
+
* Add-on's tables: a listener runs with authority over its own tables only, and a fact it cannot read
|
|
125
|
+
* is a fact it should have been handed. `dedupeKey` makes a repeated emit safe -- a second call with
|
|
126
|
+
* the same key writes nothing for a listener already owed the first.
|
|
127
|
+
*
|
|
128
|
+
* Refused: an event the manifest does not declare under `emits`, a call from an unverified hook, and
|
|
129
|
+
* an emit from a handler already at `PHIS_ADDON_EVENT_MAX_HOPS`. Answers how many listeners were
|
|
130
|
+
* written for; zero means nobody on this Site listens, which is not a failure.
|
|
131
|
+
*/
|
|
132
|
+
export type PhisEventsCapabilityV1 = {
|
|
133
|
+
emit(input: PhisAddonEmitEventInput): Promise<{
|
|
134
|
+
recipients: number;
|
|
135
|
+
}>;
|
|
136
|
+
};
|
|
137
|
+
export type PhisAddonEmitEventInput = {
|
|
138
|
+
event: PhisAddonEmittedEventId;
|
|
139
|
+
facts?: Readonly<Record<string, unknown>>;
|
|
140
|
+
/** Unique within the Site and the event; a second emit with the same key writes nothing new. */
|
|
141
|
+
dedupeKey?: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* What a listener is handed about an event another Add-on -- or the same one -- emitted.
|
|
145
|
+
*
|
|
146
|
+
* The emitter's id says who, `occurredAt` when (the moment the outbox row was written), `hop` how many
|
|
147
|
+
* handlers the chain has passed through so far, and `facts` what the emitter said. `dedupeKey` is the
|
|
148
|
+
* emitter's, where it gave one; the delivery's own key, stable across retries, is `deliveryId` on the
|
|
149
|
+
* context, as for every event.
|
|
150
|
+
*/
|
|
151
|
+
export type PhisAddonEmittedEventSubject = {
|
|
152
|
+
kind: "emitted";
|
|
153
|
+
emitter: string;
|
|
154
|
+
occurredAt: string;
|
|
155
|
+
hop: number;
|
|
156
|
+
dedupeKey: string | null;
|
|
157
|
+
facts: Readonly<Record<string, unknown>>;
|
|
112
158
|
};
|
|
113
159
|
/**
|
|
114
160
|
* The roles capability: `@phis/server/roles:v1`.
|
|
@@ -399,8 +445,9 @@ export type PhisSettingsCapabilityV1 = {
|
|
|
399
445
|
*
|
|
400
446
|
* No request, no headers, and above all no actor: a job is the Add-on acting on its own data, not a
|
|
401
447
|
* person acting through it. That is why the row-level ladder does not apply inside one, and why a job
|
|
402
|
-
* is
|
|
403
|
-
*
|
|
448
|
+
* is started only by Core's scheduler at the moments its `schedule` names, or by an operator with
|
|
449
|
+
* `phis addon job`, and never from a route -- an Add-on that could start its own job from a request
|
|
450
|
+
* would have built itself a way around the ladder.
|
|
404
451
|
*/
|
|
405
452
|
export type PhisAddonJobContext = {
|
|
406
453
|
addonId: string;
|
|
@@ -434,7 +481,10 @@ export type PhisAddonEventFacts = {
|
|
|
434
481
|
threadId: number;
|
|
435
482
|
messageId: number | null;
|
|
436
483
|
};
|
|
437
|
-
} | PhisPaymentChangedEventFacts
|
|
484
|
+
} | PhisPaymentChangedEventFacts | {
|
|
485
|
+
event: PhisAddonEmittedEventId;
|
|
486
|
+
subject: PhisAddonEmittedEventSubject;
|
|
487
|
+
};
|
|
438
488
|
export type PhisAddonEventContext = PhisAddonEventFacts & {
|
|
439
489
|
addonId: string;
|
|
440
490
|
/** This delivery, stable across retries, so a handler can recognise one it already settled. */
|
package/dist/core.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export declare const PHIS_CORE_CAPABILITIES: {
|
|
|
50
50
|
readonly paymentNotifications: "@phis/server/payment-notifications:v1";
|
|
51
51
|
/** Writing to a person about an occasion the Add-on declared, with Core owning frame and transport. */
|
|
52
52
|
readonly notifications: "@phis/server/notifications:v1";
|
|
53
|
+
/** Announcing an event this Add-on declared under its own name, for the Add-ons of this Site that listen. */
|
|
54
|
+
readonly events: "@phis/server/events:v1";
|
|
53
55
|
};
|
|
54
56
|
export type PhisCoreCapabilityId = (typeof PHIS_CORE_CAPABILITIES)[keyof typeof PHIS_CORE_CAPABILITIES];
|
|
55
57
|
/**
|
|
@@ -75,8 +77,27 @@ export declare const PHIS_ADDON_EVENTS: {
|
|
|
75
77
|
readonly paymentChanged: "payment.changed";
|
|
76
78
|
};
|
|
77
79
|
export type PhisAddonThreadEventId = Exclude<(typeof PHIS_ADDON_EVENTS)[keyof typeof PHIS_ADDON_EVENTS], "payment.changed">;
|
|
78
|
-
/**
|
|
79
|
-
export type
|
|
80
|
+
/** The events Core itself emits: the thread events and the Payment event. */
|
|
81
|
+
export type PhisCoreEventId = PhisAddonThreadEventId | PhisPaymentChangedEventFacts["event"];
|
|
82
|
+
/**
|
|
83
|
+
* An event an Add-on emits, named under the Add-on that emits it: `@scope/name:what.happened`.
|
|
84
|
+
*
|
|
85
|
+
* The emitter's own id is the first half, so two Add-ons cannot name the same event and a reader of
|
|
86
|
+
* a manifest sees who announces it without looking anything up. The second half is the Add-on's own
|
|
87
|
+
* word for it, `^[a-z][a-z0-9.-]*$`, the shape Core's own event names have. Core's names stay bare:
|
|
88
|
+
* a bare name is Core's, a prefixed one is an Add-on's, and the two can never be confused.
|
|
89
|
+
*/
|
|
90
|
+
export type PhisAddonEmittedEventId = `@${string}/${string}:${string}`;
|
|
91
|
+
/** Delivered v1 event vocabulary: Core's own events, and what an installed Add-on emits. */
|
|
92
|
+
export type PhisAddonEventId = PhisCoreEventId | PhisAddonEmittedEventId;
|
|
93
|
+
/**
|
|
94
|
+
* How many Add-ons an event may pass through.
|
|
95
|
+
*
|
|
96
|
+
* An Add-on that emits inside an event handler writes an event with the incoming hop plus one. A
|
|
97
|
+
* handler that answers every event with an event, met by another doing the same, would otherwise
|
|
98
|
+
* write forever; at this depth the emit is refused and logged instead of delivered.
|
|
99
|
+
*/
|
|
100
|
+
export declare const PHIS_ADDON_EVENT_MAX_HOPS = 4;
|
|
80
101
|
/**
|
|
81
102
|
* The Core-owned service kinds an Add-on may implement.
|
|
82
103
|
*
|
package/dist/core.js
CHANGED
|
@@ -49,6 +49,8 @@ export const PHIS_CORE_CAPABILITIES = {
|
|
|
49
49
|
paymentNotifications: "@phis/server/payment-notifications:v1",
|
|
50
50
|
/** Writing to a person about an occasion the Add-on declared, with Core owning frame and transport. */
|
|
51
51
|
notifications: "@phis/server/notifications:v1",
|
|
52
|
+
/** Announcing an event this Add-on declared under its own name, for the Add-ons of this Site that listen. */
|
|
53
|
+
events: "@phis/server/events:v1",
|
|
52
54
|
};
|
|
53
55
|
/**
|
|
54
56
|
* What Core tells an Add-on about after the fact.
|
|
@@ -72,6 +74,14 @@ export const PHIS_ADDON_EVENTS = {
|
|
|
72
74
|
threadLinkRemoved: "thread.link.removed",
|
|
73
75
|
paymentChanged: "payment.changed",
|
|
74
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* How many Add-ons an event may pass through.
|
|
79
|
+
*
|
|
80
|
+
* An Add-on that emits inside an event handler writes an event with the incoming hop plus one. A
|
|
81
|
+
* handler that answers every event with an event, met by another doing the same, would otherwise
|
|
82
|
+
* write forever; at this depth the emit is refused and logged instead of delivered.
|
|
83
|
+
*/
|
|
84
|
+
export const PHIS_ADDON_EVENT_MAX_HOPS = 4;
|
|
75
85
|
/**
|
|
76
86
|
* The Core-owned service kinds an Add-on may implement.
|
|
77
87
|
*
|