@twin.org/auditable-item-stream-service 0.0.3-next.9 → 0.9.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/README.md +2 -2
- package/dist/es/auditableItemStreamRoutes.js +401 -73
- package/dist/es/auditableItemStreamRoutes.js.map +1 -1
- package/dist/es/auditableItemStreamService.js +304 -113
- package/dist/es/auditableItemStreamService.js.map +1 -1
- package/dist/es/entities/auditableItemStream.js +17 -3
- package/dist/es/entities/auditableItemStream.js.map +1 -1
- package/dist/es/models/IAuditableItemStreamServiceConfig.js.map +1 -1
- package/dist/es/models/IAuditableItemStreamServiceConstructorOptions.js.map +1 -1
- package/dist/es/models/IAuditableItemStreamServiceContext.js.map +1 -1
- package/dist/es/restEntryPoints.js +3 -0
- package/dist/es/restEntryPoints.js.map +1 -1
- package/dist/types/auditableItemStreamRoutes.d.ts +37 -5
- package/dist/types/auditableItemStreamService.d.ts +37 -38
- package/dist/types/entities/auditableItemStream.d.ts +10 -1
- package/dist/types/models/IAuditableItemStreamServiceConfig.d.ts +4 -0
- package/dist/types/models/IAuditableItemStreamServiceConstructorOptions.d.ts +4 -0
- package/dist/types/models/IAuditableItemStreamServiceContext.d.ts +4 -0
- package/dist/types/restEntryPoints.d.ts +3 -0
- package/docs/changelog.md +396 -92
- package/docs/examples.md +211 -1
- package/docs/open-api/spec.json +832 -169
- package/docs/reference/classes/AuditableItemStream.md +30 -14
- package/docs/reference/classes/AuditableItemStreamEntry.md +13 -13
- package/docs/reference/classes/AuditableItemStreamService.md +109 -84
- package/docs/reference/functions/auditableItemStreamClose.md +31 -0
- package/docs/reference/functions/auditableItemStreamDeleteEntry.md +1 -1
- package/docs/reference/functions/auditableItemStreamList.md +1 -1
- package/docs/reference/functions/auditableItemStreamListEntries.md +1 -1
- package/docs/reference/functions/auditableItemStreamListEntriesNoStream.md +31 -0
- package/docs/reference/functions/auditableItemStreamListEntryObjects.md +1 -1
- package/docs/reference/functions/auditableItemStreamListEntryObjectsNoStream.md +31 -0
- package/docs/reference/functions/auditableItemStreamRemoveProof.md +31 -0
- package/docs/reference/index.md +4 -0
- package/docs/reference/interfaces/IAuditableItemStreamServiceConfig.md +10 -2
- package/docs/reference/interfaces/IAuditableItemStreamServiceConstructorOptions.md +18 -10
- package/docs/reference/variables/restEntryPoints.md +2 -0
- package/locales/en.json +6 -1
- package/package.json +19 -18
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { AuditableItemStreamContexts, AuditableItemStreamTopics, AuditableItemStreamTypes } from "@twin.org/auditable-item-stream-models";
|
|
4
|
-
import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
5
|
-
import { Coerce, ComponentFactory, GeneralError, Guards, Is, NotFoundError, ObjectHelper, RandomHelper, Urn, Validation } from "@twin.org/core";
|
|
6
|
-
import {
|
|
3
|
+
import { AuditableItemStreamContexts, AuditableItemStreamDataTypes, AuditableItemStreamMetricIds, AuditableItemStreamMetrics, AuditableItemStreamModes, AuditableItemStreamTopics, AuditableItemStreamTypes } from "@twin.org/auditable-item-stream-models";
|
|
4
|
+
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
5
|
+
import { Coerce, ComponentFactory, GeneralError, Guards, Is, Mutex, NotFoundError, ObjectHelper, RandomHelper, Urn, Validation } from "@twin.org/core";
|
|
6
|
+
import { DataTypeHelper } from "@twin.org/data-core";
|
|
7
|
+
import { JsonLdDataTypes, JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
7
8
|
import { ComparisonOperator, LogicalOperator, SortDirection } from "@twin.org/entity";
|
|
8
9
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
9
|
-
import { ImmutableProofContexts } from "@twin.org/immutable-proof-models";
|
|
10
|
+
import { ImmutableProofContexts, ImmutableProofDataTypes } from "@twin.org/immutable-proof-models";
|
|
10
11
|
import { SchemaOrgContexts, SchemaOrgDataTypes, SchemaOrgTypes } from "@twin.org/standards-schema-org";
|
|
12
|
+
import { MetricHelper } from "@twin.org/telemetry-models";
|
|
11
13
|
/**
|
|
12
14
|
* Class for performing auditable item stream operations.
|
|
13
15
|
*/
|
|
@@ -68,11 +70,21 @@ export class AuditableItemStreamService {
|
|
|
68
70
|
* @internal
|
|
69
71
|
*/
|
|
70
72
|
_eventBusComponent;
|
|
73
|
+
/**
|
|
74
|
+
* The telemetry component.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
_telemetryComponent;
|
|
71
78
|
/**
|
|
72
79
|
* The default interval for the integrity checks.
|
|
73
80
|
* @internal
|
|
74
81
|
*/
|
|
75
82
|
_defaultImmutableInterval;
|
|
83
|
+
/**
|
|
84
|
+
* The timeout in milliseconds when acquiring a mutex lock.
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
_mutexTimeoutMs;
|
|
76
88
|
/**
|
|
77
89
|
* Create a new instance of AuditableItemStreamService.
|
|
78
90
|
* @param options The dependencies for the auditable item stream connector.
|
|
@@ -84,9 +96,14 @@ export class AuditableItemStreamService {
|
|
|
84
96
|
if (Is.stringValue(options?.eventBusComponentType)) {
|
|
85
97
|
this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType);
|
|
86
98
|
}
|
|
99
|
+
this._telemetryComponent = ComponentFactory.getIfExists(options?.telemetryComponentType);
|
|
87
100
|
this._config = options?.config ?? {};
|
|
88
101
|
this._defaultImmutableInterval = this._config.defaultImmutableInterval ?? 10;
|
|
102
|
+
this._mutexTimeoutMs = Coerce.integer(options?.config?.mutexTimeoutMs);
|
|
89
103
|
SchemaOrgDataTypes.registerRedirects();
|
|
104
|
+
AuditableItemStreamDataTypes.registerTypes();
|
|
105
|
+
JsonLdDataTypes.registerTypes();
|
|
106
|
+
ImmutableProofDataTypes.registerTypes();
|
|
90
107
|
}
|
|
91
108
|
/**
|
|
92
109
|
* Returns the class name of the component.
|
|
@@ -95,49 +112,59 @@ export class AuditableItemStreamService {
|
|
|
95
112
|
className() {
|
|
96
113
|
return AuditableItemStreamService.CLASS_NAME;
|
|
97
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Register all AIS metrics with the telemetry component.
|
|
117
|
+
* @returns A promise that resolves when the metrics have been registered.
|
|
118
|
+
*/
|
|
119
|
+
async start() {
|
|
120
|
+
if (Is.undefined(this._telemetryComponent)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await MetricHelper.createMetrics(this._telemetryComponent, AuditableItemStreamMetrics);
|
|
124
|
+
}
|
|
98
125
|
/**
|
|
99
126
|
* Create a new stream.
|
|
100
127
|
* @param stream The stream to create.
|
|
101
|
-
* @param stream.annotationObject The object for the stream as JSON-LD.
|
|
102
|
-
* @param stream.entries Entries to store in the stream.
|
|
103
|
-
* @param options Options for creating the stream.
|
|
104
|
-
* @param options.immutableInterval After how many entries do we add immutable checks, defaults to service configured value.
|
|
105
|
-
* A value of 0 will disable integrity checks, 1 will be every item, or any other integer for an interval.
|
|
106
128
|
* @returns The id of the new stream item.
|
|
107
129
|
*/
|
|
108
|
-
async create(stream
|
|
130
|
+
async create(stream) {
|
|
109
131
|
Guards.object(AuditableItemStreamService.CLASS_NAME, "stream", stream);
|
|
110
132
|
const contextIds = await ContextIdStore.getContextIds();
|
|
133
|
+
ContextIdHelper.guard(contextIds, ContextIdKeys.Organization);
|
|
111
134
|
try {
|
|
135
|
+
const ownerOrganizationId = contextIds[ContextIdKeys.UserOrganization] ?? contextIds[ContextIdKeys.Organization];
|
|
136
|
+
const id = RandomHelper.generateUuidV7("compact");
|
|
137
|
+
const schemaValidationFailures = [];
|
|
138
|
+
await DataTypeHelper.validate("stream", `${AuditableItemStreamContexts.Namespace}${AuditableItemStreamTypes.Stream}Base`, stream, schemaValidationFailures);
|
|
139
|
+
Validation.asValidationError(AuditableItemStreamService.CLASS_NAME, "stream", schemaValidationFailures);
|
|
140
|
+
if (stream.closed && !Is.arrayValue(stream.entries?.[SchemaOrgTypes.ItemListElement])) {
|
|
141
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "closedRequiresEntries");
|
|
142
|
+
}
|
|
112
143
|
if (Is.object(stream.annotationObject)) {
|
|
113
144
|
const validationFailures = [];
|
|
114
145
|
await JsonLdHelper.validate(stream.annotationObject, validationFailures);
|
|
115
146
|
Validation.asValidationError(AuditableItemStreamService.CLASS_NAME, "stream.annotationObject", validationFailures);
|
|
116
147
|
}
|
|
117
|
-
const id = RandomHelper.generateUuidV7("compact");
|
|
118
148
|
const context = {
|
|
119
149
|
now: new Date(Date.now()).toISOString(),
|
|
120
150
|
contextIds,
|
|
121
151
|
indexCounter: 0,
|
|
122
|
-
immutableInterval:
|
|
152
|
+
immutableInterval: stream?.immutableInterval ?? this._defaultImmutableInterval,
|
|
153
|
+
organizationIdentity: ownerOrganizationId
|
|
123
154
|
};
|
|
124
155
|
const streamEntity = {
|
|
125
156
|
id,
|
|
126
|
-
organizationIdentity:
|
|
157
|
+
organizationIdentity: ownerOrganizationId,
|
|
127
158
|
userIdentity: contextIds?.[ContextIdKeys.User],
|
|
128
159
|
dateCreated: context.now,
|
|
129
160
|
immutableInterval: context.immutableInterval,
|
|
161
|
+
closed: stream.closed,
|
|
162
|
+
mode: stream.mode,
|
|
130
163
|
numberOfItems: 0
|
|
131
164
|
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Create the proof for the stream object
|
|
136
|
-
if (context.immutableInterval > 0) {
|
|
137
|
-
streamEntity.proofId = await this._immutableProofComponent.create(streamModel);
|
|
138
|
-
}
|
|
139
|
-
if (Is.arrayValue(stream.entries)) {
|
|
140
|
-
for (const entry of stream.entries) {
|
|
165
|
+
const streamUrn = await this.createStreamProof(streamEntity, context.immutableInterval);
|
|
166
|
+
if (Is.arrayValue(stream.entries?.[SchemaOrgTypes.ItemListElement])) {
|
|
167
|
+
for (const entry of stream.entries[SchemaOrgTypes.ItemListElement]) {
|
|
141
168
|
await this.setEntry(context, id, entry);
|
|
142
169
|
}
|
|
143
170
|
}
|
|
@@ -146,25 +173,23 @@ export class AuditableItemStreamService {
|
|
|
146
173
|
streamEntity.annotationObject = stream.annotationObject;
|
|
147
174
|
streamEntity.numberOfItems = context.indexCounter;
|
|
148
175
|
await this._streamStorage.set(streamEntity);
|
|
149
|
-
await
|
|
150
|
-
|
|
176
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.StreamsCreated, {
|
|
177
|
+
mode: streamEntity.mode ?? AuditableItemStreamModes.Default,
|
|
178
|
+
immutableInterval: context.immutableInterval
|
|
179
|
+
});
|
|
180
|
+
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamCreated, { id: streamUrn });
|
|
181
|
+
return streamUrn;
|
|
151
182
|
}
|
|
152
183
|
catch (error) {
|
|
153
184
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "createFailed", undefined, error);
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
/**
|
|
157
|
-
*
|
|
158
|
-
* @param id The id of the stream to
|
|
159
|
-
* @
|
|
160
|
-
* @param options.includeEntries Whether to include the entries, defaults to false.
|
|
161
|
-
* @param options.includeDeleted Whether to include deleted entries, defaults to false.
|
|
162
|
-
* @param options.verifyStream Should the stream be verified, defaults to false.
|
|
163
|
-
* @param options.verifyEntries Should the entries be verified, defaults to false.
|
|
164
|
-
* @returns The stream and entries if found.
|
|
165
|
-
* @throws NotFoundError if the stream is not found
|
|
188
|
+
* Close a stream.
|
|
189
|
+
* @param id The id of the stream to close.
|
|
190
|
+
* @returns A promise that resolves when the stream has been closed.
|
|
166
191
|
*/
|
|
167
|
-
async
|
|
192
|
+
async close(id) {
|
|
168
193
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "id", id);
|
|
169
194
|
const urnParsed = Urn.fromValidString(id);
|
|
170
195
|
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
@@ -173,39 +198,32 @@ export class AuditableItemStreamService {
|
|
|
173
198
|
id
|
|
174
199
|
});
|
|
175
200
|
}
|
|
201
|
+
const streamId = urnParsed.namespaceSpecific(0);
|
|
202
|
+
await Mutex.lock(streamId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
176
203
|
try {
|
|
177
|
-
const streamId = urnParsed.namespaceSpecific(0);
|
|
178
204
|
const streamEntity = await this._streamStorage.get(streamId);
|
|
179
205
|
if (Is.empty(streamEntity)) {
|
|
180
206
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", id);
|
|
181
207
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
streamModel.cursor = result.cursor;
|
|
189
|
-
}
|
|
190
|
-
if (verifyStream && Is.stringValue(streamEntity.proofId)) {
|
|
191
|
-
streamModel.verification = await this._immutableProofComponent.verify(streamEntity.proofId);
|
|
192
|
-
}
|
|
193
|
-
if (verifyStream || verifyEntries) {
|
|
194
|
-
streamModel["@context"].push(ImmutableProofContexts.Context);
|
|
208
|
+
if (!streamEntity.closed) {
|
|
209
|
+
streamEntity.closed = true;
|
|
210
|
+
streamEntity.dateModified = new Date(Date.now()).toISOString();
|
|
211
|
+
await this._streamStorage.set(streamEntity);
|
|
212
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.StreamsClosed);
|
|
213
|
+
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamUpdated, { id });
|
|
195
214
|
}
|
|
196
|
-
const result = await JsonLdProcessor.compact(streamModel, streamModel["@context"]);
|
|
197
|
-
return result;
|
|
198
215
|
}
|
|
199
216
|
catch (error) {
|
|
200
|
-
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "
|
|
217
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "closeFailed", undefined, error);
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
Mutex.unlock(streamId);
|
|
201
221
|
}
|
|
202
222
|
}
|
|
203
223
|
/**
|
|
204
224
|
* Update a stream.
|
|
205
|
-
* @param stream The stream to update.
|
|
206
|
-
* @
|
|
207
|
-
* @param stream.annotationObject The object for the stream as JSON-LD.
|
|
208
|
-
* @returns Nothing.
|
|
225
|
+
* @param stream The stream to update, does not update entries.
|
|
226
|
+
* @returns A promise that resolves when the stream has been updated.
|
|
209
227
|
*/
|
|
210
228
|
async update(stream) {
|
|
211
229
|
Guards.object(AuditableItemStreamService.CLASS_NAME, "stream", stream);
|
|
@@ -217,8 +235,12 @@ export class AuditableItemStreamService {
|
|
|
217
235
|
id: stream.id
|
|
218
236
|
});
|
|
219
237
|
}
|
|
238
|
+
const streamId = urnParsed.namespaceSpecific(0);
|
|
239
|
+
await Mutex.lock(streamId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
220
240
|
try {
|
|
221
|
-
const
|
|
241
|
+
const schemaValidationFailures = [];
|
|
242
|
+
await DataTypeHelper.validate("stream", `${AuditableItemStreamContexts.Namespace}${AuditableItemStreamTypes.Stream}`, stream, schemaValidationFailures);
|
|
243
|
+
Validation.asValidationError(AuditableItemStreamService.CLASS_NAME, "stream", schemaValidationFailures);
|
|
222
244
|
const streamEntity = await this._streamStorage.get(streamId);
|
|
223
245
|
if (Is.empty(streamEntity)) {
|
|
224
246
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", stream.id);
|
|
@@ -228,21 +250,99 @@ export class AuditableItemStreamService {
|
|
|
228
250
|
await JsonLdHelper.validate(stream.annotationObject, validationFailures);
|
|
229
251
|
Validation.asValidationError(AuditableItemStreamService.CLASS_NAME, "stream.annotationObject", validationFailures);
|
|
230
252
|
}
|
|
253
|
+
let changed = false;
|
|
231
254
|
if (!ObjectHelper.equal(streamEntity.annotationObject, stream.annotationObject, false)) {
|
|
232
255
|
streamEntity.annotationObject = stream.annotationObject;
|
|
256
|
+
changed = true;
|
|
257
|
+
}
|
|
258
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
259
|
+
const ownerOrganizationId = contextIds?.[ContextIdKeys.UserOrganization] ?? contextIds?.[ContextIdKeys.Organization];
|
|
260
|
+
if (!Is.stringValue(streamEntity.organizationIdentity) &&
|
|
261
|
+
Is.stringValue(ownerOrganizationId)) {
|
|
262
|
+
streamEntity.organizationIdentity = ownerOrganizationId;
|
|
263
|
+
changed = true;
|
|
264
|
+
}
|
|
265
|
+
if (!Is.stringValue(streamEntity.proofId) &&
|
|
266
|
+
Is.stringValue(streamEntity.organizationIdentity)) {
|
|
267
|
+
await this.createStreamProof(streamEntity, streamEntity.immutableInterval ?? this._defaultImmutableInterval);
|
|
268
|
+
if (Is.stringValue(streamEntity.proofId)) {
|
|
269
|
+
changed = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (changed) {
|
|
233
273
|
streamEntity.dateModified = new Date(Date.now()).toISOString();
|
|
234
274
|
await this._streamStorage.set(streamEntity);
|
|
275
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.StreamsUpdated);
|
|
235
276
|
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamUpdated, { id: stream.id });
|
|
236
277
|
}
|
|
237
278
|
}
|
|
238
279
|
catch (error) {
|
|
239
280
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "updatingFailed", undefined, error);
|
|
240
281
|
}
|
|
282
|
+
finally {
|
|
283
|
+
Mutex.unlock(streamId);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get a stream header without the entries.
|
|
288
|
+
* @param id The id of the stream to get.
|
|
289
|
+
* @param cursor Cursor to use for next chunk of entries.
|
|
290
|
+
* @param limit Limit the number of entries to return, only applicable if includeEntries is true.
|
|
291
|
+
* @param options Additional options for the get operation.
|
|
292
|
+
* @param options.includeEntries Whether to include the entries, defaults to false.
|
|
293
|
+
* @param options.includeDeleted Whether to include deleted entries, defaults to false.
|
|
294
|
+
* @param options.verifyStream Should the stream be verified, defaults to false.
|
|
295
|
+
* @param options.verifyEntries Should the entries be verified, defaults to false.
|
|
296
|
+
* @returns The stream and entries if found.
|
|
297
|
+
* @throws NotFoundError if the stream is not found
|
|
298
|
+
*/
|
|
299
|
+
async get(id, cursor, limit, options) {
|
|
300
|
+
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "id", id);
|
|
301
|
+
const urnParsed = Urn.fromValidString(id);
|
|
302
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
303
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
304
|
+
namespace: AuditableItemStreamService._NAMESPACE,
|
|
305
|
+
id
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const streamId = urnParsed.namespaceSpecific(0);
|
|
310
|
+
const streamEntity = await this._streamStorage.get(streamId);
|
|
311
|
+
if (Is.empty(streamEntity)) {
|
|
312
|
+
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", id);
|
|
313
|
+
}
|
|
314
|
+
const verifyStream = options?.verifyStream ?? false;
|
|
315
|
+
const verifyEntries = options?.verifyEntries ?? false;
|
|
316
|
+
const streamModel = this.streamEntityToJsonLd(streamEntity);
|
|
317
|
+
let returnCursor;
|
|
318
|
+
if (options?.includeEntries) {
|
|
319
|
+
const result = await this.findEntries(streamId, options?.includeDeleted, verifyEntries, undefined, undefined, undefined, limit, cursor);
|
|
320
|
+
streamModel.entries = {
|
|
321
|
+
type: SchemaOrgTypes.ItemList,
|
|
322
|
+
[SchemaOrgTypes.ItemListElement]: result.entries
|
|
323
|
+
};
|
|
324
|
+
returnCursor = result.cursor;
|
|
325
|
+
}
|
|
326
|
+
if (verifyStream && Is.stringValue(streamEntity.proofId)) {
|
|
327
|
+
streamModel.verification = await this._immutableProofComponent.verify(streamEntity.proofId);
|
|
328
|
+
}
|
|
329
|
+
if (verifyStream || verifyEntries) {
|
|
330
|
+
streamModel["@context"].push(ImmutableProofContexts.Context);
|
|
331
|
+
}
|
|
332
|
+
const result = await JsonLdProcessor.compact(streamModel, streamModel["@context"]);
|
|
333
|
+
return {
|
|
334
|
+
stream: result,
|
|
335
|
+
cursor: returnCursor
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "getFailed", undefined, error);
|
|
340
|
+
}
|
|
241
341
|
}
|
|
242
342
|
/**
|
|
243
343
|
* Delete the stream.
|
|
244
344
|
* @param id The id of the stream to remove.
|
|
245
|
-
* @returns
|
|
345
|
+
* @returns A promise that resolves when the stream has been removed.
|
|
246
346
|
*/
|
|
247
347
|
async remove(id) {
|
|
248
348
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "id", id);
|
|
@@ -253,19 +353,24 @@ export class AuditableItemStreamService {
|
|
|
253
353
|
id
|
|
254
354
|
});
|
|
255
355
|
}
|
|
356
|
+
const streamId = urnParsed.namespaceSpecific(0);
|
|
357
|
+
await Mutex.lock(streamId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
256
358
|
try {
|
|
257
|
-
const streamId = urnParsed.namespaceSpecific(0);
|
|
258
359
|
const streamEntity = await this._streamStorage.get(streamId);
|
|
259
360
|
if (Is.empty(streamEntity)) {
|
|
260
361
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", id);
|
|
261
362
|
}
|
|
262
363
|
await this.internalRemoveEntries(streamEntity, false);
|
|
263
364
|
await this._streamStorage.remove(streamEntity.id);
|
|
365
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.StreamsDeleted);
|
|
264
366
|
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamDeleted, { id });
|
|
265
367
|
}
|
|
266
368
|
catch (error) {
|
|
267
369
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "removingFailed", undefined, error);
|
|
268
370
|
}
|
|
371
|
+
finally {
|
|
372
|
+
Mutex.unlock(streamId);
|
|
373
|
+
}
|
|
269
374
|
}
|
|
270
375
|
/**
|
|
271
376
|
* Query all the streams, will not return entries.
|
|
@@ -339,17 +444,25 @@ export class AuditableItemStreamService {
|
|
|
339
444
|
id: streamId
|
|
340
445
|
});
|
|
341
446
|
}
|
|
447
|
+
const streamIdParts = urnParsed.namespaceSpecific(0);
|
|
448
|
+
await Mutex.lock(streamIdParts, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
342
449
|
try {
|
|
343
|
-
const streamIdParts = urnParsed.namespaceSpecific(0);
|
|
344
450
|
const streamEntity = await this._streamStorage.get(streamIdParts);
|
|
345
451
|
if (Is.empty(streamEntity)) {
|
|
346
452
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamIdParts);
|
|
347
453
|
}
|
|
454
|
+
if (streamEntity.closed) {
|
|
455
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ClosedStreamRejections, { operation: "createEntry" });
|
|
456
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "streamClosed", {
|
|
457
|
+
id: streamId
|
|
458
|
+
});
|
|
459
|
+
}
|
|
348
460
|
const context = {
|
|
349
461
|
now: new Date(Date.now()).toISOString(),
|
|
350
462
|
contextIds,
|
|
351
463
|
indexCounter: streamEntity.numberOfItems,
|
|
352
|
-
immutableInterval: streamEntity.immutableInterval
|
|
464
|
+
immutableInterval: streamEntity.immutableInterval,
|
|
465
|
+
organizationIdentity: streamEntity.organizationIdentity ?? contextIds?.[ContextIdKeys.Organization]
|
|
353
466
|
};
|
|
354
467
|
const createdId = await this.setEntry(context, streamEntity.id, {
|
|
355
468
|
entryObject
|
|
@@ -357,6 +470,10 @@ export class AuditableItemStreamService {
|
|
|
357
470
|
streamEntity.dateModified = context.now;
|
|
358
471
|
streamEntity.numberOfItems = context.indexCounter;
|
|
359
472
|
await this._streamStorage.set(streamEntity);
|
|
473
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.EntriesCreated, {
|
|
474
|
+
hasProof: context.immutableInterval > 0 &&
|
|
475
|
+
(context.indexCounter - 1) % context.immutableInterval === 0
|
|
476
|
+
});
|
|
360
477
|
const fullId = new Urn(AuditableItemStreamService._NAMESPACE, [
|
|
361
478
|
streamEntity.id,
|
|
362
479
|
createdId
|
|
@@ -367,6 +484,9 @@ export class AuditableItemStreamService {
|
|
|
367
484
|
catch (error) {
|
|
368
485
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "creatingEntryFailed", undefined, error);
|
|
369
486
|
}
|
|
487
|
+
finally {
|
|
488
|
+
Mutex.unlock(streamIdParts);
|
|
489
|
+
}
|
|
370
490
|
}
|
|
371
491
|
/**
|
|
372
492
|
* Get the entry from the stream.
|
|
@@ -408,7 +528,7 @@ export class AuditableItemStreamService {
|
|
|
408
528
|
}
|
|
409
529
|
const entry = this.streamEntryEntityToJsonLd(result.entity);
|
|
410
530
|
if (verifyEntry) {
|
|
411
|
-
entry["@context"].
|
|
531
|
+
entry["@context"] = JsonLdProcessor.combineContexts(entry["@context"], ImmutableProofContexts.Context);
|
|
412
532
|
entry.verification = result.verification;
|
|
413
533
|
}
|
|
414
534
|
const result2 = await JsonLdProcessor.compact(entry, entry["@context"]);
|
|
@@ -464,12 +584,11 @@ export class AuditableItemStreamService {
|
|
|
464
584
|
* @param streamId The id of the stream to update.
|
|
465
585
|
* @param entryId The id of the entry to update.
|
|
466
586
|
* @param entryObject The object for the entry as JSON-LD.
|
|
467
|
-
* @returns
|
|
587
|
+
* @returns A promise that resolves when the entry has been updated.
|
|
468
588
|
*/
|
|
469
589
|
async updateEntry(streamId, entryId, entryObject) {
|
|
470
590
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "streamId", streamId);
|
|
471
591
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "entryId", entryId);
|
|
472
|
-
const contextIds = await ContextIdStore.getContextIds();
|
|
473
592
|
const urnParsed = Urn.fromValidString(streamId);
|
|
474
593
|
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
475
594
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
@@ -484,8 +603,9 @@ export class AuditableItemStreamService {
|
|
|
484
603
|
id: entryId
|
|
485
604
|
});
|
|
486
605
|
}
|
|
606
|
+
const streamNamespaceId = urnParsed.namespaceSpecific(0);
|
|
607
|
+
await Mutex.lock(streamNamespaceId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
487
608
|
try {
|
|
488
|
-
const streamNamespaceId = urnParsed.namespaceMethod();
|
|
489
609
|
const streamEntryNamespaceId = urnParsedEntry.namespaceMethod();
|
|
490
610
|
if (streamNamespaceId !== streamEntryNamespaceId) {
|
|
491
611
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
@@ -497,16 +617,31 @@ export class AuditableItemStreamService {
|
|
|
497
617
|
if (Is.empty(streamEntity)) {
|
|
498
618
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamId);
|
|
499
619
|
}
|
|
620
|
+
if (streamEntity.closed) {
|
|
621
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ClosedStreamRejections, { operation: "updateEntry" });
|
|
622
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "streamClosed", {
|
|
623
|
+
id: streamId
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (streamEntity.mode === AuditableItemStreamModes.AppendOnly) {
|
|
627
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.AppendOnlyRejections, { operation: "updateEntry" });
|
|
628
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "appendOnlyNoEntryUpdates", {
|
|
629
|
+
id: streamId
|
|
630
|
+
});
|
|
631
|
+
}
|
|
500
632
|
const entryNamespaceId = urnParsedEntry.namespaceSpecific(1);
|
|
501
633
|
const existing = await this.findEntry(streamEntity.id, entryNamespaceId);
|
|
502
634
|
if (Is.empty(existing)) {
|
|
503
635
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamEntryNotFound", entryId);
|
|
504
636
|
}
|
|
637
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
638
|
+
const ownerOrganizationId = contextIds?.[ContextIdKeys.UserOrganization] ?? contextIds?.[ContextIdKeys.Organization];
|
|
505
639
|
const context = {
|
|
506
640
|
now: new Date(Date.now()).toISOString(),
|
|
507
641
|
contextIds,
|
|
508
642
|
indexCounter: streamEntity.numberOfItems,
|
|
509
|
-
immutableInterval: streamEntity.immutableInterval
|
|
643
|
+
immutableInterval: streamEntity.immutableInterval,
|
|
644
|
+
organizationIdentity: streamEntity.organizationIdentity ?? ownerOrganizationId
|
|
510
645
|
};
|
|
511
646
|
await this.setEntry(context, streamEntity.id, {
|
|
512
647
|
...existing.entity,
|
|
@@ -515,22 +650,25 @@ export class AuditableItemStreamService {
|
|
|
515
650
|
streamEntity.dateModified = context.now;
|
|
516
651
|
streamEntity.numberOfItems = context.indexCounter;
|
|
517
652
|
await this._streamStorage.set(streamEntity);
|
|
653
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.EntriesUpdated);
|
|
518
654
|
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamEntryUpdated, { id: streamId, entryId });
|
|
519
655
|
}
|
|
520
656
|
catch (error) {
|
|
521
657
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "updatingEntryFailed", undefined, error);
|
|
522
658
|
}
|
|
659
|
+
finally {
|
|
660
|
+
Mutex.unlock(streamNamespaceId);
|
|
661
|
+
}
|
|
523
662
|
}
|
|
524
663
|
/**
|
|
525
664
|
* Delete from the stream.
|
|
526
665
|
* @param streamId The id of the stream to remove from.
|
|
527
666
|
* @param entryId The id of the entry to remove.
|
|
528
|
-
* @returns
|
|
667
|
+
* @returns A promise that resolves when the entry has been removed.
|
|
529
668
|
*/
|
|
530
669
|
async removeEntry(streamId, entryId) {
|
|
531
670
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "streamId", streamId);
|
|
532
671
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "entryId", entryId);
|
|
533
|
-
const contextIds = await ContextIdStore.getContextIds();
|
|
534
672
|
const urnParsed = Urn.fromValidString(streamId);
|
|
535
673
|
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
536
674
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
@@ -545,8 +683,9 @@ export class AuditableItemStreamService {
|
|
|
545
683
|
id: entryId
|
|
546
684
|
});
|
|
547
685
|
}
|
|
686
|
+
const streamNamespaceId = urnParsed.namespaceSpecific(0);
|
|
687
|
+
await Mutex.lock(streamNamespaceId, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
548
688
|
try {
|
|
549
|
-
const streamNamespaceId = urnParsed.namespaceMethod();
|
|
550
689
|
const streamEntryNamespaceId = urnParsedEntry.namespaceMethod();
|
|
551
690
|
if (streamNamespaceId !== streamEntryNamespaceId) {
|
|
552
691
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
@@ -558,17 +697,26 @@ export class AuditableItemStreamService {
|
|
|
558
697
|
if (Is.empty(streamEntity)) {
|
|
559
698
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamId);
|
|
560
699
|
}
|
|
700
|
+
if (streamEntity.mode === AuditableItemStreamModes.AppendOnly) {
|
|
701
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.AppendOnlyRejections, { operation: "removeEntry" });
|
|
702
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "appendOnlyNoEntryRemovals", {
|
|
703
|
+
id: streamId
|
|
704
|
+
});
|
|
705
|
+
}
|
|
561
706
|
const entryNamespaceId = urnParsedEntry.namespaceSpecific(1);
|
|
562
707
|
const result = await this.findEntry(streamNamespaceId, entryNamespaceId);
|
|
563
708
|
if (Is.empty(result)) {
|
|
564
709
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamEntryNotFound", entryId);
|
|
565
710
|
}
|
|
566
711
|
if (Is.empty(result.entity.dateDeleted)) {
|
|
712
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
713
|
+
const ownerOrganizationId = contextIds?.[ContextIdKeys.UserOrganization] ?? contextIds?.[ContextIdKeys.Organization];
|
|
567
714
|
const context = {
|
|
568
715
|
now: new Date(Date.now()).toISOString(),
|
|
569
716
|
contextIds,
|
|
570
717
|
indexCounter: streamEntity.numberOfItems,
|
|
571
|
-
immutableInterval: streamEntity.immutableInterval
|
|
718
|
+
immutableInterval: streamEntity.immutableInterval,
|
|
719
|
+
organizationIdentity: streamEntity.organizationIdentity ?? ownerOrganizationId
|
|
572
720
|
};
|
|
573
721
|
await this.setEntry(context, streamEntity.id, {
|
|
574
722
|
...result.entity,
|
|
@@ -577,16 +725,20 @@ export class AuditableItemStreamService {
|
|
|
577
725
|
streamEntity.dateModified = context.now;
|
|
578
726
|
streamEntity.numberOfItems = context.indexCounter;
|
|
579
727
|
await this._streamStorage.set(streamEntity);
|
|
728
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.EntriesDeleted);
|
|
580
729
|
await this._eventBusComponent?.publish(AuditableItemStreamTopics.StreamEntryDeleted, { id: streamId, entryId });
|
|
581
730
|
}
|
|
582
731
|
}
|
|
583
732
|
catch (error) {
|
|
584
733
|
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "removingEntryFailed", undefined, error);
|
|
585
734
|
}
|
|
735
|
+
finally {
|
|
736
|
+
Mutex.unlock(streamNamespaceId);
|
|
737
|
+
}
|
|
586
738
|
}
|
|
587
739
|
/**
|
|
588
740
|
* Get the entries for the stream.
|
|
589
|
-
* @param streamId The id of the stream to get.
|
|
741
|
+
* @param streamId The id of the stream to get, if undefined returns all matching entries.
|
|
590
742
|
* @param options Additional options for the get operation.
|
|
591
743
|
* @param options.conditions The conditions to filter the stream.
|
|
592
744
|
* @param options.includeDeleted Whether to include deleted entries, defaults to false.
|
|
@@ -598,19 +750,24 @@ export class AuditableItemStreamService {
|
|
|
598
750
|
* @throws NotFoundError if the stream is not found.
|
|
599
751
|
*/
|
|
600
752
|
async getEntries(streamId, options) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
753
|
+
let streamNamespaceId;
|
|
754
|
+
if (!Is.empty(streamId)) {
|
|
755
|
+
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "streamId", streamId);
|
|
756
|
+
const urnParsed = Urn.fromValidString(streamId);
|
|
757
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
758
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
759
|
+
namespace: AuditableItemStreamService._NAMESPACE,
|
|
760
|
+
id: streamId
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
streamNamespaceId = urnParsed.namespaceSpecific(0);
|
|
608
764
|
}
|
|
609
765
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
766
|
+
if (Is.stringValue(streamNamespaceId)) {
|
|
767
|
+
const streamEntity = await this._streamStorage.get(streamNamespaceId);
|
|
768
|
+
if (Is.empty(streamEntity)) {
|
|
769
|
+
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamId);
|
|
770
|
+
}
|
|
614
771
|
}
|
|
615
772
|
const verifyEntries = options?.verifyEntries ?? false;
|
|
616
773
|
const result = await this.findEntries(streamNamespaceId, options?.includeDeleted, verifyEntries, options?.conditions, options?.order, undefined, options?.limit, options?.cursor);
|
|
@@ -638,7 +795,7 @@ export class AuditableItemStreamService {
|
|
|
638
795
|
}
|
|
639
796
|
/**
|
|
640
797
|
* Get the entry objects for the stream.
|
|
641
|
-
* @param streamId The id of the stream to get.
|
|
798
|
+
* @param streamId The id of the stream to get, if undefined returns all matching entries.
|
|
642
799
|
* @param options Additional options for the get operation.
|
|
643
800
|
* @param options.conditions The conditions to filter the stream.
|
|
644
801
|
* @param options.includeDeleted Whether to include deleted entries, defaults to false.
|
|
@@ -649,19 +806,24 @@ export class AuditableItemStreamService {
|
|
|
649
806
|
* @throws NotFoundError if the stream is not found.
|
|
650
807
|
*/
|
|
651
808
|
async getEntryObjects(streamId, options) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
809
|
+
let streamNamespaceId;
|
|
810
|
+
if (!Is.empty(streamId)) {
|
|
811
|
+
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "streamId", streamId);
|
|
812
|
+
const urnParsed = Urn.fromValidString(streamId);
|
|
813
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
814
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "namespaceMismatch", {
|
|
815
|
+
namespace: AuditableItemStreamService._NAMESPACE,
|
|
816
|
+
id: streamId
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
streamNamespaceId = urnParsed.namespaceSpecific(0);
|
|
659
820
|
}
|
|
660
821
|
try {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
822
|
+
if (Is.stringValue(streamNamespaceId)) {
|
|
823
|
+
const streamEntity = await this._streamStorage.get(streamNamespaceId);
|
|
824
|
+
if (Is.empty(streamEntity)) {
|
|
825
|
+
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamId);
|
|
826
|
+
}
|
|
665
827
|
}
|
|
666
828
|
const result = await this.findEntries(streamNamespaceId, options?.includeDeleted, false, options?.conditions, options?.order, undefined, options?.limit, options?.cursor);
|
|
667
829
|
const list = {
|
|
@@ -684,12 +846,12 @@ export class AuditableItemStreamService {
|
|
|
684
846
|
}
|
|
685
847
|
}
|
|
686
848
|
/**
|
|
687
|
-
* Remove the
|
|
688
|
-
* @param streamId The id of the stream to remove the
|
|
689
|
-
* @returns
|
|
849
|
+
* Remove the proof for the stream and entries.
|
|
850
|
+
* @param streamId The id of the stream to remove the proof from.
|
|
851
|
+
* @returns A promise that resolves when the proof has been removed.
|
|
690
852
|
* @throws NotFoundError if the vertex is not found.
|
|
691
853
|
*/
|
|
692
|
-
async
|
|
854
|
+
async removeProof(streamId) {
|
|
693
855
|
Guards.stringValue(AuditableItemStreamService.CLASS_NAME, "streamId", streamId);
|
|
694
856
|
const urnParsed = Urn.fromValidString(streamId);
|
|
695
857
|
if (urnParsed.namespaceIdentifier() !== AuditableItemStreamService._NAMESPACE) {
|
|
@@ -698,8 +860,9 @@ export class AuditableItemStreamService {
|
|
|
698
860
|
id: streamId
|
|
699
861
|
});
|
|
700
862
|
}
|
|
863
|
+
const streamIdParts = urnParsed.namespaceSpecific(0);
|
|
864
|
+
await Mutex.lock(streamIdParts, { throwOnTimeout: true, timeoutMs: this._mutexTimeoutMs });
|
|
701
865
|
try {
|
|
702
|
-
const streamIdParts = urnParsed.namespaceSpecific(0);
|
|
703
866
|
const streamEntity = await this._streamStorage.get(streamIdParts);
|
|
704
867
|
if (Is.empty(streamEntity)) {
|
|
705
868
|
throw new NotFoundError(AuditableItemStreamService.CLASS_NAME, "streamNotFound", streamIdParts);
|
|
@@ -707,9 +870,27 @@ export class AuditableItemStreamService {
|
|
|
707
870
|
await this.internalRemoveEntries(streamEntity, true);
|
|
708
871
|
}
|
|
709
872
|
catch (error) {
|
|
710
|
-
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "
|
|
873
|
+
throw new GeneralError(AuditableItemStreamService.CLASS_NAME, "removeProofFailed", undefined, error);
|
|
874
|
+
}
|
|
875
|
+
finally {
|
|
876
|
+
Mutex.unlock(streamIdParts);
|
|
711
877
|
}
|
|
712
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Create an immutable proof for the stream entity if the conditions are met.
|
|
881
|
+
* @param streamEntity The stream entity to create the proof for.
|
|
882
|
+
* @param immutableInterval The immutable interval for the stream.
|
|
883
|
+
* @returns The proof id.
|
|
884
|
+
* @internal
|
|
885
|
+
*/
|
|
886
|
+
async createStreamProof(streamEntity, immutableInterval) {
|
|
887
|
+
const streamModel = this.streamEntityToJsonLd(ObjectHelper.pick(streamEntity, AuditableItemStreamService._PROOF_KEYS_STREAM));
|
|
888
|
+
if (immutableInterval > 0 && Is.stringValue(streamModel.organizationIdentity)) {
|
|
889
|
+
streamEntity.proofId = await this._immutableProofComponent.create(streamModel);
|
|
890
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ProofsCreatedStream);
|
|
891
|
+
}
|
|
892
|
+
return streamModel.id;
|
|
893
|
+
}
|
|
713
894
|
/**
|
|
714
895
|
* Map the stream entity to a JSON-LD model.
|
|
715
896
|
* @param streamEntity The stream entity.
|
|
@@ -719,9 +900,9 @@ export class AuditableItemStreamService {
|
|
|
719
900
|
streamEntityToJsonLd(streamEntity) {
|
|
720
901
|
const model = {
|
|
721
902
|
"@context": [
|
|
903
|
+
SchemaOrgContexts.Context,
|
|
722
904
|
AuditableItemStreamContexts.Context,
|
|
723
|
-
AuditableItemStreamContexts.ContextCommon
|
|
724
|
-
SchemaOrgContexts.Context
|
|
905
|
+
AuditableItemStreamContexts.ContextCommon
|
|
725
906
|
],
|
|
726
907
|
type: AuditableItemStreamTypes.Stream,
|
|
727
908
|
id: `${AuditableItemStreamService._NAMESPACE}:${streamEntity.id}`,
|
|
@@ -732,7 +913,9 @@ export class AuditableItemStreamService {
|
|
|
732
913
|
annotationObject: streamEntity.annotationObject,
|
|
733
914
|
immutableInterval: streamEntity.immutableInterval,
|
|
734
915
|
proofId: streamEntity.proofId,
|
|
735
|
-
numberOfItems: streamEntity.numberOfItems
|
|
916
|
+
numberOfItems: streamEntity.numberOfItems,
|
|
917
|
+
closed: streamEntity.closed,
|
|
918
|
+
mode: streamEntity.mode
|
|
736
919
|
};
|
|
737
920
|
return model;
|
|
738
921
|
}
|
|
@@ -794,8 +977,12 @@ export class AuditableItemStreamService {
|
|
|
794
977
|
// Create the JSON-LD object we want to use for the proof
|
|
795
978
|
// this is a subset of fixed properties from the stream entry object.
|
|
796
979
|
const streamEntryModel = this.streamEntryEntityToJsonLd(ObjectHelper.pick(entity, AuditableItemStreamService._PROOF_KEYS_STREAM_ENTRY));
|
|
797
|
-
// Create the proof for the stream object
|
|
798
|
-
|
|
980
|
+
// Create the proof for the stream object but only if we have an organization identity,
|
|
981
|
+
// either from the stream or the context, as this is needed for the proof creation and immutability.
|
|
982
|
+
if (Is.stringValue(context.organizationIdentity)) {
|
|
983
|
+
entity.proofId = await this._immutableProofComponent.create(JsonLdHelper.toNodeObject(streamEntryModel));
|
|
984
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ProofsCreatedEntry, { index: entity.index });
|
|
985
|
+
}
|
|
799
986
|
}
|
|
800
987
|
await this._streamEntryStorage.set(entity);
|
|
801
988
|
return entity.id;
|
|
@@ -805,6 +992,7 @@ export class AuditableItemStreamService {
|
|
|
805
992
|
* @param streamId The stream id.
|
|
806
993
|
* @param entryId The entry id.
|
|
807
994
|
* @param verifyEntry Should the entry be verified.
|
|
995
|
+
* @returns The entry entity and optional verification result, or undefined if not found.
|
|
808
996
|
* @internal
|
|
809
997
|
*/
|
|
810
998
|
async findEntry(streamId, entryId, verifyEntry) {
|
|
@@ -851,18 +1039,19 @@ export class AuditableItemStreamService {
|
|
|
851
1039
|
* @param propertiesToReturn The properties to return.
|
|
852
1040
|
* @param limit Limit the number of entities when finding.
|
|
853
1041
|
* @param cursor The cursor.
|
|
854
|
-
* @
|
|
1042
|
+
* @returns The stream entries and optional next cursor.
|
|
855
1043
|
* @internal
|
|
856
1044
|
*/
|
|
857
1045
|
async findEntries(streamId, includeDeleted, verifyEntries, conditions, sortDirection, propertiesToReturn, limit, cursor) {
|
|
858
1046
|
const needToVerify = verifyEntries ?? false;
|
|
859
|
-
const combinedConditions = [
|
|
860
|
-
|
|
1047
|
+
const combinedConditions = [];
|
|
1048
|
+
if (Is.stringValue(streamId)) {
|
|
1049
|
+
combinedConditions.push({
|
|
861
1050
|
property: "streamId",
|
|
862
1051
|
comparison: ComparisonOperator.Equals,
|
|
863
1052
|
value: streamId
|
|
864
|
-
}
|
|
865
|
-
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
866
1055
|
if (Is.stringValue(cursor)) {
|
|
867
1056
|
const parts = cursor.split("|");
|
|
868
1057
|
if (parts.length > 1) {
|
|
@@ -923,14 +1112,15 @@ export class AuditableItemStreamService {
|
|
|
923
1112
|
* Remove the verifiable storage for the stream and entries.
|
|
924
1113
|
* @param streamEntity The stream entity.
|
|
925
1114
|
* @param removeOnlyProof Should only the proof be removed.
|
|
926
|
-
* @returns
|
|
1115
|
+
* @returns A promise that resolves when the entries have been processed.
|
|
927
1116
|
* @internal
|
|
928
1117
|
*/
|
|
929
1118
|
async internalRemoveEntries(streamEntity, removeOnlyProof) {
|
|
930
1119
|
if (Is.stringValue(streamEntity.proofId)) {
|
|
931
|
-
await this._immutableProofComponent.
|
|
1120
|
+
await this._immutableProofComponent.removeNotarization(streamEntity.proofId);
|
|
932
1121
|
delete streamEntity.proofId;
|
|
933
1122
|
await this._streamStorage.set(streamEntity);
|
|
1123
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ProofsRemovedStream);
|
|
934
1124
|
}
|
|
935
1125
|
const entryIds = [];
|
|
936
1126
|
let entriesResult;
|
|
@@ -948,8 +1138,9 @@ export class AuditableItemStreamService {
|
|
|
948
1138
|
for (const streamEntry of entriesResult.entities) {
|
|
949
1139
|
entryIds.push(streamEntry.id);
|
|
950
1140
|
if (Is.stringValue(streamEntry.proofId)) {
|
|
951
|
-
await this._immutableProofComponent.
|
|
1141
|
+
await this._immutableProofComponent.removeNotarization(streamEntry.proofId);
|
|
952
1142
|
delete streamEntry.proofId;
|
|
1143
|
+
await MetricHelper.metricIncrement(this._telemetryComponent, AuditableItemStreamMetricIds.ProofsRemovedEntry);
|
|
953
1144
|
// If we are only removing the proof, we need to set the entry
|
|
954
1145
|
// otherwise the entry is going to be removed later anyway.
|
|
955
1146
|
if (removeOnlyProof) {
|