@jskit-ai/kernel 0.1.245 → 0.1.247
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/client/appConfig.js +2 -1
- package/client/asyncModuleRecovery.js +2 -4
- package/client/index.d.ts +13 -75
- package/client/index.js +1 -1
- package/client/logging.js +8 -2
- package/client/moduleBootstrap.js +41 -33
- package/client/moduleBootstrap.test.js +35 -0
- package/client/shellBootstrap.js +1 -8
- package/client/shellBootstrap.test.js +32 -5
- package/client/shellRouting.js +16 -61
- package/client/shellRouting.test.js +30 -1
- package/client/vite/clientBootstrapPlugin.js +0 -6
- package/client/vite/clientBootstrapPlugin.test.js +2 -3
- package/internal/node/installedPackages.js +19 -62
- package/internal/node/installedPackages.test.js +33 -0
- package/package.json +2 -4
- package/server/actions/actionCatalogue.js +13 -34
- package/{shared → server}/actions/actionContributorHelpers.js +0 -5
- package/{shared → server}/actions/actionDefinitions.js +24 -131
- package/{shared → server}/actions/actionDefinitions.test.js +75 -21
- package/server/actions/actionEvents.js +97 -2
- package/server/actions/actionEvents.test.js +79 -0
- package/{shared → server}/actions/executionContext.js +3 -13
- package/server/actions/idempotency.js +24 -0
- package/server/actions/pipeline.js +87 -0
- package/{shared → server}/actions/policies.js +20 -33
- package/{shared → server}/actions/policies.test.js +6 -6
- package/{shared → server}/actions/registry.js +3 -22
- package/{shared → server}/actions/registry.test.js +70 -32
- package/server/actions/requestMeta.js +17 -0
- package/server/http/capabilityHttpRuntime.js +1 -1
- package/server/http/capabilityHttpRuntime.test.js +56 -0
- package/server/http/errorHandling.js +291 -0
- package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
- package/server/http/lib/routeRegistration.js +36 -150
- package/server/http/lib/routeTransport.js +2 -0
- package/server/http/lib/routeValidator.js +5 -47
- package/server/http/lib/routeValidator.test.js +15 -17
- package/server/http/lib/router.js +10 -94
- package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
- package/server/platform/providerRuntime/helpers.js +1 -34
- package/server/runtime/integers.js +1 -5
- package/server/runtime/serviceAuthorization.js +4 -9
- package/server/support/appConfig.js +2 -1
- package/server/support/appConfigFiles.js +1 -38
- package/server/support/appConfigFiles.test.js +3 -39
- package/server/support/index.js +1 -20
- package/shared/capabilities/defineProvider.js +1 -1
- package/shared/capabilities/index.js +1 -1
- package/shared/index.js +1 -1
- package/shared/support/index.js +1 -4
- package/shared/support/mobileConfig.js +99 -0
- package/shared/support/mobileConfig.test.js +113 -0
- package/shared/support/normalize.js +0 -108
- package/shared/support/normalize.test.js +0 -134
- package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
- package/shared/support/returnToPath.js +1 -20
- package/shared/support/returnToPath.test.js +1 -36
- package/shared/support/shellLayoutTargets.js +2 -140
- package/shared/support/shellLayoutTargets.test.js +1 -110
- package/shared/support/sorting.js +1 -9
- package/shared/surface/apiPaths.js +1 -82
- package/shared/surface/paths.js +38 -171
- package/shared/surface/registry.js +2 -1
- package/shared/surface/runtime.js +18 -109
- package/shared/surface/runtime.test.js +57 -0
- package/shared/validators/index.js +1 -4
- package/shared/validators/jsonRestSchemaSupport.js +0 -9
- package/shared/validators/recordIdParamsValidator.js +0 -6
- package/shared/validators/schemaDefinitions.js +4 -30
- package/shared/validators/schemaPayloadValidation.js +2 -6
- package/test/barrelExposure.test.js +3 -7
- package/test/exportsContract.test.js +1 -3
- package/client/mobileLaunchRouting.js +0 -231
- package/client/mobileLaunchRouting.test.js +0 -230
- package/server/http/lib/controller.js +0 -183
- package/server/http/lib/controller.test.js +0 -143
- package/server/http/lib/index.js +0 -5
- package/server/runtime/canonicalJson.js +0 -74
- package/server/runtime/entityChangeEvents.js +0 -307
- package/server/runtime/entityChangeEvents.test.js +0 -274
- package/server/runtime/fastifyBootstrap.js +0 -553
- package/server/runtime/moduleConfig.js +0 -223
- package/server/runtime/moduleConfig.test.js +0 -165
- package/server/runtime/pagination.js +0 -13
- package/server/runtime/realtimeNormalization.js +0 -21
- package/server/runtime/routeUtils.js +0 -20
- package/server/runtime/securityAudit.js +0 -269
- package/server/runtime/securityAudit.test.js +0 -41
- package/server/support/pageTargets.js +0 -772
- package/server/support/pageTargets.test.js +0 -659
- package/server/support/shellOutlets.js +0 -576
- package/server/support/shellOutlets.test.js +0 -336
- package/server/surface/index.js +0 -18
- package/shared/actions/audit.js +0 -7
- package/shared/actions/idempotency.js +0 -62
- package/shared/actions/observability.js +0 -10
- package/shared/actions/pipeline.js +0 -309
- package/shared/actions/requestMeta.js +0 -36
- package/shared/actions/textNormalization.js +0 -3
- package/shared/actions/withActionDefaults.js +0 -34
- /package/{shared → server}/actions/executionContext.test.js +0 -0
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import { normalizeObject, normalizeOpaqueId, normalizeText } from "../../shared/support/normalize.js";
|
|
2
|
-
import { resolveServiceContext } from "./serviceAuthorization.js";
|
|
3
|
-
|
|
4
|
-
const ENTITY_CHANGE_OPERATIONS = new Set(["created", "updated", "deleted"]);
|
|
5
|
-
|
|
6
|
-
function resolveContextScope(context = {}) {
|
|
7
|
-
const sourceScope = normalizeObject(context?.scope || context?.requestMeta?.scope || context?.request?.scope);
|
|
8
|
-
const kind = normalizeText(sourceScope.kind).toLowerCase();
|
|
9
|
-
if (!kind) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
if (kind === "global") {
|
|
13
|
-
return {
|
|
14
|
-
kind: "global",
|
|
15
|
-
id: null
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const scopeId = normalizeOpaqueId(sourceScope.id);
|
|
20
|
-
if (scopeId == null) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const resolvedScope = {
|
|
25
|
-
kind,
|
|
26
|
-
id: scopeId
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const scopeUserId = normalizeOpaqueId(sourceScope.userId);
|
|
30
|
-
if (scopeUserId != null) {
|
|
31
|
-
resolvedScope.userId = scopeUserId;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const scopedScopeId = normalizeOpaqueId(sourceScope.scopeId);
|
|
35
|
-
if (scopedScopeId != null) {
|
|
36
|
-
resolvedScope.scopeId = scopedScopeId;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return resolvedScope;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function resolveVisibilityScope(visibilityContext = {}, runtimeContext = {}) {
|
|
43
|
-
const visibility = normalizeText(visibilityContext.visibility).toLowerCase();
|
|
44
|
-
const scopeKind = normalizeText(visibilityContext.scopeKind || visibility).toLowerCase();
|
|
45
|
-
const scopeOwnerId = normalizeOpaqueId(visibilityContext.scopeOwnerId);
|
|
46
|
-
const userId = normalizeOpaqueId(visibilityContext.userId);
|
|
47
|
-
const requiresActorScope = visibilityContext.requiresActorScope === true;
|
|
48
|
-
|
|
49
|
-
if (requiresActorScope && userId == null) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (scopeKind && scopeOwnerId != null) {
|
|
54
|
-
const scope = {
|
|
55
|
-
kind: scopeKind,
|
|
56
|
-
id: scopeOwnerId
|
|
57
|
-
};
|
|
58
|
-
if (requiresActorScope) {
|
|
59
|
-
scope.scopeId = scopeOwnerId;
|
|
60
|
-
scope.userId = userId;
|
|
61
|
-
}
|
|
62
|
-
return scope;
|
|
63
|
-
}
|
|
64
|
-
if (scopeKind && scopeKind !== "user") {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!scopeKind && scopeOwnerId != null) {
|
|
69
|
-
return {
|
|
70
|
-
kind: "scope",
|
|
71
|
-
id: scopeOwnerId
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (scopeKind === "user" && userId != null) {
|
|
76
|
-
return {
|
|
77
|
-
kind: "user",
|
|
78
|
-
id: userId
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function resolveDefaultScope(visibilityContext = {}, runtime = {}) {
|
|
86
|
-
const runtimeContext = normalizeObject(runtime?.context);
|
|
87
|
-
|
|
88
|
-
const visibilityScope = resolveVisibilityScope(visibilityContext, runtimeContext);
|
|
89
|
-
if (visibilityScope) {
|
|
90
|
-
return visibilityScope;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const contextScope = resolveContextScope(runtimeContext);
|
|
94
|
-
if (contextScope) {
|
|
95
|
-
return contextScope;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
kind: "global",
|
|
100
|
-
id: null
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function resolveCommandId(requestMeta = {}) {
|
|
105
|
-
return normalizeText(requestMeta.commandId || requestMeta.idempotencyKey || "") || null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function resolveSourceClientId(requestMeta = {}) {
|
|
109
|
-
return normalizeText(requestMeta.sourceClientId) || null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function createEntityChangePublisher({
|
|
113
|
-
domainEvents,
|
|
114
|
-
source,
|
|
115
|
-
entity,
|
|
116
|
-
scopeResolver = resolveDefaultScope
|
|
117
|
-
} = {}) {
|
|
118
|
-
if (!domainEvents || typeof domainEvents.publish !== "function") {
|
|
119
|
-
throw new TypeError("createEntityChangePublisher requires domainEvents.publish().");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const normalizedSource = normalizeText(source);
|
|
123
|
-
if (!normalizedSource) {
|
|
124
|
-
throw new TypeError("createEntityChangePublisher requires source.");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const normalizedEntity = normalizeText(entity);
|
|
128
|
-
if (!normalizedEntity) {
|
|
129
|
-
throw new TypeError("createEntityChangePublisher requires entity.");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return async function publishEntityChange(operation, entityId, options = {}, meta = null) {
|
|
133
|
-
const normalizedOperation = normalizeText(operation).toLowerCase();
|
|
134
|
-
if (!ENTITY_CHANGE_OPERATIONS.has(normalizedOperation)) {
|
|
135
|
-
throw new TypeError("publishEntityChange operation must be one of: created, updated, deleted.");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const normalizedEntityId = normalizeOpaqueId(entityId);
|
|
139
|
-
if (normalizedEntityId == null) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const context = resolveServiceContext(options);
|
|
144
|
-
const requestMeta = normalizeObject(context.requestMeta);
|
|
145
|
-
const visibilityContext = normalizeObject(options.visibilityContext || context.visibilityContext);
|
|
146
|
-
const scope = scopeResolver(visibilityContext, {
|
|
147
|
-
context,
|
|
148
|
-
options
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const payload = {
|
|
152
|
-
source: normalizedSource,
|
|
153
|
-
entity: normalizedEntity,
|
|
154
|
-
operation: normalizedOperation,
|
|
155
|
-
entityId: normalizedEntityId,
|
|
156
|
-
scope: scope && typeof scope === "object" ? scope : resolveDefaultScope(visibilityContext),
|
|
157
|
-
actorId: normalizeOpaqueId(context?.actor?.id),
|
|
158
|
-
commandId: resolveCommandId(requestMeta),
|
|
159
|
-
sourceClientId: resolveSourceClientId(requestMeta),
|
|
160
|
-
occurredAt: new Date().toISOString()
|
|
161
|
-
};
|
|
162
|
-
const normalizedMeta = normalizeObject(meta);
|
|
163
|
-
if (Object.keys(normalizedMeta).length > 0) {
|
|
164
|
-
payload.meta = normalizedMeta;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await domainEvents.publish(payload);
|
|
168
|
-
return payload;
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function normalizeMetaTextField(source = {}, fieldName = "", { context = "realtime entity change" } = {}) {
|
|
173
|
-
if (!Object.hasOwn(source, fieldName)) {
|
|
174
|
-
return "";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const value = source[fieldName];
|
|
178
|
-
if (value == null) {
|
|
179
|
-
return "";
|
|
180
|
-
}
|
|
181
|
-
if (typeof value !== "string") {
|
|
182
|
-
throw new TypeError(`${context}.${fieldName} must be a non-empty string.`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const normalized = normalizeText(value);
|
|
186
|
-
if (!normalized) {
|
|
187
|
-
throw new TypeError(`${context}.${fieldName} must be a non-empty string.`);
|
|
188
|
-
}
|
|
189
|
-
return normalized;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function normalizeRealtimePayload(value, { context = "realtime entity change.payload" } = {}) {
|
|
193
|
-
if (value == null) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
197
|
-
throw new TypeError(`${context} must be an object.`);
|
|
198
|
-
}
|
|
199
|
-
return Object.freeze({
|
|
200
|
-
...value
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function createRealtimeEntityChangeMeta({
|
|
205
|
-
serviceId,
|
|
206
|
-
methodName,
|
|
207
|
-
event,
|
|
208
|
-
change = {}
|
|
209
|
-
} = {}) {
|
|
210
|
-
const source = normalizeObject(change);
|
|
211
|
-
const baseMeta = normalizeObject(source.meta);
|
|
212
|
-
const meta = {
|
|
213
|
-
...baseMeta,
|
|
214
|
-
service: Object.freeze({
|
|
215
|
-
id: serviceId,
|
|
216
|
-
method: methodName
|
|
217
|
-
})
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const action = normalizeMetaTextField(source, "action");
|
|
221
|
-
if (action) {
|
|
222
|
-
meta.action = action;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const reason = normalizeMetaTextField(source, "reason");
|
|
226
|
-
if (reason) {
|
|
227
|
-
meta.reason = reason;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const realtimeSource = normalizeObject(source.realtime);
|
|
231
|
-
const payloadSource = Object.hasOwn(source, "payload") ? source.payload : realtimeSource.payload;
|
|
232
|
-
const payload = normalizeRealtimePayload(payloadSource);
|
|
233
|
-
meta.realtime = payload
|
|
234
|
-
? Object.freeze({
|
|
235
|
-
event,
|
|
236
|
-
payload
|
|
237
|
-
})
|
|
238
|
-
: Object.freeze({
|
|
239
|
-
event
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return Object.freeze(meta);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function resolveRealtimeEntityChangeOptions(change = {}, options = {}) {
|
|
246
|
-
const source = normalizeObject(change);
|
|
247
|
-
if (Object.hasOwn(source, "options")) {
|
|
248
|
-
return normalizeObject(source.options);
|
|
249
|
-
}
|
|
250
|
-
return normalizeObject(options);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function createRealtimeEntityChangePublisher({
|
|
254
|
-
domainEvents,
|
|
255
|
-
source,
|
|
256
|
-
entity,
|
|
257
|
-
event,
|
|
258
|
-
serviceId,
|
|
259
|
-
methodName,
|
|
260
|
-
scopeResolver = resolveDefaultScope
|
|
261
|
-
} = {}) {
|
|
262
|
-
const normalizedEvent = normalizeText(event);
|
|
263
|
-
if (!normalizedEvent) {
|
|
264
|
-
throw new TypeError("createRealtimeEntityChangePublisher requires event.");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const normalizedServiceId = normalizeText(serviceId);
|
|
268
|
-
if (!normalizedServiceId) {
|
|
269
|
-
throw new TypeError("createRealtimeEntityChangePublisher requires serviceId.");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const normalizedMethodName = normalizeText(methodName);
|
|
273
|
-
if (!normalizedMethodName) {
|
|
274
|
-
throw new TypeError("createRealtimeEntityChangePublisher requires methodName.");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
278
|
-
domainEvents,
|
|
279
|
-
source,
|
|
280
|
-
entity,
|
|
281
|
-
scopeResolver
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
return async function publishRealtimeEntityChange(operation, entityId, change = {}, options = {}) {
|
|
285
|
-
const runtimeOptions = resolveRealtimeEntityChangeOptions(change, options);
|
|
286
|
-
const meta = createRealtimeEntityChangeMeta({
|
|
287
|
-
serviceId: normalizedServiceId,
|
|
288
|
-
methodName: normalizedMethodName,
|
|
289
|
-
event: normalizedEvent,
|
|
290
|
-
change
|
|
291
|
-
});
|
|
292
|
-
return publishEntityChange(operation, entityId, runtimeOptions, meta);
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function createNoopEntityChangePublisher() {
|
|
297
|
-
return async function publishEntityChange() {
|
|
298
|
-
return null;
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export {
|
|
303
|
-
resolveDefaultScope,
|
|
304
|
-
createEntityChangePublisher,
|
|
305
|
-
createRealtimeEntityChangePublisher,
|
|
306
|
-
createNoopEntityChangePublisher
|
|
307
|
-
};
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
createEntityChangePublisher,
|
|
5
|
-
createRealtimeEntityChangePublisher
|
|
6
|
-
} from "./entityChangeEvents.js";
|
|
7
|
-
|
|
8
|
-
test("entity change publisher emits normalized event payload", async () => {
|
|
9
|
-
const published = [];
|
|
10
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
11
|
-
domainEvents: {
|
|
12
|
-
async publish(payload) {
|
|
13
|
-
published.push(payload);
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
source: "crud",
|
|
17
|
-
entity: "record"
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const payload = await publishEntityChange("created", 5, {
|
|
21
|
-
context: {
|
|
22
|
-
actor: { id: 17 },
|
|
23
|
-
requestMeta: {
|
|
24
|
-
commandId: "cmd-1",
|
|
25
|
-
sourceClientId: "client-a"
|
|
26
|
-
},
|
|
27
|
-
visibilityContext: {
|
|
28
|
-
scopeOwnerId: 23
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}, {
|
|
32
|
-
service: {
|
|
33
|
-
token: "crud.customers",
|
|
34
|
-
method: "createRecord"
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
assert.equal(payload?.operation, "created");
|
|
39
|
-
assert.equal(payload?.entityId, "5");
|
|
40
|
-
assert.deepEqual(payload?.scope, { kind: "scope", id: "23" });
|
|
41
|
-
assert.equal(payload?.actorId, "17");
|
|
42
|
-
assert.equal(payload?.commandId, "cmd-1");
|
|
43
|
-
assert.equal(payload?.sourceClientId, "client-a");
|
|
44
|
-
assert.equal(payload?.meta?.service?.token, "crud.customers");
|
|
45
|
-
assert.equal(payload?.meta?.service?.method, "createRecord");
|
|
46
|
-
assert.equal(published.length, 1);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("entity change publisher accepts opaque entity identifiers", async () => {
|
|
50
|
-
const published = [];
|
|
51
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
52
|
-
domainEvents: {
|
|
53
|
-
async publish(payload) {
|
|
54
|
-
published.push(payload);
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
source: "crud",
|
|
58
|
-
entity: "record"
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const payload = await publishEntityChange("updated", "record_4f5d2f6a-607e-4b4b-9bfa-4f8c2b1d3c8e");
|
|
62
|
-
|
|
63
|
-
assert.equal(payload?.entityId, "record_4f5d2f6a-607e-4b4b-9bfa-4f8c2b1d3c8e");
|
|
64
|
-
assert.equal(published.length, 1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("entity change publisher ignores missing entity identifiers", async () => {
|
|
68
|
-
const published = [];
|
|
69
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
70
|
-
domainEvents: {
|
|
71
|
-
async publish(payload) {
|
|
72
|
-
published.push(payload);
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
source: "crud",
|
|
76
|
-
entity: "record"
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const payload = await publishEntityChange("updated", null);
|
|
80
|
-
|
|
81
|
-
assert.equal(payload, null);
|
|
82
|
-
assert.equal(published.length, 0);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("entity change publisher infers scoped owner from service context when visibility owner ids are missing", async () => {
|
|
86
|
-
const published = [];
|
|
87
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
88
|
-
domainEvents: {
|
|
89
|
-
async publish(payload) {
|
|
90
|
-
published.push(payload);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
source: "workspace",
|
|
94
|
-
entity: "settings"
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const payload = await publishEntityChange(
|
|
98
|
-
"updated",
|
|
99
|
-
11,
|
|
100
|
-
{
|
|
101
|
-
context: {
|
|
102
|
-
actor: { id: 17 },
|
|
103
|
-
scope: { kind: "workspace", id: 23 },
|
|
104
|
-
visibilityContext: {
|
|
105
|
-
visibility: "workspace"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
service: {
|
|
111
|
-
token: "users.workspace.settings.service",
|
|
112
|
-
method: "updateWorkspaceSettings"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
assert.deepEqual(payload?.scope, { kind: "workspace", id: "23" });
|
|
118
|
-
assert.equal(published.length, 1);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("entity change publisher supports opaque actor and scope identifiers", async () => {
|
|
122
|
-
const published = [];
|
|
123
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
124
|
-
domainEvents: {
|
|
125
|
-
async publish(payload) {
|
|
126
|
-
published.push(payload);
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
source: "workspace",
|
|
130
|
-
entity: "settings"
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const payload = await publishEntityChange("updated", 11, {
|
|
134
|
-
context: {
|
|
135
|
-
actor: { id: "user_17" },
|
|
136
|
-
visibilityContext: {
|
|
137
|
-
scopeKind: "workspace_user",
|
|
138
|
-
scopeOwnerId: "workspace_23",
|
|
139
|
-
userId: "user_17",
|
|
140
|
-
requiresActorScope: true
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
assert.deepEqual(payload?.scope, {
|
|
146
|
-
kind: "workspace_user",
|
|
147
|
-
id: "workspace_23",
|
|
148
|
-
scopeId: "workspace_23",
|
|
149
|
-
userId: "user_17"
|
|
150
|
-
});
|
|
151
|
-
assert.equal(payload?.actorId, "user_17");
|
|
152
|
-
assert.equal(published.length, 1);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("entity change publisher does not infer actor-scoped ownership from actor.id", async () => {
|
|
156
|
-
const published = [];
|
|
157
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
158
|
-
domainEvents: {
|
|
159
|
-
async publish(payload) {
|
|
160
|
-
published.push(payload);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
source: "workspace",
|
|
164
|
-
entity: "settings"
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const payload = await publishEntityChange("updated", 11, {
|
|
168
|
-
context: {
|
|
169
|
-
actor: { id: "user_17" },
|
|
170
|
-
visibilityContext: {
|
|
171
|
-
scopeKind: "workspace_user",
|
|
172
|
-
scopeOwnerId: "workspace_23",
|
|
173
|
-
requiresActorScope: true
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
assert.deepEqual(payload?.scope, {
|
|
179
|
-
kind: "global",
|
|
180
|
-
id: null
|
|
181
|
-
});
|
|
182
|
-
assert.equal(payload?.actorId, "user_17");
|
|
183
|
-
assert.equal(published.length, 1);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("entity change publisher does not infer actor scope from scope kind suffix", async () => {
|
|
187
|
-
const published = [];
|
|
188
|
-
const publishEntityChange = createEntityChangePublisher({
|
|
189
|
-
domainEvents: {
|
|
190
|
-
async publish(payload) {
|
|
191
|
-
published.push(payload);
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
source: "workspace",
|
|
195
|
-
entity: "settings"
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const payload = await publishEntityChange("updated", 11, {
|
|
199
|
-
context: {
|
|
200
|
-
visibilityContext: {
|
|
201
|
-
scopeKind: "workspace_user",
|
|
202
|
-
scopeOwnerId: "workspace_23",
|
|
203
|
-
requiresActorScope: false
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
assert.deepEqual(payload?.scope, {
|
|
209
|
-
kind: "workspace_user",
|
|
210
|
-
id: "workspace_23"
|
|
211
|
-
});
|
|
212
|
-
assert.equal(payload?.actorId, null);
|
|
213
|
-
assert.equal(published.length, 1);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("realtime entity change publisher adds service, semantic, and realtime metadata", async () => {
|
|
217
|
-
const published = [];
|
|
218
|
-
const publishProjectChange = createRealtimeEntityChangePublisher({
|
|
219
|
-
domainEvents: {
|
|
220
|
-
async publish(payload) {
|
|
221
|
-
published.push(payload);
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
source: "vibe64",
|
|
225
|
-
entity: "project",
|
|
226
|
-
event: "vibe64.project.changed",
|
|
227
|
-
serviceId: "vibe64.terminals",
|
|
228
|
-
methodName: "projectRuntime"
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const payload = await publishProjectChange("updated", "project_acme", {
|
|
232
|
-
action: "runtime-closed",
|
|
233
|
-
reason: "user-request",
|
|
234
|
-
payload: {
|
|
235
|
-
message: "Project is closed.",
|
|
236
|
-
runtime: {
|
|
237
|
-
open: false
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
options: {
|
|
241
|
-
context: {
|
|
242
|
-
actor: {
|
|
243
|
-
id: "user_17"
|
|
244
|
-
},
|
|
245
|
-
visibilityContext: {
|
|
246
|
-
scopeKind: "workspace",
|
|
247
|
-
scopeOwnerId: "workspace_23"
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
assert.equal(payload?.source, "vibe64");
|
|
254
|
-
assert.equal(payload?.entity, "project");
|
|
255
|
-
assert.equal(payload?.operation, "updated");
|
|
256
|
-
assert.equal(payload?.entityId, "project_acme");
|
|
257
|
-
assert.deepEqual(payload?.scope, {
|
|
258
|
-
kind: "workspace",
|
|
259
|
-
id: "workspace_23"
|
|
260
|
-
});
|
|
261
|
-
assert.equal(payload?.actorId, "user_17");
|
|
262
|
-
assert.equal(payload?.meta?.service?.id, "vibe64.terminals");
|
|
263
|
-
assert.equal(payload?.meta?.service?.method, "projectRuntime");
|
|
264
|
-
assert.equal(payload?.meta?.action, "runtime-closed");
|
|
265
|
-
assert.equal(payload?.meta?.reason, "user-request");
|
|
266
|
-
assert.equal(payload?.meta?.realtime?.event, "vibe64.project.changed");
|
|
267
|
-
assert.deepEqual(payload?.meta?.realtime?.payload, {
|
|
268
|
-
message: "Project is closed.",
|
|
269
|
-
runtime: {
|
|
270
|
-
open: false
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
assert.equal(published.length, 1);
|
|
274
|
-
});
|