@ultrst-armada/krnl 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -2
- package/packages/cli/dist/bin/krnl.cjs +3 -15
- package/packages/cli/dist/bin/krnl.js +1 -1
- package/packages/cli/dist/{chunk-GHWYEAPD.js → chunk-3DD775A7.js} +2 -14
- package/packages/cli/dist/index.cjs +3 -15
- package/packages/cli/dist/index.js +1 -1
- package/packages/core/dist/index.cjs +244 -57
- package/packages/core/dist/index.d.cts +161 -24
- package/packages/core/dist/index.d.ts +161 -24
- package/packages/core/dist/index.js +240 -57
- package/packages/core/package.json +1 -1
|
@@ -26,35 +26,6 @@ var ServiceStateStore = class {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
// src/events/ServiceEventBus.ts
|
|
30
|
-
var ServiceEventBus = class {
|
|
31
|
-
handlers = /* @__PURE__ */ new Map();
|
|
32
|
-
on(event, handler) {
|
|
33
|
-
if (!this.handlers.has(event)) {
|
|
34
|
-
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
35
|
-
}
|
|
36
|
-
const set = this.handlers.get(event);
|
|
37
|
-
set.add(handler);
|
|
38
|
-
return () => {
|
|
39
|
-
set.delete(handler);
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
emit(event, payload) {
|
|
43
|
-
const set = this.handlers.get(event);
|
|
44
|
-
if (!set) return;
|
|
45
|
-
for (const handler of Array.from(set)) {
|
|
46
|
-
try {
|
|
47
|
-
handler(payload);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.error(`[ServiceEventBus] Handler error for '${event}':`, err);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
clear() {
|
|
54
|
-
this.handlers.clear();
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
29
|
// src/lifecycle/LifecycleManager.ts
|
|
59
30
|
var TRANSITIONS = {
|
|
60
31
|
uninitialized: ["initializing"],
|
|
@@ -99,7 +70,6 @@ var ServiceInstance = class {
|
|
|
99
70
|
this.name = definition.name;
|
|
100
71
|
this.version = definition.version;
|
|
101
72
|
this.lifecycle = new LifecycleManager();
|
|
102
|
-
this.eventBus = new ServiceEventBus();
|
|
103
73
|
if (definition.state) {
|
|
104
74
|
this.stateStore = new ServiceStateStore(definition.state.initial);
|
|
105
75
|
}
|
|
@@ -109,7 +79,6 @@ var ServiceInstance = class {
|
|
|
109
79
|
name;
|
|
110
80
|
version;
|
|
111
81
|
stateStore;
|
|
112
|
-
eventBus;
|
|
113
82
|
lifecycle;
|
|
114
83
|
initialized = false;
|
|
115
84
|
disposed = false;
|
|
@@ -185,12 +154,6 @@ var ServiceInstance = class {
|
|
|
185
154
|
}
|
|
186
155
|
this.stateStore.setState(mutation);
|
|
187
156
|
}
|
|
188
|
-
on(event, handler) {
|
|
189
|
-
return this.eventBus.on(event, handler);
|
|
190
|
-
}
|
|
191
|
-
emit(event, payload) {
|
|
192
|
-
this.eventBus.emit(event, payload);
|
|
193
|
-
}
|
|
194
157
|
};
|
|
195
158
|
|
|
196
159
|
// src/services/ServiceProxy.ts
|
|
@@ -340,12 +303,6 @@ function validateServiceDefinition(definition) {
|
|
|
340
303
|
throw new Error("[Service:" + definition.name + "] Operation '" + key + "' must define a handler.");
|
|
341
304
|
}
|
|
342
305
|
}
|
|
343
|
-
for (const [key, event] of Object.entries(definition.events ?? {})) {
|
|
344
|
-
if (!event || typeof event !== "object") {
|
|
345
|
-
throw new Error("[Service:" + definition.name + "] Invalid event '" + key + "'.");
|
|
346
|
-
}
|
|
347
|
-
assertNonEmptyString(event.name, "events." + key + ".name");
|
|
348
|
-
}
|
|
349
306
|
for (const [key, schema] of Object.entries(definition.inputs ?? {})) assertRecord(schema, "inputs." + key);
|
|
350
307
|
for (const [key, schema] of Object.entries(definition.outputs ?? {})) assertRecord(schema, "outputs." + key);
|
|
351
308
|
if (definition.state?.schema !== void 0) assertRecord(definition.state.schema, "state.schema");
|
|
@@ -378,6 +335,137 @@ function createServiceProxy(definition, adapter, endpoint) {
|
|
|
378
335
|
return new ServiceProxy(definition, adapter, endpoint);
|
|
379
336
|
}
|
|
380
337
|
|
|
338
|
+
// src/events/EventBus.ts
|
|
339
|
+
var EventBus = class {
|
|
340
|
+
handlers = /* @__PURE__ */ new Map();
|
|
341
|
+
subscribe(event, handler) {
|
|
342
|
+
let listeners = this.handlers.get(event);
|
|
343
|
+
if (!listeners) {
|
|
344
|
+
listeners = /* @__PURE__ */ new Set();
|
|
345
|
+
this.handlers.set(event, listeners);
|
|
346
|
+
}
|
|
347
|
+
listeners.add(handler);
|
|
348
|
+
return {
|
|
349
|
+
unsubscribe: () => {
|
|
350
|
+
listeners?.delete(handler);
|
|
351
|
+
if (listeners && listeners.size === 0) this.handlers.delete(event);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
on(event, handler) {
|
|
356
|
+
return this.subscribe(event, handler).unsubscribe;
|
|
357
|
+
}
|
|
358
|
+
emit(event, payload) {
|
|
359
|
+
const listeners = this.handlers.get(event);
|
|
360
|
+
if (!listeners) return;
|
|
361
|
+
for (const listener of Array.from(listeners)) {
|
|
362
|
+
try {
|
|
363
|
+
listener(payload);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(`[EventBus] Handler error for '${event}':`, error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
clear() {
|
|
370
|
+
this.handlers.clear();
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
function createEventChannels(bus, names) {
|
|
374
|
+
const channels = {};
|
|
375
|
+
for (const name of names) {
|
|
376
|
+
channels[name] = {
|
|
377
|
+
name,
|
|
378
|
+
subscribe: (handler) => bus.subscribe(name, handler),
|
|
379
|
+
on: (handler) => bus.on(name, handler)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return channels;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/functionalities/FunctionalityFacade.ts
|
|
386
|
+
var FunctionalityFacade = class {
|
|
387
|
+
id;
|
|
388
|
+
name;
|
|
389
|
+
description;
|
|
390
|
+
bubbleEvents;
|
|
391
|
+
definition;
|
|
392
|
+
eventBus;
|
|
393
|
+
events;
|
|
394
|
+
constructor(id, definition, options = {}) {
|
|
395
|
+
if (!id || typeof id !== "string") {
|
|
396
|
+
throw new Error("[FunctionalityFacade] Functionality id must be a non-empty string.");
|
|
397
|
+
}
|
|
398
|
+
if (!definition || typeof definition.execute !== "function") {
|
|
399
|
+
throw new Error(`[FunctionalityFacade:${id}] Functionality must define execute().`);
|
|
400
|
+
}
|
|
401
|
+
this.id = id;
|
|
402
|
+
this.name = definition.name || id;
|
|
403
|
+
this.description = definition.description || "";
|
|
404
|
+
this.bubbleEvents = options.bubbleEvents ?? definition.bubbleEvents ?? true;
|
|
405
|
+
this.definition = definition;
|
|
406
|
+
this.eventBus = new EventBus();
|
|
407
|
+
for (const reserved of ["triggered", "completed", "failed"]) {
|
|
408
|
+
if (definition.events && reserved in definition.events) {
|
|
409
|
+
throw new Error(`[FunctionalityFacade:${id}] Event '${reserved}' is reserved for functionality lifecycle.`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const eventNames = [
|
|
413
|
+
"triggered",
|
|
414
|
+
"completed",
|
|
415
|
+
"failed",
|
|
416
|
+
...Object.keys(definition.events ?? {})
|
|
417
|
+
];
|
|
418
|
+
this.events = createEventChannels(this.eventBus, eventNames);
|
|
419
|
+
}
|
|
420
|
+
getEventDefinitions() {
|
|
421
|
+
return {
|
|
422
|
+
triggered: { name: "triggered", description: "Functionality execution started." },
|
|
423
|
+
completed: { name: "completed", description: "Functionality execution completed successfully." },
|
|
424
|
+
failed: { name: "failed", description: "Functionality execution failed." },
|
|
425
|
+
...this.definition.events ?? {}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
subscribe(event, handler) {
|
|
429
|
+
return this.eventBus.on(event, handler);
|
|
430
|
+
}
|
|
431
|
+
emit(event, payload) {
|
|
432
|
+
this.eventBus.emit(event, payload);
|
|
433
|
+
}
|
|
434
|
+
async execute(input, context) {
|
|
435
|
+
const lifecycle = {
|
|
436
|
+
serviceId: context.serviceId,
|
|
437
|
+
functionalityId: this.id,
|
|
438
|
+
functionalityName: this.name,
|
|
439
|
+
correlationId: context.correlationId,
|
|
440
|
+
timestamp: Date.now(),
|
|
441
|
+
input
|
|
442
|
+
};
|
|
443
|
+
this.emit("triggered", lifecycle);
|
|
444
|
+
try {
|
|
445
|
+
const output = await this.definition.execute(input, {
|
|
446
|
+
...context,
|
|
447
|
+
emit: (event, payload) => this.emit(event, payload)
|
|
448
|
+
});
|
|
449
|
+
this.emit("completed", {
|
|
450
|
+
...lifecycle,
|
|
451
|
+
timestamp: Date.now(),
|
|
452
|
+
output
|
|
453
|
+
});
|
|
454
|
+
return output;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.emit("failed", {
|
|
457
|
+
...lifecycle,
|
|
458
|
+
timestamp: Date.now(),
|
|
459
|
+
error: error?.message || String(error)
|
|
460
|
+
});
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
clearEventSubscriptions() {
|
|
465
|
+
this.eventBus.clear();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
381
469
|
// ../../node_modules/.pnpm/p-queue@10.0.0/node_modules/p-queue/dist/lower-bound.js
|
|
382
470
|
function lowerBound(array, value, comparator) {
|
|
383
471
|
let first = 0;
|
|
@@ -2298,7 +2386,10 @@ var ServiceFacade = class {
|
|
|
2298
2386
|
this.name = config.name || config.id;
|
|
2299
2387
|
this.version = config.version || "1.0.0";
|
|
2300
2388
|
this.description = config.description || "";
|
|
2301
|
-
this.functionalities =
|
|
2389
|
+
this.functionalities = {};
|
|
2390
|
+
for (const [name, functionality] of Object.entries(config.functionalities ?? {})) {
|
|
2391
|
+
this.registerFunctionality(name, functionality);
|
|
2392
|
+
}
|
|
2302
2393
|
this.functionalityOrchestrations = config.functionalityOrchestrations ? { ...config.functionalityOrchestrations } : config.orchestrations ? { ...config.orchestrations } : {};
|
|
2303
2394
|
this.libraries = config.libraries ? { ...config.libraries } : {};
|
|
2304
2395
|
this.dependencies = Object.freeze(config.dependencies ? [...config.dependencies] : []);
|
|
@@ -2370,8 +2461,11 @@ var ServiceFacade = class {
|
|
|
2370
2461
|
};
|
|
2371
2462
|
const result = await this.queue.executeCommand(command, async (payload, workerCtx) => {
|
|
2372
2463
|
WorkerExecutionBoundary.assertWorkerContext(workerCtx);
|
|
2373
|
-
|
|
2374
|
-
|
|
2464
|
+
const functionality = this.functionalities[op];
|
|
2465
|
+
if (functionality) {
|
|
2466
|
+
return await functionality.execute(payload, {
|
|
2467
|
+
serviceId: this.id,
|
|
2468
|
+
functionalityId: functionality.id,
|
|
2375
2469
|
correlationId: workerCtx.correlationId,
|
|
2376
2470
|
state: this.instance.getState()
|
|
2377
2471
|
});
|
|
@@ -2407,6 +2501,13 @@ var ServiceFacade = class {
|
|
|
2407
2501
|
idempotencyKey: options.idempotencyKey,
|
|
2408
2502
|
createdAt: Date.now()
|
|
2409
2503
|
};
|
|
2504
|
+
orch.events.emit("triggered", {
|
|
2505
|
+
serviceId: this.id,
|
|
2506
|
+
orchestrationId: orch.id,
|
|
2507
|
+
orchestrationName: orch.name,
|
|
2508
|
+
correlationId,
|
|
2509
|
+
timestamp: Date.now()
|
|
2510
|
+
});
|
|
2410
2511
|
const cmdResult = await this.queue.executeCommand(
|
|
2411
2512
|
command,
|
|
2412
2513
|
async (payload, workerCtx) => {
|
|
@@ -2496,8 +2597,33 @@ var ServiceFacade = class {
|
|
|
2496
2597
|
);
|
|
2497
2598
|
}
|
|
2498
2599
|
);
|
|
2499
|
-
|
|
2500
|
-
|
|
2600
|
+
const orchestrationResult = cmdResult.data;
|
|
2601
|
+
if (cmdResult.status === "COMPLETED" && orchestrationResult) {
|
|
2602
|
+
const lifecycleEvent = orchestrationResult.status === "COMPLETED" ? "completed" : "failed";
|
|
2603
|
+
orch.events.emit(lifecycleEvent, {
|
|
2604
|
+
serviceId: this.id,
|
|
2605
|
+
orchestrationId: orch.id,
|
|
2606
|
+
orchestrationName: orch.name,
|
|
2607
|
+
correlationId,
|
|
2608
|
+
executionId: String(orchestrationResult.executionMetadata.executionId ?? ""),
|
|
2609
|
+
timestamp: Date.now(),
|
|
2610
|
+
status: orchestrationResult.status,
|
|
2611
|
+
output: orchestrationResult.output,
|
|
2612
|
+
error: orchestrationResult.error
|
|
2613
|
+
});
|
|
2614
|
+
} else {
|
|
2615
|
+
orch.events.emit("failed", {
|
|
2616
|
+
serviceId: this.id,
|
|
2617
|
+
orchestrationId: orch.id,
|
|
2618
|
+
orchestrationName: orch.name,
|
|
2619
|
+
correlationId,
|
|
2620
|
+
timestamp: Date.now(),
|
|
2621
|
+
status: cmdResult.status,
|
|
2622
|
+
error: cmdResult.error
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
if (cmdResult.status === "FAILED" || orchestrationResult?.status === "FAILED") {
|
|
2626
|
+
throw new Error(orchestrationResult?.error || cmdResult.error || `[ServiceFacade:${this.id}] Orchestration failed.`);
|
|
2501
2627
|
}
|
|
2502
2628
|
return cmdResult.data;
|
|
2503
2629
|
}
|
|
@@ -2525,17 +2651,29 @@ var ServiceFacade = class {
|
|
|
2525
2651
|
setState(mutation) {
|
|
2526
2652
|
this.instance.setState(mutation);
|
|
2527
2653
|
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2654
|
+
getFunctionality(name) {
|
|
2655
|
+
const functionality = this.functionalities[name];
|
|
2656
|
+
if (!functionality) {
|
|
2657
|
+
throw new Error(`[ServiceFacade:${this.id}] Functionality "${name}" not found.`);
|
|
2658
|
+
}
|
|
2659
|
+
return functionality;
|
|
2533
2660
|
}
|
|
2534
|
-
registerFunctionality(name, functionality) {
|
|
2661
|
+
registerFunctionality(name, functionality, options = {}) {
|
|
2535
2662
|
if (!name || typeof name !== "string") {
|
|
2536
2663
|
throw new Error("[ServiceFacade] Functionality name must be a non-empty string.");
|
|
2537
2664
|
}
|
|
2538
|
-
|
|
2665
|
+
if (functionality instanceof FunctionalityFacade) {
|
|
2666
|
+
this.functionalities[name] = functionality;
|
|
2667
|
+
return this;
|
|
2668
|
+
}
|
|
2669
|
+
if (!functionality || typeof functionality !== "object" || typeof functionality.execute !== "function") {
|
|
2670
|
+
throw new Error(`[ServiceFacade:${this.id}] Functionality "${name}" must define execute().`);
|
|
2671
|
+
}
|
|
2672
|
+
this.functionalities[name] = new FunctionalityFacade(
|
|
2673
|
+
name,
|
|
2674
|
+
functionality,
|
|
2675
|
+
options
|
|
2676
|
+
);
|
|
2539
2677
|
return this;
|
|
2540
2678
|
}
|
|
2541
2679
|
registerFunctionalityOrchestration(name, orchestration) {
|
|
@@ -2573,14 +2711,22 @@ var ServiceFacade = class {
|
|
|
2573
2711
|
return this.getNamespace();
|
|
2574
2712
|
}
|
|
2575
2713
|
getNamespace() {
|
|
2714
|
+
const bubbledFunctionalities = {};
|
|
2715
|
+
for (const [name, functionality] of Object.entries(this.functionalities)) {
|
|
2716
|
+
if (functionality.bubbleEvents) bubbledFunctionalities[name] = functionality;
|
|
2717
|
+
}
|
|
2718
|
+
const bubbledOrchestrations = {};
|
|
2719
|
+
for (const [name, orchestration] of Object.entries(this.functionalityOrchestrations)) {
|
|
2720
|
+
if (orchestration.bubbleEvents) bubbledOrchestrations[name] = orchestration;
|
|
2721
|
+
}
|
|
2576
2722
|
return {
|
|
2577
2723
|
serviceId: this.id,
|
|
2578
2724
|
serviceName: this.name,
|
|
2579
2725
|
version: this.version,
|
|
2580
2726
|
description: this.description,
|
|
2581
2727
|
libraries: { ...this.libraries },
|
|
2582
|
-
functionalities:
|
|
2583
|
-
functionalityOrchestrations:
|
|
2728
|
+
functionalities: bubbledFunctionalities,
|
|
2729
|
+
functionalityOrchestrations: bubbledOrchestrations,
|
|
2584
2730
|
capabilities: this.capabilities,
|
|
2585
2731
|
dependencies: this.dependencies
|
|
2586
2732
|
};
|
|
@@ -3277,6 +3423,34 @@ var KrnlPackageReconciler = class {
|
|
|
3277
3423
|
}
|
|
3278
3424
|
};
|
|
3279
3425
|
|
|
3426
|
+
// src/events/ServiceEventBus.ts
|
|
3427
|
+
var ServiceEventBus = class extends EventBus {
|
|
3428
|
+
};
|
|
3429
|
+
|
|
3430
|
+
// src/events/OrchestrationEventFacade.ts
|
|
3431
|
+
var OrchestrationEventFacade = class {
|
|
3432
|
+
eventBus = new EventBus();
|
|
3433
|
+
events;
|
|
3434
|
+
constructor() {
|
|
3435
|
+
this.events = createEventChannels(this.eventBus, ["triggered", "completed", "failed"]);
|
|
3436
|
+
}
|
|
3437
|
+
get triggered() {
|
|
3438
|
+
return this.events.triggered;
|
|
3439
|
+
}
|
|
3440
|
+
get completed() {
|
|
3441
|
+
return this.events.completed;
|
|
3442
|
+
}
|
|
3443
|
+
get failed() {
|
|
3444
|
+
return this.events.failed;
|
|
3445
|
+
}
|
|
3446
|
+
emit(event, payload) {
|
|
3447
|
+
this.eventBus.emit(event, payload);
|
|
3448
|
+
}
|
|
3449
|
+
clear() {
|
|
3450
|
+
this.eventBus.clear();
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
|
|
3280
3454
|
// src/connectivity/ProtocolResolver.ts
|
|
3281
3455
|
var ProtocolResolver = class {
|
|
3282
3456
|
static selectProtocol(requiredCapabilities, availableEndpoints) {
|
|
@@ -3297,6 +3471,8 @@ var FunctionalityOrchestration = class {
|
|
|
3297
3471
|
description;
|
|
3298
3472
|
timeoutMs;
|
|
3299
3473
|
scope = "functionality";
|
|
3474
|
+
bubbleEvents;
|
|
3475
|
+
events;
|
|
3300
3476
|
steps = [];
|
|
3301
3477
|
flows = /* @__PURE__ */ new Map();
|
|
3302
3478
|
constructor(descriptor) {
|
|
@@ -3308,6 +3484,8 @@ var FunctionalityOrchestration = class {
|
|
|
3308
3484
|
this.version = descriptor.version || "1.0.0";
|
|
3309
3485
|
this.description = descriptor.description || "";
|
|
3310
3486
|
this.timeoutMs = descriptor.timeoutMs;
|
|
3487
|
+
this.bubbleEvents = descriptor.bubbleEvents ?? true;
|
|
3488
|
+
this.events = new OrchestrationEventFacade();
|
|
3311
3489
|
}
|
|
3312
3490
|
/**
|
|
3313
3491
|
* Adds a sequential functionality step to the DAG.
|
|
@@ -3468,6 +3646,7 @@ var FunctionalityOrchestration = class {
|
|
|
3468
3646
|
description: this.description,
|
|
3469
3647
|
timeoutMs: this.timeoutMs,
|
|
3470
3648
|
scope: "functionality",
|
|
3649
|
+
bubbleEvents: this.bubbleEvents,
|
|
3471
3650
|
dependencies: [],
|
|
3472
3651
|
steps: this.steps.map((s) => ({
|
|
3473
3652
|
id: s.id,
|
|
@@ -3571,6 +3750,8 @@ var FunctionalityOrchestration = class {
|
|
|
3571
3750
|
// src/index.ts
|
|
3572
3751
|
import * as gutts from "@dinilmusick/gutts";
|
|
3573
3752
|
export {
|
|
3753
|
+
EventBus,
|
|
3754
|
+
FunctionalityFacade,
|
|
3574
3755
|
FunctionalityOrchestration,
|
|
3575
3756
|
HttpProtocolAdapter,
|
|
3576
3757
|
KrnlPackageReconciler,
|
|
@@ -3578,6 +3759,7 @@ export {
|
|
|
3578
3759
|
KrnlWorkerPool,
|
|
3579
3760
|
LifecycleManager,
|
|
3580
3761
|
MemoryTransport,
|
|
3762
|
+
OrchestrationEventFacade,
|
|
3581
3763
|
P2PProtocolAdapter,
|
|
3582
3764
|
ProtocolResolver,
|
|
3583
3765
|
ServiceDiscovery,
|
|
@@ -3591,6 +3773,7 @@ export {
|
|
|
3591
3773
|
UdpProtocolAdapter,
|
|
3592
3774
|
WebSocketProtocolAdapter,
|
|
3593
3775
|
WorkerExecutionBoundary,
|
|
3776
|
+
createEventChannels,
|
|
3594
3777
|
createService,
|
|
3595
3778
|
createServiceDefinition,
|
|
3596
3779
|
createServiceFacade,
|