@modelprofile.com/flexharness 11.3.1 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.core.d.ts +4 -0
- package/dist_ts/classes.flexharness.core.js +69 -25
- package/dist_ts/classes.flexharness.imports.js +4 -3
- package/dist_ts/classes.flexharness.reversions.d.ts +7 -1
- package/dist_ts/classes.flexharness.reversions.js +43 -29
- package/dist_ts/classes.flexharness.sessions.js +2 -6
- package/dist_ts/classes.stores.js +20 -18
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.d.ts +24 -2
- package/dist_ts/interfaces.flexharness.d.ts +8 -1
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.projectionchange.d.ts +34 -0
- package/dist_ts/utils.projectionchange.js +135 -0
- package/dist_ts/utils.storecodecs.d.ts +10 -3
- package/dist_ts/utils.storecodecs.js +29 -4
- package/dist_ts/utils.storerules.d.ts +1 -1
- package/dist_ts/utils.storerules.js +1 -1
- package/dist_ts_migration/v3_legacyflexharness.js +6 -5
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +5 -4
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +44 -25
- package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +152 -20
- package/dist_ts_stores_nosqldb/classes.recordstore.js +369 -66
- package/dist_ts_stores_nosqldb/index.d.ts +1 -1
- package/dist_ts_stores_nosqldb/index.js +2 -2
- package/dist_ts_stores_nosqldb/plugins.d.ts +1 -1
- package/dist_ts_stores_nosqldb/plugins.js +1 -1
- package/dist_ts_stores_testing/contract.d.ts +33 -0
- package/dist_ts_stores_testing/contract.js +566 -0
- package/dist_ts_stores_testing/fixtures.d.ts +20 -0
- package/dist_ts_stores_testing/fixtures.js +112 -0
- package/dist_ts_stores_testing/index.d.ts +2 -0
- package/dist_ts_stores_testing/index.js +3 -0
- package/dist_ts_stores_testing/plugins.d.ts +5 -0
- package/dist_ts_stores_testing/plugins.js +7 -0
- package/package.json +9 -3
- package/readme.md +6 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.core.ts +74 -24
- package/ts/classes.flexharness.imports.ts +3 -2
- package/ts/classes.flexharness.reversions.ts +53 -28
- package/ts/classes.flexharness.sessions.ts +2 -5
- package/ts/classes.stores.ts +40 -37
- package/ts/index.ts +5 -0
- package/ts/interfaces.flexharness.ts +8 -1
- package/ts/interfaces.ts +27 -2
- package/ts/plugins.ts +12 -7
- package/ts/readme.md +6 -3
- package/ts/utils.projectionchange.ts +190 -0
- package/ts/utils.storecodecs.ts +42 -7
- package/ts/utils.storerules.ts +2 -2
- package/ts_migration/v3_legacyflexharness.ts +11 -6
- package/ts_stores_nosqldb/classes.nosqldbstores.ts +59 -46
- package/ts_stores_nosqldb/classes.recordstore.ts +445 -64
- package/ts_stores_nosqldb/index.ts +1 -0
- package/ts_stores_nosqldb/plugins.ts +4 -3
- package/ts_stores_nosqldb/readme.md +6 -2
- package/ts_stores_testing/contract.ts +776 -0
- package/ts_stores_testing/fixtures.ts +127 -0
- package/ts_stores_testing/index.ts +17 -0
- package/ts_stores_testing/plugins.ts +20 -0
- package/ts_stores_testing/readme.md +62 -0
package/ts/utils.storerules.ts
CHANGED
|
@@ -144,8 +144,8 @@ export function isProjectManagementTombstoneAdmitted(
|
|
|
144
144
|
|
|
145
145
|
/** An archive is written once; repeating it with the same events is a no-op. */
|
|
146
146
|
export function assertSameArchiveEvents(
|
|
147
|
-
existing: plugins.
|
|
148
|
-
next: plugins.
|
|
147
|
+
existing: plugins.IAgentEventArchive,
|
|
148
|
+
next: plugins.IAgentEventArchive,
|
|
149
149
|
): void {
|
|
150
150
|
if (
|
|
151
151
|
JSON.stringify(existing.events.map((event) => event.id))
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
assertJsonSerializable,
|
|
19
19
|
cloneSerializable,
|
|
20
20
|
} from '../ts/utils.json.js';
|
|
21
|
+
import { createWholeProjectionChange } from '../ts/utils.projectionchange.js';
|
|
21
22
|
|
|
22
23
|
export interface IFlexLegacyStoredSessionSnapshot {
|
|
23
24
|
session: IFlexSession;
|
|
@@ -59,8 +60,8 @@ interface IInspectedSessionDestination {
|
|
|
59
60
|
plan: ILegacySessionMigrationPlan;
|
|
60
61
|
projection: TFlexProjectionSnapshot | undefined;
|
|
61
62
|
permission: IFlexPermissionSnapshot | undefined;
|
|
62
|
-
eventStore: plugins.
|
|
63
|
-
eventSnapshot: plugins.
|
|
63
|
+
eventStore: plugins.IAgentEventStore;
|
|
64
|
+
eventSnapshot: plugins.IAgentEventSnapshot | undefined;
|
|
64
65
|
jobSnapshot: plugins.IToolJobSnapshot | undefined;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -341,7 +342,7 @@ function createSessionAgentEvents(
|
|
|
341
342
|
|
|
342
343
|
const jsonEvents = JSON.parse(JSON.stringify(events)) as plugins.TAgentEvent[];
|
|
343
344
|
try {
|
|
344
|
-
plugins.
|
|
345
|
+
plugins.validateAgentEventSnapshot({
|
|
345
346
|
schemaVersion: 2,
|
|
346
347
|
sessionId,
|
|
347
348
|
revision: 1,
|
|
@@ -508,7 +509,7 @@ async function inspectDestinations(
|
|
|
508
509
|
projection,
|
|
509
510
|
permission,
|
|
510
511
|
eventStore,
|
|
511
|
-
eventSnapshot
|
|
512
|
+
eventSnapshot,
|
|
512
513
|
jobSnapshot,
|
|
513
514
|
});
|
|
514
515
|
}
|
|
@@ -596,7 +597,7 @@ export async function migrateLegacyFlexHarnessSnapshot(
|
|
|
596
597
|
await stores.projections.save(
|
|
597
598
|
storageKey,
|
|
598
599
|
session.sessionId,
|
|
599
|
-
session.projectionSnapshot,
|
|
600
|
+
createWholeProjectionChange(session.projectionSnapshot),
|
|
600
601
|
0,
|
|
601
602
|
);
|
|
602
603
|
}
|
|
@@ -609,7 +610,11 @@ export async function migrateLegacyFlexHarnessSnapshot(
|
|
|
609
610
|
);
|
|
610
611
|
}
|
|
611
612
|
if (!destination.eventSnapshot) {
|
|
612
|
-
await destination.eventStore.save(
|
|
613
|
+
await destination.eventStore.save(
|
|
614
|
+
session.sessionId,
|
|
615
|
+
{ remove: [], replace: [], append: session.agentEvents },
|
|
616
|
+
0,
|
|
617
|
+
);
|
|
613
618
|
}
|
|
614
619
|
}
|
|
615
620
|
if (!destinations.scope) {
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
IFlexProjectManagementStore,
|
|
10
10
|
IFlexProjectManagementTombstone,
|
|
11
11
|
IFlexProjectManagementWriteContext,
|
|
12
|
-
|
|
12
|
+
IFlexProjectionChange,
|
|
13
13
|
IFlexProjectionStore,
|
|
14
14
|
IFlexResultStore,
|
|
15
15
|
IFlexScopeSnapshot,
|
|
@@ -30,10 +30,16 @@ import {
|
|
|
30
30
|
assertFlexProjectManagementSnapshot,
|
|
31
31
|
assertFlexProjectManagementTombstone,
|
|
32
32
|
} from '../ts/utils.projectmanagement.js';
|
|
33
|
+
import {
|
|
34
|
+
applyFlexProjectionChange,
|
|
35
|
+
assertFlexProjectionChange,
|
|
36
|
+
validateProjectionChangeSave,
|
|
37
|
+
} from '../ts/utils.projectionchange.js';
|
|
33
38
|
import {
|
|
34
39
|
assertFlexToolJobSnapshot,
|
|
35
|
-
|
|
40
|
+
createChangedAgentEventSnapshot,
|
|
36
41
|
createToolJobSnapshot,
|
|
42
|
+
normalizeAgentEventChange,
|
|
37
43
|
normalizeJsonObjectUndefined,
|
|
38
44
|
validateAgentEventArchive,
|
|
39
45
|
validateAgentEventSnapshot,
|
|
@@ -49,7 +55,11 @@ import {
|
|
|
49
55
|
validateResultIndex,
|
|
50
56
|
validateSnapshotSave,
|
|
51
57
|
} from '../ts/utils.storerules.js';
|
|
52
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
FlexRecordStore,
|
|
60
|
+
type IFlexRecordLog,
|
|
61
|
+
type IFlexRecordReplacement,
|
|
62
|
+
} from './classes.recordstore.js';
|
|
53
63
|
|
|
54
64
|
/** Replaces a record with a snapshot while the record's revision is the expected one. */
|
|
55
65
|
function revisionReplacement<TSnapshot extends { revision: number }>(
|
|
@@ -112,42 +122,43 @@ class NoSqlScopeStore implements IFlexScopeStore {
|
|
|
112
122
|
}
|
|
113
123
|
}
|
|
114
124
|
|
|
125
|
+
/** A projection is a log record: a save writes its change, checked in form. */
|
|
115
126
|
class NoSqlProjectionStore implements IFlexProjectionStore {
|
|
116
127
|
constructor(private readonly records: FlexRecordStore) {}
|
|
117
128
|
|
|
118
|
-
private
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
private log(sessionId: string): IFlexRecordLog<TFlexProjectionSnapshot, IFlexProjectionChange> {
|
|
130
|
+
return {
|
|
131
|
+
validate: (value) => {
|
|
132
|
+
assertFlexProjectionSnapshot(value, sessionId);
|
|
133
|
+
return value;
|
|
134
|
+
},
|
|
135
|
+
validateChange: (value) => {
|
|
136
|
+
assertFlexProjectionChange(value);
|
|
137
|
+
return value;
|
|
138
|
+
},
|
|
139
|
+
apply: applyFlexProjectionChange,
|
|
122
140
|
};
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
public load(storageKey: string, sessionId: string): Promise<TFlexProjectionSnapshot | undefined> {
|
|
126
144
|
providerKey(storageKey, sessionId);
|
|
127
|
-
return this.records.
|
|
128
|
-
{ domain: 'projection', storageKey, sessionId },
|
|
129
|
-
this.validator(sessionId),
|
|
130
|
-
);
|
|
145
|
+
return this.records.readLog({ domain: 'projection', storageKey, sessionId }, this.log(sessionId));
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
public async save(
|
|
134
149
|
storageKey: string,
|
|
135
150
|
sessionId: string,
|
|
136
|
-
|
|
151
|
+
change: IFlexProjectionChange,
|
|
137
152
|
expectedRevision: number,
|
|
138
153
|
): Promise<void> {
|
|
139
154
|
const key = providerKey(storageKey, sessionId);
|
|
140
|
-
const validated =
|
|
141
|
-
|
|
155
|
+
const validated = validateProjectionChangeSave(change, expectedRevision);
|
|
156
|
+
await this.records.append({ domain: 'projection', storageKey, sessionId }, this.log(sessionId), {
|
|
142
157
|
expectedRevision,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await this.records.replace(
|
|
148
|
-
{ domain: 'projection', storageKey, sessionId },
|
|
149
|
-
revisionReplacement(validated, expectedRevision, conflict),
|
|
150
|
-
);
|
|
158
|
+
value: validated,
|
|
159
|
+
conflict: (actualRevision) =>
|
|
160
|
+
new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision),
|
|
161
|
+
});
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
public deleteSession(storageKey: string, sessionId: string): Promise<void> {
|
|
@@ -255,9 +266,8 @@ class NoSqlProjectManagementStore implements IFlexProjectManagementStore {
|
|
|
255
266
|
}
|
|
256
267
|
}
|
|
257
268
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
269
|
+
/** A session's event log is a log record: a save writes its change, checked in form. */
|
|
270
|
+
class NoSqlAgentEventStore implements plugins.IAgentEventStore {
|
|
261
271
|
constructor(
|
|
262
272
|
private readonly records: FlexRecordStore,
|
|
263
273
|
private readonly storageKey: string,
|
|
@@ -272,36 +282,38 @@ class NoSqlAgentEventStore implements plugins.IAgentEventStoreV2 {
|
|
|
272
282
|
}
|
|
273
283
|
}
|
|
274
284
|
|
|
275
|
-
private readonly
|
|
276
|
-
validateAgentEventSnapshot(value, this.sessionId)
|
|
285
|
+
private readonly log: IFlexRecordLog<plugins.IAgentEventSnapshot, plugins.IAgentEventChange> = {
|
|
286
|
+
validate: (value) => validateAgentEventSnapshot(value, this.sessionId),
|
|
287
|
+
validateChange: normalizeAgentEventChange,
|
|
288
|
+
apply: (current, change) => createChangedAgentEventSnapshot(this.sessionId, current, change),
|
|
289
|
+
};
|
|
277
290
|
|
|
278
|
-
public load(sessionId: string): Promise<plugins.
|
|
291
|
+
public load(sessionId: string): Promise<plugins.IAgentEventSnapshot | undefined> {
|
|
279
292
|
this.assertSession(sessionId);
|
|
280
|
-
return this.records.
|
|
293
|
+
return this.records.readLog(
|
|
281
294
|
{ domain: 'agentEvents', storageKey: this.storageKey, sessionId },
|
|
282
|
-
this.
|
|
295
|
+
this.log,
|
|
283
296
|
);
|
|
284
297
|
}
|
|
285
298
|
|
|
286
299
|
public async save(
|
|
287
300
|
sessionId: string,
|
|
288
|
-
|
|
301
|
+
change: plugins.IAgentEventChange,
|
|
289
302
|
expectedRevision: number,
|
|
290
303
|
): Promise<number> {
|
|
291
304
|
this.assertSession(sessionId);
|
|
292
305
|
validateExpectedRevision(expectedRevision);
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return snapshot.revision;
|
|
306
|
+
const normalized = normalizeAgentEventChange(change);
|
|
307
|
+
await this.records.append({ domain: 'agentEvents', storageKey: this.storageKey, sessionId }, this.log, {
|
|
308
|
+
expectedRevision,
|
|
309
|
+
value: normalized,
|
|
310
|
+
conflict: (actualRevision) =>
|
|
311
|
+
new plugins.AgentEventStoreConflictError(sessionId, expectedRevision, actualRevision),
|
|
312
|
+
});
|
|
313
|
+
return expectedRevision + 1;
|
|
302
314
|
}
|
|
303
315
|
|
|
304
|
-
public async archive(archive: plugins.
|
|
316
|
+
public async archive(archive: plugins.IAgentEventArchive): Promise<void> {
|
|
305
317
|
this.assertSession(archive.sessionId);
|
|
306
318
|
const normalized = normalizeJsonObjectUndefined(archive, '$archive');
|
|
307
319
|
const validated = validateAgentEventArchive(normalized, this.sessionId, archive.archiveId);
|
|
@@ -326,7 +338,7 @@ class NoSqlAgentEventStore implements plugins.IAgentEventStoreV2 {
|
|
|
326
338
|
public loadArchive(
|
|
327
339
|
sessionId: string,
|
|
328
340
|
archiveId: string,
|
|
329
|
-
): Promise<plugins.
|
|
341
|
+
): Promise<plugins.IAgentEventArchive | undefined> {
|
|
330
342
|
this.assertSession(sessionId);
|
|
331
343
|
validateIdentifier(archiveId, 'archiveId');
|
|
332
344
|
return this.records.read(
|
|
@@ -505,8 +517,9 @@ function validateResult(value: unknown): TJsonValue {
|
|
|
505
517
|
/**
|
|
506
518
|
* Every FlexHarness persistence domain on `@lossless.org/client/nosqldb`, with compare-and-swap
|
|
507
519
|
* that holds across processes. Records live in `flexharness_records`; payloads longer than one
|
|
508
|
-
* chunk live in `flexharness_chunks`;
|
|
509
|
-
*
|
|
520
|
+
* chunk live in `flexharness_chunks`; the changes saved to a projection or an Agent event log
|
|
521
|
+
* since its last compaction live in its head and, beyond the head's inline room, in pages in
|
|
522
|
+
* `flexharness_changes`; `flexharness_namespaces` holds each storage namespace's fence.
|
|
510
523
|
*/
|
|
511
524
|
export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
|
|
512
525
|
public readonly scopes: IFlexScopeStore;
|
|
@@ -533,7 +546,7 @@ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
|
|
|
533
546
|
}
|
|
534
547
|
|
|
535
548
|
/**
|
|
536
|
-
* Creates or verifies the
|
|
549
|
+
* Creates or verifies the four collections and their indexes. Every store operation awaits the
|
|
537
550
|
* same preparation, so calling it at startup only moves that work, and its failure, forward.
|
|
538
551
|
*/
|
|
539
552
|
public prepare(): Promise<void> {
|
|
@@ -541,7 +554,7 @@ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
|
|
|
541
554
|
}
|
|
542
555
|
|
|
543
556
|
/**
|
|
544
|
-
* Physically removes every record and
|
|
557
|
+
* Physically removes every record, chunk and change of one storage namespace in every domain: the
|
|
545
558
|
* scope snapshot, each session's projection, permissions, Agent events and archives, jobs and
|
|
546
559
|
* results, and the project-management records and tombstones. This is the host's durable
|
|
547
560
|
* deletion of a scope. Stop new admission for every alias of the scope first. Without `fence`,
|