@astrosheep/square 0.3.86 → 0.3.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +14 -10
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +1 -0
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +8 -11
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +3 -4
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.js +8 -3
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +20 -9
- package/dist/sqlite-square.js +471 -339
- package/dist/square-api.d.ts +1 -0
- package/dist/square-api.js +3 -4
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +14 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +1 -1
- package/dist/square-stdio.js +12 -7
- package/dist/square-tools.js +29 -2
- package/dist/stdio-server.js +4 -2
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +50 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +1 -1
- package/guides/migration.md +107 -67
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +61 -28
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +1 -1
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +55 -17
- package/skills/square/SKILL.md +1 -1
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/host-ledger.js +0 -1
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -38
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -23
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
package/dist/sqlite-square.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
+
import { formatActivityId } from './square-core.js';
|
|
1
2
|
import { squareLocation, localSquareLocation } from './square-location.js';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
-
import { SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
5
|
-
import { resolveKnownName, validateDoneBody } from './decisions.js';
|
|
5
|
+
import { InternalSquareError, SquareError, isSquareError, nameKey, sameBody, validateName } from './model.js';
|
|
6
|
+
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct, decideImplicitJoin, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
6
7
|
import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
7
|
-
import { catchLimit, observeCatchSelection } from './catch-decisions.js';
|
|
8
|
+
import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
|
|
8
9
|
import { directedSuffix } from './activity-feed.js';
|
|
9
10
|
import { expressHint } from './hints.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { hasAutomaticDeliveryIdentity, hostLedgerForEnv, localParticipantName, localSessionIdentities, presenceEpoch, readParticipantOwner } from './registry.js';
|
|
15
|
-
import { projectSessionBindings } from './square-projections.js';
|
|
16
|
-
import { sessionIdsFromEnvironment } from './participant-identity.js';
|
|
11
|
+
import { asSeatRef, participantAuthority, requireBody, requireJoinName, requireSeat } from './seats.js';
|
|
12
|
+
import { recoveryObservation, entryPresentation, snapshot, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
13
|
+
import { sessionInboxCounts } from './inbox.js';
|
|
14
|
+
import { rememberSquarePath, squareIndexPath } from './square-index.js';
|
|
17
15
|
import { createDefaultWakeTransport } from './notifications.js';
|
|
16
|
+
import { deliverPending } from './delivery-operations.js';
|
|
17
|
+
import { participantIdentity } from './participant-identity.js';
|
|
18
18
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
19
|
-
import { createSquare, diagnoseSquareFile } from './artifact.js';
|
|
19
|
+
import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
|
|
20
20
|
import { migrateSquareFile } from './square-migration.js';
|
|
21
21
|
import { discoverSquares } from './list.js';
|
|
22
22
|
import { streamSquare } from './stream.js';
|
|
23
|
-
export { createSquareResourceOwner } from './
|
|
23
|
+
export { createSquareResourceOwner } from './artifact.js';
|
|
24
24
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
25
25
|
export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
|
|
26
26
|
export { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
@@ -35,337 +35,469 @@ export function squareClock(env, clock) {
|
|
|
35
35
|
return clock;
|
|
36
36
|
if (env.SQUARE_NOW_MS === undefined)
|
|
37
37
|
return Date.now;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
38
|
+
const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
|
|
39
|
+
if (!Number.isFinite(value))
|
|
40
|
+
throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
|
|
41
|
+
return () => value;
|
|
44
42
|
}
|
|
45
43
|
function failure(error, detail) {
|
|
46
44
|
const normalized = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
47
45
|
Object.assign(normalized, { operation: detail });
|
|
48
46
|
return normalized;
|
|
49
47
|
}
|
|
50
|
-
function
|
|
51
|
-
let transport;
|
|
52
|
-
return () => transport ??= square.wakeTransport !== undefined ? Promise.resolve(square.wakeTransport)
|
|
53
|
-
: square.automaticDelivery === false || square.location === 'memory' || square.hostLedger === undefined ? Promise.resolve(undefined)
|
|
54
|
-
: createDefaultWakeTransport(square.hostLedger, square.clock, square.env).catch(() => undefined);
|
|
55
|
-
}
|
|
56
|
-
function observedContext(square, standingCount, held, participant, now = square.clock()) {
|
|
57
|
-
return { location: square.location, standingCount, held, now,
|
|
58
|
-
...(participant === undefined ? {} : { participant }), automaticDeliveryAvailable: square.automaticDelivery !== false && hasAutomaticDeliveryIdentity(square.env) };
|
|
59
|
-
}
|
|
60
|
-
async function contextObservation(square, participant, control) {
|
|
61
|
-
const observation = (await square.artifact.read((facts) => ({ standingCount: inSquareCount(facts), held: facts.hold().active }), control?.signal)).value;
|
|
62
|
-
return observedContext(square, observation.standingCount, observation.held, participant);
|
|
63
|
-
}
|
|
64
|
-
async function prepareExpression(square, requested, control) {
|
|
65
|
-
try {
|
|
66
|
-
checkControl(control);
|
|
67
|
-
const result = await join(square, requested, control, { requireKnown: true });
|
|
68
|
-
return { participant: result.name, activity: result.activity };
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
throw failure(error, { operation: 'express', subject: 'caller', participant: requested, expressEffect: 'unsent' });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/** One retry/commit owner for every participant, regardless of transport. */
|
|
75
|
-
async function landExpression(square, name, body, options, transport, control) {
|
|
76
|
-
let effect = 'unsent';
|
|
77
|
-
let waited = false, heldWaitedMs = 0;
|
|
78
|
-
try {
|
|
79
|
-
const wakeTransport = await transport();
|
|
80
|
-
while (true) {
|
|
81
|
-
checkControl(control);
|
|
82
|
-
try {
|
|
83
|
-
effect = 'unknown';
|
|
84
|
-
return { ...await express({ ...square, wakeTransport }, name, body, options, control), waited };
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
if (isSquareError(error) && error.facts?.activityUnsent === true)
|
|
88
|
-
effect = 'unsent';
|
|
89
|
-
if (!isSquareError(error) || options.noWait || (error.code !== 'held' && error.code !== 'throttled'))
|
|
90
|
-
throw error;
|
|
91
|
-
if (error.code === 'held' && heldWaitedMs >= HELD_WAIT_BUDGET_MS)
|
|
92
|
-
throw error;
|
|
93
|
-
waited = true;
|
|
94
|
-
control?.onProgress?.({ kind: 'waiting', reason: error.code,
|
|
95
|
-
...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
|
|
96
|
-
...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
|
|
97
|
-
...(error.facts?.holdReason === undefined ? {} : { holdReason: error.facts.holdReason }),
|
|
98
|
-
});
|
|
99
|
-
if (error.code === 'throttled')
|
|
100
|
-
await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
|
|
101
|
-
else {
|
|
102
|
-
const started = Date.now();
|
|
103
|
-
const observation = await square.artifact.read((facts) => facts.hold().active, control?.signal);
|
|
104
|
-
if (observation.value)
|
|
105
|
-
await square.artifact.changed(observation.version, Math.max(1, HELD_WAIT_BUDGET_MS - heldWaitedMs), control?.signal);
|
|
106
|
-
heldWaitedMs += Date.now() - started;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
const recipient = isSquareError(error) && ['not_standing', 'bell_mention_conflict'].includes(error.facts?.reason ?? '');
|
|
113
|
-
throw failure(error, { operation: 'express', participant: name, subject: recipient ? 'recipient' : 'caller', expressEffect: effect });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/** Leases, quiet waiting and receipt selection share one lifecycle. */
|
|
117
|
-
async function catchOwned(square, requested, options = {}, control) {
|
|
118
|
-
let name = requested, subject = 'caller';
|
|
119
|
-
let leaseId;
|
|
120
|
-
let waitStarted = false, replaced = false;
|
|
121
|
-
let selection = options;
|
|
122
|
-
const idle = options.idle ?? 0;
|
|
123
|
-
async function complete(caught, stop) {
|
|
124
|
-
const selected = caught ?? (await square.artifact.read((facts) => observeCatchSelection(facts, name, [], {
|
|
125
|
-
unseenOnly: true, senders: selection.from, ...(selection.mention === true ? { attentionOnly: true } : {}),
|
|
126
|
-
}, square.clock()))).value;
|
|
127
|
-
const terminal = stop === 'empty-now' || stop === undefined ? selected.terminalStatus ?? stop : stop;
|
|
128
|
-
if (leaseId !== undefined) {
|
|
129
|
-
await releaseWatchLease(square, name, leaseId).catch(() => undefined);
|
|
130
|
-
leaseId = undefined;
|
|
131
|
-
}
|
|
132
|
-
return { selection: selected, stop: terminal, waitStarted, replaced };
|
|
133
|
-
}
|
|
134
|
-
const empty = (stop) => complete(undefined, stop);
|
|
48
|
+
function rejectUnsentActivity(operation) {
|
|
135
49
|
try {
|
|
136
|
-
|
|
137
|
-
if (!Number.isFinite(idle) || idle < 0)
|
|
138
|
-
throw new SquareError('invalid_args', 'Catch idle duration must be a non-negative number');
|
|
139
|
-
if (options.id !== undefined && ['idle', 'from', 'mention', 'limit', 'replace'].some((key) => Object.hasOwn(options, key)))
|
|
140
|
-
throw new SquareError('invalid_args', 'Catch id cannot be combined with idle, from, mention, limit or replace');
|
|
141
|
-
if (Object.hasOwn(options, 'replace') && idle === 0)
|
|
142
|
-
throw new SquareError('invalid_args', 'Catch replacement requires a positive idle duration');
|
|
143
|
-
catchLimit(options.limit);
|
|
144
|
-
const from = (await square.artifact.read((facts) => {
|
|
145
|
-
name = resolveKnownName(facts, requested);
|
|
146
|
-
subject = 'filter';
|
|
147
|
-
return options.from?.map((target) => resolveKnownName(facts, target));
|
|
148
|
-
}, control?.signal)).value;
|
|
149
|
-
selection = { ...options, ...(from === undefined ? {} : { from }) };
|
|
150
|
-
subject = 'caller';
|
|
151
|
-
if (idle === 0) {
|
|
152
|
-
const data = await catchUp(square, name, selection, control);
|
|
153
|
-
return await complete(data, data.activities.length === 0 ? 'empty-now' : undefined);
|
|
154
|
-
}
|
|
155
|
-
if (!await assertLiveOwner(square, name))
|
|
156
|
-
throw new SquareError('already_joined', `${name} already stands here — another session holds the name`);
|
|
157
|
-
const watch = {
|
|
158
|
-
...(from === undefined ? {} : { participants: [...from] }), ...(options.mention ? { mention: name } : {}),
|
|
159
|
-
...(options.replace === undefined ? {} : { replace: options.replace }),
|
|
160
|
-
};
|
|
161
|
-
const start = await acquireWatchLease(square, name, crypto.randomUUID(), watch, control?.signal);
|
|
162
|
-
if (start.type === 'active')
|
|
163
|
-
return await empty('already-active');
|
|
164
|
-
leaseId = start.leaseId;
|
|
165
|
-
waitStarted = true;
|
|
166
|
-
replaced = start.replaced;
|
|
167
|
-
control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced });
|
|
168
|
-
let quietSince = square.clock(), wasHeld = false, nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
169
|
-
while (true) {
|
|
170
|
-
checkControl(control);
|
|
171
|
-
// Capture before evaluation so a commit during selection cannot be missed.
|
|
172
|
-
const baseline = await square.artifact.revision(control?.signal);
|
|
173
|
-
const pulse = await pulseWatchLease(square, name, leaseId, watch, square.clock() >= nextHeartbeat, control?.signal);
|
|
174
|
-
if (pulse.type === 'replaced')
|
|
175
|
-
return await empty('replaced');
|
|
176
|
-
if (pulse.type === 'terminal')
|
|
177
|
-
return await empty(pulse.status);
|
|
178
|
-
if (pulse.heartbeatAt !== undefined)
|
|
179
|
-
nextHeartbeat = pulse.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
180
|
-
if (pulse.type === 'sleep') {
|
|
181
|
-
const caught = await catchUp(square, name, selection, control, leaseId);
|
|
182
|
-
if (caught === null)
|
|
183
|
-
return await empty('replaced');
|
|
184
|
-
if (caught.activities.length > 0)
|
|
185
|
-
return await complete(caught);
|
|
186
|
-
}
|
|
187
|
-
if (pulse.type === 'held' || wasHeld)
|
|
188
|
-
quietSince = square.clock();
|
|
189
|
-
wasHeld = pulse.type === 'held';
|
|
190
|
-
if (!wasHeld && square.clock() - quietSince >= idle) {
|
|
191
|
-
const owns = await ownsWatchLease(square, name, leaseId, control?.signal);
|
|
192
|
-
return await empty(owns ? 'stale' : 'replaced');
|
|
193
|
-
}
|
|
194
|
-
const deadline = Math.min(nextHeartbeat, wasHeld ? Infinity : quietSince + idle);
|
|
195
|
-
await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
|
|
196
|
-
}
|
|
50
|
+
return operation();
|
|
197
51
|
}
|
|
198
52
|
catch (error) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// An aborted request still releases its own lease, never its replacement's.
|
|
203
|
-
if (leaseId !== undefined)
|
|
204
|
-
await releaseWatchLease(square, name, leaseId).catch(() => undefined);
|
|
53
|
+
if (isSquareError(error))
|
|
54
|
+
error.facts = { ...error.facts, activityUnsent: true };
|
|
55
|
+
throw error;
|
|
205
56
|
}
|
|
206
57
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
square;
|
|
211
|
-
transport;
|
|
212
|
-
constructor(name, owner, square, transport) {
|
|
213
|
-
this.name = name;
|
|
214
|
-
this.owner = owner;
|
|
215
|
-
this.square = square;
|
|
216
|
-
this.transport = transport;
|
|
217
|
-
}
|
|
218
|
-
async express(body, options = {}, control) {
|
|
219
|
-
const entry = await prepareExpression(this.square, this.name, control);
|
|
220
|
-
const name = entry.participant;
|
|
221
|
-
let result;
|
|
222
|
-
try {
|
|
223
|
-
const feed = !options.observe ? undefined : (await this.square.artifact.read((facts) => {
|
|
224
|
-
const pendingPublic = directedSuffix(facts, name);
|
|
225
|
-
return { pendingPublic, sayNumbers: facts.sayOrdinals(pendingPublic.map((item) => item.index)),
|
|
226
|
-
perceptions: Object.fromEntries(pendingPublic.map((item) => [item.index, 'full'])) };
|
|
227
|
-
}, control?.signal)).value;
|
|
228
|
-
result = await landExpression(this.square, name, body, options, this.transport, control);
|
|
229
|
-
const { observation: committed, ...data } = result;
|
|
230
|
-
const hint = expressHint(committed.ownActivityCount);
|
|
231
|
-
return { ...data, ...(hint === undefined ? {} : { hint }), ...(feed === undefined ? {} : { observation: {
|
|
232
|
-
...observedContext(this.square, committed.standingCount, committed.held, name, committed.now), ...committed, ...feed, participant: name,
|
|
233
|
-
...(entry.activity === null ? {} : { reentry: entry.activity }),
|
|
234
|
-
} }) };
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
if (result === undefined && error?.operation?.operation === 'express')
|
|
238
|
-
throw error;
|
|
239
|
-
throw failure(error, { operation: 'express', participant: name, subject: 'caller', expressEffect: result === undefined ? 'unsent' : 'landed',
|
|
240
|
-
...(result === undefined ? {} : { landedId: result.activity.id }) });
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async listener(verb, target, options, control) {
|
|
244
|
-
const name = (await resolveParticipant(this.square, this.name, control)).name;
|
|
245
|
-
const { observation: committed, ...result } = await (verb === 'listen' ? listen : verb === 'ignore' ? ignore : unlisten)(this.square, name, target, control);
|
|
246
|
-
return { ...result, ...(!options.observe ? {} : { observation: {
|
|
247
|
-
...observedContext(this.square, committed.standingCount, committed.held, name, committed.now),
|
|
248
|
-
participant: name, requestedTarget: target, target: result.activity?.target ?? target,
|
|
249
|
-
} }) };
|
|
250
|
-
}
|
|
251
|
-
listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
|
|
252
|
-
ignore(target, options = {}, control) { return this.listener('ignore', target, options, control); }
|
|
253
|
-
unlisten(target, options = {}, control) { return this.listener('unlisten', target, options, control); }
|
|
254
|
-
async listening(options = {}, control) {
|
|
255
|
-
const name = (await resolveParticipant(this.square, this.name, control)).name;
|
|
256
|
-
return { listening: await listening(this.square, name, control),
|
|
257
|
-
...(!options.observe ? {} : { observation: await contextObservation(this.square, name, control) }) };
|
|
258
|
-
}
|
|
259
|
-
async catch(options = {}, control) {
|
|
260
|
-
const result = await catchOwned(this.square, this.name, options, control), view = result.selection, stop = result.stop;
|
|
261
|
-
const presence = !options.observe ? undefined : await catchPresence(this.square, control).catch(() => undefined);
|
|
262
|
-
const status = stop === 'already-active' || stop === 'replaced' || stop === 'capped' || stop === 'quorum' ? stop : undefined;
|
|
263
|
-
return { activities: view.activities.map((activity) => ({ ...toPublicActivity(activity), perception: 'full' })),
|
|
264
|
-
consumedThrough: view.consumedThrough, remaining: view.remaining, idleExpired: stop === 'stale',
|
|
265
|
-
...(status === undefined ? {} : { status }), ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
266
|
-
...observedContext(this.square, view.standingCount, view.held, view.participant, view.now), participant: view.participant,
|
|
267
|
-
sayNumbers: view.sayNumbers, ownActivityCount: view.ownActivityCount, hardCap: view.hardCap, stop,
|
|
268
|
-
...(presence === undefined ? { enrichmentUnavailable: true } : { presence }), waitStarted: result.waitStarted, replaced: result.replaced,
|
|
269
|
-
} }) };
|
|
270
|
-
}
|
|
271
|
-
history(query, control) { return this.owner.history(query, control); }
|
|
272
|
-
async lifecycle(verb, body, options, control) {
|
|
273
|
-
if (verb === 'done')
|
|
274
|
-
validateDoneBody(body);
|
|
275
|
-
const identity = localSessionIdentities(this.square.env ?? {})[0];
|
|
276
|
-
const owner = verb !== 'done' || identity === undefined || this.square.hostLedger === undefined ? undefined
|
|
277
|
-
: await readParticipantOwner(this.square.location, this.name, this.square.hostLedger);
|
|
278
|
-
const fenced = identity !== undefined && owner?.session === identity.sessionId && presenceEpoch(owner) > 0;
|
|
279
|
-
const name = fenced ? this.name : (await join(this.square, this.name, control)).name;
|
|
280
|
-
const result = verb === 'done' ? await done(this.square, name, body ?? '', fenced ? { expectedEpoch: presenceEpoch(owner) } : {}, control)
|
|
281
|
-
: verb === 'hold' ? await hold(this.square, name, body, control) : await resume(this.square, name, control);
|
|
282
|
-
const { observation: committed, ...receipt } = result;
|
|
283
|
-
return { ...receipt, ...(!options.observe ? {} : { observation: {
|
|
284
|
-
...observedContext(this.square, committed.standingCount, committed.held, name, committed.now), ...committed,
|
|
285
|
-
} }) };
|
|
286
|
-
}
|
|
287
|
-
hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
|
|
288
|
-
resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
|
|
289
|
-
done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
|
|
58
|
+
/** A generic managed address must not displace the receiver's native registration on reconnect. */
|
|
59
|
+
function canRefreshWakeAddress(current, proposed) {
|
|
60
|
+
return proposed.kind !== 'paseo' || current === undefined || current.kind === 'paseo';
|
|
290
61
|
}
|
|
291
|
-
/** One concrete
|
|
62
|
+
/** One concrete local resource, including its entry-resolved dependencies and lifetime. */
|
|
292
63
|
export class SqliteSquare {
|
|
293
64
|
location;
|
|
294
|
-
|
|
65
|
+
artifact;
|
|
66
|
+
clock;
|
|
67
|
+
body;
|
|
68
|
+
wakeAddress;
|
|
69
|
+
wakeTransport;
|
|
70
|
+
indexPath;
|
|
71
|
+
automaticDelivery;
|
|
72
|
+
ownsArtifact;
|
|
295
73
|
defaultParticipant;
|
|
296
|
-
|
|
297
|
-
constructor(location, square, defaultParticipant) {
|
|
74
|
+
constructor(location, artifact, clock, body, wakeAddress, wakeTransport, indexPath, automaticDelivery = true, ownsArtifact = true, defaultParticipant) {
|
|
298
75
|
this.location = location;
|
|
299
|
-
this.
|
|
76
|
+
this.artifact = artifact;
|
|
77
|
+
this.clock = clock;
|
|
78
|
+
this.body = body;
|
|
79
|
+
this.wakeAddress = wakeAddress;
|
|
80
|
+
this.wakeTransport = wakeTransport;
|
|
81
|
+
this.indexPath = indexPath;
|
|
82
|
+
this.automaticDelivery = automaticDelivery;
|
|
83
|
+
this.ownsArtifact = ownsArtifact;
|
|
300
84
|
this.defaultParticipant = defaultParticipant;
|
|
301
|
-
this.transport = defaultTransport(square);
|
|
302
85
|
}
|
|
303
86
|
static async at(input, control, automaticDelivery = true) {
|
|
304
87
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
305
|
-
|
|
88
|
+
const clock = squareClock(env, input.clock);
|
|
89
|
+
const wakeTransport = input.wakeTransport ?? (automaticDelivery
|
|
90
|
+
? await createDefaultWakeTransport(env).catch(() => undefined) : undefined);
|
|
91
|
+
// A request can abort its own borrowed reads, never the owner's connection lifetime.
|
|
92
|
+
const borrowed = input.resources?.artifact(location);
|
|
93
|
+
const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
|
|
94
|
+
try {
|
|
95
|
+
await artifact.read(facts => facts.header(), control?.signal);
|
|
96
|
+
const resource = new SqliteSquare(location, artifact, clock, input.body, input.wakeAddress, wakeTransport, squareIndexPath(env), automaticDelivery, borrowed === undefined, input.participant);
|
|
97
|
+
await resource.publishIdentity();
|
|
98
|
+
return resource;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (borrowed === undefined)
|
|
102
|
+
await artifact.close();
|
|
103
|
+
if (error instanceof InternalSquareError && error.code === 'not_found')
|
|
104
|
+
throw new SquareError('unavailable', `Square is unavailable at ${location}`);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
306
107
|
}
|
|
307
108
|
static async build(input, control, automaticDelivery = true) {
|
|
308
109
|
checkControl(control);
|
|
309
110
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
310
111
|
await createSquare(location, { hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
|
|
311
112
|
template: input.template, force: input.force ?? false }, input.markdown, control?.signal);
|
|
312
|
-
//
|
|
113
|
+
// Publication already committed; cancellation cannot erase the completed build.
|
|
313
114
|
return SqliteSquare.at({ ...input, path: location, env }, undefined, automaticDelivery);
|
|
314
115
|
}
|
|
315
116
|
static inMemory(input) {
|
|
316
117
|
const env = { ...(input.env ?? process.env) };
|
|
317
|
-
return new SqliteSquare('memory',
|
|
118
|
+
return new SqliteSquare('memory', createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.body, input.wakeAddress, input.wakeTransport, undefined, true, true, input.participant);
|
|
119
|
+
}
|
|
120
|
+
/** Discovery is best effort and never grants write authority. Finish it before returning. */
|
|
121
|
+
async publishIdentity() {
|
|
122
|
+
if (this.location !== 'memory' && this.body !== undefined && this.indexPath !== undefined)
|
|
123
|
+
await rememberSquarePath(this.location, this.body, this.indexPath).catch(() => undefined);
|
|
124
|
+
}
|
|
125
|
+
observation(standingCount, held, participant, now = this.clock()) {
|
|
126
|
+
return { location: this.location, standingCount, held, now,
|
|
127
|
+
...(participant === undefined ? {} : { participant }), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined };
|
|
318
128
|
}
|
|
319
|
-
|
|
129
|
+
async contextObservation(participant, control) {
|
|
130
|
+
return (await this.artifact.read(facts => this.observation(inSquareCount(facts), facts.hold().active, participant), control?.signal)).value;
|
|
131
|
+
}
|
|
132
|
+
handle(name, seat) { return new SqliteSquare.ParticipantHandle(name, this, seat); }
|
|
133
|
+
// The nested handle can use its resource's private dependencies without widening
|
|
134
|
+
// the concrete-derived backend contract or copying them into a second context.
|
|
135
|
+
static ParticipantHandle = class {
|
|
136
|
+
name;
|
|
137
|
+
owner;
|
|
138
|
+
seat;
|
|
139
|
+
constructor(name, owner, seat) {
|
|
140
|
+
this.name = name;
|
|
141
|
+
this.owner = owner;
|
|
142
|
+
this.seat = seat;
|
|
143
|
+
}
|
|
144
|
+
async express(bodyOrInput, optionsOrControl = {}, operationControl) {
|
|
145
|
+
const square = this.owner;
|
|
146
|
+
const input = typeof bodyOrInput === 'object' && bodyOrInput !== null ? bodyOrInput : undefined;
|
|
147
|
+
const body = typeof bodyOrInput === 'string' || bodyOrInput === undefined ? bodyOrInput : bodyOrInput.body;
|
|
148
|
+
const options = (input ?? optionsOrControl);
|
|
149
|
+
const control = input === undefined ? operationControl : optionsOrControl;
|
|
150
|
+
let name = this.name, effect = 'unsent';
|
|
151
|
+
let landedId;
|
|
152
|
+
let waited = false, heldWaitedMs = 0;
|
|
153
|
+
try {
|
|
154
|
+
checkControl(control);
|
|
155
|
+
name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
156
|
+
const seat = requireSeat(this.seat, square.body, name);
|
|
157
|
+
const feed = !options.observe ? undefined : (await square.artifact.read(facts => {
|
|
158
|
+
const pendingPublic = directedSuffix(facts, name);
|
|
159
|
+
return { pendingPublic, sayNumbers: facts.sayOrdinals(pendingPublic.map(item => item.index)),
|
|
160
|
+
perceptions: Object.fromEntries(pendingPublic.map(item => [item.index, 'full'])) };
|
|
161
|
+
}, control?.signal)).value;
|
|
162
|
+
while (true) {
|
|
163
|
+
checkControl(control);
|
|
164
|
+
try {
|
|
165
|
+
effect = 'unknown';
|
|
166
|
+
const now = square.clock();
|
|
167
|
+
const reply = rejectUnsentActivity(() => {
|
|
168
|
+
try {
|
|
169
|
+
return options.reply === undefined ? undefined : parseRequiredActivityId(options.reply);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (isSquareError(error))
|
|
173
|
+
error.facts = { ...error.facts, reason: 'invalid_reply', activityId: options.reply };
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const committed = (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
178
|
+
// Decoding selected rows is storage work, not proof of safe admission refusal.
|
|
179
|
+
const proposed = decideAct(facts, { name, body, force: options.force ?? false, metadata: options.metadata, now, mentions: options.mentions,
|
|
180
|
+
...(options.reach === undefined ? {} : { reach: options.reach }), ...(options.noMention === undefined ? {} : { noMention: options.noMention }), ...(reply === undefined ? {} : { reply }) });
|
|
181
|
+
const holder = proposed.type === 'held' ? facts.hold().actor : undefined;
|
|
182
|
+
const decision = rejectUnsentActivity(() => {
|
|
183
|
+
if (proposed.type === 'blocked')
|
|
184
|
+
throw new SquareError('behind', `${participantIdentity(name)} has pending activity`, { pending: proposed.activitySummaries.reduce((count, summary) => count + summary.count, 0) });
|
|
185
|
+
if (proposed.type === 'held')
|
|
186
|
+
throw new SquareError('held', 'The square is held', { ...(holder === undefined ? {} : { holder }), ...(proposed.reason === undefined ? {} : { holdReason: proposed.reason }) });
|
|
187
|
+
if (proposed.type === 'capped')
|
|
188
|
+
throw new SquareError('capped', `${participantIdentity(name)} reached the activity cap`);
|
|
189
|
+
if (proposed.type === 'throttled')
|
|
190
|
+
throw new SquareError('throttled', `${name} is throttled`, { retryAfterMs: proposed.delayMs });
|
|
191
|
+
if (proposed.type === 'bell_quota')
|
|
192
|
+
throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
|
|
193
|
+
return proposed;
|
|
194
|
+
});
|
|
195
|
+
return { stored: facts.land(decision.act), standingCount: inSquareCount(facts), held: facts.hold().active,
|
|
196
|
+
ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
|
|
197
|
+
...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
|
|
198
|
+
...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
|
|
199
|
+
}, control?.signal)).value;
|
|
200
|
+
landedId = formatActivityId(committed.stored.index);
|
|
201
|
+
effect = 'landed';
|
|
202
|
+
await square.publishIdentity();
|
|
203
|
+
const delivery = square.wakeTransport !== undefined && square.location !== 'memory'
|
|
204
|
+
? await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.location, activity: committed.stored.index, now })
|
|
205
|
+
.catch(() => ({ attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 }))
|
|
206
|
+
: { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
|
|
207
|
+
const { stored, standingCount, held, ownActivityCount, hardCap, ...metadata } = committed;
|
|
208
|
+
const hint = expressHint(ownActivityCount);
|
|
209
|
+
return { activity: toPublicActivity(stored), delivery, waited, ...metadata,
|
|
210
|
+
...(hint === undefined ? {} : { hint }), ...(feed === undefined ? {} : { observation: {
|
|
211
|
+
...square.observation(standingCount, held, name, now), ownActivityCount, hardCap, ...feed, participant: name,
|
|
212
|
+
} }) };
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (effect !== 'landed' && isSquareError(error) && error.facts?.activityUnsent === true)
|
|
216
|
+
effect = 'unsent';
|
|
217
|
+
if (effect === 'landed' || !isSquareError(error) || options.noWait || (error.code !== 'held' && error.code !== 'throttled'))
|
|
218
|
+
throw error;
|
|
219
|
+
if (error.code === 'held' && heldWaitedMs >= HELD_WAIT_BUDGET_MS)
|
|
220
|
+
throw error;
|
|
221
|
+
waited = true;
|
|
222
|
+
control?.onProgress?.({ kind: 'waiting', reason: error.code,
|
|
223
|
+
...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
|
|
224
|
+
...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
|
|
225
|
+
...(error.facts?.holdReason === undefined ? {} : { holdReason: error.facts.holdReason }) });
|
|
226
|
+
if (error.code === 'throttled')
|
|
227
|
+
await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
|
|
228
|
+
else {
|
|
229
|
+
const started = Date.now();
|
|
230
|
+
const observation = await square.artifact.read(facts => facts.hold().active, control?.signal);
|
|
231
|
+
if (observation.value)
|
|
232
|
+
await square.artifact.changed(observation.version, Math.max(1, HELD_WAIT_BUDGET_MS - heldWaitedMs), control?.signal);
|
|
233
|
+
heldWaitedMs += Date.now() - started;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
const recipient = isSquareError(error) && ['not_standing', 'bell_mention_conflict'].includes(error.facts?.reason ?? '');
|
|
240
|
+
throw failure(error, { operation: 'express', participant: name, subject: recipient ? 'recipient' : 'caller', expressEffect: effect,
|
|
241
|
+
...(landedId === undefined ? {} : { landedId }) });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async listener(verb, target, options, control) {
|
|
245
|
+
const square = this.owner;
|
|
246
|
+
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
247
|
+
checkControl(control);
|
|
248
|
+
const seat = requireSeat(this.seat, square.body, name), now = square.clock();
|
|
249
|
+
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
250
|
+
const act = verb === 'listen' ? coreListen(facts, name, target, now) : verb === 'ignore' ? coreIgnore(facts, name, target, now) : coreUnlisten(facts, name, target, now);
|
|
251
|
+
const activity = act === undefined ? null : toPublicActivity(facts.land(act));
|
|
252
|
+
return { activity, ...(!options.observe ? {} : { observation: {
|
|
253
|
+
...square.observation(inSquareCount(facts), facts.hold().active, name, now), participant: name,
|
|
254
|
+
requestedTarget: target, target: activity?.target ?? target,
|
|
255
|
+
} }) };
|
|
256
|
+
}, control?.signal)).value;
|
|
257
|
+
}
|
|
258
|
+
listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
|
|
259
|
+
ignore(target, options = {}, control) { return this.listener('ignore', target, options, control); }
|
|
260
|
+
unlisten(target, options = {}, control) { return this.listener('unlisten', target, options, control); }
|
|
261
|
+
async listening(options = {}, control) {
|
|
262
|
+
const square = this.owner;
|
|
263
|
+
return (await square.artifact.read(facts => {
|
|
264
|
+
const name = resolveKnownName(facts, this.name);
|
|
265
|
+
return { listening: coreListening(facts, name), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), facts.hold().active, name) }) };
|
|
266
|
+
}, control?.signal)).value;
|
|
267
|
+
}
|
|
268
|
+
/** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
|
|
269
|
+
async catchPage(name, seat, options, control, leaseId) {
|
|
270
|
+
return (await this.owner.artifact.transactAs(participantAuthority(seat), facts => {
|
|
271
|
+
if (leaseId !== undefined && facts.lease(name)?.leaseId !== leaseId)
|
|
272
|
+
return null;
|
|
273
|
+
return decideCatch(facts, name, options, this.owner.clock());
|
|
274
|
+
}, control?.signal)).value;
|
|
275
|
+
}
|
|
276
|
+
async catch(options = {}, control) {
|
|
277
|
+
const square = this.owner;
|
|
278
|
+
let name = this.name, subject = 'caller';
|
|
279
|
+
let leaseId, seat;
|
|
280
|
+
let waitStarted = false, replaced = false;
|
|
281
|
+
let selection = options;
|
|
282
|
+
const idle = options.idle ?? 0;
|
|
283
|
+
const complete = async (caught, stop) => {
|
|
284
|
+
const view = caught ?? (await square.artifact.read(facts => observeCatchSelection(facts, name, [], {
|
|
285
|
+
unseenOnly: true, senders: selection.from, ...(selection.mention === true ? { attentionOnly: true } : {}),
|
|
286
|
+
}, square.clock()))).value;
|
|
287
|
+
if (stop === 'empty-now' || stop === undefined)
|
|
288
|
+
stop = view.terminalStatus ?? stop;
|
|
289
|
+
if (leaseId !== undefined) {
|
|
290
|
+
await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
|
|
291
|
+
leaseId = undefined;
|
|
292
|
+
}
|
|
293
|
+
let presence;
|
|
294
|
+
if (options.observe) {
|
|
295
|
+
try {
|
|
296
|
+
presence = (await square.artifact.read(facts => catchPresence(facts, square.clock()), control?.signal)).value;
|
|
297
|
+
}
|
|
298
|
+
catch { /* Optional presence cannot erase the completed selection or receipts. */ }
|
|
299
|
+
}
|
|
300
|
+
const status = stop === 'already-active' || stop === 'replaced' || stop === 'capped' || stop === 'quorum' ? stop : undefined;
|
|
301
|
+
return { activities: view.activities.map(activity => ({ ...toPublicActivity(activity), perception: 'full' })),
|
|
302
|
+
consumedThrough: view.consumedThrough, remaining: view.remaining, idleExpired: stop === 'stale',
|
|
303
|
+
...(status === undefined ? {} : { status }), ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
304
|
+
...square.observation(view.standingCount, view.held, view.participant, view.now), participant: view.participant,
|
|
305
|
+
sayNumbers: view.sayNumbers, ownActivityCount: view.ownActivityCount, hardCap: view.hardCap, stop,
|
|
306
|
+
...(presence === undefined ? { enrichmentUnavailable: true } : { presence }), waitStarted, replaced,
|
|
307
|
+
} }) };
|
|
308
|
+
};
|
|
309
|
+
try {
|
|
310
|
+
checkControl(control);
|
|
311
|
+
validateName(name);
|
|
312
|
+
if (!Number.isFinite(idle) || idle < 0)
|
|
313
|
+
throw new SquareError('invalid_args', 'Catch idle duration must be a non-negative number');
|
|
314
|
+
if (options.id !== undefined && ['idle', 'from', 'mention', 'limit', 'replace'].some(key => Object.hasOwn(options, key)))
|
|
315
|
+
throw new SquareError('invalid_args', 'Catch id cannot be combined with idle, from, mention, limit or replace');
|
|
316
|
+
if (Object.hasOwn(options, 'replace') && idle === 0)
|
|
317
|
+
throw new SquareError('invalid_args', 'Catch replacement requires a positive idle duration');
|
|
318
|
+
catchLimit(options.limit);
|
|
319
|
+
const probe = (await square.artifact.read(facts => {
|
|
320
|
+
name = resolveKnownName(facts, this.name);
|
|
321
|
+
seat = requireSeat(this.seat, square.body, name);
|
|
322
|
+
subject = 'filter';
|
|
323
|
+
const from = options.from?.map(target => resolveKnownName(facts, target));
|
|
324
|
+
selection = { ...options, ...(from === undefined ? {} : { from }) };
|
|
325
|
+
subject = 'caller';
|
|
326
|
+
if (idle !== 0 || options.id !== undefined)
|
|
327
|
+
return undefined;
|
|
328
|
+
const page = selectCatchPage(facts, name, selection);
|
|
329
|
+
return page.indexes.length === 0 ? observeCatchSelection(facts, page.participant, [], page.filter, square.clock()) : undefined;
|
|
330
|
+
}, control?.signal)).value;
|
|
331
|
+
if (idle === 0) {
|
|
332
|
+
const data = probe ?? (await this.catchPage(name, seat, selection, control));
|
|
333
|
+
return await complete(data, data.activities.length === 0 ? 'empty-now' : undefined);
|
|
334
|
+
}
|
|
335
|
+
const watch = {
|
|
336
|
+
...(selection.from === undefined ? {} : { participants: [...selection.from] }), ...(options.mention ? { mention: name } : {}),
|
|
337
|
+
...(options.replace === undefined ? {} : { replace: options.replace }),
|
|
338
|
+
};
|
|
339
|
+
const start = await acquireWatchLease(square.artifact, seat, square.clock(), crypto.randomUUID(), watch, control?.signal);
|
|
340
|
+
if (start.type === 'active')
|
|
341
|
+
return await complete(undefined, 'already-active');
|
|
342
|
+
leaseId = start.leaseId;
|
|
343
|
+
waitStarted = true;
|
|
344
|
+
replaced = start.replaced;
|
|
345
|
+
control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced });
|
|
346
|
+
let quietSince = square.clock(), wasHeld = false, nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
347
|
+
while (true) {
|
|
348
|
+
checkControl(control);
|
|
349
|
+
// Arm before selecting: a commit during selection must not be missed.
|
|
350
|
+
const baseline = await square.artifact.revision(control?.signal);
|
|
351
|
+
const pulse = await pulseWatchLease(square.artifact, seat, square.clock(), leaseId, watch, square.clock() >= nextHeartbeat, control?.signal);
|
|
352
|
+
if (pulse.type === 'replaced')
|
|
353
|
+
return await complete(undefined, 'replaced');
|
|
354
|
+
if (pulse.type === 'terminal')
|
|
355
|
+
return await complete(undefined, pulse.status);
|
|
356
|
+
if (pulse.heartbeatAt !== undefined)
|
|
357
|
+
nextHeartbeat = pulse.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
358
|
+
if (pulse.type === 'sleep') {
|
|
359
|
+
const caught = await this.catchPage(name, seat, selection, control, leaseId);
|
|
360
|
+
if (caught === null)
|
|
361
|
+
return await complete(undefined, 'replaced');
|
|
362
|
+
if (caught.activities.length > 0)
|
|
363
|
+
return await complete(caught);
|
|
364
|
+
}
|
|
365
|
+
if (pulse.type === 'held' || wasHeld)
|
|
366
|
+
quietSince = square.clock();
|
|
367
|
+
wasHeld = pulse.type === 'held';
|
|
368
|
+
if (!wasHeld && square.clock() - quietSince >= idle) {
|
|
369
|
+
const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
|
|
370
|
+
return await complete(undefined, owns ? 'stale' : 'replaced');
|
|
371
|
+
}
|
|
372
|
+
const deadline = Math.min(nextHeartbeat, wasHeld ? Infinity : quietSince + idle);
|
|
373
|
+
await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
throw failure(error, { operation: 'catch', subject, participant: name });
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
// Cancellation releases only this captured generation and this lease.
|
|
381
|
+
if (leaseId !== undefined)
|
|
382
|
+
await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
history(query, control) { return this.owner.history(query, control); }
|
|
386
|
+
async lifecycle(verb, body, options, control) {
|
|
387
|
+
if (verb === 'done')
|
|
388
|
+
validateDoneBody(body);
|
|
389
|
+
const square = this.owner;
|
|
390
|
+
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
391
|
+
const seat = requireSeat(this.seat, square.body, name), now = square.clock();
|
|
392
|
+
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
393
|
+
const stored = facts.land(verb === 'done' ? coreDone(facts, name, body ?? '', now) : verb === 'hold' ? coreHold(facts, name, body ?? '', now) : coreResume(facts, name, now));
|
|
394
|
+
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: {
|
|
395
|
+
...square.observation(inSquareCount(facts), facts.hold().active, name, now), activity: stored,
|
|
396
|
+
} }) };
|
|
397
|
+
}, control?.signal)).value;
|
|
398
|
+
}
|
|
399
|
+
hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
|
|
400
|
+
resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
|
|
401
|
+
done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
|
|
402
|
+
};
|
|
320
403
|
async participant(requested) {
|
|
321
|
-
const selected = requested ?? this.defaultParticipant
|
|
322
|
-
if (
|
|
404
|
+
const selected = requested ?? this.defaultParticipant;
|
|
405
|
+
if (selected !== undefined) {
|
|
323
406
|
validateName(selected);
|
|
324
|
-
|
|
407
|
+
const seat = this.body === undefined ? undefined : (await this.artifact.read(facts => facts.seat(selected))).value;
|
|
408
|
+
return this.handle(selected, seat);
|
|
409
|
+
}
|
|
410
|
+
if (this.body !== undefined) {
|
|
411
|
+
const seats = (await this.artifact.read(facts => facts.seatsForBody(this.body))).value;
|
|
412
|
+
if (seats.length === 1)
|
|
413
|
+
return this.handle(seats[0].participant, seats[0]);
|
|
414
|
+
if (seats.length > 1)
|
|
415
|
+
throw new SquareError('invalid_args', 'Square needs an explicit participant name: this body holds multiple names');
|
|
325
416
|
}
|
|
326
|
-
const discovered = this.square.hostLedger === undefined ? undefined : await localParticipantName(this.location, this.square.hostLedger, this.square.env);
|
|
327
|
-
if (discovered !== undefined)
|
|
328
|
-
return this.handle(discovered);
|
|
329
417
|
throw new SquareError('invalid_args', 'Square needs one unambiguous participant name');
|
|
330
418
|
}
|
|
331
419
|
async join(requested, options = {}, control) {
|
|
332
420
|
let name = requested;
|
|
333
421
|
try {
|
|
334
422
|
checkControl(control);
|
|
335
|
-
name = (
|
|
423
|
+
name = requireJoinName(requested);
|
|
336
424
|
const last = options.last ?? HISTORY_DEFAULT_LIMIT;
|
|
337
425
|
if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
|
|
338
426
|
throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
427
|
+
const body = requireBody(this.body), now = this.clock();
|
|
428
|
+
const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
|
|
429
|
+
const authority = initial === undefined ? { kind: 'join', participant: name, body, wakeAddress: this.wakeAddress }
|
|
430
|
+
: { kind: 'takeover', participant: initial.participant, body, expectedGeneration: initial.generation, wakeAddress: this.wakeAddress };
|
|
431
|
+
const committed = (await this.artifact.transactAs(authority, facts => {
|
|
432
|
+
const participant = facts.participant(name)?.name ?? name;
|
|
433
|
+
const current = facts.participant(participant), currentSeat = facts.seat(participant);
|
|
434
|
+
let kind = 'joined', activity = null;
|
|
435
|
+
if (initial !== undefined) {
|
|
436
|
+
if (!current?.joined)
|
|
437
|
+
throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
|
|
438
|
+
facts.land(coreDone(facts, participant, '', now));
|
|
439
|
+
kind = 'taken-over';
|
|
440
|
+
}
|
|
441
|
+
else if (current?.joined) {
|
|
442
|
+
if (currentSeat === undefined || !sameBody(currentSeat.body, body))
|
|
443
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another body; take it over explicitly`);
|
|
444
|
+
if (this.wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, this.wakeAddress))
|
|
445
|
+
facts.updateWakeAddress(asSeatRef(currentSeat), this.wakeAddress);
|
|
446
|
+
kind = 'reconnected';
|
|
447
|
+
}
|
|
448
|
+
if (kind !== 'reconnected') {
|
|
449
|
+
const decision = decideJoin(facts, participant, now);
|
|
450
|
+
if (decision.joinAct === undefined)
|
|
451
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
|
|
452
|
+
activity = toPublicActivity(facts.land(decision.joinAct));
|
|
453
|
+
}
|
|
454
|
+
const seat = facts.seat(participant);
|
|
455
|
+
if (seat === undefined)
|
|
456
|
+
throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
|
|
457
|
+
return { participant: this.handle(seat.participant, seat), activity, kind,
|
|
458
|
+
participantCount: facts.roster().filter(item => item.joined || item.done).length,
|
|
459
|
+
...(!options.observe ? {} : { observation: this.observation(inSquareCount(facts), facts.hold().active, seat.participant, now) }) };
|
|
460
|
+
}, control?.signal)).value;
|
|
461
|
+
await this.publishIdentity();
|
|
462
|
+
if (!options.observe)
|
|
463
|
+
return committed;
|
|
464
|
+
let entry;
|
|
465
|
+
try {
|
|
466
|
+
entry = (await this.artifact.read(facts => entryPresentation(facts, committed.participant.name, last), control?.signal)).value;
|
|
350
467
|
}
|
|
351
|
-
|
|
352
|
-
return { ...
|
|
353
|
-
observation: {
|
|
354
|
-
...observedContext(this.square, committed.standingCount, committed.held, result.participant, committed.now),
|
|
355
|
-
...(entry === undefined ? { enrichmentUnavailable: true } : { entry }),
|
|
356
|
-
},
|
|
357
|
-
}) };
|
|
468
|
+
catch { /* Keep the committed join even when optional history is unavailable. */ }
|
|
469
|
+
return { ...committed, observation: { ...committed.observation, ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
|
|
358
470
|
}
|
|
359
471
|
catch (error) {
|
|
360
472
|
throw failure(error, { operation: 'join', subject: 'caller', participant: name });
|
|
361
473
|
}
|
|
362
474
|
}
|
|
363
|
-
doneOwnedSession(name, body, expectedEpoch, control) { return done(this.square, name, body, { expectedEpoch }, control); }
|
|
364
475
|
async implicitJoin(name, control) {
|
|
365
|
-
|
|
366
|
-
|
|
476
|
+
checkControl(control);
|
|
477
|
+
validateName(name);
|
|
478
|
+
const body = requireBody(this.body), now = this.clock();
|
|
479
|
+
const result = (await this.artifact.transactAs({ kind: 'join', participant: name, body, wakeAddress: this.wakeAddress }, facts => {
|
|
480
|
+
const decision = decideImplicitJoin(facts, name, now);
|
|
481
|
+
let seat = facts.seat(decision.joinedName);
|
|
482
|
+
if (decision.joinAct === undefined) {
|
|
483
|
+
if (decision.state === 'active' && (seat === undefined || !sameBody(seat.body, body)))
|
|
484
|
+
throw new SquareError('already_joined', `${participantIdentity(decision.joinedName)} is held by another body`);
|
|
485
|
+
if (decision.state === 'active' && this.wakeAddress !== undefined && canRefreshWakeAddress(seat.wakeAddress, this.wakeAddress))
|
|
486
|
+
facts.updateWakeAddress(asSeatRef(seat), this.wakeAddress);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
facts.land(decision.joinAct);
|
|
490
|
+
seat = facts.seat(decision.joinedName);
|
|
491
|
+
if (seat === undefined)
|
|
492
|
+
throw new SquareError('invalid_args', 'The implicit join did not establish its artifact seat.');
|
|
493
|
+
}
|
|
494
|
+
return { state: decision.state, ...(decision.state === 'done' ? {} : { participant: this.handle(decision.joinedName, seat) }) };
|
|
495
|
+
}, control?.signal)).value;
|
|
496
|
+
if (result.participant !== undefined)
|
|
497
|
+
await this.publishIdentity();
|
|
498
|
+
return result;
|
|
367
499
|
}
|
|
368
|
-
snapshot(control) { return snapshot(this.
|
|
500
|
+
async snapshot(control) { return (await this.artifact.read(facts => snapshot(facts, this.clock()), control?.signal)).value; }
|
|
369
501
|
async history(query = {}, control) {
|
|
370
502
|
if (query.limit !== undefined && query.limit !== null && (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > HISTORY_MAX_LIMIT))
|
|
371
503
|
throw new SquareError('invalid_args', `History limit must be a positive integer no greater than ${HISTORY_MAX_LIMIT}`);
|
|
@@ -381,89 +513,89 @@ export class SqliteSquare {
|
|
|
381
513
|
const limit = query.limit === undefined ? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT) : query.limit;
|
|
382
514
|
const contextual = !!query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined;
|
|
383
515
|
const kinds = contextual || query.since !== undefined ? undefined : NON_READ;
|
|
384
|
-
return (await this.
|
|
516
|
+
return (await this.artifact.read(facts => {
|
|
385
517
|
const selected = selectHistory(facts, query, limit, kinds, query.observe === true);
|
|
386
518
|
if (contextual && limit === null && selected.totalMatches > HISTORY_MAX_LIMIT)
|
|
387
519
|
throw failure(new SquareError('invalid_args', `History context exceeds ${HISTORY_MAX_LIMIT} activities`), { operation: 'history', historyBounds: { maxLimit: HISTORY_MAX_LIMIT, totalMatches: selected.totalMatches } });
|
|
388
520
|
const activities = publicActivities(selected.activities);
|
|
389
521
|
if (!query.observe)
|
|
390
522
|
return { activities };
|
|
391
|
-
const indexes = selected.activities.map(
|
|
523
|
+
const indexes = selected.activities.map(activity => activity.index), presenceAnchors = {};
|
|
392
524
|
for (const participant of facts.roster()) {
|
|
393
525
|
const anchor = presenceAnchor(facts, participant.name);
|
|
394
526
|
if (indexes.includes(anchor))
|
|
395
527
|
(presenceAnchors[anchor] ??= []).push(participant.name);
|
|
396
528
|
}
|
|
397
|
-
return { activities, observation: { ...
|
|
529
|
+
return { activities, observation: { ...this.observation(inSquareCount(facts), facts.hold().active), ...selected,
|
|
398
530
|
sayNumbers: facts.sayOrdinals(indexes), presenceAnchors } };
|
|
399
531
|
}, control?.signal)).value;
|
|
400
532
|
}
|
|
401
533
|
async status(options = {}, control) {
|
|
402
534
|
const viewer = !options.observe ? '' : options.viewer ?? (await this.participant().catch(() => undefined))?.name ?? '';
|
|
403
|
-
return (await this.
|
|
404
|
-
location: this.location, now: this.
|
|
535
|
+
return (await this.artifact.read(facts => queryStatus(facts, {
|
|
536
|
+
location: this.location, now: this.clock(), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined,
|
|
405
537
|
}, viewer, options.observe === true), control?.signal)).value;
|
|
406
538
|
}
|
|
407
539
|
async participants(options = {}, control) {
|
|
408
|
-
return (await this.
|
|
409
|
-
location: this.location, now: this.
|
|
540
|
+
return (await this.artifact.read(facts => queryParticipants(facts, {
|
|
541
|
+
location: this.location, now: this.clock(), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined,
|
|
410
542
|
}, options.observe === true), control?.signal)).value;
|
|
411
543
|
}
|
|
412
544
|
async recovery(requested, control) {
|
|
413
545
|
const name = (await this.participant(requested).catch(() => undefined))?.name;
|
|
414
|
-
const facts = name === undefined ? undefined : await recoveryObservation(
|
|
415
|
-
return facts ?? await contextObservation(
|
|
546
|
+
const facts = name === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, name, this.clock()), control?.signal).catch(() => undefined))?.value;
|
|
547
|
+
return facts ?? await this.contextObservation(name, control);
|
|
416
548
|
}
|
|
417
|
-
stream(options, control) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
549
|
+
stream(options, control) {
|
|
550
|
+
// A path stream follows replacements independently of this connection's inode
|
|
551
|
+
// and lifetime. Only explicit artifact readers borrow an existing port.
|
|
552
|
+
return streamSquare(this.location, options, control);
|
|
553
|
+
}
|
|
554
|
+
async recognize() {
|
|
555
|
+
if (this.body === undefined)
|
|
556
|
+
return [];
|
|
557
|
+
const candidates = (await this.artifact.read(facts => facts.seatsForBody(this.body))).value;
|
|
558
|
+
return candidates.map(seat => this.handle(seat.participant, seat));
|
|
559
|
+
}
|
|
560
|
+
close() { return this.ownsArtifact ? this.artifact.close() : Promise.resolve(); }
|
|
561
|
+
async cancelDeliveries(name, expectedGeneration) {
|
|
562
|
+
const body = this.body;
|
|
563
|
+
if (body === undefined)
|
|
564
|
+
return false;
|
|
565
|
+
const seat = (await this.artifact.read(facts => facts.seat(name))).value;
|
|
566
|
+
if (seat === undefined || seat.generation !== expectedGeneration || !sameBody(seat.body, body))
|
|
567
|
+
return false;
|
|
568
|
+
return this.artifact.cancelDeliveries(asSeatRef(seat));
|
|
569
|
+
}
|
|
570
|
+
async endOwnedSession(name, sessionId, expectedGeneration, body) {
|
|
571
|
+
const captured = (await this.artifact.read(facts => facts.seat(name))).value;
|
|
572
|
+
if (captured === undefined || captured.body.session !== sessionId || (body !== undefined && !sameBody(captured.body, body))
|
|
573
|
+
|| (expectedGeneration !== undefined && captured.generation !== expectedGeneration))
|
|
423
574
|
return null;
|
|
424
|
-
|
|
425
|
-
|
|
575
|
+
return (await this.artifact.transactAs(participantAuthority(captured), facts => {
|
|
576
|
+
const current = facts.seat(name);
|
|
577
|
+
if (current === undefined || current.generation !== captured.generation || !sameBody(current.body, captured.body))
|
|
578
|
+
return null;
|
|
579
|
+
return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
|
|
580
|
+
})).value;
|
|
426
581
|
}
|
|
427
|
-
close() { return closeOpenSquare(this.square); }
|
|
428
|
-
endOwnedSession(name, sessionId, expectedEpoch) { return endOwnedSession(this.square, name, sessionId, expectedEpoch); }
|
|
429
|
-
retireEndedSessionRoutes(sessionId) { return retireEndedSessionRoutes(this.square, sessionId); }
|
|
430
582
|
}
|
|
431
583
|
export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(localSquareLocation(input)); }
|
|
432
|
-
export function migrateSquare(input, control) { checkControl(control); return migrateSquareFile(localSquareLocation(input), control?.signal); }
|
|
584
|
+
export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(localSquareLocation(input), control?.signal, env); }
|
|
433
585
|
export function discover(input, options = {}, control) {
|
|
434
586
|
if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
|
|
435
587
|
localSquareLocation(input);
|
|
436
588
|
const env = { ...(input.env ?? process.env) };
|
|
437
|
-
return discoverSquares(input.cwd ?? process.cwd(), options, control, {
|
|
589
|
+
return discoverSquares(input.cwd ?? process.cwd(), options, control, { clock: squareClock(env, input.clock) });
|
|
438
590
|
}
|
|
439
|
-
export async function inbox(input,
|
|
591
|
+
export async function inbox(input, identity, options = {}, control) {
|
|
440
592
|
checkControl(control);
|
|
441
|
-
const env = { ...(input.env ?? process.env) },
|
|
593
|
+
const env = { ...(input.env ?? process.env) }, limit = options.limit ?? 20;
|
|
442
594
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
443
595
|
throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
let square;
|
|
448
|
-
try {
|
|
449
|
-
square = await openSquare(binding.location, { ...input, env, hostLedger, clock: squareClock(env, input.clock) }, control);
|
|
450
|
-
const row = (await square.artifact.read((facts) => {
|
|
451
|
-
const participant = facts.participant(binding.participant);
|
|
452
|
-
return !participant?.joined ? undefined : { name: participant.name, squarePath: binding.location,
|
|
453
|
-
pending: facts.directedCount(participant.name, { unseenOnly: true }) };
|
|
454
|
-
}, control?.signal)).value;
|
|
455
|
-
if (row !== undefined)
|
|
456
|
-
rows.push(row);
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
if (control?.signal?.aborted)
|
|
460
|
-
throw error;
|
|
461
|
-
}
|
|
462
|
-
finally {
|
|
463
|
-
if (square !== undefined)
|
|
464
|
-
await closeOpenSquare(square);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
596
|
+
const query = typeof identity === 'string' ? { sessionId: identity, env } : { body: identity, env };
|
|
597
|
+
const rows = await sessionInboxCounts(query, control?.signal);
|
|
598
|
+
checkControl(control);
|
|
467
599
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
468
600
|
rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));
|
|
469
601
|
return { rows: rows.slice(0, limit), total: rows.length };
|