@toa.io/core 1.0.0-alpha.31 → 1.0.0-alpha.310
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/package.json +28 -12
- package/transpiled/abandon.d.ts +9 -0
- package/transpiled/abandon.js +37 -0
- package/transpiled/abandon.js.map +1 -0
- package/transpiled/assignment.d.ts +6 -0
- package/transpiled/assignment.js +25 -0
- package/transpiled/assignment.js.map +1 -0
- package/transpiled/call.d.ts +10 -0
- package/transpiled/call.js +165 -0
- package/transpiled/call.js.map +1 -0
- package/transpiled/cascade.d.ts +7 -0
- package/transpiled/cascade.js +28 -0
- package/transpiled/cascade.js.map +1 -0
- package/transpiled/component.d.ts +15 -0
- package/transpiled/component.js +148 -0
- package/transpiled/component.js.map +1 -0
- package/transpiled/composition.d.ts +6 -0
- package/transpiled/composition.js +20 -0
- package/transpiled/composition.js.map +1 -0
- package/transpiled/connector.d.ts +71 -0
- package/transpiled/connector.js +248 -0
- package/transpiled/connector.js.map +1 -0
- package/transpiled/context.d.ts +25 -0
- package/transpiled/context.js +55 -0
- package/transpiled/context.js.map +1 -0
- package/transpiled/contract/component.d.ts +24 -0
- package/transpiled/contract/component.js +48 -0
- package/transpiled/contract/component.js.map +1 -0
- package/transpiled/contract/contract.d.ts +10 -0
- package/transpiled/contract/contract.js +14 -0
- package/transpiled/contract/contract.js.map +1 -0
- package/transpiled/contract/index.d.ts +3 -0
- package/transpiled/contract/index.js +4 -0
- package/transpiled/contract/index.js.map +1 -0
- package/transpiled/contract/reply.d.ts +7 -0
- package/transpiled/contract/reply.js +48 -0
- package/transpiled/contract/reply.js.map +1 -0
- package/transpiled/contract/request.d.ts +39 -0
- package/transpiled/contract/request.js +115 -0
- package/transpiled/contract/request.js.map +1 -0
- package/transpiled/contract/schemas.d.ts +4 -0
- package/transpiled/contract/schemas.js +36 -0
- package/transpiled/contract/schemas.js.map +1 -0
- package/transpiled/deliveries.d.ts +22 -0
- package/transpiled/deliveries.js +31 -0
- package/transpiled/deliveries.js.map +1 -0
- package/transpiled/effect.d.ts +7 -0
- package/transpiled/effect.js +13 -0
- package/transpiled/effect.js.map +1 -0
- package/transpiled/emission.d.ts +10 -0
- package/transpiled/emission.js +16 -0
- package/transpiled/emission.js.map +1 -0
- package/transpiled/encoded.d.ts +16 -0
- package/transpiled/encoded.js +25 -0
- package/transpiled/encoded.js.map +1 -0
- package/transpiled/entities/changeset.d.ts +10 -0
- package/transpiled/entities/changeset.js +27 -0
- package/transpiled/entities/changeset.js.map +1 -0
- package/transpiled/entities/entity.d.ts +19 -0
- package/transpiled/entities/entity.js +137 -0
- package/transpiled/entities/entity.js.map +1 -0
- package/transpiled/entities/factory.d.ts +27 -0
- package/transpiled/entities/factory.js +38 -0
- package/transpiled/entities/factory.js.map +1 -0
- package/transpiled/entities/index.d.ts +5 -0
- package/transpiled/entities/index.js +6 -0
- package/transpiled/entities/index.js.map +1 -0
- package/transpiled/entities/newid.d.ts +11 -0
- package/transpiled/entities/newid.js +41 -0
- package/transpiled/entities/newid.js.map +1 -0
- package/transpiled/entities/set.d.ts +10 -0
- package/transpiled/entities/set.js +19 -0
- package/transpiled/entities/set.js.map +1 -0
- package/transpiled/event.d.ts +14 -0
- package/transpiled/event.js +53 -0
- package/transpiled/event.js.map +1 -0
- package/transpiled/exceptions.d.ts +104 -0
- package/transpiled/exceptions.js +205 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/gate.d.ts +40 -0
- package/transpiled/gate.js +84 -0
- package/transpiled/gate.js.map +1 -0
- package/transpiled/guard.d.ts +7 -0
- package/transpiled/guard.js +12 -0
- package/transpiled/guard.js.map +1 -0
- package/transpiled/halting.d.ts +16 -0
- package/transpiled/halting.js +25 -0
- package/transpiled/halting.js.map +1 -0
- package/transpiled/index.d.ts +41 -0
- package/transpiled/index.js +34 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/instance.d.ts +10 -0
- package/transpiled/instance.js +30 -0
- package/transpiled/instance.js.map +1 -0
- package/transpiled/locator.d.ts +12 -0
- package/transpiled/locator.js +32 -0
- package/transpiled/locator.js.map +1 -0
- package/transpiled/measurements.d.ts +20 -0
- package/transpiled/measurements.js +40 -0
- package/transpiled/measurements.js.map +1 -0
- package/transpiled/observation.d.ts +7 -0
- package/transpiled/observation.js +14 -0
- package/transpiled/observation.js.map +1 -0
- package/transpiled/operation.d.ts +69 -0
- package/transpiled/operation.js +174 -0
- package/transpiled/operation.js.map +1 -0
- package/transpiled/outbox.d.ts +45 -0
- package/transpiled/outbox.js +448 -0
- package/transpiled/outbox.js.map +1 -0
- package/transpiled/outbox.measurements.d.ts +4 -0
- package/transpiled/outbox.measurements.js +38 -0
- package/transpiled/outbox.measurements.js.map +1 -0
- package/transpiled/query/criteria.d.ts +6 -0
- package/transpiled/query/criteria.js +74 -0
- package/transpiled/query/criteria.js.map +1 -0
- package/transpiled/query/options.d.ts +3 -0
- package/transpiled/query/options.js +34 -0
- package/transpiled/query/options.js.map +1 -0
- package/transpiled/query.d.ts +9 -0
- package/transpiled/query.js +57 -0
- package/transpiled/query.js.map +1 -0
- package/transpiled/receiver.d.ts +20 -0
- package/transpiled/receiver.js +113 -0
- package/transpiled/receiver.js.map +1 -0
- package/transpiled/reflection.d.ts +9 -0
- package/transpiled/reflection.js +13 -0
- package/transpiled/reflection.js.map +1 -0
- package/transpiled/remote.d.ts +10 -0
- package/transpiled/remote.js +11 -0
- package/transpiled/remote.js.map +1 -0
- package/transpiled/safety.d.ts +5 -0
- package/transpiled/safety.js +26 -0
- package/transpiled/safety.js.map +1 -0
- package/transpiled/state.d.ts +36 -0
- package/transpiled/state.js +131 -0
- package/transpiled/state.js.map +1 -0
- package/transpiled/trail.d.ts +78 -0
- package/transpiled/trail.js +112 -0
- package/transpiled/trail.js.map +1 -0
- package/transpiled/transition.d.ts +14 -0
- package/transpiled/transition.js +51 -0
- package/transpiled/transition.js.map +1 -0
- package/transpiled/transmission.d.ts +8 -0
- package/transpiled/transmission.js +30 -0
- package/transpiled/transmission.js.map +1 -0
- package/transpiled/types/atomicity.d.ts +58 -0
- package/transpiled/types/atomicity.js +2 -0
- package/transpiled/types/atomicity.js.map +1 -0
- package/transpiled/types/bindings.d.ts +77 -0
- package/transpiled/types/bindings.js +2 -0
- package/transpiled/types/bindings.js.map +1 -0
- package/transpiled/types/bridges.d.ts +41 -0
- package/transpiled/types/bridges.js +2 -0
- package/transpiled/types/bridges.js.map +1 -0
- package/transpiled/types/extensions.d.ts +116 -0
- package/transpiled/types/extensions.js +2 -0
- package/transpiled/types/extensions.js.map +1 -0
- package/transpiled/types/inbox.d.ts +22 -0
- package/transpiled/types/inbox.js +2 -0
- package/transpiled/types/inbox.js.map +1 -0
- package/transpiled/types/index.d.ts +14 -0
- package/transpiled/types/index.js +9 -0
- package/transpiled/types/index.js.map +1 -0
- package/transpiled/types/message.d.ts +16 -0
- package/transpiled/types/message.js +2 -0
- package/transpiled/types/message.js.map +1 -0
- package/transpiled/types/operations.d.ts +15 -0
- package/transpiled/types/operations.js +2 -0
- package/transpiled/types/operations.js.map +1 -0
- package/transpiled/types/outbox.d.ts +54 -0
- package/transpiled/types/outbox.js +2 -0
- package/transpiled/types/outbox.js.map +1 -0
- package/transpiled/types/receiver.d.ts +9 -0
- package/transpiled/types/receiver.js +2 -0
- package/transpiled/types/receiver.js.map +1 -0
- package/transpiled/types/request.d.ts +119 -0
- package/transpiled/types/request.js +2 -0
- package/transpiled/types/request.js.map +1 -0
- package/transpiled/types/state.d.ts +9 -0
- package/transpiled/types/state.js +2 -0
- package/transpiled/types/state.js.map +1 -0
- package/transpiled/types/storages.d.ts +146 -0
- package/transpiled/types/storages.js +2 -0
- package/transpiled/types/storages.js.map +1 -0
- package/transpiled/unmanaged.d.ts +5 -0
- package/transpiled/unmanaged.js +7 -0
- package/transpiled/unmanaged.js.map +1 -0
- package/src/assignment.js +0 -31
- package/src/call.js +0 -42
- package/src/cascade.js +0 -35
- package/src/component.js +0 -41
- package/src/composition.js +0 -25
- package/src/connector.js +0 -183
- package/src/context.js +0 -55
- package/src/contract/conditions.js +0 -21
- package/src/contract/index.js +0 -7
- package/src/contract/reply.js +0 -25
- package/src/contract/request.js +0 -67
- package/src/contract/schemas/error.yaml +0 -9
- package/src/contract/schemas/index.js +0 -7
- package/src/contract/schemas/query.yaml +0 -30
- package/src/discovery.js +0 -44
- package/src/emission.js +0 -23
- package/src/entities/changeset.js +0 -38
- package/src/entities/entity.js +0 -74
- package/src/entities/factory.js +0 -33
- package/src/entities/index.js +0 -5
- package/src/entities/set.js +0 -15
- package/src/event.js +0 -40
- package/src/exceptions.js +0 -100
- package/src/exposition.js +0 -27
- package/src/index.js +0 -47
- package/src/locator.js +0 -54
- package/src/observation.js +0 -20
- package/src/operation.js +0 -69
- package/src/query/criteria.js +0 -41
- package/src/query/options.js +0 -40
- package/src/query.js +0 -41
- package/src/receiver.js +0 -61
- package/src/reflection.js +0 -28
- package/src/remote.js +0 -17
- package/src/state.js +0 -99
- package/src/transition.js +0 -67
- package/src/transmission.js +0 -33
- package/test/call.fixtures.js +0 -27
- package/test/call.test.js +0 -46
- package/test/component.fixtures.js +0 -21
- package/test/component.test.js +0 -40
- package/test/connector.fixtures.js +0 -40
- package/test/connector.test.js +0 -176
- package/test/context.fixtures.js +0 -18
- package/test/context.test.js +0 -29
- package/test/contract/conditions.test.js +0 -26
- package/test/contract/contract.fixtures.js +0 -33
- package/test/contract/request.test.js +0 -112
- package/test/emission.fixtures.js +0 -17
- package/test/emission.test.js +0 -35
- package/test/entities/entity.fixtures.js +0 -29
- package/test/entities/entity.test.js +0 -65
- package/test/entities/factory.fixtures.js +0 -18
- package/test/entities/factory.test.js +0 -48
- package/test/entities/set.fixtures.js +0 -11
- package/test/entities/set.test.js +0 -12
- package/test/event.fixtures.js +0 -29
- package/test/event.test.js +0 -118
- package/test/locator.test.js +0 -113
- package/test/query.fixtures.js +0 -100
- package/test/query.test.js +0 -86
- package/test/receiver.fixtures.js +0 -23
- package/test/receiver.test.js +0 -95
- package/test/reflection.test.js +0 -30
- package/test/state.fixtures.js +0 -47
- package/test/state.test.js +0 -46
- package/test/transmission.fixtures.js +0 -15
- package/test/transmission.test.js +0 -46
- package/types/bindings.d.ts +0 -40
- package/types/bridges.ts +0 -30
- package/types/component.d.ts +0 -9
- package/types/connector.d.ts +0 -18
- package/types/context.d.ts +0 -21
- package/types/entity.d.ts +0 -46
- package/types/event.d.ts +0 -11
- package/types/exception.d.ts +0 -4
- package/types/extensions.d.ts +0 -29
- package/types/index.ts +0 -14
- package/types/locator.d.ts +0 -13
- package/types/message.d.ts +0 -3
- package/types/operations.d.ts +0 -2
- package/types/query.d.ts +0 -15
- package/types/receiver.d.ts +0 -12
- package/types/reflection.d.ts +0 -14
- package/types/request.d.ts +0 -23
- package/types/state.d.ts +0 -35
- package/types/storages.d.ts +0 -71
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
import { Connector } from './connector.js';
|
|
3
|
+
import { newid } from './entities/newid.js';
|
|
4
|
+
import * as trail from './trail.js';
|
|
5
|
+
import * as measure from './outbox.measurements.js';
|
|
6
|
+
import { environment } from '@toa.io/generic';
|
|
7
|
+
/**
|
|
8
|
+
* Owns the intent to publish. A row is built before the write so that the storage can commit
|
|
9
|
+
* it in the same transaction as the entity; publication then happens off the operation's path,
|
|
10
|
+
* and anything that fails to publish is recovered from the row.
|
|
11
|
+
*
|
|
12
|
+
* The mechanism is a safety net: in a healthy system the row is written, published within
|
|
13
|
+
* milliseconds by the same process, and marked published on that process's next tick.
|
|
14
|
+
*
|
|
15
|
+
* A storage that cannot commit a row atomically has no outbox, and this degrades to the
|
|
16
|
+
* inline emission it replaces.
|
|
17
|
+
*/
|
|
18
|
+
export class Outbox extends Connector {
|
|
19
|
+
#destinations;
|
|
20
|
+
#storage;
|
|
21
|
+
#atom;
|
|
22
|
+
#gap;
|
|
23
|
+
#interval;
|
|
24
|
+
#batch;
|
|
25
|
+
#defer;
|
|
26
|
+
/*
|
|
27
|
+
* Everything a destination is answerable for is held per destination, which is what keeps
|
|
28
|
+
* one from delaying another: a dead one fills its own in-flight cap and the cycle stops
|
|
29
|
+
* handing it rows, while the rest go on being published and settled.
|
|
30
|
+
*/
|
|
31
|
+
/** ids this process has published, by destination, held until a cycle marks them */
|
|
32
|
+
#published = new Map();
|
|
33
|
+
/** in-flight publications, by destination, awaited (with a bound) on close */
|
|
34
|
+
#inflight = new Map();
|
|
35
|
+
/** rows this replica is publishing right now, so a cycle does not pick them up again */
|
|
36
|
+
#publishing = new Map();
|
|
37
|
+
#timer;
|
|
38
|
+
#off;
|
|
39
|
+
#pumping = false;
|
|
40
|
+
/** cycles in a row this replica has been assigned no lane */
|
|
41
|
+
#unassigned = 0;
|
|
42
|
+
#closing = false;
|
|
43
|
+
// eslint-disable-next-line max-params
|
|
44
|
+
constructor(destinations, storage, atom, options = {}) {
|
|
45
|
+
super();
|
|
46
|
+
this.#destinations = destinations;
|
|
47
|
+
this.#storage = storage;
|
|
48
|
+
this.#atom = atom;
|
|
49
|
+
for (const destination of destinations) {
|
|
50
|
+
this.#published.set(destination.name, new Set());
|
|
51
|
+
this.#inflight.set(destination.name, new Set());
|
|
52
|
+
this.#publishing.set(destination.name, new Set());
|
|
53
|
+
}
|
|
54
|
+
this.#interval = number('TOA_OUTBOX_INTERVAL', options.interval, INTERVAL);
|
|
55
|
+
this.#batch = number('TOA_OUTBOX_BATCH', options.batch, BATCH);
|
|
56
|
+
this.#gap = options.gap ?? this.#interval * K;
|
|
57
|
+
this.#defer = environment.get('TOA_OUTBOX_DEFER') === '1';
|
|
58
|
+
// one at a time: an array would link the group it makes rather than its members
|
|
59
|
+
for (const destination of destinations)
|
|
60
|
+
this.depends(destination);
|
|
61
|
+
this.depends(atom);
|
|
62
|
+
if (storage !== undefined)
|
|
63
|
+
this.depends(storage);
|
|
64
|
+
}
|
|
65
|
+
/** whether the storage can commit a row atomically with the entity */
|
|
66
|
+
get durable() {
|
|
67
|
+
return this.#storage?.outbox !== undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* An assignment's images are the write's own, so it hands over an event with neither, and
|
|
71
|
+
* the storage fills them in.
|
|
72
|
+
*/
|
|
73
|
+
row(event) {
|
|
74
|
+
const row = {
|
|
75
|
+
id: newid(),
|
|
76
|
+
lane: this.#lane(),
|
|
77
|
+
published: false,
|
|
78
|
+
pending: Date.now() + this.#gap,
|
|
79
|
+
outstanding: this.#destinations.map((destination) => destination.name),
|
|
80
|
+
event: event
|
|
81
|
+
};
|
|
82
|
+
/*
|
|
83
|
+
* This runs on the operation's own path, which is the only moment the chain is in scope:
|
|
84
|
+
* the pump that publishes the row may be another replica, an hour later. A row that had
|
|
85
|
+
* no chain stores no field.
|
|
86
|
+
*/
|
|
87
|
+
const hops = trail.current()?.hops;
|
|
88
|
+
if (hops !== undefined)
|
|
89
|
+
row.trail = hops;
|
|
90
|
+
return row;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Hands a committed row over. Awaited by the caller only on the legacy path — with an
|
|
94
|
+
* outbox this returns at once and the broker leaves the operation's path.
|
|
95
|
+
*/
|
|
96
|
+
publish(row) {
|
|
97
|
+
// without a durable outbox this is the inline path, and the caller awaits every destination
|
|
98
|
+
if (!this.durable)
|
|
99
|
+
return this.#inline(row);
|
|
100
|
+
/*
|
|
101
|
+
* A publication started while the pump is closing would outlive the emitters it needs,
|
|
102
|
+
* and `comq` waits on a connection that is going rather than failing. The row is already
|
|
103
|
+
* durable, so leaving it is exactly what it is for.
|
|
104
|
+
*/
|
|
105
|
+
if (this.#closing || this.#defer)
|
|
106
|
+
return;
|
|
107
|
+
this.#publish(row);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The inline path, where there is no row to come back to: every destination is attempted and
|
|
111
|
+
* the operation waits for all of them, so a failure reaches whoever wrote.
|
|
112
|
+
*/
|
|
113
|
+
async #inline(row) {
|
|
114
|
+
const sending = this.#destinations
|
|
115
|
+
.filter((destination) => row.outstanding.includes(destination.name))
|
|
116
|
+
.map(async (destination) => destination.emit(row));
|
|
117
|
+
await Promise.all(sending);
|
|
118
|
+
}
|
|
119
|
+
async open() {
|
|
120
|
+
if (!this.durable)
|
|
121
|
+
return;
|
|
122
|
+
if (this.#defer)
|
|
123
|
+
console.warn('Outbox immediate publication is deferred; events are published by the pump only');
|
|
124
|
+
this.#timer = setInterval(() => {
|
|
125
|
+
this.#tick();
|
|
126
|
+
}, this.#interval);
|
|
127
|
+
this.#timer.unref();
|
|
128
|
+
/*
|
|
129
|
+
* A lane changing hands is exactly when rows stranded in it become this replica's to
|
|
130
|
+
* publish, and the cycle would not notice for up to an interval. Being told costs a cycle
|
|
131
|
+
* that finds nothing in the usual case, where the claim arrives once and never changes.
|
|
132
|
+
*/
|
|
133
|
+
this.#off = this.#atom.onassigned(() => {
|
|
134
|
+
this.#tick();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async close() {
|
|
138
|
+
this.#closing = true;
|
|
139
|
+
this.#off?.();
|
|
140
|
+
if (this.#timer !== undefined)
|
|
141
|
+
clearInterval(this.#timer);
|
|
142
|
+
await this.#drain();
|
|
143
|
+
await this.#mark();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Publishes one row and swallows the failure: the row stays unpublished and comes back on a
|
|
147
|
+
* later cycle, which is the whole point of having written it.
|
|
148
|
+
*
|
|
149
|
+
* There is no timeout here on purpose. A publication is a confirmed write to a durable
|
|
150
|
+
* exchange, and `comq` waits for the broker to come back rather than failing — abandoning
|
|
151
|
+
* it would not stop it, it would only mean the row is published twice once it lands. What
|
|
152
|
+
* bounds this instead is the in-flight cap and the drain on close.
|
|
153
|
+
*
|
|
154
|
+
*/
|
|
155
|
+
/** @returns what it started, so a cycle can wait for it before marking */
|
|
156
|
+
#publish(row) {
|
|
157
|
+
const started = [];
|
|
158
|
+
for (const destination of this.#destinations)
|
|
159
|
+
if (this.#due(row, destination.name))
|
|
160
|
+
started.push(this.#send(row, destination));
|
|
161
|
+
return started;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Whether this row is still this process's to send to that destination. A destination the
|
|
165
|
+
* row names and this process does not have is not its to send, and is left where it is.
|
|
166
|
+
*/
|
|
167
|
+
#due(row, destination) {
|
|
168
|
+
if (!row.outstanding.includes(destination))
|
|
169
|
+
return false;
|
|
170
|
+
const published = this.#published.get(destination);
|
|
171
|
+
if (published === undefined)
|
|
172
|
+
return false;
|
|
173
|
+
return !published.has(row.id) && !this.#publishing.get(destination).has(row.id);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Publishes one row to one destination and swallows the failure: the row stays outstanding
|
|
177
|
+
* for it and comes back on a later cycle, which is the whole point of having written it.
|
|
178
|
+
* Nothing here reaches the other destinations of the same row.
|
|
179
|
+
*
|
|
180
|
+
* There is no timeout on purpose. A publication is a confirmed write to a durable exchange,
|
|
181
|
+
* and `comq` waits for the broker to come back rather than failing — abandoning it would not
|
|
182
|
+
* stop it, it would only mean the row is published twice once it lands. What bounds this
|
|
183
|
+
* instead is the in-flight cap, which is per destination so that a dead one cannot stop the
|
|
184
|
+
* cycle from feeding the others, and the drain on close.
|
|
185
|
+
*/
|
|
186
|
+
async #send(row, destination) {
|
|
187
|
+
const name = destination.name;
|
|
188
|
+
const published = this.#published.get(name);
|
|
189
|
+
const publishing = this.#publishing.get(name);
|
|
190
|
+
const inflight = this.#inflight.get(name);
|
|
191
|
+
if (inflight.size >= INFLIGHT || published.size >= PUBLISHED)
|
|
192
|
+
return;
|
|
193
|
+
publishing.add(row.id);
|
|
194
|
+
const sending = destination.emit(row);
|
|
195
|
+
inflight.add(sending);
|
|
196
|
+
try {
|
|
197
|
+
await sending;
|
|
198
|
+
published.add(row.id);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
// the message, not the error: an `Error` has no enumerable own properties, so what
|
|
202
|
+
// reached the log was `{}` and a publication that keeps failing said nothing about why
|
|
203
|
+
measure.failed(name);
|
|
204
|
+
console.warn('Outbox publication failed', {
|
|
205
|
+
row: row.id,
|
|
206
|
+
destination: name,
|
|
207
|
+
message: error?.message
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
inflight.delete(sending);
|
|
212
|
+
publishing.delete(row.id);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* `comq` retries a publish for as long as the broker is down rather than rejecting, so an
|
|
217
|
+
* unbounded drain outlives any grace period.
|
|
218
|
+
*
|
|
219
|
+
*/
|
|
220
|
+
async #drain() {
|
|
221
|
+
const inflight = [...this.#inflight.values()].flatMap((set) => [...set]);
|
|
222
|
+
if (inflight.length === 0)
|
|
223
|
+
return;
|
|
224
|
+
await Promise.race([Promise.allSettled(inflight), delay(DRAIN)]);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Reads what is due, publishes it, and marks everything this process has sent — what it just
|
|
228
|
+
* published and what the immediate path published since the last cycle. One cycle at a time.
|
|
229
|
+
*
|
|
230
|
+
*/
|
|
231
|
+
#tick() {
|
|
232
|
+
if (this.#pumping)
|
|
233
|
+
return;
|
|
234
|
+
this.#pumping = true;
|
|
235
|
+
void this.#pump().finally(() => (this.#pumping = false));
|
|
236
|
+
}
|
|
237
|
+
async #pump() {
|
|
238
|
+
this.#assignment();
|
|
239
|
+
let page;
|
|
240
|
+
let after;
|
|
241
|
+
// what this cycle saw, so that a destination it read nothing for is set to zero rather
|
|
242
|
+
// than left holding whatever the cycle before it found
|
|
243
|
+
const counts = new Map();
|
|
244
|
+
const oldest = new Map();
|
|
245
|
+
do {
|
|
246
|
+
page = await this.#read(after);
|
|
247
|
+
if (page.length === 0)
|
|
248
|
+
break;
|
|
249
|
+
// so a page is never read twice
|
|
250
|
+
after = page[page.length - 1]?.id;
|
|
251
|
+
/*
|
|
252
|
+
* A row is unpublished in the database until a cycle marks it, so a page includes what
|
|
253
|
+
* this replica is sending right now and what a failed marking left behind. Only this
|
|
254
|
+
* process knows either.
|
|
255
|
+
*/
|
|
256
|
+
const rows = page.filter((row) => this.#destinations.some((destination) => this.#due(row, destination.name)));
|
|
257
|
+
this.#outstanding(rows, counts, oldest);
|
|
258
|
+
if (rows.length > 0) {
|
|
259
|
+
console.debug('Outbox recovering unpublished rows', { count: rows.length });
|
|
260
|
+
// every row is given its chance, at every destination it is still outstanding for;
|
|
261
|
+
// what a broker refused stays outstanding there and comes back on a later cycle
|
|
262
|
+
await this.#awaited(rows.flatMap((row) => this.#publish(row)));
|
|
263
|
+
}
|
|
264
|
+
// a full page is a page that may have been cut short
|
|
265
|
+
} while (page.length === this.#batch);
|
|
266
|
+
measure.cycled(this.#destinations.map((destination) => destination.name), counts, oldest);
|
|
267
|
+
await this.#mark();
|
|
268
|
+
}
|
|
269
|
+
/** What a page leaves outstanding, by destination: how many, and the oldest of them. */
|
|
270
|
+
#outstanding(rows, counts, oldest) {
|
|
271
|
+
for (const row of rows)
|
|
272
|
+
for (const { name } of this.#destinations) {
|
|
273
|
+
if (!this.#due(row, name))
|
|
274
|
+
continue;
|
|
275
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
276
|
+
oldest.set(name, Math.min(oldest.get(name) ?? row.pending, row.pending));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Says when this replica has owned nothing for long enough that nothing is going to assign
|
|
281
|
+
* it anything.
|
|
282
|
+
*
|
|
283
|
+
* Owning no lane is ordinary for a moment — a replica that has just started has not been
|
|
284
|
+
* told yet — and is why nothing here refuses at boot: one cycle of it cannot be told from an
|
|
285
|
+
* atomicity that is absent. Ten in a row can, and what it means is that this outbox recovers
|
|
286
|
+
* nothing: a publication that failed is never retried, and that change is lost with the
|
|
287
|
+
* process that failed to make it.
|
|
288
|
+
*
|
|
289
|
+
* Said every ten cycles rather than once, so that it is still there to be found in a log
|
|
290
|
+
* read long after the deployment that broke it, and once when it is over.
|
|
291
|
+
*/
|
|
292
|
+
#assignment() {
|
|
293
|
+
const lanes = this.#atom.slots(LANES);
|
|
294
|
+
if (lanes !== null && lanes.length > 0) {
|
|
295
|
+
if (this.#unassigned >= UNASSIGNED)
|
|
296
|
+
console.info('Outbox lanes assigned, recovery resumed');
|
|
297
|
+
this.#unassigned = 0;
|
|
298
|
+
measure.starving(0);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.#unassigned++;
|
|
302
|
+
measure.starving(this.#unassigned);
|
|
303
|
+
if (this.#unassigned % UNASSIGNED === 0)
|
|
304
|
+
console.warn('Outbox owns no lane and recovers nothing', {
|
|
305
|
+
cycles: this.#unassigned,
|
|
306
|
+
interval: this.#interval
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* One page of what is due. In a healthy system the first one is empty, every cycle — a row is
|
|
311
|
+
* due only if the process that wrote it failed to publish or died before marking it.
|
|
312
|
+
*
|
|
313
|
+
* Reading is suspended, not stopped, while this replica does not know which lanes are its
|
|
314
|
+
* own: the cycle keeps running and keeps marking, and reading resumes as soon as an
|
|
315
|
+
* assignment arrives. Reading without an assignment would be a different guarantee, where
|
|
316
|
+
* every replica publishes every stranded row.
|
|
317
|
+
*
|
|
318
|
+
* @param after the last id of the page before, so a page is never read twice
|
|
319
|
+
*/
|
|
320
|
+
async #read(after) {
|
|
321
|
+
const lanes = this.#atom.slots(LANES);
|
|
322
|
+
if (lanes === null || lanes.length === 0)
|
|
323
|
+
return [];
|
|
324
|
+
return this.#storage.outbox.pending(lanes, Date.now(), this.#batch, after).catch((error) => {
|
|
325
|
+
console.warn('Outbox read failed', { error });
|
|
326
|
+
return [];
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Waits for what this cycle started, so that it marks what it sent rather than leaving it to
|
|
331
|
+
* the next one — but not past its own interval. A publication is never abandoned: it stays in
|
|
332
|
+
* flight, `#publishing` keeps a later cycle from sending it again, and the drain on close
|
|
333
|
+
* still waits for it. What is bounded is only how long a cycle waits before marking the rest,
|
|
334
|
+
* so that one destination with nothing to say cannot hold up the settling of another.
|
|
335
|
+
*/
|
|
336
|
+
async #awaited(started) {
|
|
337
|
+
if (started.length === 0)
|
|
338
|
+
return;
|
|
339
|
+
await Promise.race([Promise.allSettled(started), delay(this.#interval)]);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* One batched write for many rows, which is why the ids are held in memory rather than
|
|
343
|
+
* marked one by one. Ids that fail to be marked are kept and retried; a row that is never
|
|
344
|
+
* marked is simply published again, which is within the contract.
|
|
345
|
+
*
|
|
346
|
+
* Rows are grouped by the destinations that completed for them, so the ordinary case — every
|
|
347
|
+
* destination landing in the same window — is one group and one write, as it was when there
|
|
348
|
+
* was only ever one. A second write happens where the destinations diverged, which is the
|
|
349
|
+
* failure this exists for.
|
|
350
|
+
*/
|
|
351
|
+
async #mark() {
|
|
352
|
+
for (const [destinations, ids] of this.#groups())
|
|
353
|
+
await this.#settle(destinations, ids);
|
|
354
|
+
}
|
|
355
|
+
/** @private */
|
|
356
|
+
async #settle(destinations, ids) {
|
|
357
|
+
try {
|
|
358
|
+
await this.#storage.outbox.settle(ids, destinations);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
console.warn('Outbox marking failed', { count: ids.length, error });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
for (const destination of destinations) {
|
|
365
|
+
const published = this.#published.get(destination);
|
|
366
|
+
for (const id of ids)
|
|
367
|
+
published.delete(id);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* What has been published since the last cycle, as rows sharing the destinations that
|
|
372
|
+
* completed for them.
|
|
373
|
+
*/
|
|
374
|
+
#groups() {
|
|
375
|
+
/** the destinations that completed for an id, in the order the destinations are held */
|
|
376
|
+
const completed = new Map();
|
|
377
|
+
for (const [destination, ids] of this.#published)
|
|
378
|
+
for (const id of ids) {
|
|
379
|
+
const names = completed.get(id);
|
|
380
|
+
if (names === undefined)
|
|
381
|
+
completed.set(id, [destination]);
|
|
382
|
+
else
|
|
383
|
+
names.push(destination);
|
|
384
|
+
}
|
|
385
|
+
const groups = new Map();
|
|
386
|
+
for (const [id, destinations] of completed) {
|
|
387
|
+
const key = destinations.join(SEPARATOR);
|
|
388
|
+
const group = groups.get(key);
|
|
389
|
+
if (group === undefined)
|
|
390
|
+
groups.set(key, [destinations, [id]]);
|
|
391
|
+
else
|
|
392
|
+
group[1].push(id);
|
|
393
|
+
}
|
|
394
|
+
return [...groups.values()];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* A lane this replica currently owns, so that in steady state it settles its own rows
|
|
398
|
+
* before it ever reads them. Any lane at all when it owns none: the row still has to be
|
|
399
|
+
* written, and whoever ends up owning that lane will pump it.
|
|
400
|
+
*
|
|
401
|
+
*/
|
|
402
|
+
#lane() {
|
|
403
|
+
const owned = this.#atom.slots(LANES);
|
|
404
|
+
return owned === null || owned.length === 0
|
|
405
|
+
? Math.floor(Math.random() * LANES)
|
|
406
|
+
: owned[Math.floor(Math.random() * owned.length)];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function number(variable, declared, fallback) {
|
|
410
|
+
if (declared !== undefined)
|
|
411
|
+
return declared;
|
|
412
|
+
const value = Number(environment.get(variable));
|
|
413
|
+
return Number.isNaN(value) || value <= 0 ? fallback : value;
|
|
414
|
+
}
|
|
415
|
+
async function delay(ms) {
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
setTimeout(resolve, ms).unref();
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Constant, never configuration: rows carry their lane, so lowering this would leave rows in
|
|
422
|
+
* lanes nobody reads any more. It is also the ceiling on replicas of one component, and a
|
|
423
|
+
* power of two so that the common replica counts divide evenly.
|
|
424
|
+
*/
|
|
425
|
+
export const LANES = 128;
|
|
426
|
+
/** one cycle reads, publishes and marks; in steady state it finds nothing to read */
|
|
427
|
+
/**
|
|
428
|
+
* Cycles owning nothing before that is said out loud, and between one saying and the next.
|
|
429
|
+
* Long enough that a replica which has just started, or one whose assignment is being redrawn,
|
|
430
|
+
* says nothing at all.
|
|
431
|
+
*/
|
|
432
|
+
const UNASSIGNED = 10;
|
|
433
|
+
const INTERVAL = 5000;
|
|
434
|
+
/**
|
|
435
|
+
* `gap = interval * K`. Not a steady-state necessity — a replica writes into a lane it owns
|
|
436
|
+
* and marks what it published — but a guard for when a lane changes hands between the write
|
|
437
|
+
* and the settle. Two cycles of separation, plus one of margin.
|
|
438
|
+
*/
|
|
439
|
+
const K = 3;
|
|
440
|
+
/** how many rows one read brings back; the pump reads on while a page comes back full */
|
|
441
|
+
const BATCH = 200;
|
|
442
|
+
const DRAIN = 10_000;
|
|
443
|
+
/** publications in flight at once, per destination */
|
|
444
|
+
const INFLIGHT = 1000;
|
|
445
|
+
const PUBLISHED = 10_000;
|
|
446
|
+
/** what a group of destinations is keyed by; no name can hold it */
|
|
447
|
+
const SEPARATOR = '\u0000';
|
|
448
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../source/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function failed(destination: string): void;
|
|
2
|
+
export declare function starving(cycles: number): void;
|
|
3
|
+
/** What one cycle saw, set together so that a destination it read nothing for reads as zero. */
|
|
4
|
+
export declare function cycled(destinations: string[], counts: Map<string, number>, oldest: Map<string, number>): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { registry } from 'openspan';
|
|
2
|
+
/**
|
|
3
|
+
* What the outbox measures, and the zone with the most to gain from being measured: the pump
|
|
4
|
+
* swallows a publication failure into a log line, so events stop leaving and nothing says so.
|
|
5
|
+
*/
|
|
6
|
+
const meters = registry();
|
|
7
|
+
const DESTINATION = { destination: null };
|
|
8
|
+
/** Rows this replica has read as still outstanding, at the end of a cycle. */
|
|
9
|
+
const pending = meters.gauge('toa.outbox.pending', DESTINATION);
|
|
10
|
+
/**
|
|
11
|
+
* How long the oldest outstanding row has waited. The number to alert on, because it is one
|
|
12
|
+
* threshold a person can choose — *events are more than N seconds behind* — where a count of rows
|
|
13
|
+
* has none: a handful four hours old is a stuck destination, a thousand a second old is a busy one.
|
|
14
|
+
*/
|
|
15
|
+
const age = meters.gauge('toa.outbox.age', DESTINATION);
|
|
16
|
+
const failures = meters.counter('toa.outbox.failures', DESTINATION);
|
|
17
|
+
/**
|
|
18
|
+
* Cycles in a row this replica was given no lane. Past ten it means this outbox recovers nothing:
|
|
19
|
+
* a publication that failed is never retried, and that change is lost with the process that failed
|
|
20
|
+
* to make it.
|
|
21
|
+
*/
|
|
22
|
+
const unassigned = meters.gauge('toa.outbox.unassigned');
|
|
23
|
+
export function failed(destination) {
|
|
24
|
+
failures.add(1, { destination });
|
|
25
|
+
}
|
|
26
|
+
export function starving(cycles) {
|
|
27
|
+
unassigned.set(cycles);
|
|
28
|
+
}
|
|
29
|
+
/** What one cycle saw, set together so that a destination it read nothing for reads as zero. */
|
|
30
|
+
export function cycled(destinations, counts, oldest) {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
for (const destination of destinations) {
|
|
33
|
+
pending.set(counts.get(destination) ?? 0, { destination });
|
|
34
|
+
const since = oldest.get(destination);
|
|
35
|
+
age.set(since === undefined ? 0 : Math.max(0, (now - since) / 1000), { destination });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=outbox.measurements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.measurements.js","sourceRoot":"","sources":["../source/outbox.measurements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC;;;GAGG;AACH,MAAM,MAAM,GAAG,QAAQ,EAAE,CAAA;AAEzB,MAAM,WAAW,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;AAEzC,8EAA8E;AAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAA;AAE/D;;;;GAIG;AACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAA;AAEvD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;AAExD,MAAM,UAAU,MAAM,CAAC,WAAmB;IACxC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AACxB,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,MAAM,CACpB,YAAsB,EACtB,MAA2B,EAC3B,MAA2B;IAE3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAEtB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;QAE1D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAErC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;IACvF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Node } from '../types/storages.ts';
|
|
2
|
+
/** What a component declares about the properties a criteria may select on. */
|
|
3
|
+
export type Properties = Record<string, {
|
|
4
|
+
type: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function criteria(expression: string, properties?: Properties): Node;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parse } from '@rsql/parser';
|
|
2
|
+
import { QuerySyntaxException } from '../exceptions.js';
|
|
3
|
+
export function criteria(expression, properties) {
|
|
4
|
+
let ast;
|
|
5
|
+
try {
|
|
6
|
+
ast = parse(expression);
|
|
7
|
+
}
|
|
8
|
+
catch (e) {
|
|
9
|
+
throw new QuerySyntaxException(e.message);
|
|
10
|
+
}
|
|
11
|
+
if (properties !== undefined)
|
|
12
|
+
read(ast, properties);
|
|
13
|
+
return ast;
|
|
14
|
+
}
|
|
15
|
+
function read(node, properties) {
|
|
16
|
+
if (node.type === 'COMPARISON' &&
|
|
17
|
+
node.left?.type === 'SELECTOR' &&
|
|
18
|
+
node.right?.type === 'VALUE') {
|
|
19
|
+
const selector = node.left.selector;
|
|
20
|
+
const property = properties[selector];
|
|
21
|
+
if (property === undefined) {
|
|
22
|
+
throw new QuerySyntaxException(`Criteria selector '${selector}' is not defined`);
|
|
23
|
+
}
|
|
24
|
+
const cast = CAST[property.type];
|
|
25
|
+
// `=in=` and `=out=` carry a list, and casting that as one value gives whatever
|
|
26
|
+
// a comma-separated string reads as
|
|
27
|
+
if (cast !== undefined)
|
|
28
|
+
node.right.value = Array.isArray(node.right.value)
|
|
29
|
+
? node.right.value.map((value) => cast(value, selector))
|
|
30
|
+
: cast(node.right.value, selector);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
if (node.left !== undefined)
|
|
34
|
+
read(node.left, properties);
|
|
35
|
+
if (node.right !== undefined)
|
|
36
|
+
read(node.right, properties);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A criteria arrives as text, so a value is read as what the property it selects on holds. What
|
|
41
|
+
* cannot be read as that is refused rather than passed on: `parseInt` answers `NaN` for a word
|
|
42
|
+
* and `12` for `12kg`, and a storage asked to match either finds nothing and says nothing —
|
|
43
|
+
* a mistyped filter reads as an empty result.
|
|
44
|
+
*
|
|
45
|
+
* A string is left as it came, because every text is one: what a criteria may compare a string
|
|
46
|
+
* to is not the type's to say.
|
|
47
|
+
*/
|
|
48
|
+
const CAST = {
|
|
49
|
+
number: (value, selector) => finite(value, selector, 'a number'),
|
|
50
|
+
integer: (value, selector) => {
|
|
51
|
+
const number = finite(value, selector, 'an integer');
|
|
52
|
+
if (!Number.isInteger(number))
|
|
53
|
+
refuse(selector, 'an integer', value);
|
|
54
|
+
return number;
|
|
55
|
+
},
|
|
56
|
+
boolean: (value, selector) => {
|
|
57
|
+
if (value === 'true')
|
|
58
|
+
return true;
|
|
59
|
+
if (value === 'false')
|
|
60
|
+
return false;
|
|
61
|
+
return refuse(selector, 'a boolean', value);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
function finite(value, selector, expected) {
|
|
65
|
+
// `Number` reads a blank string as zero, and a criteria that says nothing says nothing
|
|
66
|
+
const number = value.trim() === '' ? Number.NaN : Number(value);
|
|
67
|
+
if (!Number.isFinite(number))
|
|
68
|
+
refuse(selector, expected, value);
|
|
69
|
+
return number;
|
|
70
|
+
}
|
|
71
|
+
function refuse(selector, expected, value) {
|
|
72
|
+
throw new QuerySyntaxException(`Criteria selector '${selector}' takes ${expected}, and '${value}' is not one`);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=criteria.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criteria.js","sourceRoot":"","sources":["../../source/query/criteria.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { QuerySyntaxException } from '../exceptions.js';
|
|
2
|
+
export function options(given, properties) {
|
|
3
|
+
if (given.sort !== undefined)
|
|
4
|
+
given.sort = sort(given.sort, properties);
|
|
5
|
+
if (given.projection !== undefined)
|
|
6
|
+
given.projection = projection(given.projection, properties);
|
|
7
|
+
return given;
|
|
8
|
+
}
|
|
9
|
+
function sort(sort, properties) {
|
|
10
|
+
const result = [];
|
|
11
|
+
for (const sorting of sort) {
|
|
12
|
+
const [property, direction] = sorting.split(':');
|
|
13
|
+
if (properties[property] === undefined)
|
|
14
|
+
throw new QuerySyntaxException(`Sort property '${property}' is not defined`);
|
|
15
|
+
result.push([property, direction ?? 'asc']);
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A copy, because a route declares one projection and sends it with every request it serves,
|
|
21
|
+
* and the system properties are what every read answers besides.
|
|
22
|
+
*/
|
|
23
|
+
function projection(declared, properties) {
|
|
24
|
+
for (const property of declared)
|
|
25
|
+
if (properties[property] === undefined)
|
|
26
|
+
throw new QuerySyntaxException(`Projection property '${property}' is not defined`);
|
|
27
|
+
const projection = declared.slice();
|
|
28
|
+
for (const property of SYSTEM)
|
|
29
|
+
if (!projection.includes(property))
|
|
30
|
+
projection.push(property);
|
|
31
|
+
return projection;
|
|
32
|
+
}
|
|
33
|
+
const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED', 'REGION'];
|
|
34
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../source/query/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAIvD,MAAM,UAAU,OAAO,CAAC,KAA0B,EAAE,UAAsB;IACxE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IAEvE,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAChC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAE7D,OAAO,KAAgB,CAAA;AACzB,CAAC;AAED,SAAS,IAAI,CAAC,IAAc,EAAE,UAAsB;IAClD,MAAM,MAAM,GAA4B,EAAE,CAAA;IAE1C,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAEhD,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,SAAS;YACpC,MAAM,IAAI,oBAAoB,CAAC,kBAAkB,QAAQ,kBAAkB,CAAC,CAAA;QAE9E,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,IAAI,KAAK,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,QAAkB,EAAE,UAAsB;IAC5D,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAC7B,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,SAAS;YACpC,MAAM,IAAI,oBAAoB,CAAC,wBAAwB,QAAQ,kBAAkB,CAAC,CAAA;IAEtF,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAA;IAEnC,KAAK,MAAM,QAAQ,IAAI,MAAM;QAC3B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAE/D,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Properties } from './query/criteria.ts';
|
|
2
|
+
import type { Query as Parsed } from './types/storages.ts';
|
|
3
|
+
import type { Query as Requested } from './types/request.ts';
|
|
4
|
+
/** Translates the query a request carries into the one a storage is given. */
|
|
5
|
+
export declare class Query {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(properties: Properties);
|
|
8
|
+
parse(query: Requested): Parsed;
|
|
9
|
+
}
|