@cadenza.io/service 2.10.0 → 2.12.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 +12 -0
- package/dist/index.d.mts +48 -8
- package/dist/index.d.ts +48 -8
- package/dist/index.js +1691 -1350
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1653 -1312
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -30,25 +30,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
Actor: () =>
|
|
33
|
+
Actor: () => import_core5.Actor,
|
|
34
34
|
DatabaseTask: () => DatabaseTask,
|
|
35
|
-
DebounceTask: () =>
|
|
35
|
+
DebounceTask: () => import_core5.DebounceTask,
|
|
36
36
|
DeputyTask: () => DeputyTask,
|
|
37
|
-
EphemeralTask: () =>
|
|
37
|
+
EphemeralTask: () => import_core5.EphemeralTask,
|
|
38
38
|
GraphMetadataController: () => GraphMetadataController,
|
|
39
|
-
GraphRoutine: () =>
|
|
39
|
+
GraphRoutine: () => import_core5.GraphRoutine,
|
|
40
40
|
RestController: () => RestController,
|
|
41
41
|
ServiceRegistry: () => ServiceRegistry,
|
|
42
42
|
SignalController: () => SignalController,
|
|
43
43
|
SignalTransmissionTask: () => SignalTransmissionTask,
|
|
44
44
|
SocketController: () => SocketController,
|
|
45
|
-
Task: () =>
|
|
45
|
+
Task: () => import_core5.Task,
|
|
46
46
|
default: () => index_default
|
|
47
47
|
});
|
|
48
48
|
module.exports = __toCommonJS(index_exports);
|
|
49
49
|
|
|
50
50
|
// src/Cadenza.ts
|
|
51
|
-
var
|
|
51
|
+
var import_core4 = __toESM(require("@cadenza.io/core"));
|
|
52
52
|
|
|
53
53
|
// src/graph/definition/DeputyTask.ts
|
|
54
54
|
var import_uuid = require("uuid");
|
|
@@ -1698,7 +1698,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1698
1698
|
}
|
|
1699
1699
|
).emits("meta.service_registry.service_inserted").emitsOnFail("meta.service_registry.service_insertion_failed");
|
|
1700
1700
|
this.insertServiceInstanceTask = CadenzaService.createCadenzaDBInsertTask(
|
|
1701
|
-
"
|
|
1701
|
+
"service_instance",
|
|
1702
1702
|
{},
|
|
1703
1703
|
{
|
|
1704
1704
|
inputSchema: {
|
|
@@ -2642,20 +2642,44 @@ var RestController = class _RestController {
|
|
|
2642
2642
|
let ctx2;
|
|
2643
2643
|
ctx2 = req.body;
|
|
2644
2644
|
deputyExecId = ctx2.__metadata.__deputyExecId;
|
|
2645
|
+
const remoteRoutineName = ctx2.__remoteRoutineName;
|
|
2646
|
+
const targetNotFoundSignal = `meta.rest.delegation_target_not_found:${deputyExecId}`;
|
|
2647
|
+
let resolved = false;
|
|
2648
|
+
const resolveDelegation = (endCtx, status) => {
|
|
2649
|
+
if (resolved || res.headersSent) {
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
resolved = true;
|
|
2653
|
+
const metadata = endCtx?.__metadata && typeof endCtx.__metadata === "object" ? endCtx.__metadata : {};
|
|
2654
|
+
if (endCtx?.__metadata) {
|
|
2655
|
+
delete endCtx.__metadata;
|
|
2656
|
+
}
|
|
2657
|
+
res.json({
|
|
2658
|
+
...endCtx,
|
|
2659
|
+
...metadata,
|
|
2660
|
+
__status: status
|
|
2661
|
+
});
|
|
2662
|
+
};
|
|
2645
2663
|
CadenzaService.createEphemeralMetaTask(
|
|
2646
2664
|
"Resolve delegation",
|
|
2647
|
-
(endCtx) =>
|
|
2648
|
-
const metadata = endCtx.__metadata;
|
|
2649
|
-
delete endCtx.__metadata;
|
|
2650
|
-
res.json({
|
|
2651
|
-
...endCtx,
|
|
2652
|
-
...metadata,
|
|
2653
|
-
__status: "success"
|
|
2654
|
-
});
|
|
2655
|
-
},
|
|
2665
|
+
(endCtx) => resolveDelegation(endCtx, "success"),
|
|
2656
2666
|
"Resolves a delegation request",
|
|
2657
2667
|
{ register: false }
|
|
2658
2668
|
).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.rest.delegation_resolved:${deputyExecId}`);
|
|
2669
|
+
CadenzaService.createEphemeralMetaTask(
|
|
2670
|
+
"Resolve delegation target lookup failure",
|
|
2671
|
+
(endCtx) => resolveDelegation(endCtx, "error"),
|
|
2672
|
+
"Resolves delegation requests that cannot find a local task or routine",
|
|
2673
|
+
{ register: false }
|
|
2674
|
+
).doOn(targetNotFoundSignal);
|
|
2675
|
+
if (!CadenzaService.get(remoteRoutineName) && !CadenzaService.registry.routines.get(remoteRoutineName)) {
|
|
2676
|
+
CadenzaService.emit(targetNotFoundSignal, {
|
|
2677
|
+
...ctx2,
|
|
2678
|
+
__error: `No task or routine registered for delegation target ${remoteRoutineName}.`,
|
|
2679
|
+
errored: true
|
|
2680
|
+
});
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2659
2683
|
CadenzaService.emit("meta.rest.delegation_requested", {
|
|
2660
2684
|
...ctx2,
|
|
2661
2685
|
__name: ctx2.__remoteRoutineName
|
|
@@ -2826,8 +2850,16 @@ var RestController = class _RestController {
|
|
|
2826
2850
|
CadenzaService.runner.run(routine, context);
|
|
2827
2851
|
return true;
|
|
2828
2852
|
} else {
|
|
2853
|
+
const deputyExecId = context.__metadata?.__deputyExecId ?? context.__deputyExecId;
|
|
2854
|
+
const remoteRoutineName = context.__remoteRoutineName ?? context.__name ?? "unknown";
|
|
2829
2855
|
context.errored = true;
|
|
2830
|
-
context.__error =
|
|
2856
|
+
context.__error = `No task or routine registered for delegation target ${remoteRoutineName}.`;
|
|
2857
|
+
if (deputyExecId) {
|
|
2858
|
+
emit(
|
|
2859
|
+
`meta.rest.delegation_target_not_found:${deputyExecId}`,
|
|
2860
|
+
context
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2831
2863
|
emit("meta.runner.failed", context);
|
|
2832
2864
|
return false;
|
|
2833
2865
|
}
|
|
@@ -2850,25 +2882,25 @@ var RestController = class _RestController {
|
|
|
2850
2882
|
(ctx) => {
|
|
2851
2883
|
const { serviceName, serviceAddress, servicePort, protocol } = ctx;
|
|
2852
2884
|
const port = protocol === "https" ? 443 : servicePort;
|
|
2853
|
-
const
|
|
2885
|
+
const URL2 = `${protocol}://${serviceAddress}:${port}`;
|
|
2854
2886
|
const fetchId = `${serviceAddress}_${port}`;
|
|
2855
2887
|
const fetchDiagnostics = this.ensureFetchClientDiagnostics(
|
|
2856
2888
|
fetchId,
|
|
2857
2889
|
serviceName,
|
|
2858
|
-
|
|
2890
|
+
URL2
|
|
2859
2891
|
);
|
|
2860
2892
|
fetchDiagnostics.destroyed = false;
|
|
2861
2893
|
fetchDiagnostics.updatedAt = Date.now();
|
|
2862
|
-
if (CadenzaService.get(`Send Handshake to ${
|
|
2863
|
-
console.error("Fetch client already exists",
|
|
2894
|
+
if (CadenzaService.get(`Send Handshake to ${URL2}`)) {
|
|
2895
|
+
console.error("Fetch client already exists", URL2);
|
|
2864
2896
|
return;
|
|
2865
2897
|
}
|
|
2866
2898
|
const handshakeTask = CadenzaService.createMetaTask(
|
|
2867
|
-
`Send Handshake to ${
|
|
2899
|
+
`Send Handshake to ${URL2}`,
|
|
2868
2900
|
async (ctx2, emit) => {
|
|
2869
2901
|
try {
|
|
2870
2902
|
const response = await this.fetchDataWithTimeout(
|
|
2871
|
-
`${
|
|
2903
|
+
`${URL2}/handshake`,
|
|
2872
2904
|
{
|
|
2873
2905
|
headers: {
|
|
2874
2906
|
"Content-Type": "application/json"
|
|
@@ -2883,10 +2915,10 @@ var RestController = class _RestController {
|
|
|
2883
2915
|
fetchDiagnostics.connected = false;
|
|
2884
2916
|
fetchDiagnostics.lastHandshakeError = error;
|
|
2885
2917
|
fetchDiagnostics.updatedAt = Date.now();
|
|
2886
|
-
this.recordFetchClientError(fetchId, serviceName,
|
|
2918
|
+
this.recordFetchClientError(fetchId, serviceName, URL2, error);
|
|
2887
2919
|
CadenzaService.log(
|
|
2888
2920
|
"Fetch handshake failed.",
|
|
2889
|
-
{ error, serviceName, URL },
|
|
2921
|
+
{ error, serviceName, URL: URL2 },
|
|
2890
2922
|
"warning"
|
|
2891
2923
|
);
|
|
2892
2924
|
emit(`meta.fetch.handshake_failed:${fetchId}`, response);
|
|
@@ -2901,7 +2933,7 @@ var RestController = class _RestController {
|
|
|
2901
2933
|
CadenzaService.log("Fetch client connected.", {
|
|
2902
2934
|
response,
|
|
2903
2935
|
serviceName,
|
|
2904
|
-
URL
|
|
2936
|
+
URL: URL2
|
|
2905
2937
|
});
|
|
2906
2938
|
for (const communicationType of ctx2.communicationTypes) {
|
|
2907
2939
|
emit("global.meta.fetch.service_communication_established", {
|
|
@@ -2916,10 +2948,10 @@ var RestController = class _RestController {
|
|
|
2916
2948
|
fetchDiagnostics.connected = false;
|
|
2917
2949
|
fetchDiagnostics.lastHandshakeError = this.getErrorMessage(e);
|
|
2918
2950
|
fetchDiagnostics.updatedAt = Date.now();
|
|
2919
|
-
this.recordFetchClientError(fetchId, serviceName,
|
|
2951
|
+
this.recordFetchClientError(fetchId, serviceName, URL2, e);
|
|
2920
2952
|
CadenzaService.log(
|
|
2921
2953
|
"Error in fetch handshake",
|
|
2922
|
-
{ error: e, serviceName, URL, ctx: ctx2 },
|
|
2954
|
+
{ error: e, serviceName, URL: URL2, ctx: ctx2 },
|
|
2923
2955
|
"error"
|
|
2924
2956
|
);
|
|
2925
2957
|
return { ...ctx2, __error: e, errored: true };
|
|
@@ -2933,7 +2965,7 @@ var RestController = class _RestController {
|
|
|
2933
2965
|
"global.meta.fetch.service_communication_established"
|
|
2934
2966
|
);
|
|
2935
2967
|
const delegateTask = CadenzaService.createMetaTask(
|
|
2936
|
-
`Delegate flow to REST server ${
|
|
2968
|
+
`Delegate flow to REST server ${URL2}`,
|
|
2937
2969
|
async (ctx2, emit) => {
|
|
2938
2970
|
if (ctx2.__remoteRoutineName === void 0) {
|
|
2939
2971
|
return;
|
|
@@ -2943,7 +2975,7 @@ var RestController = class _RestController {
|
|
|
2943
2975
|
let resultContext;
|
|
2944
2976
|
try {
|
|
2945
2977
|
resultContext = await this.fetchDataWithTimeout(
|
|
2946
|
-
`${
|
|
2978
|
+
`${URL2}/delegation`,
|
|
2947
2979
|
{
|
|
2948
2980
|
headers: {
|
|
2949
2981
|
"Content-Type": "application/json"
|
|
@@ -2959,7 +2991,7 @@ var RestController = class _RestController {
|
|
|
2959
2991
|
this.recordFetchClientError(
|
|
2960
2992
|
fetchId,
|
|
2961
2993
|
serviceName,
|
|
2962
|
-
|
|
2994
|
+
URL2,
|
|
2963
2995
|
resultContext?.__error ?? resultContext?.error ?? "Delegation failed"
|
|
2964
2996
|
);
|
|
2965
2997
|
}
|
|
@@ -2967,7 +2999,7 @@ var RestController = class _RestController {
|
|
|
2967
2999
|
console.error("Error in delegation", e);
|
|
2968
3000
|
fetchDiagnostics.delegationFailures++;
|
|
2969
3001
|
fetchDiagnostics.updatedAt = Date.now();
|
|
2970
|
-
this.recordFetchClientError(fetchId, serviceName,
|
|
3002
|
+
this.recordFetchClientError(fetchId, serviceName, URL2, e);
|
|
2971
3003
|
resultContext = {
|
|
2972
3004
|
__error: `Error: ${e}`,
|
|
2973
3005
|
errored: true,
|
|
@@ -2988,7 +3020,7 @@ var RestController = class _RestController {
|
|
|
2988
3020
|
`meta.service_registry.socket_failed:${fetchId}`
|
|
2989
3021
|
).emitsOnFail("meta.fetch.delegate_failed").attachSignal("meta.fetch.delegated");
|
|
2990
3022
|
const transmitTask = CadenzaService.createMetaTask(
|
|
2991
|
-
`Transmit signal to server ${
|
|
3023
|
+
`Transmit signal to server ${URL2}`,
|
|
2992
3024
|
async (ctx2, emit) => {
|
|
2993
3025
|
if (ctx2.__signalName === void 0) {
|
|
2994
3026
|
return;
|
|
@@ -2998,7 +3030,7 @@ var RestController = class _RestController {
|
|
|
2998
3030
|
let response;
|
|
2999
3031
|
try {
|
|
3000
3032
|
response = await this.fetchDataWithTimeout(
|
|
3001
|
-
`${
|
|
3033
|
+
`${URL2}/signal`,
|
|
3002
3034
|
{
|
|
3003
3035
|
headers: {
|
|
3004
3036
|
"Content-Type": "application/json"
|
|
@@ -3017,7 +3049,7 @@ var RestController = class _RestController {
|
|
|
3017
3049
|
this.recordFetchClientError(
|
|
3018
3050
|
fetchId,
|
|
3019
3051
|
serviceName,
|
|
3020
|
-
|
|
3052
|
+
URL2,
|
|
3021
3053
|
response?.__error ?? response?.error ?? "Signal transmission failed"
|
|
3022
3054
|
);
|
|
3023
3055
|
}
|
|
@@ -3025,7 +3057,7 @@ var RestController = class _RestController {
|
|
|
3025
3057
|
console.error("Error in transmission", e);
|
|
3026
3058
|
fetchDiagnostics.signalFailures++;
|
|
3027
3059
|
fetchDiagnostics.updatedAt = Date.now();
|
|
3028
|
-
this.recordFetchClientError(fetchId, serviceName,
|
|
3060
|
+
this.recordFetchClientError(fetchId, serviceName, URL2, e);
|
|
3029
3061
|
response = {
|
|
3030
3062
|
__error: `Error: ${e}`,
|
|
3031
3063
|
errored: true,
|
|
@@ -3041,14 +3073,14 @@ var RestController = class _RestController {
|
|
|
3041
3073
|
"meta.signal_controller.wildcard_signal_registered"
|
|
3042
3074
|
).emitsOnFail("meta.fetch.signal_transmission_failed").attachSignal("meta.fetch.transmitted");
|
|
3043
3075
|
const statusTask = CadenzaService.createMetaTask(
|
|
3044
|
-
`Request status from ${
|
|
3076
|
+
`Request status from ${URL2}`,
|
|
3045
3077
|
async (ctx2) => {
|
|
3046
3078
|
fetchDiagnostics.statusChecks++;
|
|
3047
3079
|
fetchDiagnostics.updatedAt = Date.now();
|
|
3048
3080
|
let status;
|
|
3049
3081
|
try {
|
|
3050
3082
|
status = await this.fetchDataWithTimeout(
|
|
3051
|
-
`${
|
|
3083
|
+
`${URL2}/status`,
|
|
3052
3084
|
{
|
|
3053
3085
|
method: "GET"
|
|
3054
3086
|
},
|
|
@@ -3060,14 +3092,14 @@ var RestController = class _RestController {
|
|
|
3060
3092
|
this.recordFetchClientError(
|
|
3061
3093
|
fetchId,
|
|
3062
3094
|
serviceName,
|
|
3063
|
-
|
|
3095
|
+
URL2,
|
|
3064
3096
|
status?.__error ?? status?.error ?? "Status check failed"
|
|
3065
3097
|
);
|
|
3066
3098
|
}
|
|
3067
3099
|
} catch (e) {
|
|
3068
3100
|
fetchDiagnostics.statusFailures++;
|
|
3069
3101
|
fetchDiagnostics.updatedAt = Date.now();
|
|
3070
|
-
this.recordFetchClientError(fetchId, serviceName,
|
|
3102
|
+
this.recordFetchClientError(fetchId, serviceName, URL2, e);
|
|
3071
3103
|
status = {
|
|
3072
3104
|
__error: `Error: ${e}`,
|
|
3073
3105
|
errored: true,
|
|
@@ -3082,7 +3114,7 @@ var RestController = class _RestController {
|
|
|
3082
3114
|
fetchDiagnostics.connected = false;
|
|
3083
3115
|
fetchDiagnostics.destroyed = true;
|
|
3084
3116
|
fetchDiagnostics.updatedAt = Date.now();
|
|
3085
|
-
CadenzaService.log("Destroying fetch client", { URL, serviceName });
|
|
3117
|
+
CadenzaService.log("Destroying fetch client", { URL: URL2, serviceName });
|
|
3086
3118
|
handshakeTask.destroy();
|
|
3087
3119
|
delegateTask.destroy();
|
|
3088
3120
|
transmitTask.destroy();
|
|
@@ -3484,13 +3516,13 @@ var SocketController = class _SocketController {
|
|
|
3484
3516
|
Object.assign(base, patch);
|
|
3485
3517
|
base.fetchId = fetchId;
|
|
3486
3518
|
base.updatedAt = now;
|
|
3487
|
-
const
|
|
3488
|
-
if (
|
|
3489
|
-
base.lastError =
|
|
3519
|
+
const errorMessage2 = input.error !== void 0 ? this.getErrorMessage(input.error) : void 0;
|
|
3520
|
+
if (errorMessage2) {
|
|
3521
|
+
base.lastError = errorMessage2;
|
|
3490
3522
|
base.lastErrorAt = now;
|
|
3491
3523
|
base.errorHistory.push({
|
|
3492
3524
|
at: new Date(now).toISOString(),
|
|
3493
|
-
message:
|
|
3525
|
+
message: errorMessage2
|
|
3494
3526
|
});
|
|
3495
3527
|
if (base.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
|
|
3496
3528
|
base.errorHistory.splice(
|
|
@@ -3992,12 +4024,12 @@ var SocketController = class _SocketController {
|
|
|
3992
4024
|
if (ack) ack(response);
|
|
3993
4025
|
resolve(response);
|
|
3994
4026
|
};
|
|
3995
|
-
const resolveWithError = (
|
|
4027
|
+
const resolveWithError = (errorMessage2, fallbackError) => {
|
|
3996
4028
|
settle({
|
|
3997
4029
|
...data,
|
|
3998
4030
|
errored: true,
|
|
3999
|
-
__error:
|
|
4000
|
-
error: fallbackError instanceof Error ? fallbackError.message :
|
|
4031
|
+
__error: errorMessage2,
|
|
4032
|
+
error: fallbackError instanceof Error ? fallbackError.message : errorMessage2,
|
|
4001
4033
|
socketId: runtimeHandle.socket.id,
|
|
4002
4034
|
serviceName,
|
|
4003
4035
|
url
|
|
@@ -4302,19 +4334,19 @@ var SocketController = class _SocketController {
|
|
|
4302
4334
|
url
|
|
4303
4335
|
});
|
|
4304
4336
|
} else {
|
|
4305
|
-
const
|
|
4337
|
+
const errorMessage2 = result?.__error ?? result?.error ?? "Socket handshake failed";
|
|
4306
4338
|
upsertDiagnostics(
|
|
4307
4339
|
{
|
|
4308
4340
|
connected: false,
|
|
4309
4341
|
handshake: false,
|
|
4310
|
-
lastHandshakeError:
|
|
4342
|
+
lastHandshakeError: errorMessage2
|
|
4311
4343
|
},
|
|
4312
|
-
|
|
4344
|
+
errorMessage2
|
|
4313
4345
|
);
|
|
4314
4346
|
applySessionOperation("handshake", {
|
|
4315
4347
|
connected: false,
|
|
4316
4348
|
handshake: false,
|
|
4317
|
-
lastHandshakeError:
|
|
4349
|
+
lastHandshakeError: errorMessage2
|
|
4318
4350
|
});
|
|
4319
4351
|
CadenzaService.log(
|
|
4320
4352
|
"Socket handshake failed",
|
|
@@ -4362,15 +4394,15 @@ var SocketController = class _SocketController {
|
|
|
4362
4394
|
});
|
|
4363
4395
|
}
|
|
4364
4396
|
if (resultContext?.errored || resultContext?.failed) {
|
|
4365
|
-
const
|
|
4397
|
+
const errorMessage2 = resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed";
|
|
4366
4398
|
upsertDiagnostics(
|
|
4367
4399
|
{
|
|
4368
|
-
lastHandshakeError: String(
|
|
4400
|
+
lastHandshakeError: String(errorMessage2)
|
|
4369
4401
|
},
|
|
4370
|
-
|
|
4402
|
+
errorMessage2
|
|
4371
4403
|
);
|
|
4372
4404
|
applySessionOperation("delegate", {
|
|
4373
|
-
lastHandshakeError: String(
|
|
4405
|
+
lastHandshakeError: String(errorMessage2)
|
|
4374
4406
|
});
|
|
4375
4407
|
}
|
|
4376
4408
|
return resultContext;
|
|
@@ -4817,6 +4849,7 @@ var SignalController = class _SignalController {
|
|
|
4817
4849
|
};
|
|
4818
4850
|
|
|
4819
4851
|
// src/graph/controllers/GraphMetadataController.ts
|
|
4852
|
+
var import_core3 = require("@cadenza.io/core");
|
|
4820
4853
|
var GraphMetadataController = class _GraphMetadataController {
|
|
4821
4854
|
static get instance() {
|
|
4822
4855
|
if (!this._instance) this._instance = new _GraphMetadataController();
|
|
@@ -5050,6 +5083,123 @@ var GraphMetadataController = class _GraphMetadataController {
|
|
|
5050
5083
|
}
|
|
5051
5084
|
};
|
|
5052
5085
|
}).doOn("meta.actor.task_associated").emits("global.meta.graph_metadata.actor_task_associated");
|
|
5086
|
+
const actorSessionStateInsertTask = CadenzaService.get("dbInsertActorSessionState") ?? CadenzaService.get("Insert actor_session_state in CadenzaDB") ?? CadenzaService.createCadenzaDBInsertTask(
|
|
5087
|
+
"actor_session_state",
|
|
5088
|
+
{},
|
|
5089
|
+
{ concurrency: 100, isSubMeta: true }
|
|
5090
|
+
);
|
|
5091
|
+
const validateActorSessionStatePersistenceTask = CadenzaService.createMetaTask(
|
|
5092
|
+
"Validate actor session state persistence",
|
|
5093
|
+
(ctx) => {
|
|
5094
|
+
if (ctx.errored || ctx.failed || ctx.__success !== true) {
|
|
5095
|
+
throw new Error(
|
|
5096
|
+
String(
|
|
5097
|
+
ctx.__error ?? ctx.error ?? "actor_session_state persistence query failed"
|
|
5098
|
+
)
|
|
5099
|
+
);
|
|
5100
|
+
}
|
|
5101
|
+
const rowCount = Number(ctx.rowCount ?? 0);
|
|
5102
|
+
if (!Number.isFinite(rowCount) || rowCount <= 0) {
|
|
5103
|
+
throw new Error(
|
|
5104
|
+
"actor_session_state persistence did not affect any rows (possible stale durable_version)"
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
return {
|
|
5108
|
+
__success: true,
|
|
5109
|
+
persisted: true,
|
|
5110
|
+
actor_name: ctx.actor_name,
|
|
5111
|
+
actor_version: ctx.actor_version,
|
|
5112
|
+
actor_key: ctx.actor_key,
|
|
5113
|
+
service_name: ctx.service_name,
|
|
5114
|
+
durable_version: ctx.durable_version
|
|
5115
|
+
};
|
|
5116
|
+
},
|
|
5117
|
+
"Enforces strict actor session persistence success contract.",
|
|
5118
|
+
{ isSubMeta: true, concurrency: 100 }
|
|
5119
|
+
);
|
|
5120
|
+
const insertAndValidateActorSessionStateTask = actorSessionStateInsertTask.then(
|
|
5121
|
+
validateActorSessionStatePersistenceTask
|
|
5122
|
+
);
|
|
5123
|
+
CadenzaService.createMetaTask(
|
|
5124
|
+
"Persist actor session state",
|
|
5125
|
+
(ctx) => {
|
|
5126
|
+
const actorName = typeof ctx.actor_name === "string" ? ctx.actor_name.trim() : "";
|
|
5127
|
+
const actorKey = typeof ctx.actor_key === "string" ? ctx.actor_key.trim() : "";
|
|
5128
|
+
const actorVersion = Number(ctx.actor_version ?? 1);
|
|
5129
|
+
const durableVersion = Number(ctx.durable_version);
|
|
5130
|
+
if (!actorName) {
|
|
5131
|
+
throw new Error("actor_name is required for actor session persistence");
|
|
5132
|
+
}
|
|
5133
|
+
if (!actorKey) {
|
|
5134
|
+
throw new Error("actor_key is required for actor session persistence");
|
|
5135
|
+
}
|
|
5136
|
+
if (!Number.isInteger(actorVersion) || actorVersion < 1) {
|
|
5137
|
+
throw new Error("actor_version must be a positive integer");
|
|
5138
|
+
}
|
|
5139
|
+
if (!Number.isInteger(durableVersion) || durableVersion < 0) {
|
|
5140
|
+
throw new Error("durable_version must be a non-negative integer");
|
|
5141
|
+
}
|
|
5142
|
+
if (typeof ctx.durable_state !== "object" || ctx.durable_state === null || Array.isArray(ctx.durable_state)) {
|
|
5143
|
+
throw new Error("durable_state must be a non-null object");
|
|
5144
|
+
}
|
|
5145
|
+
const serviceName = CadenzaService.serviceRegistry.serviceName;
|
|
5146
|
+
if (!serviceName) {
|
|
5147
|
+
throw new Error("service_name is not available for actor session persistence");
|
|
5148
|
+
}
|
|
5149
|
+
let expiresAt = null;
|
|
5150
|
+
if (ctx.expires_at !== void 0 && ctx.expires_at !== null) {
|
|
5151
|
+
if (ctx.expires_at instanceof Date) {
|
|
5152
|
+
expiresAt = ctx.expires_at.toISOString();
|
|
5153
|
+
} else if (typeof ctx.expires_at === "string" && ctx.expires_at.trim().length > 0) {
|
|
5154
|
+
expiresAt = ctx.expires_at;
|
|
5155
|
+
} else {
|
|
5156
|
+
throw new Error("expires_at must be null, Date, or non-empty string");
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5160
|
+
return {
|
|
5161
|
+
...ctx,
|
|
5162
|
+
actor_name: actorName,
|
|
5163
|
+
actor_key: actorKey,
|
|
5164
|
+
actor_version: actorVersion,
|
|
5165
|
+
durable_version: durableVersion,
|
|
5166
|
+
expires_at: expiresAt,
|
|
5167
|
+
service_name: serviceName,
|
|
5168
|
+
queryData: {
|
|
5169
|
+
data: {
|
|
5170
|
+
actor_name: actorName,
|
|
5171
|
+
actor_version: actorVersion,
|
|
5172
|
+
actor_key: actorKey,
|
|
5173
|
+
service_name: serviceName,
|
|
5174
|
+
durable_state: ctx.durable_state,
|
|
5175
|
+
durable_version: durableVersion,
|
|
5176
|
+
expires_at: expiresAt,
|
|
5177
|
+
updated: updatedAt
|
|
5178
|
+
},
|
|
5179
|
+
onConflict: {
|
|
5180
|
+
target: [
|
|
5181
|
+
"actor_name",
|
|
5182
|
+
"actor_version",
|
|
5183
|
+
"actor_key",
|
|
5184
|
+
"service_name"
|
|
5185
|
+
],
|
|
5186
|
+
action: {
|
|
5187
|
+
do: "update",
|
|
5188
|
+
set: {
|
|
5189
|
+
durable_state: "excluded",
|
|
5190
|
+
durable_version: "excluded",
|
|
5191
|
+
expires_at: "excluded",
|
|
5192
|
+
updated: "excluded"
|
|
5193
|
+
},
|
|
5194
|
+
where: "actor_session_state.durable_version <= excluded.durable_version"
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
};
|
|
5199
|
+
},
|
|
5200
|
+
"Validates and prepares actor_session_state payload for strict write-through persistence.",
|
|
5201
|
+
{ isSubMeta: true, concurrency: 100 }
|
|
5202
|
+
).then(insertAndValidateActorSessionStateTask).respondsTo(import_core3.META_ACTOR_SESSION_STATE_PERSIST_INTENT);
|
|
5053
5203
|
CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
|
|
5054
5204
|
const intentName = ctx.data?.name;
|
|
5055
5205
|
return {
|
|
@@ -5087,565 +5237,174 @@ var SCHEMA_TYPES = [
|
|
|
5087
5237
|
// src/database/DatabaseController.ts
|
|
5088
5238
|
var import_pg = require("pg");
|
|
5089
5239
|
var import_lodash_es = require("lodash-es");
|
|
5090
|
-
function
|
|
5091
|
-
const
|
|
5092
|
-
|
|
5093
|
-
|
|
5240
|
+
function normalizeIntentToken(value) {
|
|
5241
|
+
const normalized = (0, import_lodash_es.kebabCase)(String(value ?? "").trim());
|
|
5242
|
+
if (!normalized) {
|
|
5243
|
+
throw new Error("Actor token cannot be empty");
|
|
5244
|
+
}
|
|
5245
|
+
return normalized;
|
|
5246
|
+
}
|
|
5247
|
+
function validateIntentName(intentName) {
|
|
5248
|
+
if (!intentName || typeof intentName !== "string") {
|
|
5249
|
+
throw new Error("Intent name must be a non-empty string");
|
|
5250
|
+
}
|
|
5251
|
+
if (intentName.length > 100) {
|
|
5252
|
+
throw new Error(`Intent name must be <= 100 characters: ${intentName}`);
|
|
5253
|
+
}
|
|
5254
|
+
if (intentName.includes(" ") || intentName.includes(".") || intentName.includes("\\")) {
|
|
5255
|
+
throw new Error(
|
|
5256
|
+
`Intent name cannot contain spaces, dots or backslashes: ${intentName}`
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
function defaultOperationIntentDescription(operation, tableName) {
|
|
5261
|
+
return `Perform a ${operation} operation on the ${tableName} table`;
|
|
5262
|
+
}
|
|
5263
|
+
function readCustomIntentConfig(customIntent) {
|
|
5264
|
+
if (typeof customIntent === "string") {
|
|
5265
|
+
return {
|
|
5266
|
+
intent: customIntent
|
|
5267
|
+
};
|
|
5268
|
+
}
|
|
5269
|
+
return {
|
|
5270
|
+
intent: customIntent.intent,
|
|
5271
|
+
description: customIntent.description,
|
|
5272
|
+
input: customIntent.input
|
|
5273
|
+
};
|
|
5274
|
+
}
|
|
5275
|
+
function resolveTableOperationIntents(actorName, tableName, table, operation, defaultInputSchema) {
|
|
5276
|
+
const actorToken = normalizeIntentToken(actorName);
|
|
5277
|
+
const defaultIntentName = `${operation}-pg-${actorToken}-${tableName}`;
|
|
5278
|
+
validateIntentName(defaultIntentName);
|
|
5094
5279
|
const intents = [
|
|
5095
5280
|
{
|
|
5096
5281
|
name: defaultIntentName,
|
|
5097
|
-
description:
|
|
5282
|
+
description: defaultOperationIntentDescription(operation, tableName),
|
|
5098
5283
|
input: defaultInputSchema
|
|
5099
5284
|
}
|
|
5100
5285
|
];
|
|
5101
|
-
const
|
|
5102
|
-
const
|
|
5103
|
-
for (const
|
|
5104
|
-
const
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
continue;
|
|
5110
|
-
}
|
|
5111
|
-
if (name.length > 100) {
|
|
5112
|
-
warnings.push(
|
|
5113
|
-
`Skipped custom query intent '${name}' for table '${tableName}': name must be <= 100 characters.`
|
|
5114
|
-
);
|
|
5115
|
-
continue;
|
|
5116
|
-
}
|
|
5117
|
-
if (name.includes(" ") || name.includes(".") || name.includes("\\")) {
|
|
5118
|
-
warnings.push(
|
|
5119
|
-
`Skipped custom query intent '${name}' for table '${tableName}': name cannot contain spaces, dots or backslashes.`
|
|
5286
|
+
const seenNames = /* @__PURE__ */ new Set([defaultIntentName]);
|
|
5287
|
+
const customIntentList = table.customIntents?.[operation] ?? [];
|
|
5288
|
+
for (const rawCustomIntent of customIntentList) {
|
|
5289
|
+
const customIntent = readCustomIntentConfig(rawCustomIntent);
|
|
5290
|
+
const intentName = String(customIntent.intent ?? "").trim();
|
|
5291
|
+
if (!intentName) {
|
|
5292
|
+
throw new Error(
|
|
5293
|
+
`Invalid custom ${operation} intent on table '${tableName}': intent must be a non-empty string`
|
|
5120
5294
|
);
|
|
5121
|
-
continue;
|
|
5122
5295
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5296
|
+
validateIntentName(intentName);
|
|
5297
|
+
if (seenNames.has(intentName)) {
|
|
5298
|
+
throw new Error(
|
|
5299
|
+
`Duplicate ${operation} intent '${intentName}' on table '${tableName}'`
|
|
5126
5300
|
);
|
|
5127
|
-
continue;
|
|
5128
5301
|
}
|
|
5129
|
-
|
|
5302
|
+
seenNames.add(intentName);
|
|
5130
5303
|
intents.push({
|
|
5131
|
-
name,
|
|
5132
|
-
description:
|
|
5133
|
-
input:
|
|
5304
|
+
name: intentName,
|
|
5305
|
+
description: customIntent.description ?? defaultOperationIntentDescription(operation, tableName),
|
|
5306
|
+
input: customIntent.input ?? defaultInputSchema
|
|
5134
5307
|
});
|
|
5135
5308
|
}
|
|
5136
|
-
return { intents
|
|
5309
|
+
return { intents };
|
|
5310
|
+
}
|
|
5311
|
+
function ensurePlainObject(value, label) {
|
|
5312
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5313
|
+
throw new Error(`${label} must be an object`);
|
|
5314
|
+
}
|
|
5315
|
+
return value;
|
|
5316
|
+
}
|
|
5317
|
+
function sleep(timeoutMs) {
|
|
5318
|
+
return new Promise((resolve) => {
|
|
5319
|
+
setTimeout(resolve, timeoutMs);
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
function normalizePositiveInteger(value, fallback, min = 1) {
|
|
5323
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5324
|
+
return fallback;
|
|
5325
|
+
}
|
|
5326
|
+
const normalized = Math.trunc(value);
|
|
5327
|
+
if (normalized < min) {
|
|
5328
|
+
return fallback;
|
|
5329
|
+
}
|
|
5330
|
+
return normalized;
|
|
5331
|
+
}
|
|
5332
|
+
function errorMessage(error) {
|
|
5333
|
+
if (error instanceof Error) {
|
|
5334
|
+
return error.message;
|
|
5335
|
+
}
|
|
5336
|
+
return String(error);
|
|
5337
|
+
}
|
|
5338
|
+
function isTransientDatabaseError(error) {
|
|
5339
|
+
if (!error || typeof error !== "object") {
|
|
5340
|
+
return false;
|
|
5341
|
+
}
|
|
5342
|
+
const dbError = error;
|
|
5343
|
+
const code = String(dbError.code ?? "");
|
|
5344
|
+
if (["40001", "40P01", "57P03", "53300", "08006", "08001"].includes(code)) {
|
|
5345
|
+
return true;
|
|
5346
|
+
}
|
|
5347
|
+
const message = String(dbError.message ?? "").toLowerCase();
|
|
5348
|
+
return message.includes("timeout") || message.includes("terminating connection") || message.includes("connection reset");
|
|
5349
|
+
}
|
|
5350
|
+
function isSqlIdentifier(value) {
|
|
5351
|
+
return /^[a-z_][a-z0-9_]*$/.test(value);
|
|
5352
|
+
}
|
|
5353
|
+
function toSafeSqlIdentifier(value, fallback) {
|
|
5354
|
+
const normalized = (0, import_lodash_es.snakeCase)(value || "").replace(/[^a-z0-9_]/g, "_");
|
|
5355
|
+
const candidate = normalized || fallback;
|
|
5356
|
+
if (!isSqlIdentifier(candidate)) {
|
|
5357
|
+
throw new Error(`Invalid SQL identifier: ${value}`);
|
|
5358
|
+
}
|
|
5359
|
+
return candidate;
|
|
5360
|
+
}
|
|
5361
|
+
function isSupportedAggregateFunction(value) {
|
|
5362
|
+
const fn = String(value ?? "").toLowerCase();
|
|
5363
|
+
return ["count", "sum", "avg", "min", "max"].includes(fn);
|
|
5364
|
+
}
|
|
5365
|
+
function buildAggregateAlias(aggregate, index) {
|
|
5366
|
+
const fn = String(aggregate.fn ?? "").toLowerCase();
|
|
5367
|
+
const hasField = typeof aggregate.field === "string" && aggregate.field.trim().length > 0;
|
|
5368
|
+
return toSafeSqlIdentifier(
|
|
5369
|
+
String(aggregate.as ?? `${fn}_${hasField ? aggregate.field : "all"}_${index}`),
|
|
5370
|
+
`${fn || "aggregate"}_${index}`
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
function resolveDataRows(data) {
|
|
5374
|
+
if (!data) {
|
|
5375
|
+
return [];
|
|
5376
|
+
}
|
|
5377
|
+
if (Array.isArray(data)) {
|
|
5378
|
+
return data.map((entry) => ensurePlainObject(entry, "data item"));
|
|
5379
|
+
}
|
|
5380
|
+
return [ensurePlainObject(data, "data")];
|
|
5137
5381
|
}
|
|
5138
5382
|
var DatabaseController = class _DatabaseController {
|
|
5139
|
-
/**
|
|
5140
|
-
* Constructor for initializing the `DatabaseService` class.
|
|
5141
|
-
*
|
|
5142
|
-
* This constructor method initializes a sequence of meta tasks to perform the following database-related operations:
|
|
5143
|
-
*
|
|
5144
|
-
* 1. **Database Creation**: Creates a new database with the specified name if it doesn't already exist.
|
|
5145
|
-
* Validates the database name to ensure it conforms to the required format.
|
|
5146
|
-
* 2. **Database Schema Validation**: Validates the structure and constraints of the schema definition provided.
|
|
5147
|
-
* 3. **Table Dependency Management**: Sorts tables within the schema by their dependencies to ensure proper creation order.
|
|
5148
|
-
* 4. **Schema Definition Processing**:
|
|
5149
|
-
* - Converts schema definitions into Data Definition Language (DDL) based on table and field specifications.
|
|
5150
|
-
* - Handles constraints, relationships, and field attributes such as uniqueness, primary keys, nullable fields, etc.
|
|
5151
|
-
* 5. **Index and Primary Key Definition**: Generates SQL for indices and primary keys based on the schema configuration.
|
|
5152
|
-
*
|
|
5153
|
-
* These tasks are encapsulated within a meta routine to provide a structured and procedural approach to database initialization and schema management.
|
|
5154
|
-
*/
|
|
5155
5383
|
constructor() {
|
|
5156
|
-
this.
|
|
5157
|
-
this.
|
|
5384
|
+
this.registrationsByService = /* @__PURE__ */ new Map();
|
|
5385
|
+
this.adminDbClient = new import_pg.Pool({
|
|
5158
5386
|
connectionString: process.env.DATABASE_ADDRESS ?? "",
|
|
5159
5387
|
database: "postgres",
|
|
5160
5388
|
ssl: {
|
|
5161
5389
|
rejectUnauthorized: false
|
|
5162
|
-
// ← This bypasses the chain validation error
|
|
5163
5390
|
}
|
|
5164
5391
|
});
|
|
5165
|
-
CadenzaService.
|
|
5166
|
-
"
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
console.log(`Creating database ${databaseName}`, {
|
|
5182
|
-
connectionString: process.env.DATABASE_ADDRESS ?? "",
|
|
5183
|
-
database: "postgres"
|
|
5184
|
-
});
|
|
5185
|
-
await this.dbClient.query(`CREATE DATABASE ${databaseName}`);
|
|
5186
|
-
console.log(`Database ${databaseName} created`);
|
|
5187
|
-
this.dbClient = new import_pg.Pool({
|
|
5188
|
-
connectionString: process.env.DATABASE_ADDRESS ? process.env.DATABASE_ADDRESS.slice(
|
|
5189
|
-
0,
|
|
5190
|
-
process.env.DATABASE_ADDRESS.lastIndexOf("/")
|
|
5191
|
-
) + "/" + databaseName + "?sslmode=disable" : "",
|
|
5192
|
-
ssl: {
|
|
5193
|
-
rejectUnauthorized: false
|
|
5194
|
-
// ← This bypasses the chain validation error
|
|
5195
|
-
}
|
|
5196
|
-
});
|
|
5197
|
-
this.databaseName = databaseName;
|
|
5198
|
-
return true;
|
|
5199
|
-
} catch (error) {
|
|
5200
|
-
if (error.code === "42P04") {
|
|
5201
|
-
console.log("Database already exists");
|
|
5202
|
-
return true;
|
|
5203
|
-
}
|
|
5204
|
-
console.error("Failed to create database", error);
|
|
5205
|
-
throw new Error(`Failed to create database: ${error.message}`);
|
|
5206
|
-
}
|
|
5207
|
-
},
|
|
5208
|
-
"Creates the target database if it doesn't exist"
|
|
5209
|
-
).then(
|
|
5210
|
-
CadenzaService.createMetaTask(
|
|
5211
|
-
"Validate schema",
|
|
5212
|
-
(ctx) => {
|
|
5213
|
-
const { schema } = ctx;
|
|
5214
|
-
if (!schema?.tables || typeof schema.tables !== "object") {
|
|
5215
|
-
throw new Error("Invalid schema: missing or invalid tables");
|
|
5216
|
-
}
|
|
5217
|
-
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
5218
|
-
if (!table.fields || typeof table.fields !== "object") {
|
|
5219
|
-
console.log(tableName, "missing fields");
|
|
5220
|
-
throw new Error(`Invalid table ${tableName}: missing fields`);
|
|
5221
|
-
}
|
|
5222
|
-
for (const [fieldName, field] of Object.entries(table.fields)) {
|
|
5223
|
-
if (!fieldName.split("").every((c) => /[a-z_]/.test(c))) {
|
|
5224
|
-
console.log(tableName, "field not lowercase", fieldName);
|
|
5225
|
-
throw new Error(
|
|
5226
|
-
`Invalid field name ${fieldName} for ${tableName}. Field names must only contain lowercase alphanumeric characters and underscores`
|
|
5227
|
-
);
|
|
5228
|
-
}
|
|
5229
|
-
if (!Object.values(SCHEMA_TYPES).includes(field.type)) {
|
|
5230
|
-
console.log(
|
|
5231
|
-
tableName,
|
|
5232
|
-
"field invalid type",
|
|
5233
|
-
fieldName,
|
|
5234
|
-
field.type
|
|
5235
|
-
);
|
|
5236
|
-
throw new Error(
|
|
5237
|
-
`Invalid type ${field.type} for ${tableName}.${fieldName}`
|
|
5238
|
-
);
|
|
5239
|
-
}
|
|
5240
|
-
if (field.references && !field.references.match(/^[\w]+[(\w)]+$/)) {
|
|
5241
|
-
console.log(
|
|
5242
|
-
tableName,
|
|
5243
|
-
"invalid reference",
|
|
5244
|
-
fieldName,
|
|
5245
|
-
field.references
|
|
5246
|
-
);
|
|
5247
|
-
throw new Error(
|
|
5248
|
-
`Invalid reference ${field.references} for ${tableName}.${fieldName}`
|
|
5249
|
-
);
|
|
5250
|
-
}
|
|
5251
|
-
if (table.customSignals) {
|
|
5252
|
-
for (const op of ["query", "insert", "update", "delete"]) {
|
|
5253
|
-
const triggers = table.customSignals.triggers?.[op];
|
|
5254
|
-
const emissions = table.customSignals.emissions?.[op];
|
|
5255
|
-
if (triggers && !Array.isArray(triggers) && typeof triggers !== "object") {
|
|
5256
|
-
console.log(
|
|
5257
|
-
tableName,
|
|
5258
|
-
"invalid triggers",
|
|
5259
|
-
op,
|
|
5260
|
-
triggers
|
|
5261
|
-
);
|
|
5262
|
-
throw new Error(
|
|
5263
|
-
`Invalid triggers for ${tableName}.${op}`
|
|
5264
|
-
);
|
|
5265
|
-
}
|
|
5266
|
-
if (emissions && !Array.isArray(emissions) && typeof emissions !== "object") {
|
|
5267
|
-
console.log(
|
|
5268
|
-
tableName,
|
|
5269
|
-
"invalid emissions",
|
|
5270
|
-
op,
|
|
5271
|
-
emissions
|
|
5272
|
-
);
|
|
5273
|
-
throw new Error(
|
|
5274
|
-
`Invalid emissions for ${tableName}.${op}`
|
|
5275
|
-
);
|
|
5276
|
-
}
|
|
5277
|
-
}
|
|
5278
|
-
}
|
|
5279
|
-
if (table.customIntents?.query) {
|
|
5280
|
-
if (!Array.isArray(table.customIntents.query)) {
|
|
5281
|
-
throw new Error(
|
|
5282
|
-
`Invalid customIntents.query for ${tableName}: expected array`
|
|
5283
|
-
);
|
|
5284
|
-
}
|
|
5285
|
-
for (const customIntent of table.customIntents.query) {
|
|
5286
|
-
if (typeof customIntent !== "string" && (typeof customIntent !== "object" || !customIntent || typeof customIntent.intent !== "string")) {
|
|
5287
|
-
throw new Error(
|
|
5288
|
-
`Invalid custom query intent on ${tableName}: expected string or object with intent`
|
|
5289
|
-
);
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
}
|
|
5293
|
-
}
|
|
5294
|
-
}
|
|
5295
|
-
console.log("SCHEMA VALIDATED");
|
|
5296
|
-
return true;
|
|
5297
|
-
},
|
|
5298
|
-
"Validates database schema structure and content"
|
|
5299
|
-
).then(
|
|
5300
|
-
CadenzaService.createMetaTask(
|
|
5301
|
-
"Sort tables by dependencies",
|
|
5302
|
-
this.sortTablesByReferences.bind(this),
|
|
5303
|
-
"Sorts tables by dependencies"
|
|
5304
|
-
).then(
|
|
5305
|
-
CadenzaService.createMetaTask(
|
|
5306
|
-
"Split schema into tables",
|
|
5307
|
-
this.splitTables.bind(this),
|
|
5308
|
-
"Generates DDL for database schema"
|
|
5309
|
-
).then(
|
|
5310
|
-
CadenzaService.createMetaTask(
|
|
5311
|
-
"Generate DDL from table",
|
|
5312
|
-
async (ctx) => {
|
|
5313
|
-
const {
|
|
5314
|
-
ddl,
|
|
5315
|
-
table,
|
|
5316
|
-
tableName,
|
|
5317
|
-
schema,
|
|
5318
|
-
options,
|
|
5319
|
-
sortedTables
|
|
5320
|
-
} = ctx;
|
|
5321
|
-
const fieldDefs = Object.entries(table.fields).map((value) => {
|
|
5322
|
-
const [fieldName, field] = value;
|
|
5323
|
-
let def = `${fieldName} ${field.type.toUpperCase()}`;
|
|
5324
|
-
if (field.type === "varchar")
|
|
5325
|
-
def += `(${field.constraints?.maxLength ?? 255})`;
|
|
5326
|
-
if (field.type === "decimal")
|
|
5327
|
-
def += `(${field.constraints?.precision ?? 10},${field.constraints?.scale ?? 2})`;
|
|
5328
|
-
if (field.primary) def += " PRIMARY KEY";
|
|
5329
|
-
if (field.unique) def += " UNIQUE";
|
|
5330
|
-
if (field.default !== void 0)
|
|
5331
|
-
def += ` DEFAULT ${field.default === "" ? "''" : String(field.default)}`;
|
|
5332
|
-
if (field.required && !field.nullable)
|
|
5333
|
-
def += " NOT NULL";
|
|
5334
|
-
if (field.nullable) def += " NULL";
|
|
5335
|
-
if (field.generated)
|
|
5336
|
-
def += ` GENERATED ALWAYS AS ${field.generated.toUpperCase()} STORED`;
|
|
5337
|
-
if (field.references)
|
|
5338
|
-
def += ` REFERENCES ${field.references} ON DELETE ${field.onDelete || "CASCADE"}`;
|
|
5339
|
-
if (field.encrypted) def += " ENCRYPTED";
|
|
5340
|
-
if (field.constraints?.check) {
|
|
5341
|
-
def += ` CHECK (${field.constraints.check})`;
|
|
5342
|
-
}
|
|
5343
|
-
return def;
|
|
5344
|
-
}).join(", ");
|
|
5345
|
-
if (schema.meta?.dropExisting) {
|
|
5346
|
-
const result = await this.dbClient.query(
|
|
5347
|
-
`DELETE FROM ${tableName};`
|
|
5348
|
-
);
|
|
5349
|
-
console.log("DROP TABLE", tableName, result);
|
|
5350
|
-
}
|
|
5351
|
-
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${fieldDefs})`;
|
|
5352
|
-
ddl.push(sql);
|
|
5353
|
-
return {
|
|
5354
|
-
ddl,
|
|
5355
|
-
table,
|
|
5356
|
-
tableName,
|
|
5357
|
-
schema,
|
|
5358
|
-
options,
|
|
5359
|
-
sortedTables
|
|
5360
|
-
};
|
|
5361
|
-
}
|
|
5362
|
-
).then(
|
|
5363
|
-
CadenzaService.createMetaTask("Generate index DDL", (ctx) => {
|
|
5364
|
-
const {
|
|
5365
|
-
ddl,
|
|
5366
|
-
table,
|
|
5367
|
-
tableName,
|
|
5368
|
-
schema,
|
|
5369
|
-
options,
|
|
5370
|
-
sortedTables
|
|
5371
|
-
} = ctx;
|
|
5372
|
-
if (table.indexes) {
|
|
5373
|
-
table.indexes.forEach((fields) => {
|
|
5374
|
-
ddl.push(
|
|
5375
|
-
`CREATE INDEX IF NOT EXISTS idx_${tableName}_${fields.join("_")} ON ${tableName} (${fields.join(", ")});`
|
|
5376
|
-
);
|
|
5377
|
-
});
|
|
5378
|
-
}
|
|
5379
|
-
return {
|
|
5380
|
-
ddl,
|
|
5381
|
-
table,
|
|
5382
|
-
tableName,
|
|
5383
|
-
schema,
|
|
5384
|
-
options,
|
|
5385
|
-
sortedTables
|
|
5386
|
-
};
|
|
5387
|
-
}).then(
|
|
5388
|
-
CadenzaService.createMetaTask(
|
|
5389
|
-
"Generate primary key ddl",
|
|
5390
|
-
(ctx) => {
|
|
5391
|
-
const {
|
|
5392
|
-
ddl,
|
|
5393
|
-
table,
|
|
5394
|
-
tableName,
|
|
5395
|
-
schema,
|
|
5396
|
-
options,
|
|
5397
|
-
sortedTables
|
|
5398
|
-
} = ctx;
|
|
5399
|
-
if (table.primaryKey) {
|
|
5400
|
-
ddl.push(
|
|
5401
|
-
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS unique_${tableName}_${table.primaryKey.join("_")};`,
|
|
5402
|
-
// TODO: should be cascade?
|
|
5403
|
-
`ALTER TABLE ${tableName} ADD CONSTRAINT unique_${tableName}_${table.primaryKey.join("_")} PRIMARY KEY (${table.primaryKey.join(", ")});`
|
|
5404
|
-
);
|
|
5405
|
-
}
|
|
5406
|
-
return {
|
|
5407
|
-
ddl,
|
|
5408
|
-
table,
|
|
5409
|
-
tableName,
|
|
5410
|
-
schema,
|
|
5411
|
-
options,
|
|
5412
|
-
sortedTables
|
|
5413
|
-
};
|
|
5414
|
-
}
|
|
5415
|
-
).then(
|
|
5416
|
-
CadenzaService.createMetaTask(
|
|
5417
|
-
"Generate unique index DDL",
|
|
5418
|
-
(ctx) => {
|
|
5419
|
-
const {
|
|
5420
|
-
ddl,
|
|
5421
|
-
table,
|
|
5422
|
-
tableName,
|
|
5423
|
-
schema,
|
|
5424
|
-
options,
|
|
5425
|
-
sortedTables
|
|
5426
|
-
} = ctx;
|
|
5427
|
-
if (table.uniqueConstraints) {
|
|
5428
|
-
table.uniqueConstraints.forEach(
|
|
5429
|
-
(fields) => {
|
|
5430
|
-
ddl.push(
|
|
5431
|
-
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS unique_${tableName}_${fields.join("_")};`,
|
|
5432
|
-
// TODO: should be cascade?
|
|
5433
|
-
`ALTER TABLE ${tableName} ADD CONSTRAINT unique_${tableName}_${fields.join("_")} UNIQUE (${fields.join(", ")});`
|
|
5434
|
-
);
|
|
5435
|
-
}
|
|
5436
|
-
);
|
|
5437
|
-
}
|
|
5438
|
-
return {
|
|
5439
|
-
ddl,
|
|
5440
|
-
table,
|
|
5441
|
-
tableName,
|
|
5442
|
-
schema,
|
|
5443
|
-
options,
|
|
5444
|
-
sortedTables
|
|
5445
|
-
};
|
|
5446
|
-
}
|
|
5447
|
-
).then(
|
|
5448
|
-
CadenzaService.createMetaTask(
|
|
5449
|
-
"Generate foreign key DDL",
|
|
5450
|
-
(ctx) => {
|
|
5451
|
-
const {
|
|
5452
|
-
ddl,
|
|
5453
|
-
table,
|
|
5454
|
-
tableName,
|
|
5455
|
-
schema,
|
|
5456
|
-
options,
|
|
5457
|
-
sortedTables
|
|
5458
|
-
} = ctx;
|
|
5459
|
-
if (table.foreignKeys) {
|
|
5460
|
-
for (const foreignKey of table.foreignKeys) {
|
|
5461
|
-
const foreignKeyName = `fk_${tableName}_${foreignKey.fields.join("_")}`;
|
|
5462
|
-
ddl.push(
|
|
5463
|
-
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${foreignKeyName};`,
|
|
5464
|
-
// TODO: should be cascade?
|
|
5465
|
-
`ALTER TABLE ${tableName} ADD CONSTRAINT ${foreignKeyName} FOREIGN KEY (${foreignKey.fields.join(
|
|
5466
|
-
", "
|
|
5467
|
-
)}) REFERENCES ${foreignKey.tableName} (${foreignKey.referenceFields.join(
|
|
5468
|
-
", "
|
|
5469
|
-
)});`
|
|
5470
|
-
);
|
|
5471
|
-
}
|
|
5472
|
-
}
|
|
5473
|
-
return {
|
|
5474
|
-
ddl,
|
|
5475
|
-
table,
|
|
5476
|
-
tableName,
|
|
5477
|
-
schema,
|
|
5478
|
-
options,
|
|
5479
|
-
sortedTables
|
|
5480
|
-
};
|
|
5481
|
-
}
|
|
5482
|
-
).then(
|
|
5483
|
-
CadenzaService.createMetaTask(
|
|
5484
|
-
"Generate trigger DDL",
|
|
5485
|
-
(ctx) => {
|
|
5486
|
-
const {
|
|
5487
|
-
ddl,
|
|
5488
|
-
table,
|
|
5489
|
-
tableName,
|
|
5490
|
-
schema,
|
|
5491
|
-
options,
|
|
5492
|
-
sortedTables
|
|
5493
|
-
} = ctx;
|
|
5494
|
-
if (table.triggers) {
|
|
5495
|
-
for (const [
|
|
5496
|
-
triggerName,
|
|
5497
|
-
trigger
|
|
5498
|
-
] of Object.entries(table.triggers)) {
|
|
5499
|
-
ddl.push(
|
|
5500
|
-
`CREATE TRIGGER ${triggerName} ${trigger.when} ${trigger.event} ON ${tableName} FOR EACH STATEMENT EXECUTE FUNCTION ${trigger.function};`
|
|
5501
|
-
);
|
|
5502
|
-
}
|
|
5503
|
-
}
|
|
5504
|
-
return {
|
|
5505
|
-
ddl,
|
|
5506
|
-
table,
|
|
5507
|
-
tableName,
|
|
5508
|
-
schema,
|
|
5509
|
-
options,
|
|
5510
|
-
sortedTables
|
|
5511
|
-
};
|
|
5512
|
-
}
|
|
5513
|
-
).then(
|
|
5514
|
-
CadenzaService.createMetaTask(
|
|
5515
|
-
"Generate initial data DDL",
|
|
5516
|
-
(ctx) => {
|
|
5517
|
-
const {
|
|
5518
|
-
ddl,
|
|
5519
|
-
table,
|
|
5520
|
-
tableName,
|
|
5521
|
-
schema,
|
|
5522
|
-
options,
|
|
5523
|
-
sortedTables
|
|
5524
|
-
} = ctx;
|
|
5525
|
-
if (table.initialData) {
|
|
5526
|
-
ddl.push(
|
|
5527
|
-
`INSERT INTO ${tableName} (${table.initialData.fields.join(", ")}) VALUES ${table.initialData.data.map(
|
|
5528
|
-
(row) => `(${row.map(
|
|
5529
|
-
(value) => value === void 0 ? "NULL" : value.charAt(0) === "'" ? value : `'${value}'`
|
|
5530
|
-
).join(", ")})`
|
|
5531
|
-
// TODO: handle non string data
|
|
5532
|
-
).join(", ")} ON CONFLICT DO NOTHING;`
|
|
5533
|
-
);
|
|
5534
|
-
}
|
|
5535
|
-
return {
|
|
5536
|
-
ddl,
|
|
5537
|
-
table,
|
|
5538
|
-
tableName,
|
|
5539
|
-
schema,
|
|
5540
|
-
options,
|
|
5541
|
-
sortedTables
|
|
5542
|
-
};
|
|
5543
|
-
}
|
|
5544
|
-
).then(
|
|
5545
|
-
CadenzaService.createUniqueMetaTask(
|
|
5546
|
-
"Join DDL",
|
|
5547
|
-
(ctx) => {
|
|
5548
|
-
const { joinedContexts } = ctx;
|
|
5549
|
-
const ddl = [];
|
|
5550
|
-
for (const joinedContext of joinedContexts) {
|
|
5551
|
-
ddl.push(...joinedContext.ddl);
|
|
5552
|
-
}
|
|
5553
|
-
ddl.flat();
|
|
5554
|
-
return {
|
|
5555
|
-
ddl,
|
|
5556
|
-
schema: joinedContexts[0].schema,
|
|
5557
|
-
options: joinedContexts[0].options,
|
|
5558
|
-
table: joinedContexts[0].table,
|
|
5559
|
-
tableName: joinedContexts[0].tableName,
|
|
5560
|
-
sortedTables: joinedContexts[0].sortedTables
|
|
5561
|
-
};
|
|
5562
|
-
}
|
|
5563
|
-
).then(
|
|
5564
|
-
CadenzaService.createMetaTask(
|
|
5565
|
-
"Apply Database Changes",
|
|
5566
|
-
async (ctx) => {
|
|
5567
|
-
const { ddl } = ctx;
|
|
5568
|
-
if (ddl && ddl.length > 0) {
|
|
5569
|
-
for (const sql of ddl) {
|
|
5570
|
-
try {
|
|
5571
|
-
await this.dbClient.query(sql);
|
|
5572
|
-
} catch (error) {
|
|
5573
|
-
console.error(
|
|
5574
|
-
"Error applying DDL",
|
|
5575
|
-
sql,
|
|
5576
|
-
error
|
|
5577
|
-
);
|
|
5578
|
-
}
|
|
5579
|
-
}
|
|
5580
|
-
}
|
|
5581
|
-
return true;
|
|
5582
|
-
},
|
|
5583
|
-
"Applies generated DDL to the database"
|
|
5584
|
-
).then(
|
|
5585
|
-
CadenzaService.createMetaTask(
|
|
5586
|
-
"Split schema into tables for task creation",
|
|
5587
|
-
this.splitTables.bind(this),
|
|
5588
|
-
"Splits schema into tables for task creation"
|
|
5589
|
-
).then(
|
|
5590
|
-
CadenzaService.createMetaTask(
|
|
5591
|
-
"Generate tasks",
|
|
5592
|
-
(ctx) => {
|
|
5593
|
-
const { table, tableName, options } = ctx;
|
|
5594
|
-
this.createDatabaseTask(
|
|
5595
|
-
"query",
|
|
5596
|
-
tableName,
|
|
5597
|
-
table,
|
|
5598
|
-
this.queryFunction.bind(this),
|
|
5599
|
-
options
|
|
5600
|
-
);
|
|
5601
|
-
this.createDatabaseTask(
|
|
5602
|
-
"insert",
|
|
5603
|
-
tableName,
|
|
5604
|
-
table,
|
|
5605
|
-
this.insertFunction.bind(this),
|
|
5606
|
-
options
|
|
5607
|
-
);
|
|
5608
|
-
this.createDatabaseTask(
|
|
5609
|
-
"update",
|
|
5610
|
-
tableName,
|
|
5611
|
-
table,
|
|
5612
|
-
this.updateFunction.bind(this),
|
|
5613
|
-
options
|
|
5614
|
-
);
|
|
5615
|
-
this.createDatabaseTask(
|
|
5616
|
-
"delete",
|
|
5617
|
-
tableName,
|
|
5618
|
-
table,
|
|
5619
|
-
this.deleteFunction.bind(this),
|
|
5620
|
-
options
|
|
5621
|
-
);
|
|
5622
|
-
return true;
|
|
5623
|
-
},
|
|
5624
|
-
"Generates auto-tasks for database schema"
|
|
5625
|
-
).then(
|
|
5626
|
-
CadenzaService.createUniqueMetaTask(
|
|
5627
|
-
"Join table tasks",
|
|
5628
|
-
() => {
|
|
5629
|
-
return true;
|
|
5630
|
-
}
|
|
5631
|
-
).emits("meta.database.setup_done")
|
|
5632
|
-
)
|
|
5633
|
-
)
|
|
5634
|
-
)
|
|
5635
|
-
)
|
|
5636
|
-
)
|
|
5637
|
-
)
|
|
5638
|
-
)
|
|
5639
|
-
)
|
|
5640
|
-
)
|
|
5641
|
-
)
|
|
5642
|
-
)
|
|
5643
|
-
)
|
|
5644
|
-
)
|
|
5645
|
-
)
|
|
5646
|
-
)
|
|
5647
|
-
],
|
|
5648
|
-
"Initializes the database service with schema parsing and task/signal generation"
|
|
5392
|
+
CadenzaService.createMetaTask(
|
|
5393
|
+
"Route PostgresActor setup requests",
|
|
5394
|
+
(ctx) => {
|
|
5395
|
+
const serviceName = String(ctx.options?.serviceName ?? ctx.serviceName ?? "");
|
|
5396
|
+
if (!serviceName) {
|
|
5397
|
+
return ctx;
|
|
5398
|
+
}
|
|
5399
|
+
const registration = this.registrationsByService.get(serviceName);
|
|
5400
|
+
if (!registration) {
|
|
5401
|
+
return ctx;
|
|
5402
|
+
}
|
|
5403
|
+
CadenzaService.emit(`meta.postgres_actor.setup_requested.${registration.actorToken}`, ctx);
|
|
5404
|
+
return ctx;
|
|
5405
|
+
},
|
|
5406
|
+
"Routes generic database init requests to actor-scoped setup signal.",
|
|
5407
|
+
{ isMeta: true, isSubMeta: true }
|
|
5649
5408
|
).doOn("meta.database_init_requested");
|
|
5650
5409
|
}
|
|
5651
5410
|
static get instance() {
|
|
@@ -5653,632 +5412,1013 @@ var DatabaseController = class _DatabaseController {
|
|
|
5653
5412
|
return this._instance;
|
|
5654
5413
|
}
|
|
5655
5414
|
reset() {
|
|
5656
|
-
this.
|
|
5415
|
+
for (const registration of this.registrationsByService.values()) {
|
|
5416
|
+
const runtimeState = registration.actor.getRuntimeState(registration.actorKey);
|
|
5417
|
+
if (runtimeState?.pool) {
|
|
5418
|
+
runtimeState.pool.end().catch(() => void 0);
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
this.registrationsByService.clear();
|
|
5422
|
+
this.adminDbClient.end().catch(() => void 0);
|
|
5657
5423
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
const
|
|
5667
|
-
const
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
)
|
|
5679
|
-
}, 5e3);
|
|
5680
|
-
client.query = (...args) => {
|
|
5681
|
-
client.lastQuery = args;
|
|
5682
|
-
return query.apply(client, args);
|
|
5424
|
+
createPostgresActor(serviceName, schema, options) {
|
|
5425
|
+
const existing = this.registrationsByService.get(serviceName);
|
|
5426
|
+
if (existing) {
|
|
5427
|
+
return existing;
|
|
5428
|
+
}
|
|
5429
|
+
const actorName = `${serviceName}PostgresActor`;
|
|
5430
|
+
const actorToken = normalizeIntentToken(actorName);
|
|
5431
|
+
const actorKey = String(options.databaseName ?? (0, import_lodash_es.snakeCase)(serviceName));
|
|
5432
|
+
const optionTimeout = typeof options.timeoutMs === "number" ? Number(options.timeoutMs) : Number(options.timeout);
|
|
5433
|
+
const safetyPolicy = {
|
|
5434
|
+
statementTimeoutMs: normalizePositiveInteger(
|
|
5435
|
+
optionTimeout,
|
|
5436
|
+
normalizePositiveInteger(Number(process.env.DATABASE_STATEMENT_TIMEOUT_MS ?? 15e3), 15e3)
|
|
5437
|
+
),
|
|
5438
|
+
retryCount: normalizePositiveInteger(options.retryCount, 3, 0),
|
|
5439
|
+
retryDelayMs: normalizePositiveInteger(Number(process.env.DATABASE_RETRY_DELAY_MS ?? 100), 100),
|
|
5440
|
+
retryDelayMaxMs: normalizePositiveInteger(
|
|
5441
|
+
Number(process.env.DATABASE_RETRY_DELAY_MAX_MS ?? 1e3),
|
|
5442
|
+
1e3
|
|
5443
|
+
),
|
|
5444
|
+
retryDelayFactor: Number(process.env.DATABASE_RETRY_DELAY_FACTOR ?? 2)
|
|
5683
5445
|
};
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5446
|
+
const actor = CadenzaService.createActor(
|
|
5447
|
+
{
|
|
5448
|
+
name: actorName,
|
|
5449
|
+
description: "Specialized PostgresActor owning pool runtime state and schema-driven DB task generation.",
|
|
5450
|
+
defaultKey: actorKey,
|
|
5451
|
+
keyResolver: (input) => typeof input.databaseName === "string" ? input.databaseName : void 0,
|
|
5452
|
+
loadPolicy: "eager",
|
|
5453
|
+
writeContract: "overwrite",
|
|
5454
|
+
initState: {
|
|
5455
|
+
actorName,
|
|
5456
|
+
actorToken,
|
|
5457
|
+
serviceName,
|
|
5458
|
+
databaseName: actorKey,
|
|
5459
|
+
status: "idle",
|
|
5460
|
+
schemaVersion: Number(schema.version ?? 1),
|
|
5461
|
+
setupStartedAt: null,
|
|
5462
|
+
setupCompletedAt: null,
|
|
5463
|
+
lastHealthCheckAt: null,
|
|
5464
|
+
lastError: null,
|
|
5465
|
+
tables: Object.keys(schema.tables ?? {}),
|
|
5466
|
+
safetyPolicy
|
|
5467
|
+
}
|
|
5468
|
+
},
|
|
5469
|
+
{ isMeta: Boolean(options.isMeta) }
|
|
5470
|
+
);
|
|
5471
|
+
const registration = {
|
|
5472
|
+
serviceName,
|
|
5473
|
+
databaseName: actorKey,
|
|
5474
|
+
actorName,
|
|
5475
|
+
actorToken,
|
|
5476
|
+
actorKey,
|
|
5477
|
+
actor,
|
|
5478
|
+
schema,
|
|
5479
|
+
options,
|
|
5480
|
+
tasksGenerated: false,
|
|
5481
|
+
intentNames: /* @__PURE__ */ new Set()
|
|
5689
5482
|
};
|
|
5690
|
-
|
|
5483
|
+
this.registrationsByService.set(serviceName, registration);
|
|
5484
|
+
this.registerSetupTask(registration);
|
|
5485
|
+
return registration;
|
|
5691
5486
|
}
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
} else {
|
|
5701
|
-
CadenzaService.log(
|
|
5702
|
-
"Database query errored",
|
|
5703
|
-
{ error: err, context },
|
|
5704
|
-
"warning"
|
|
5487
|
+
registerSetupTask(registration) {
|
|
5488
|
+
const setupSignal = `meta.postgres_actor.setup_requested.${registration.actorToken}`;
|
|
5489
|
+
CadenzaService.createMetaTask(
|
|
5490
|
+
`Setup ${registration.actorName}`,
|
|
5491
|
+
registration.actor.task(
|
|
5492
|
+
async ({ input, state, runtimeState, setState, setRuntimeState, emit }) => {
|
|
5493
|
+
const requestedDatabaseName = String(
|
|
5494
|
+
input.options?.databaseName ?? input.databaseName ?? registration.databaseName
|
|
5705
5495
|
);
|
|
5706
|
-
|
|
5707
|
-
|
|
5496
|
+
if (requestedDatabaseName !== registration.databaseName) {
|
|
5497
|
+
return input;
|
|
5498
|
+
}
|
|
5499
|
+
setState({
|
|
5500
|
+
...state,
|
|
5501
|
+
status: "initializing",
|
|
5502
|
+
setupStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5503
|
+
lastError: null
|
|
5504
|
+
});
|
|
5505
|
+
const priorRuntimePool = runtimeState?.pool ?? null;
|
|
5506
|
+
if (priorRuntimePool) {
|
|
5507
|
+
await priorRuntimePool.end().catch(() => void 0);
|
|
5508
|
+
}
|
|
5509
|
+
try {
|
|
5510
|
+
await this.createDatabaseIfMissing(requestedDatabaseName);
|
|
5511
|
+
const pool = this.createTargetPool(
|
|
5512
|
+
requestedDatabaseName,
|
|
5513
|
+
state.safetyPolicy.statementTimeoutMs
|
|
5514
|
+
);
|
|
5515
|
+
await this.checkPoolHealth(pool, state.safetyPolicy);
|
|
5516
|
+
this.validateSchema({
|
|
5517
|
+
schema: registration.schema,
|
|
5518
|
+
options: registration.options
|
|
5519
|
+
});
|
|
5520
|
+
const sortedTables = this.sortTablesByReferences({
|
|
5521
|
+
schema: registration.schema
|
|
5522
|
+
}).sortedTables;
|
|
5523
|
+
const ddlStatements = this.buildSchemaDdlStatements(
|
|
5524
|
+
registration.schema,
|
|
5525
|
+
sortedTables
|
|
5526
|
+
);
|
|
5527
|
+
await this.applyDdlStatements(pool, ddlStatements);
|
|
5528
|
+
if (!registration.tasksGenerated) {
|
|
5529
|
+
this.generateDatabaseTasks(registration);
|
|
5530
|
+
registration.tasksGenerated = true;
|
|
5531
|
+
}
|
|
5532
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5533
|
+
setRuntimeState({
|
|
5534
|
+
pool,
|
|
5535
|
+
ready: true,
|
|
5536
|
+
pendingQueries: 0,
|
|
5537
|
+
lastHealthCheckAt: Date.now(),
|
|
5538
|
+
lastError: null
|
|
5539
|
+
});
|
|
5540
|
+
setState({
|
|
5541
|
+
...state,
|
|
5542
|
+
status: "ready",
|
|
5543
|
+
setupCompletedAt: nowIso,
|
|
5544
|
+
lastHealthCheckAt: nowIso,
|
|
5545
|
+
lastError: null,
|
|
5546
|
+
tables: Object.keys(registration.schema.tables ?? {})
|
|
5547
|
+
});
|
|
5548
|
+
emit("meta.database.setup_done", {
|
|
5549
|
+
serviceName: registration.serviceName,
|
|
5550
|
+
databaseName: registration.databaseName,
|
|
5551
|
+
actorName: registration.actorName,
|
|
5552
|
+
__success: true
|
|
5553
|
+
});
|
|
5554
|
+
return {
|
|
5555
|
+
...input,
|
|
5556
|
+
__success: true,
|
|
5557
|
+
actorName: registration.actorName,
|
|
5558
|
+
databaseName: registration.databaseName
|
|
5559
|
+
};
|
|
5560
|
+
} catch (error) {
|
|
5561
|
+
const message = errorMessage(error);
|
|
5562
|
+
setRuntimeState({
|
|
5563
|
+
pool: null,
|
|
5564
|
+
ready: false,
|
|
5565
|
+
pendingQueries: 0,
|
|
5566
|
+
lastHealthCheckAt: runtimeState?.lastHealthCheckAt ?? null,
|
|
5567
|
+
lastError: message
|
|
5568
|
+
});
|
|
5569
|
+
setState({
|
|
5570
|
+
...state,
|
|
5571
|
+
status: "error",
|
|
5572
|
+
setupCompletedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5573
|
+
lastError: message
|
|
5574
|
+
});
|
|
5575
|
+
throw error;
|
|
5576
|
+
}
|
|
5577
|
+
},
|
|
5578
|
+
{ mode: "write" }
|
|
5579
|
+
),
|
|
5580
|
+
"Initializes PostgresActor runtime pool, applies schema, and generates CRUD tasks/intents.",
|
|
5581
|
+
{ isMeta: true }
|
|
5582
|
+
).doOn(setupSignal);
|
|
5583
|
+
}
|
|
5584
|
+
createTargetPool(databaseName, statementTimeoutMs) {
|
|
5585
|
+
const connectionString = this.buildDatabaseConnectionString(databaseName);
|
|
5586
|
+
return new import_pg.Pool({
|
|
5587
|
+
connectionString,
|
|
5588
|
+
statement_timeout: statementTimeoutMs,
|
|
5589
|
+
query_timeout: statementTimeoutMs,
|
|
5590
|
+
ssl: {
|
|
5591
|
+
rejectUnauthorized: false
|
|
5592
|
+
}
|
|
5593
|
+
});
|
|
5594
|
+
}
|
|
5595
|
+
buildDatabaseConnectionString(databaseName) {
|
|
5596
|
+
const base = process.env.DATABASE_ADDRESS ?? "";
|
|
5597
|
+
if (!base) {
|
|
5598
|
+
throw new Error("DATABASE_ADDRESS environment variable is required");
|
|
5599
|
+
}
|
|
5600
|
+
try {
|
|
5601
|
+
const parsed = new URL(base);
|
|
5602
|
+
parsed.pathname = `/${databaseName}`;
|
|
5603
|
+
if (!parsed.searchParams.has("sslmode")) {
|
|
5604
|
+
parsed.searchParams.set("sslmode", "disable");
|
|
5605
|
+
}
|
|
5606
|
+
return parsed.toString();
|
|
5607
|
+
} catch {
|
|
5608
|
+
const lastSlashIndex = base.lastIndexOf("/");
|
|
5609
|
+
if (lastSlashIndex === -1) {
|
|
5610
|
+
throw new Error("DATABASE_ADDRESS must be a valid postgres connection string");
|
|
5708
5611
|
}
|
|
5612
|
+
const root = base.slice(0, lastSlashIndex + 1);
|
|
5613
|
+
return `${root}${databaseName}?sslmode=disable`;
|
|
5709
5614
|
}
|
|
5710
|
-
throw new Error(`Timeout waiting for database to be ready`);
|
|
5711
5615
|
}
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
* they will be noted but the process will not stop. Unreferenced tables are included at the end.
|
|
5718
|
-
*
|
|
5719
|
-
* @param {Object} ctx - The context object containing the database schema definition and table metadata.
|
|
5720
|
-
* ctx.schema {Object} - The schema definition object.
|
|
5721
|
-
* ctx.schema.tables {Object} - A mapping of table names to table definitions.
|
|
5722
|
-
* Each table definition may contain `fields` (with `references` info)
|
|
5723
|
-
* and `foreignKeys` indicating cross-table relationships.
|
|
5724
|
-
*
|
|
5725
|
-
* @return {Object} - The modified context object with an additional property:
|
|
5726
|
-
* sortedTables {string[]} - An array of table names sorted in dependency order.
|
|
5727
|
-
* hasCycles {boolean} - Indicates if the dependency graph contains cycles.
|
|
5728
|
-
*/
|
|
5729
|
-
sortTablesByReferences(ctx) {
|
|
5730
|
-
const schema = ctx.schema;
|
|
5731
|
-
const graph = /* @__PURE__ */ new Map();
|
|
5732
|
-
const allTables = Object.keys(schema.tables);
|
|
5733
|
-
allTables.forEach((table) => graph.set(table, /* @__PURE__ */ new Set()));
|
|
5734
|
-
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
5735
|
-
for (const field of Object.values(table.fields)) {
|
|
5736
|
-
if (field.references) {
|
|
5737
|
-
const [refTable] = field.references.split("(");
|
|
5738
|
-
if (refTable !== tableName && allTables.includes(refTable)) {
|
|
5739
|
-
graph.get(refTable)?.add(tableName);
|
|
5740
|
-
}
|
|
5741
|
-
}
|
|
5742
|
-
}
|
|
5743
|
-
if (table.foreignKeys) {
|
|
5744
|
-
for (const foreignKey of table.foreignKeys) {
|
|
5745
|
-
const refTable = foreignKey.tableName;
|
|
5746
|
-
if (refTable !== tableName && allTables.includes(refTable)) {
|
|
5747
|
-
graph.get(refTable)?.add(tableName);
|
|
5748
|
-
}
|
|
5749
|
-
}
|
|
5750
|
-
}
|
|
5616
|
+
async createDatabaseIfMissing(databaseName) {
|
|
5617
|
+
if (!isSqlIdentifier(databaseName)) {
|
|
5618
|
+
throw new Error(
|
|
5619
|
+
`Invalid database name '${databaseName}'. Names must contain only lowercase alphanumerics and underscores and cannot start with a number.`
|
|
5620
|
+
);
|
|
5751
5621
|
}
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
hasCycles = true;
|
|
5622
|
+
try {
|
|
5623
|
+
await this.adminDbClient.query(`CREATE DATABASE ${databaseName}`);
|
|
5624
|
+
CadenzaService.log(`Database ${databaseName} created.`);
|
|
5625
|
+
} catch (error) {
|
|
5626
|
+
if (error?.code === "42P04") {
|
|
5627
|
+
CadenzaService.log(`Database ${databaseName} already exists.`);
|
|
5759
5628
|
return;
|
|
5760
5629
|
}
|
|
5761
|
-
|
|
5762
|
-
tempMark.add(table);
|
|
5763
|
-
for (const dep of graph.get(table) || []) {
|
|
5764
|
-
visit(dep);
|
|
5765
|
-
}
|
|
5766
|
-
tempMark.delete(table);
|
|
5767
|
-
visited.add(table);
|
|
5768
|
-
sorted.push(table);
|
|
5630
|
+
throw new Error(`Failed to create database '${databaseName}': ${errorMessage(error)}`);
|
|
5769
5631
|
}
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5632
|
+
}
|
|
5633
|
+
async checkPoolHealth(pool, safetyPolicy) {
|
|
5634
|
+
await this.runWithRetries(
|
|
5635
|
+
async () => {
|
|
5636
|
+
await this.withTimeout(
|
|
5637
|
+
() => pool.query("SELECT 1 as health"),
|
|
5638
|
+
safetyPolicy.statementTimeoutMs,
|
|
5639
|
+
"Database health check timed out"
|
|
5640
|
+
);
|
|
5641
|
+
},
|
|
5642
|
+
safetyPolicy,
|
|
5643
|
+
"Health check"
|
|
5644
|
+
);
|
|
5645
|
+
}
|
|
5646
|
+
getPoolOrThrow(registration) {
|
|
5647
|
+
const runtimeState = registration.actor.getRuntimeState(
|
|
5648
|
+
registration.actorKey
|
|
5649
|
+
);
|
|
5650
|
+
if (!runtimeState || !runtimeState.ready || !runtimeState.pool) {
|
|
5651
|
+
throw new Error(
|
|
5652
|
+
`PostgresActor '${registration.actorName}' is not ready. Ensure setup completed before running DB tasks.`
|
|
5653
|
+
);
|
|
5774
5654
|
}
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5655
|
+
return runtimeState.pool;
|
|
5656
|
+
}
|
|
5657
|
+
async withTimeout(work, timeoutMs, timeoutMessage) {
|
|
5658
|
+
let timeoutHandle = null;
|
|
5659
|
+
try {
|
|
5660
|
+
return await Promise.race([
|
|
5661
|
+
work(),
|
|
5662
|
+
new Promise((_, reject) => {
|
|
5663
|
+
timeoutHandle = setTimeout(() => {
|
|
5664
|
+
reject(new Error(timeoutMessage));
|
|
5665
|
+
}, timeoutMs);
|
|
5666
|
+
})
|
|
5667
|
+
]);
|
|
5668
|
+
} finally {
|
|
5669
|
+
if (timeoutHandle) {
|
|
5670
|
+
clearTimeout(timeoutHandle);
|
|
5778
5671
|
}
|
|
5779
5672
|
}
|
|
5780
|
-
sorted.reverse();
|
|
5781
|
-
return { ...ctx, sortedTables: sorted, hasCycles };
|
|
5782
5673
|
}
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5674
|
+
async runWithRetries(work, safetyPolicy, operationLabel) {
|
|
5675
|
+
const attempts = normalizePositiveInteger(safetyPolicy.retryCount, 0, 0) + 1;
|
|
5676
|
+
let delayMs = normalizePositiveInteger(safetyPolicy.retryDelayMs, 100);
|
|
5677
|
+
const maxDelayMs = normalizePositiveInteger(safetyPolicy.retryDelayMaxMs, 1e3);
|
|
5678
|
+
const factor = Number.isFinite(safetyPolicy.retryDelayFactor) ? Math.max(1, safetyPolicy.retryDelayFactor) : 1;
|
|
5679
|
+
let lastError;
|
|
5680
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
5681
|
+
try {
|
|
5682
|
+
return await work();
|
|
5683
|
+
} catch (error) {
|
|
5684
|
+
lastError = error;
|
|
5685
|
+
const transient = isTransientDatabaseError(error);
|
|
5686
|
+
if (!transient || attempt >= attempts) {
|
|
5687
|
+
break;
|
|
5688
|
+
}
|
|
5689
|
+
CadenzaService.log(
|
|
5690
|
+
`${operationLabel} failed with transient error. Retrying...`,
|
|
5691
|
+
{
|
|
5692
|
+
attempt,
|
|
5693
|
+
attempts,
|
|
5694
|
+
delayMs,
|
|
5695
|
+
error: errorMessage(error)
|
|
5696
|
+
},
|
|
5697
|
+
"warning"
|
|
5698
|
+
);
|
|
5699
|
+
await sleep(delayMs);
|
|
5700
|
+
delayMs = Math.min(Math.trunc(delayMs * factor), maxDelayMs);
|
|
5701
|
+
}
|
|
5798
5702
|
}
|
|
5703
|
+
throw lastError;
|
|
5799
5704
|
}
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
}
|
|
5705
|
+
async executeWithTransaction(pool, transaction, callback) {
|
|
5706
|
+
if (!transaction) {
|
|
5707
|
+
return callback(pool);
|
|
5708
|
+
}
|
|
5709
|
+
const client = await pool.connect();
|
|
5710
|
+
try {
|
|
5711
|
+
await client.query("BEGIN");
|
|
5712
|
+
const result = await callback(client);
|
|
5713
|
+
await client.query("COMMIT");
|
|
5714
|
+
return result;
|
|
5715
|
+
} catch (error) {
|
|
5716
|
+
await client.query("ROLLBACK");
|
|
5717
|
+
throw error;
|
|
5718
|
+
} finally {
|
|
5719
|
+
client.release();
|
|
5720
|
+
}
|
|
5814
5721
|
}
|
|
5815
|
-
|
|
5816
|
-
* Executes a query against a specified database table with given parameters.
|
|
5817
|
-
*
|
|
5818
|
-
* @param {string} tableName - The name of the database table to query.
|
|
5819
|
-
* @param {DbOperationPayload} context - An object containing query parameters such as filters, fields, joins, sort, limit, and offset.
|
|
5820
|
-
* @return {Promise<any>} A promise that resolves with the query result, including rows, row count, and metadata, or an error object if the query fails.
|
|
5821
|
-
*/
|
|
5822
|
-
async queryFunction(tableName, context) {
|
|
5722
|
+
async queryFunction(registration, tableName, context) {
|
|
5823
5723
|
const {
|
|
5824
5724
|
filter = {},
|
|
5825
5725
|
fields = [],
|
|
5826
5726
|
joins = {},
|
|
5827
5727
|
sort = {},
|
|
5828
5728
|
limit,
|
|
5829
|
-
offset
|
|
5729
|
+
offset,
|
|
5730
|
+
queryMode = "rows",
|
|
5731
|
+
aggregates = [],
|
|
5732
|
+
groupBy = []
|
|
5830
5733
|
} = context;
|
|
5831
|
-
|
|
5734
|
+
const pool = this.getPoolOrThrow(registration);
|
|
5735
|
+
const statementTimeoutMs = this.resolveSafetyPolicy(registration).statementTimeoutMs;
|
|
5736
|
+
const resolvedMode = queryMode;
|
|
5737
|
+
const aggregateDefinitions = Array.isArray(aggregates) ? aggregates : [];
|
|
5738
|
+
const groupByFields = Array.isArray(groupBy) ? groupBy : [];
|
|
5832
5739
|
const params = [];
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
if (
|
|
5837
|
-
sql
|
|
5740
|
+
const whereClause = Object.keys(filter).length > 0 ? this.buildWhereClause(filter, params) : "";
|
|
5741
|
+
const joinClause = Object.keys(joins).length > 0 ? this.buildJoinClause(joins) : "";
|
|
5742
|
+
let sql;
|
|
5743
|
+
if (resolvedMode === "count") {
|
|
5744
|
+
sql = `SELECT COUNT(*)::bigint AS count FROM ${tableName} ${joinClause} ${whereClause}`;
|
|
5745
|
+
} else if (resolvedMode === "exists") {
|
|
5746
|
+
sql = `SELECT EXISTS(SELECT 1 FROM ${tableName} ${joinClause} ${whereClause}) AS exists`;
|
|
5747
|
+
} else if (resolvedMode === "aggregate") {
|
|
5748
|
+
if (aggregateDefinitions.length === 0) {
|
|
5749
|
+
throw new Error("Aggregate queries require at least one aggregate definition");
|
|
5750
|
+
}
|
|
5751
|
+
const aggregateExpressions = aggregateDefinitions.map(
|
|
5752
|
+
(aggregate, index) => {
|
|
5753
|
+
const fn = String(aggregate.fn ?? "").toLowerCase();
|
|
5754
|
+
if (!isSupportedAggregateFunction(fn)) {
|
|
5755
|
+
throw new Error(`Unsupported aggregate function '${aggregate.fn}'`);
|
|
5756
|
+
}
|
|
5757
|
+
const hasField = typeof aggregate.field === "string" && aggregate.field.trim().length > 0;
|
|
5758
|
+
if (fn !== "count" && !hasField) {
|
|
5759
|
+
throw new Error(`Aggregate '${fn}' requires a field`);
|
|
5760
|
+
}
|
|
5761
|
+
const fieldExpression = hasField ? (0, import_lodash_es.snakeCase)(String(aggregate.field)) : "*";
|
|
5762
|
+
const distinctPrefix = aggregate.distinct ? "DISTINCT " : "";
|
|
5763
|
+
const expression = fn === "count" && !hasField ? "COUNT(*)" : `${fn.toUpperCase()}(${distinctPrefix}${fieldExpression})`;
|
|
5764
|
+
const alias = buildAggregateAlias(aggregate, index);
|
|
5765
|
+
return `${expression} AS ${alias}`;
|
|
5766
|
+
}
|
|
5767
|
+
);
|
|
5768
|
+
const groupByExpressions = groupByFields.map((field) => (0, import_lodash_es.snakeCase)(field));
|
|
5769
|
+
const selectExpressions = [...groupByExpressions, ...aggregateExpressions];
|
|
5770
|
+
sql = `SELECT ${selectExpressions.join(", ")} FROM ${tableName} ${joinClause} ${whereClause}`;
|
|
5771
|
+
if (groupByExpressions.length > 0) {
|
|
5772
|
+
sql += ` GROUP BY ${groupByExpressions.join(", ")}`;
|
|
5773
|
+
}
|
|
5774
|
+
} else {
|
|
5775
|
+
sql = `SELECT ${fields.length ? fields.map(import_lodash_es.snakeCase).join(", ") : "*"} FROM ${tableName} ${joinClause} ${whereClause}`;
|
|
5838
5776
|
}
|
|
5839
|
-
if (Object.keys(sort).length > 0) {
|
|
5840
|
-
sql += " ORDER BY " + Object.entries(sort).map(([field, direction]) => `${field} ${direction}`).join(", ");
|
|
5777
|
+
if (Object.keys(sort).length > 0 && resolvedMode !== "count" && resolvedMode !== "exists") {
|
|
5778
|
+
sql += " ORDER BY " + Object.entries(sort).map(([field, direction]) => `${(0, import_lodash_es.snakeCase)(field)} ${direction}`).join(", ");
|
|
5841
5779
|
}
|
|
5842
|
-
if (
|
|
5780
|
+
if (resolvedMode === "one") {
|
|
5781
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
5782
|
+
params.push(1);
|
|
5783
|
+
} else if (resolvedMode !== "count" && resolvedMode !== "exists" && limit !== void 0) {
|
|
5843
5784
|
sql += ` LIMIT $${params.length + 1}`;
|
|
5844
5785
|
params.push(limit);
|
|
5845
5786
|
}
|
|
5846
|
-
if (offset !== void 0) {
|
|
5787
|
+
if (resolvedMode !== "count" && resolvedMode !== "exists" && offset !== void 0) {
|
|
5847
5788
|
sql += ` OFFSET $${params.length + 1}`;
|
|
5848
5789
|
params.push(offset);
|
|
5849
5790
|
}
|
|
5850
5791
|
try {
|
|
5851
|
-
const result = await this.
|
|
5792
|
+
const result = await this.withTimeout(
|
|
5793
|
+
() => pool.query(sql, params),
|
|
5794
|
+
statementTimeoutMs,
|
|
5795
|
+
`Query timeout on table ${tableName}`
|
|
5796
|
+
);
|
|
5852
5797
|
const rows = this.toCamelCase(result.rows);
|
|
5798
|
+
const rowCount = Number(result.rowCount ?? 0);
|
|
5799
|
+
if (resolvedMode === "count") {
|
|
5800
|
+
return {
|
|
5801
|
+
count: Number(rows[0]?.count ?? 0),
|
|
5802
|
+
rowCount: Number(rows[0]?.count ?? 0),
|
|
5803
|
+
__success: true
|
|
5804
|
+
};
|
|
5805
|
+
}
|
|
5806
|
+
if (resolvedMode === "exists") {
|
|
5807
|
+
const exists = Boolean(rows[0]?.exists);
|
|
5808
|
+
return {
|
|
5809
|
+
exists,
|
|
5810
|
+
rowCount: exists ? 1 : 0,
|
|
5811
|
+
__success: true
|
|
5812
|
+
};
|
|
5813
|
+
}
|
|
5814
|
+
if (resolvedMode === "one") {
|
|
5815
|
+
return {
|
|
5816
|
+
[`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0] ?? null,
|
|
5817
|
+
rowCount,
|
|
5818
|
+
__success: true
|
|
5819
|
+
};
|
|
5820
|
+
}
|
|
5821
|
+
if (resolvedMode === "aggregate") {
|
|
5822
|
+
return {
|
|
5823
|
+
aggregates: rows,
|
|
5824
|
+
rowCount,
|
|
5825
|
+
__success: true
|
|
5826
|
+
};
|
|
5827
|
+
}
|
|
5853
5828
|
return {
|
|
5854
5829
|
[`${(0, import_lodash_es.camelCase)(tableName)}s`]: rows,
|
|
5855
|
-
rowCount
|
|
5856
|
-
__success: true
|
|
5857
|
-
...context
|
|
5830
|
+
rowCount,
|
|
5831
|
+
__success: true
|
|
5858
5832
|
};
|
|
5859
5833
|
} catch (error) {
|
|
5860
5834
|
return {
|
|
5861
|
-
|
|
5835
|
+
rowCount: 0,
|
|
5862
5836
|
errored: true,
|
|
5863
|
-
__error: `Query failed: ${error
|
|
5837
|
+
__error: `Query failed: ${errorMessage(error)}`,
|
|
5864
5838
|
__success: false
|
|
5865
5839
|
};
|
|
5866
5840
|
}
|
|
5867
5841
|
}
|
|
5868
|
-
|
|
5869
|
-
* Inserts data into the specified database table with optional conflict handling.
|
|
5870
|
-
*
|
|
5871
|
-
* @param {string} tableName - The name of the target database table.
|
|
5872
|
-
* @param {DbOperationPayload} context - The context containing data to insert, transaction settings, field mappings, conflict resolution options, and other configurations.
|
|
5873
|
-
* - `data` (object | array): The data to be inserted into the database.
|
|
5874
|
-
* - `transaction` (boolean): Specifies whether the operation should use a transaction. Defaults to true.
|
|
5875
|
-
* - `fields` (array): The fields to return in the result after insertion.
|
|
5876
|
-
* - `onConflict` (object): Options for handling conflicts on insert.
|
|
5877
|
-
* - `target` (array): Columns to determine conflicts.
|
|
5878
|
-
* - `action` (object): Specifies the action to take on conflict, such as updating specified fields.
|
|
5879
|
-
* - `awaitExists` (object): Specifies foreign key references to wait for to ensure existence before insertion.
|
|
5880
|
-
*
|
|
5881
|
-
* @return {Promise<any>} A promise resolving to the result of the database insert operation, including the inserted rows, the row count, and metadata indicating success or error.
|
|
5882
|
-
*/
|
|
5883
|
-
async insertFunction(tableName, context) {
|
|
5842
|
+
async insertFunction(registration, tableName, context) {
|
|
5884
5843
|
const { data, transaction = true, fields = [], onConflict } = context;
|
|
5885
5844
|
if (!data || Array.isArray(data) && data.length === 0) {
|
|
5886
|
-
return {
|
|
5845
|
+
return {
|
|
5846
|
+
rowCount: 0,
|
|
5847
|
+
errored: true,
|
|
5848
|
+
__error: "No data provided for insert",
|
|
5849
|
+
__success: false
|
|
5850
|
+
};
|
|
5887
5851
|
}
|
|
5888
|
-
|
|
5889
|
-
const
|
|
5852
|
+
const pool = this.getPoolOrThrow(registration);
|
|
5853
|
+
const safetyPolicy = this.resolveSafetyPolicy(registration);
|
|
5890
5854
|
try {
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
}
|
|
5902
|
-
if (value.__effect === "decrement") {
|
|
5903
|
-
return `${Object.keys(row)[i]} - 1`;
|
|
5904
|
-
}
|
|
5905
|
-
if (value.__effect === "set") {
|
|
5906
|
-
return `${Object.keys(row)[i]} = ${value.__value}`;
|
|
5907
|
-
}
|
|
5855
|
+
const resultContext = await this.runWithRetries(
|
|
5856
|
+
async () => this.executeWithTransaction(pool, Boolean(transaction), async (client) => {
|
|
5857
|
+
const resolvedData = await this.resolveNestedData(
|
|
5858
|
+
registration,
|
|
5859
|
+
data,
|
|
5860
|
+
tableName
|
|
5861
|
+
);
|
|
5862
|
+
const rows = Array.isArray(resolvedData) ? resolvedData : [resolvedData];
|
|
5863
|
+
if (rows.length === 0) {
|
|
5864
|
+
throw new Error("No rows available for insert after resolving data");
|
|
5908
5865
|
}
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5866
|
+
const keys = Object.keys(rows[0]);
|
|
5867
|
+
const sqlPrefix = `INSERT INTO ${tableName} (${keys.map((key) => (0, import_lodash_es.snakeCase)(key)).join(", ")}) VALUES `;
|
|
5868
|
+
const params = [];
|
|
5869
|
+
const placeholders = rows.map((row) => {
|
|
5870
|
+
const tuple = keys.map((key) => {
|
|
5871
|
+
params.push(row[key]);
|
|
5872
|
+
return `$${params.length}`;
|
|
5873
|
+
}).join(", ");
|
|
5874
|
+
return `(${tuple})`;
|
|
5875
|
+
}).join(", ");
|
|
5876
|
+
let onConflictSql = "";
|
|
5877
|
+
if (onConflict) {
|
|
5878
|
+
onConflictSql = this.buildOnConflictClause(onConflict, params);
|
|
5920
5879
|
}
|
|
5921
|
-
const
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
(v) => typeof v !== "string" || !v.startsWith("excluded.")
|
|
5927
|
-
)
|
|
5880
|
+
const sql = `${sqlPrefix}${placeholders}${onConflictSql} RETURNING ${fields.length ? fields.map(import_lodash_es.snakeCase).join(", ") : "*"}`;
|
|
5881
|
+
const result = await this.withTimeout(
|
|
5882
|
+
() => client.query(sql, params),
|
|
5883
|
+
safetyPolicy.statementTimeoutMs,
|
|
5884
|
+
`Insert timeout on table ${tableName}`
|
|
5928
5885
|
);
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5886
|
+
const resultRows = this.toCamelCase(result.rows);
|
|
5887
|
+
return {
|
|
5888
|
+
[`${(0, import_lodash_es.camelCase)(tableName)}${rows.length > 1 ? "s" : ""}`]: rows.length > 1 ? resultRows : resultRows[0] ?? null,
|
|
5889
|
+
rowCount: result.rowCount,
|
|
5890
|
+
__success: true
|
|
5891
|
+
};
|
|
5892
|
+
}),
|
|
5893
|
+
safetyPolicy,
|
|
5894
|
+
`Insert ${tableName}`
|
|
5938
5895
|
);
|
|
5939
|
-
|
|
5940
|
-
const resultRows = this.toCamelCase(result.rows);
|
|
5941
|
-
resultContext = {
|
|
5942
|
-
[`${(0, import_lodash_es.camelCase)(tableName)}${isBatch ? "s" : ""}`]: isBatch ? resultRows : resultRows[0],
|
|
5943
|
-
rowCount: result.rowCount,
|
|
5944
|
-
__success: true
|
|
5945
|
-
};
|
|
5896
|
+
return resultContext;
|
|
5946
5897
|
} catch (error) {
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
} else {
|
|
5954
|
-
resultContext = {
|
|
5955
|
-
...context,
|
|
5956
|
-
errored: true,
|
|
5957
|
-
__error: `Insert failed: ${error.message}`,
|
|
5958
|
-
__success: false
|
|
5959
|
-
};
|
|
5960
|
-
}
|
|
5961
|
-
} finally {
|
|
5962
|
-
if (transaction && client) {
|
|
5963
|
-
client.release();
|
|
5964
|
-
}
|
|
5898
|
+
return {
|
|
5899
|
+
rowCount: 0,
|
|
5900
|
+
errored: true,
|
|
5901
|
+
__error: `Insert failed: ${errorMessage(error)}`,
|
|
5902
|
+
__success: false
|
|
5903
|
+
};
|
|
5965
5904
|
}
|
|
5966
|
-
return resultContext;
|
|
5967
5905
|
}
|
|
5968
|
-
|
|
5969
|
-
* Updates a database table with the provided data and filter conditions.
|
|
5970
|
-
*
|
|
5971
|
-
* @param {string} tableName - The name of the database table to update.
|
|
5972
|
-
* @param {DbOperationPayload} context - The payload for the update operation, which includes:
|
|
5973
|
-
* - data: The data to update in the table.
|
|
5974
|
-
* - filter: The conditions to identify the rows to update (default is an empty object).
|
|
5975
|
-
* - transaction: Whether the operation should run within a database transaction (default is true).
|
|
5976
|
-
* @return {Promise<any>} Returns a Promise resolving to an object that includes:
|
|
5977
|
-
* - The updated data if the update is successful.
|
|
5978
|
-
* - In case of error:
|
|
5979
|
-
* - Error details.
|
|
5980
|
-
* - The SQL query and parameters if applicable.
|
|
5981
|
-
* - A flag indicating if the update succeeded or failed.
|
|
5982
|
-
*/
|
|
5983
|
-
async updateFunction(tableName, context) {
|
|
5906
|
+
async updateFunction(registration, tableName, context) {
|
|
5984
5907
|
const { data, filter = {}, transaction = true } = context;
|
|
5985
5908
|
if (!data || Object.keys(data).length === 0) {
|
|
5986
5909
|
return {
|
|
5910
|
+
rowCount: 0,
|
|
5987
5911
|
errored: true,
|
|
5988
|
-
__error: `No data provided for update of ${tableName}
|
|
5912
|
+
__error: `No data provided for update of ${tableName}`,
|
|
5913
|
+
__success: false
|
|
5989
5914
|
};
|
|
5990
5915
|
}
|
|
5991
|
-
|
|
5992
|
-
const
|
|
5916
|
+
const pool = this.getPoolOrThrow(registration);
|
|
5917
|
+
const safetyPolicy = this.resolveSafetyPolicy(registration);
|
|
5993
5918
|
try {
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
5919
|
+
return await this.runWithRetries(
|
|
5920
|
+
async () => this.executeWithTransaction(pool, Boolean(transaction), async (client) => {
|
|
5921
|
+
const resolvedData = await this.resolveNestedData(
|
|
5922
|
+
registration,
|
|
5923
|
+
data,
|
|
5924
|
+
tableName
|
|
5925
|
+
);
|
|
5926
|
+
const params = Object.values(resolvedData);
|
|
5927
|
+
let offset = 0;
|
|
5928
|
+
const setClause = Object.keys(resolvedData).map((key, index) => {
|
|
5929
|
+
const value = resolvedData[key];
|
|
5930
|
+
const offsetIndex = index - offset;
|
|
5931
|
+
if (value?.__effect === "increment") {
|
|
5932
|
+
params.splice(offsetIndex, 1);
|
|
5933
|
+
offset += 1;
|
|
5934
|
+
return `${(0, import_lodash_es.snakeCase)(key)} = ${(0, import_lodash_es.snakeCase)(key)} + 1`;
|
|
5935
|
+
}
|
|
5936
|
+
if (value?.__effect === "decrement") {
|
|
5937
|
+
params.splice(offsetIndex, 1);
|
|
5938
|
+
offset += 1;
|
|
5939
|
+
return `${(0, import_lodash_es.snakeCase)(key)} = ${(0, import_lodash_es.snakeCase)(key)} - 1`;
|
|
5940
|
+
}
|
|
5941
|
+
if (value?.__effect === "set") {
|
|
5942
|
+
params.splice(offsetIndex, 1);
|
|
5943
|
+
offset += 1;
|
|
5944
|
+
return `${(0, import_lodash_es.snakeCase)(key)} = ${value.__value}`;
|
|
5945
|
+
}
|
|
5946
|
+
return `${(0, import_lodash_es.snakeCase)(key)} = $${offsetIndex + 1}`;
|
|
5947
|
+
}).join(", ");
|
|
5948
|
+
const whereClause = this.buildWhereClause(filter, params);
|
|
5949
|
+
const sql = `UPDATE ${tableName} SET ${setClause} ${whereClause} RETURNING *`;
|
|
5950
|
+
const result = await this.withTimeout(
|
|
5951
|
+
() => client.query(sql, params),
|
|
5952
|
+
safetyPolicy.statementTimeoutMs,
|
|
5953
|
+
`Update timeout on table ${tableName}`
|
|
5954
|
+
);
|
|
5955
|
+
const rows = this.toCamelCase(result.rows);
|
|
5956
|
+
const rowCount = Number(result.rowCount ?? 0);
|
|
5957
|
+
return {
|
|
5958
|
+
[`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0] ?? null,
|
|
5959
|
+
rowCount,
|
|
5960
|
+
__success: rowCount > 0
|
|
5961
|
+
};
|
|
5962
|
+
}),
|
|
5963
|
+
safetyPolicy,
|
|
5964
|
+
`Update ${tableName}`
|
|
5965
|
+
);
|
|
6035
5966
|
} catch (error) {
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
...context,
|
|
5967
|
+
return {
|
|
5968
|
+
rowCount: 0,
|
|
6039
5969
|
errored: true,
|
|
6040
|
-
__error: `Update failed: ${error
|
|
5970
|
+
__error: `Update failed: ${errorMessage(error)}`,
|
|
6041
5971
|
__success: false
|
|
6042
5972
|
};
|
|
6043
|
-
} finally {
|
|
6044
|
-
if (transaction && client) {
|
|
6045
|
-
client.release();
|
|
6046
|
-
}
|
|
6047
5973
|
}
|
|
6048
|
-
return resultContext;
|
|
6049
5974
|
}
|
|
6050
|
-
|
|
6051
|
-
* Deletes a record from the specified database table based on the given filter criteria.
|
|
6052
|
-
*
|
|
6053
|
-
* @param {string} tableName - The name of the database table from which records should be deleted.
|
|
6054
|
-
* @param {DbOperationPayload} context - The context for the operation, including filter conditions and transaction settings.
|
|
6055
|
-
* @param {Object} context.filter - The filter criteria to identify the records to delete.
|
|
6056
|
-
* @param {boolean} [context.transaction=true] - Indicates if the operation should be executed within a transaction.
|
|
6057
|
-
* @return {Promise<any>} A promise that resolves to an object containing information about the deleted record
|
|
6058
|
-
* or an error object if the delete operation fails.
|
|
6059
|
-
*/
|
|
6060
|
-
async deleteFunction(tableName, context) {
|
|
5975
|
+
async deleteFunction(registration, tableName, context) {
|
|
6061
5976
|
const { filter = {}, transaction = true } = context;
|
|
6062
5977
|
if (Object.keys(filter).length === 0) {
|
|
6063
|
-
return {
|
|
5978
|
+
return {
|
|
5979
|
+
rowCount: 0,
|
|
5980
|
+
errored: true,
|
|
5981
|
+
__error: "No filter provided for delete",
|
|
5982
|
+
__success: false
|
|
5983
|
+
};
|
|
6064
5984
|
}
|
|
6065
|
-
|
|
6066
|
-
const
|
|
5985
|
+
const pool = this.getPoolOrThrow(registration);
|
|
5986
|
+
const safetyPolicy = this.resolveSafetyPolicy(registration);
|
|
6067
5987
|
try {
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
5988
|
+
return await this.runWithRetries(
|
|
5989
|
+
async () => this.executeWithTransaction(pool, Boolean(transaction), async (client) => {
|
|
5990
|
+
const params = [];
|
|
5991
|
+
const whereClause = this.buildWhereClause(filter, params);
|
|
5992
|
+
const sql = `DELETE FROM ${tableName} ${whereClause} RETURNING *`;
|
|
5993
|
+
const result = await this.withTimeout(
|
|
5994
|
+
() => client.query(sql, params),
|
|
5995
|
+
safetyPolicy.statementTimeoutMs,
|
|
5996
|
+
`Delete timeout on table ${tableName}`
|
|
5997
|
+
);
|
|
5998
|
+
const rows = this.toCamelCase(result.rows);
|
|
5999
|
+
return {
|
|
6000
|
+
[`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0] ?? null,
|
|
6001
|
+
rowCount: result.rowCount,
|
|
6002
|
+
__success: true
|
|
6003
|
+
};
|
|
6004
|
+
}),
|
|
6005
|
+
safetyPolicy,
|
|
6006
|
+
`Delete ${tableName}`
|
|
6007
|
+
);
|
|
6079
6008
|
} catch (error) {
|
|
6080
|
-
|
|
6081
|
-
|
|
6009
|
+
return {
|
|
6010
|
+
rowCount: 0,
|
|
6082
6011
|
errored: true,
|
|
6083
|
-
__error: `Delete failed: ${error
|
|
6084
|
-
__errors: { delete: error.message },
|
|
6012
|
+
__error: `Delete failed: ${errorMessage(error)}`,
|
|
6085
6013
|
__success: false
|
|
6086
6014
|
};
|
|
6087
|
-
}
|
|
6088
|
-
|
|
6089
|
-
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
resolveSafetyPolicy(registration) {
|
|
6018
|
+
const durableState = registration.actor.getState(
|
|
6019
|
+
registration.actorKey
|
|
6020
|
+
);
|
|
6021
|
+
return {
|
|
6022
|
+
statementTimeoutMs: normalizePositiveInteger(
|
|
6023
|
+
durableState.safetyPolicy?.statementTimeoutMs,
|
|
6024
|
+
15e3
|
|
6025
|
+
),
|
|
6026
|
+
retryCount: normalizePositiveInteger(durableState.safetyPolicy?.retryCount, 3, 0),
|
|
6027
|
+
retryDelayMs: normalizePositiveInteger(durableState.safetyPolicy?.retryDelayMs, 100),
|
|
6028
|
+
retryDelayMaxMs: normalizePositiveInteger(
|
|
6029
|
+
durableState.safetyPolicy?.retryDelayMaxMs,
|
|
6030
|
+
1e3
|
|
6031
|
+
),
|
|
6032
|
+
retryDelayFactor: Number.isFinite(durableState.safetyPolicy?.retryDelayFactor) ? Math.max(1, Number(durableState.safetyPolicy?.retryDelayFactor)) : 1
|
|
6033
|
+
};
|
|
6034
|
+
}
|
|
6035
|
+
buildOnConflictClause(onConflict, params) {
|
|
6036
|
+
const { target, action } = onConflict;
|
|
6037
|
+
let sql = ` ON CONFLICT (${target.map(import_lodash_es.snakeCase).join(", ")})`;
|
|
6038
|
+
if (action.do === "update") {
|
|
6039
|
+
if (!action.set || Object.keys(action.set).length === 0) {
|
|
6040
|
+
throw new Error("Update action requires 'set' fields");
|
|
6041
|
+
}
|
|
6042
|
+
const assignments = Object.entries(action.set).map(([field, value]) => {
|
|
6043
|
+
if (typeof value === "string" && value === "excluded") {
|
|
6044
|
+
return `${(0, import_lodash_es.snakeCase)(field)} = excluded.${(0, import_lodash_es.snakeCase)(field)}`;
|
|
6045
|
+
}
|
|
6046
|
+
params.push(value);
|
|
6047
|
+
return `${(0, import_lodash_es.snakeCase)(field)} = $${params.length}`;
|
|
6048
|
+
});
|
|
6049
|
+
sql += ` DO UPDATE SET ${assignments.join(", ")}`;
|
|
6050
|
+
if (action.where) {
|
|
6051
|
+
sql += ` WHERE ${action.where}`;
|
|
6090
6052
|
}
|
|
6053
|
+
return sql;
|
|
6091
6054
|
}
|
|
6092
|
-
|
|
6055
|
+
sql += " DO NOTHING";
|
|
6056
|
+
return sql;
|
|
6093
6057
|
}
|
|
6094
6058
|
/**
|
|
6095
|
-
*
|
|
6096
|
-
* Builds parameterized queries to prevent SQL injection, appending parameters
|
|
6097
|
-
* to the provided params array and utilizing placeholders.
|
|
6098
|
-
*
|
|
6099
|
-
* @param {Object} filter - An object representing the filtering conditions with
|
|
6100
|
-
* keys as column names and values as their corresponding
|
|
6101
|
-
* desired values. Values can also be arrays for `IN` queries.
|
|
6102
|
-
* @param {any[]} params - An array for storing parameterized values, which will be
|
|
6103
|
-
* populated with the filter values for the constructed SQL clause.
|
|
6104
|
-
* @return {string} The constructed SQL WHERE clause as a string. If no conditions
|
|
6105
|
-
* are provided, an empty string is returned.
|
|
6059
|
+
* Validates database schema structure and content.
|
|
6106
6060
|
*/
|
|
6107
|
-
|
|
6108
|
-
const
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6061
|
+
validateSchema(ctx) {
|
|
6062
|
+
const schema = ctx.schema;
|
|
6063
|
+
if (!schema?.tables || typeof schema.tables !== "object") {
|
|
6064
|
+
throw new Error("Invalid schema: missing or invalid tables");
|
|
6065
|
+
}
|
|
6066
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
6067
|
+
if (!isSqlIdentifier(tableName)) {
|
|
6068
|
+
throw new Error(
|
|
6069
|
+
`Invalid table name ${tableName}. Table names must use lowercase snake_case identifiers.`
|
|
6070
|
+
);
|
|
6071
|
+
}
|
|
6072
|
+
if (!table.fields || typeof table.fields !== "object") {
|
|
6073
|
+
throw new Error(`Invalid table ${tableName}: missing fields`);
|
|
6074
|
+
}
|
|
6075
|
+
for (const [fieldName, field] of Object.entries(table.fields)) {
|
|
6076
|
+
if (!isSqlIdentifier(fieldName)) {
|
|
6077
|
+
throw new Error(
|
|
6078
|
+
`Invalid field name ${fieldName} for ${tableName}. Field names must use lowercase snake_case identifiers.`
|
|
6079
|
+
);
|
|
6080
|
+
}
|
|
6081
|
+
if (!SCHEMA_TYPES.includes(field.type)) {
|
|
6082
|
+
throw new Error(`Invalid type ${field.type} for ${tableName}.${fieldName}`);
|
|
6083
|
+
}
|
|
6084
|
+
if (field.references && !field.references.match(/^[\w]+\([\w]+\)$/)) {
|
|
6085
|
+
throw new Error(
|
|
6086
|
+
`Invalid reference ${field.references} for ${tableName}.${fieldName}`
|
|
6118
6087
|
);
|
|
6119
|
-
}
|
|
6120
|
-
|
|
6121
|
-
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
6090
|
+
for (const operation of ["query", "insert", "update", "delete"]) {
|
|
6091
|
+
const customIntents = table.customIntents?.[operation] ?? [];
|
|
6092
|
+
if (!Array.isArray(customIntents)) {
|
|
6093
|
+
throw new Error(
|
|
6094
|
+
`Invalid customIntents.${operation} for table ${tableName}: expected array`
|
|
6095
|
+
);
|
|
6096
|
+
}
|
|
6097
|
+
for (const customIntent of customIntents) {
|
|
6098
|
+
const parsed = readCustomIntentConfig(
|
|
6099
|
+
customIntent
|
|
6100
|
+
);
|
|
6101
|
+
validateIntentName(String(parsed.intent ?? ""));
|
|
6122
6102
|
}
|
|
6123
6103
|
}
|
|
6124
6104
|
}
|
|
6125
|
-
return
|
|
6105
|
+
return true;
|
|
6126
6106
|
}
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6107
|
+
sortTablesByReferences(ctx) {
|
|
6108
|
+
const schema = ctx.schema;
|
|
6109
|
+
const graph = /* @__PURE__ */ new Map();
|
|
6110
|
+
const allTables = Object.keys(schema.tables);
|
|
6111
|
+
allTables.forEach((table) => graph.set(table, /* @__PURE__ */ new Set()));
|
|
6112
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
6113
|
+
for (const field of Object.values(table.fields)) {
|
|
6114
|
+
if (field.references) {
|
|
6115
|
+
const [refTable] = field.references.split("(");
|
|
6116
|
+
if (refTable !== tableName && allTables.includes(refTable)) {
|
|
6117
|
+
graph.get(refTable)?.add(tableName);
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
if (table.foreignKeys) {
|
|
6122
|
+
for (const foreignKey of table.foreignKeys) {
|
|
6123
|
+
const refTable = foreignKey.tableName;
|
|
6124
|
+
if (refTable !== tableName && allTables.includes(refTable)) {
|
|
6125
|
+
graph.get(refTable)?.add(tableName);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6139
6129
|
}
|
|
6140
|
-
|
|
6130
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6131
|
+
const tempMark = /* @__PURE__ */ new Set();
|
|
6132
|
+
const sorted = [];
|
|
6133
|
+
let hasCycles = false;
|
|
6134
|
+
const visit = (table) => {
|
|
6135
|
+
if (tempMark.has(table)) {
|
|
6136
|
+
hasCycles = true;
|
|
6137
|
+
return;
|
|
6138
|
+
}
|
|
6139
|
+
if (visited.has(table)) return;
|
|
6140
|
+
tempMark.add(table);
|
|
6141
|
+
for (const dependent of graph.get(table) || []) {
|
|
6142
|
+
visit(dependent);
|
|
6143
|
+
}
|
|
6144
|
+
tempMark.delete(table);
|
|
6145
|
+
visited.add(table);
|
|
6146
|
+
sorted.push(table);
|
|
6147
|
+
};
|
|
6148
|
+
for (const table of allTables) {
|
|
6149
|
+
if (!visited.has(table)) {
|
|
6150
|
+
visit(table);
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
for (const table of allTables) {
|
|
6154
|
+
if (!visited.has(table)) {
|
|
6155
|
+
sorted.push(table);
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
sorted.reverse();
|
|
6159
|
+
return { ...ctx, sortedTables: sorted, hasCycles };
|
|
6141
6160
|
}
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6161
|
+
buildSchemaDdlStatements(schema, sortedTables) {
|
|
6162
|
+
const ddl = [];
|
|
6163
|
+
for (const tableName of sortedTables) {
|
|
6164
|
+
const table = schema.tables[tableName];
|
|
6165
|
+
const fieldDefs = Object.entries(table.fields).map(([fieldName, field]) => this.fieldDefinitionToSql(fieldName, field)).join(", ");
|
|
6166
|
+
ddl.push(`CREATE TABLE IF NOT EXISTS ${tableName} (${fieldDefs});`);
|
|
6167
|
+
for (const indexFields of table.indexes ?? []) {
|
|
6168
|
+
ddl.push(
|
|
6169
|
+
`CREATE INDEX IF NOT EXISTS idx_${tableName}_${indexFields.join("_")} ON ${tableName} (${indexFields.map(import_lodash_es.snakeCase).join(", ")});`
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
if (table.primaryKey) {
|
|
6173
|
+
ddl.push(
|
|
6174
|
+
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS pk_${tableName}_${table.primaryKey.join("_")};`,
|
|
6175
|
+
`ALTER TABLE ${tableName} ADD CONSTRAINT pk_${tableName}_${table.primaryKey.join("_")} PRIMARY KEY (${table.primaryKey.map(import_lodash_es.snakeCase).join(", ")});`
|
|
6176
|
+
);
|
|
6177
|
+
}
|
|
6178
|
+
for (const uniqueFields of table.uniqueConstraints ?? []) {
|
|
6179
|
+
ddl.push(
|
|
6180
|
+
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS uq_${tableName}_${uniqueFields.join("_")};`,
|
|
6181
|
+
`ALTER TABLE ${tableName} ADD CONSTRAINT uq_${tableName}_${uniqueFields.join("_")} UNIQUE (${uniqueFields.map(import_lodash_es.snakeCase).join(", ")});`
|
|
6182
|
+
);
|
|
6183
|
+
}
|
|
6184
|
+
for (const foreignKey of table.foreignKeys ?? []) {
|
|
6185
|
+
const fkName = `fk_${tableName}_${foreignKey.fields.join("_")}`;
|
|
6186
|
+
ddl.push(
|
|
6187
|
+
`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${fkName};`,
|
|
6188
|
+
`ALTER TABLE ${tableName} ADD CONSTRAINT ${fkName} FOREIGN KEY (${foreignKey.fields.map(import_lodash_es.snakeCase).join(", ")}) REFERENCES ${foreignKey.tableName} (${foreignKey.referenceFields.map(import_lodash_es.snakeCase).join(", ")});`
|
|
6189
|
+
);
|
|
6190
|
+
}
|
|
6191
|
+
for (const [triggerName, trigger] of Object.entries(table.triggers ?? {})) {
|
|
6192
|
+
ddl.push(
|
|
6193
|
+
`CREATE OR REPLACE TRIGGER ${triggerName} ${trigger.when} ${trigger.event} ON ${tableName} FOR EACH STATEMENT EXECUTE FUNCTION ${trigger.function};`
|
|
6194
|
+
);
|
|
6195
|
+
}
|
|
6196
|
+
if (table.initialData) {
|
|
6197
|
+
ddl.push(
|
|
6198
|
+
`INSERT INTO ${tableName} (${table.initialData.fields.map(import_lodash_es.snakeCase).join(", ")}) VALUES ${table.initialData.data.map(
|
|
6199
|
+
(row) => `(${row.map((value) => {
|
|
6200
|
+
if (value === void 0) return "NULL";
|
|
6201
|
+
if (value === null) return "NULL";
|
|
6202
|
+
if (typeof value === "number") return String(value);
|
|
6203
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
6204
|
+
const stringValue = String(value);
|
|
6205
|
+
return `'${stringValue.replace(/'/g, "''")}'`;
|
|
6206
|
+
}).join(", ")})`
|
|
6207
|
+
).join(", ")} ON CONFLICT DO NOTHING;`
|
|
6208
|
+
);
|
|
6163
6209
|
}
|
|
6164
6210
|
}
|
|
6165
|
-
return
|
|
6211
|
+
return ddl;
|
|
6166
6212
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6213
|
+
fieldDefinitionToSql(fieldName, field) {
|
|
6214
|
+
let definition = `${(0, import_lodash_es.snakeCase)(fieldName)} ${field.type.toUpperCase()}`;
|
|
6215
|
+
if (field.type === "varchar") {
|
|
6216
|
+
definition += `(${field.constraints?.maxLength ?? 255})`;
|
|
6217
|
+
}
|
|
6218
|
+
if (field.type === "decimal") {
|
|
6219
|
+
definition += `(${field.constraints?.precision ?? 10},${field.constraints?.scale ?? 2})`;
|
|
6220
|
+
}
|
|
6221
|
+
if (field.primary) definition += " PRIMARY KEY";
|
|
6222
|
+
if (field.unique) definition += " UNIQUE";
|
|
6223
|
+
if (field.default !== void 0) {
|
|
6224
|
+
definition += ` DEFAULT ${field.default === "" ? "''" : String(field.default)}`;
|
|
6225
|
+
}
|
|
6226
|
+
if (field.required && !field.nullable) definition += " NOT NULL";
|
|
6227
|
+
if (field.nullable) definition += " NULL";
|
|
6228
|
+
if (field.generated) {
|
|
6229
|
+
definition += ` GENERATED ALWAYS AS ${field.generated.toUpperCase()} STORED`;
|
|
6230
|
+
}
|
|
6231
|
+
if (field.references) {
|
|
6232
|
+
definition += ` REFERENCES ${field.references} ON DELETE ${field.onDelete || "CASCADE"}`;
|
|
6233
|
+
}
|
|
6234
|
+
if (field.constraints?.check) {
|
|
6235
|
+
definition += ` CHECK (${field.constraints.check})`;
|
|
6236
|
+
}
|
|
6237
|
+
return definition;
|
|
6238
|
+
}
|
|
6239
|
+
async applyDdlStatements(pool, statements) {
|
|
6240
|
+
for (const sql of statements) {
|
|
6241
|
+
try {
|
|
6242
|
+
await pool.query(sql);
|
|
6243
|
+
} catch (error) {
|
|
6244
|
+
CadenzaService.log(
|
|
6245
|
+
"Error applying DDL statement",
|
|
6246
|
+
{
|
|
6247
|
+
sql,
|
|
6248
|
+
error: errorMessage(error)
|
|
6249
|
+
},
|
|
6250
|
+
"error"
|
|
6251
|
+
);
|
|
6252
|
+
throw error;
|
|
6196
6253
|
}
|
|
6197
|
-
await client.query("COMMIT");
|
|
6198
|
-
return result || {};
|
|
6199
|
-
} catch (error) {
|
|
6200
|
-
await client.query("ROLLBACK");
|
|
6201
|
-
throw error;
|
|
6202
|
-
} finally {
|
|
6203
|
-
client.release();
|
|
6204
6254
|
}
|
|
6205
6255
|
}
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
const
|
|
6218
|
-
const
|
|
6256
|
+
generateDatabaseTasks(registration) {
|
|
6257
|
+
for (const [tableName, table] of Object.entries(registration.schema.tables)) {
|
|
6258
|
+
this.createDatabaseTask(registration, "query", tableName, table);
|
|
6259
|
+
this.createDatabaseTask(registration, "insert", tableName, table);
|
|
6260
|
+
this.createDatabaseTask(registration, "update", tableName, table);
|
|
6261
|
+
this.createDatabaseTask(registration, "delete", tableName, table);
|
|
6262
|
+
this.createDatabaseMacroTasks(registration, tableName, table);
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
createDatabaseMacroTasks(registration, tableName, table) {
|
|
6266
|
+
const querySchema = this.getInputSchema("query", tableName, table);
|
|
6267
|
+
const insertSchema = this.getInputSchema("insert", tableName, table);
|
|
6268
|
+
const queryMacroOperations = [
|
|
6269
|
+
"count",
|
|
6270
|
+
"exists",
|
|
6271
|
+
"one",
|
|
6272
|
+
"aggregate"
|
|
6273
|
+
];
|
|
6274
|
+
for (const macroOperation of queryMacroOperations) {
|
|
6275
|
+
const intentName = `${macroOperation}-pg-${registration.actorToken}-${tableName}`;
|
|
6276
|
+
if (registration.intentNames.has(intentName)) {
|
|
6277
|
+
throw new Error(
|
|
6278
|
+
`Duplicate macro intent '${intentName}' detected for table '${tableName}' in actor '${registration.actorName}'`
|
|
6279
|
+
);
|
|
6280
|
+
}
|
|
6281
|
+
registration.intentNames.add(intentName);
|
|
6282
|
+
CadenzaService.defineIntent({
|
|
6283
|
+
name: intentName,
|
|
6284
|
+
description: `Macro ${macroOperation} operation for table ${tableName}`,
|
|
6285
|
+
input: querySchema
|
|
6286
|
+
});
|
|
6287
|
+
CadenzaService.createThrottledTask(
|
|
6288
|
+
`${macroOperation.toUpperCase()} ${tableName}`,
|
|
6289
|
+
registration.actor.task(
|
|
6290
|
+
async ({ input }) => {
|
|
6291
|
+
const payload = typeof input.queryData === "object" && input.queryData ? input.queryData : input;
|
|
6292
|
+
const result = await this.queryFunction(registration, tableName, {
|
|
6293
|
+
...payload,
|
|
6294
|
+
queryMode: macroOperation
|
|
6295
|
+
});
|
|
6296
|
+
return {
|
|
6297
|
+
...input,
|
|
6298
|
+
...result
|
|
6299
|
+
};
|
|
6300
|
+
},
|
|
6301
|
+
{ mode: "read" }
|
|
6302
|
+
),
|
|
6303
|
+
(context) => context?.__metadata?.__executionTraceId ?? context?.__executionTraceId ?? "default",
|
|
6304
|
+
`Macro ${macroOperation} task for ${tableName}`,
|
|
6305
|
+
{
|
|
6306
|
+
isMeta: registration.options.isMeta,
|
|
6307
|
+
isSubMeta: registration.options.isMeta,
|
|
6308
|
+
validateInputContext: registration.options.securityProfile !== "low",
|
|
6309
|
+
inputSchema: querySchema
|
|
6310
|
+
}
|
|
6311
|
+
).respondsTo(intentName);
|
|
6312
|
+
}
|
|
6313
|
+
const upsertIntentName = `upsert-pg-${registration.actorToken}-${tableName}`;
|
|
6314
|
+
if (registration.intentNames.has(upsertIntentName)) {
|
|
6315
|
+
throw new Error(
|
|
6316
|
+
`Duplicate macro intent '${upsertIntentName}' detected for table '${tableName}' in actor '${registration.actorName}'`
|
|
6317
|
+
);
|
|
6318
|
+
}
|
|
6319
|
+
registration.intentNames.add(upsertIntentName);
|
|
6320
|
+
CadenzaService.defineIntent({
|
|
6321
|
+
name: upsertIntentName,
|
|
6322
|
+
description: `Macro upsert operation for table ${tableName}`,
|
|
6323
|
+
input: insertSchema
|
|
6324
|
+
});
|
|
6325
|
+
CadenzaService.createThrottledTask(
|
|
6326
|
+
`UPSERT ${tableName}`,
|
|
6327
|
+
registration.actor.task(
|
|
6328
|
+
async ({ input }) => {
|
|
6329
|
+
const payload = typeof input.queryData === "object" && input.queryData ? input.queryData : input;
|
|
6330
|
+
if (!payload.onConflict) {
|
|
6331
|
+
return {
|
|
6332
|
+
...input,
|
|
6333
|
+
errored: true,
|
|
6334
|
+
__success: false,
|
|
6335
|
+
__error: `Macro upsert requires 'onConflict' payload for table '${tableName}'`
|
|
6336
|
+
};
|
|
6337
|
+
}
|
|
6338
|
+
const result = await this.insertFunction(registration, tableName, payload);
|
|
6339
|
+
return {
|
|
6340
|
+
...input,
|
|
6341
|
+
...result
|
|
6342
|
+
};
|
|
6343
|
+
},
|
|
6344
|
+
{ mode: "write" }
|
|
6345
|
+
),
|
|
6346
|
+
(context) => context?.__metadata?.__executionTraceId ?? context?.__executionTraceId ?? "default",
|
|
6347
|
+
`Macro upsert task for ${tableName}`,
|
|
6348
|
+
{
|
|
6349
|
+
isMeta: registration.options.isMeta,
|
|
6350
|
+
isSubMeta: registration.options.isMeta,
|
|
6351
|
+
validateInputContext: registration.options.securityProfile !== "low",
|
|
6352
|
+
inputSchema: insertSchema
|
|
6353
|
+
}
|
|
6354
|
+
).respondsTo(upsertIntentName);
|
|
6355
|
+
}
|
|
6356
|
+
createDatabaseTask(registration, op, tableName, table) {
|
|
6357
|
+
const opAction = op === "query" ? "queried" : op === "insert" ? "inserted" : op === "update" ? "updated" : "deleted";
|
|
6358
|
+
const defaultSignal = `global.${registration.options.isMeta ? "meta." : ""}${tableName}.${opAction}`;
|
|
6219
6359
|
const taskName = `${op.charAt(0).toUpperCase() + op.slice(1)} ${tableName}`;
|
|
6220
6360
|
const schema = this.getInputSchema(op, tableName, table);
|
|
6221
|
-
const
|
|
6222
|
-
|
|
6223
|
-
|
|
6361
|
+
const databaseTaskFunction = registration.actor.task(
|
|
6362
|
+
async ({ input, emit }) => {
|
|
6363
|
+
let context = { ...input };
|
|
6364
|
+
let payloadModifiedByTriggers = false;
|
|
6224
6365
|
for (const action of Object.keys(table.customSignals?.triggers ?? {})) {
|
|
6225
|
-
const
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
for (const triggerCondition of triggerConditions ?? []) {
|
|
6232
|
-
if (triggerCondition.condition && !triggerCondition.condition(context)) {
|
|
6366
|
+
const triggerDefinitions = table.customSignals?.triggers?.[action];
|
|
6367
|
+
for (const trigger of triggerDefinitions ?? []) {
|
|
6368
|
+
if (typeof trigger === "string") {
|
|
6369
|
+
continue;
|
|
6370
|
+
}
|
|
6371
|
+
if (trigger.condition && !trigger.condition(context)) {
|
|
6233
6372
|
return {
|
|
6234
6373
|
failed: true,
|
|
6235
|
-
|
|
6374
|
+
__success: false,
|
|
6375
|
+
__error: `Condition for signal trigger failed: ${trigger.signal}`
|
|
6236
6376
|
};
|
|
6237
6377
|
}
|
|
6238
|
-
|
|
6239
|
-
const triggerQueryData = (
|
|
6240
|
-
// @ts-ignore
|
|
6241
|
-
table.customSignals?.triggers?.[action].filter(
|
|
6242
|
-
(trigger) => trigger.queryData
|
|
6243
|
-
)
|
|
6244
|
-
);
|
|
6245
|
-
for (const queryData of triggerQueryData ?? []) {
|
|
6246
|
-
if (context.queryData) {
|
|
6378
|
+
if (trigger.queryData) {
|
|
6247
6379
|
context.queryData = {
|
|
6248
|
-
...context.queryData,
|
|
6249
|
-
...queryData
|
|
6250
|
-
};
|
|
6251
|
-
} else {
|
|
6252
|
-
context = {
|
|
6253
|
-
...context,
|
|
6254
|
-
...queryData
|
|
6380
|
+
...context.queryData ?? {},
|
|
6381
|
+
...trigger.queryData
|
|
6255
6382
|
};
|
|
6383
|
+
payloadModifiedByTriggers = true;
|
|
6256
6384
|
}
|
|
6257
6385
|
}
|
|
6258
6386
|
}
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6387
|
+
const operationPayload = typeof context.queryData === "object" && context.queryData ? context.queryData : context;
|
|
6388
|
+
this.validateOperationPayload(
|
|
6389
|
+
registration,
|
|
6390
|
+
op,
|
|
6391
|
+
tableName,
|
|
6392
|
+
table,
|
|
6393
|
+
operationPayload,
|
|
6394
|
+
{
|
|
6395
|
+
enforceFieldAllowlist: registration.options.securityProfile === "low" || payloadModifiedByTriggers
|
|
6396
|
+
}
|
|
6397
|
+
);
|
|
6398
|
+
let result;
|
|
6399
|
+
if (op === "query") {
|
|
6400
|
+
result = await this.queryFunction(registration, tableName, operationPayload);
|
|
6401
|
+
} else if (op === "insert") {
|
|
6402
|
+
result = await this.insertFunction(registration, tableName, operationPayload);
|
|
6403
|
+
} else if (op === "update") {
|
|
6404
|
+
result = await this.updateFunction(registration, tableName, operationPayload);
|
|
6405
|
+
} else {
|
|
6406
|
+
result = await this.deleteFunction(registration, tableName, operationPayload);
|
|
6275
6407
|
}
|
|
6408
|
+
context = {
|
|
6409
|
+
...context,
|
|
6410
|
+
...result
|
|
6411
|
+
};
|
|
6276
6412
|
if (!context.errored) {
|
|
6277
6413
|
for (const signal of table.customSignals?.emissions?.[op] ?? []) {
|
|
6414
|
+
if (typeof signal === "string") {
|
|
6415
|
+
emit(signal, context);
|
|
6416
|
+
continue;
|
|
6417
|
+
}
|
|
6278
6418
|
if (signal.condition && !signal.condition(context)) {
|
|
6279
6419
|
continue;
|
|
6280
6420
|
}
|
|
6281
|
-
emit(signal.signal
|
|
6421
|
+
emit(signal.signal, context);
|
|
6282
6422
|
}
|
|
6283
6423
|
}
|
|
6284
6424
|
if (tableName !== "system_log" && context.errored) {
|
|
@@ -6308,49 +6448,263 @@ var DatabaseController = class _DatabaseController {
|
|
|
6308
6448
|
delete context.offset;
|
|
6309
6449
|
return context;
|
|
6310
6450
|
},
|
|
6451
|
+
{ mode: op === "query" ? "read" : "write" }
|
|
6452
|
+
);
|
|
6453
|
+
const task = CadenzaService.createThrottledTask(
|
|
6454
|
+
taskName,
|
|
6455
|
+
databaseTaskFunction,
|
|
6311
6456
|
(context) => context?.__metadata?.__executionTraceId ?? context?.__executionTraceId ?? "default",
|
|
6312
|
-
`Auto-generated ${op} task for ${tableName}`,
|
|
6457
|
+
`Auto-generated ${op} task for ${tableName} (PostgresActor)`,
|
|
6313
6458
|
{
|
|
6314
|
-
isMeta: options.isMeta,
|
|
6315
|
-
isSubMeta: options.isMeta,
|
|
6316
|
-
validateInputContext: options.securityProfile !== "low",
|
|
6459
|
+
isMeta: registration.options.isMeta,
|
|
6460
|
+
isSubMeta: registration.options.isMeta,
|
|
6461
|
+
validateInputContext: registration.options.securityProfile !== "low",
|
|
6317
6462
|
inputSchema: schema
|
|
6318
6463
|
}
|
|
6319
6464
|
).doOn(
|
|
6320
|
-
...table.customSignals?.triggers?.[op]?.map(
|
|
6321
|
-
|
|
6322
|
-
|
|
6465
|
+
...table.customSignals?.triggers?.[op]?.map(
|
|
6466
|
+
(signal) => typeof signal === "string" ? signal : signal.signal
|
|
6467
|
+
) ?? []
|
|
6323
6468
|
).emits(defaultSignal).attachSignal(
|
|
6324
|
-
...table.customSignals?.emissions?.[op]?.map(
|
|
6325
|
-
|
|
6326
|
-
|
|
6469
|
+
...table.customSignals?.emissions?.[op]?.map(
|
|
6470
|
+
(signal) => typeof signal === "string" ? signal : signal.signal
|
|
6471
|
+
) ?? []
|
|
6327
6472
|
);
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
{
|
|
6339
|
-
tableName,
|
|
6340
|
-
warning
|
|
6341
|
-
},
|
|
6342
|
-
"warning"
|
|
6473
|
+
const { intents } = resolveTableOperationIntents(
|
|
6474
|
+
registration.actorName,
|
|
6475
|
+
tableName,
|
|
6476
|
+
table,
|
|
6477
|
+
op,
|
|
6478
|
+
schema
|
|
6479
|
+
);
|
|
6480
|
+
for (const intent of intents) {
|
|
6481
|
+
if (registration.intentNames.has(intent.name)) {
|
|
6482
|
+
throw new Error(
|
|
6483
|
+
`Duplicate auto/custom intent '${intent.name}' detected while generating ${op} task for table '${tableName}' in actor '${registration.actorName}'`
|
|
6343
6484
|
);
|
|
6344
6485
|
}
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6486
|
+
registration.intentNames.add(intent.name);
|
|
6487
|
+
CadenzaService.defineIntent({
|
|
6488
|
+
name: intent.name,
|
|
6489
|
+
description: intent.description,
|
|
6490
|
+
input: intent.input
|
|
6491
|
+
});
|
|
6492
|
+
}
|
|
6493
|
+
task.respondsTo(...intents.map((intent) => intent.name));
|
|
6494
|
+
}
|
|
6495
|
+
validateOperationPayload(registration, operation, tableName, table, payload, options) {
|
|
6496
|
+
const allowedFields = new Set(Object.keys(table.fields));
|
|
6497
|
+
const resolvedMode = payload.queryMode ?? "rows";
|
|
6498
|
+
if (!["rows", "count", "exists", "one", "aggregate"].includes(resolvedMode)) {
|
|
6499
|
+
throw new Error(`Unsupported queryMode '${String(payload.queryMode)}'`);
|
|
6500
|
+
}
|
|
6501
|
+
const assertAllowedField = (fieldName, label) => {
|
|
6502
|
+
if (!allowedFields.has(fieldName)) {
|
|
6503
|
+
throw new Error(
|
|
6504
|
+
`Invalid field '${fieldName}' in ${label} for ${operation} on ${tableName}`
|
|
6505
|
+
);
|
|
6506
|
+
}
|
|
6507
|
+
};
|
|
6508
|
+
const aggregateDefinitions = Array.isArray(payload.aggregates) ? payload.aggregates : [];
|
|
6509
|
+
const aggregateSortAllowlist = /* @__PURE__ */ new Set();
|
|
6510
|
+
if (resolvedMode === "aggregate") {
|
|
6511
|
+
if (aggregateDefinitions.length === 0) {
|
|
6512
|
+
throw new Error(
|
|
6513
|
+
`Aggregate queryMode requires at least one aggregate on table '${tableName}'`
|
|
6514
|
+
);
|
|
6515
|
+
}
|
|
6516
|
+
for (const groupField of payload.groupBy ?? []) {
|
|
6517
|
+
assertAllowedField(groupField, "groupBy");
|
|
6518
|
+
aggregateSortAllowlist.add(groupField);
|
|
6519
|
+
}
|
|
6520
|
+
for (const [index, aggregate] of aggregateDefinitions.entries()) {
|
|
6521
|
+
if (!isSupportedAggregateFunction(aggregate.fn)) {
|
|
6522
|
+
throw new Error(
|
|
6523
|
+
`Unsupported aggregate function '${String(aggregate.fn)}' on table '${tableName}'`
|
|
6524
|
+
);
|
|
6525
|
+
}
|
|
6526
|
+
if (aggregate.fn !== "count" && !aggregate.field) {
|
|
6527
|
+
throw new Error(
|
|
6528
|
+
`Aggregate '${aggregate.fn}' requires field on table '${tableName}'`
|
|
6529
|
+
);
|
|
6530
|
+
}
|
|
6531
|
+
if (aggregate.field) {
|
|
6532
|
+
assertAllowedField(aggregate.field, "aggregates.field");
|
|
6533
|
+
}
|
|
6534
|
+
aggregateSortAllowlist.add(buildAggregateAlias(aggregate, index));
|
|
6535
|
+
}
|
|
6536
|
+
} else if (aggregateDefinitions.length > 0 || (payload.groupBy ?? []).length > 0) {
|
|
6537
|
+
throw new Error(
|
|
6538
|
+
`aggregates/groupBy payload requires queryMode='aggregate' on table '${tableName}'`
|
|
6539
|
+
);
|
|
6540
|
+
}
|
|
6541
|
+
if (options.enforceFieldAllowlist) {
|
|
6542
|
+
if (payload.fields) {
|
|
6543
|
+
for (const field of payload.fields) {
|
|
6544
|
+
assertAllowedField(field, "fields");
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
if (payload.filter) {
|
|
6548
|
+
for (const field of Object.keys(payload.filter)) {
|
|
6549
|
+
assertAllowedField(field, "filter");
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
if (payload.data) {
|
|
6553
|
+
const rows = resolveDataRows(payload.data);
|
|
6554
|
+
for (const row of rows) {
|
|
6555
|
+
for (const field of Object.keys(row)) {
|
|
6556
|
+
assertAllowedField(field, "data");
|
|
6557
|
+
}
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
if (payload.sort) {
|
|
6562
|
+
for (const field of Object.keys(payload.sort)) {
|
|
6563
|
+
if (resolvedMode === "aggregate" && aggregateSortAllowlist.has(field)) {
|
|
6564
|
+
continue;
|
|
6565
|
+
}
|
|
6566
|
+
assertAllowedField(field, "sort");
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
if (payload.onConflict) {
|
|
6570
|
+
for (const conflictField of payload.onConflict.target ?? []) {
|
|
6571
|
+
assertAllowedField(conflictField, "onConflict.target");
|
|
6572
|
+
}
|
|
6573
|
+
for (const setField of Object.keys(payload.onConflict.action?.set ?? {})) {
|
|
6574
|
+
assertAllowedField(setField, "onConflict.action.set");
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
if (payload.joins) {
|
|
6578
|
+
this.validateJoinPayload(registration.schema, payload.joins);
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
validateJoinPayload(schema, joins) {
|
|
6582
|
+
for (const [joinTableName, joinDefinition] of Object.entries(joins)) {
|
|
6583
|
+
if (!schema.tables[joinTableName]) {
|
|
6584
|
+
throw new Error(`Invalid join table '${joinTableName}'. Table does not exist in schema.`);
|
|
6585
|
+
}
|
|
6586
|
+
const joinTable = schema.tables[joinTableName];
|
|
6587
|
+
for (const field of joinDefinition.fields ?? []) {
|
|
6588
|
+
if (!joinTable.fields[field]) {
|
|
6589
|
+
throw new Error(
|
|
6590
|
+
`Invalid join field '${field}' on joined table '${joinTableName}'`
|
|
6591
|
+
);
|
|
6592
|
+
}
|
|
6593
|
+
}
|
|
6594
|
+
if (joinDefinition.filter) {
|
|
6595
|
+
for (const filterField of Object.keys(joinDefinition.filter)) {
|
|
6596
|
+
if (!joinTable.fields[filterField]) {
|
|
6597
|
+
throw new Error(
|
|
6598
|
+
`Invalid join filter field '${filterField}' on joined table '${joinTableName}'`
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
if (joinDefinition.joins) {
|
|
6604
|
+
this.validateJoinPayload(schema, joinDefinition.joins);
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
toCamelCase(rows) {
|
|
6609
|
+
return rows.map((row) => {
|
|
6610
|
+
const camelCasedRow = {};
|
|
6611
|
+
for (const [key, value] of Object.entries(row)) {
|
|
6612
|
+
camelCasedRow[(0, import_lodash_es.camelCase)(key)] = value;
|
|
6613
|
+
}
|
|
6614
|
+
return camelCasedRow;
|
|
6615
|
+
});
|
|
6616
|
+
}
|
|
6617
|
+
buildWhereClause(filter, params) {
|
|
6618
|
+
const conditions = [];
|
|
6619
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
6620
|
+
if (value !== void 0) {
|
|
6621
|
+
if (Array.isArray(value)) {
|
|
6622
|
+
const placeholders = value.map((entry) => {
|
|
6623
|
+
params.push(entry);
|
|
6624
|
+
return `$${params.length}`;
|
|
6625
|
+
}).join(", ");
|
|
6626
|
+
conditions.push(`${(0, import_lodash_es.snakeCase)(key)} IN (${placeholders})`);
|
|
6627
|
+
} else {
|
|
6628
|
+
params.push(value);
|
|
6629
|
+
conditions.push(`${(0, import_lodash_es.snakeCase)(key)} = $${params.length}`);
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
return conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
6634
|
+
}
|
|
6635
|
+
buildJoinClause(joins) {
|
|
6636
|
+
let joinSql = "";
|
|
6637
|
+
for (const [table, join] of Object.entries(joins)) {
|
|
6638
|
+
const alias = join.alias ? ` ${join.alias}` : "";
|
|
6639
|
+
joinSql += ` LEFT JOIN ${(0, import_lodash_es.snakeCase)(table)}${alias} ON ${join.on}`;
|
|
6640
|
+
if (join.joins) joinSql += " " + this.buildJoinClause(join.joins);
|
|
6641
|
+
}
|
|
6642
|
+
return joinSql;
|
|
6643
|
+
}
|
|
6644
|
+
async resolveNestedData(registration, data, tableName) {
|
|
6645
|
+
if (Array.isArray(data)) {
|
|
6646
|
+
return Promise.all(
|
|
6647
|
+
data.map((entry) => this.resolveNestedData(registration, entry, tableName))
|
|
6648
|
+
);
|
|
6649
|
+
}
|
|
6650
|
+
if (typeof data !== "object" || data === null) {
|
|
6651
|
+
return data;
|
|
6652
|
+
}
|
|
6653
|
+
const resolved = { ...data };
|
|
6654
|
+
for (const [key, value] of Object.entries(data)) {
|
|
6655
|
+
if (typeof value === "object" && value !== null && "subOperation" in value) {
|
|
6656
|
+
const subOperation = value;
|
|
6657
|
+
resolved[key] = await this.executeSubOperation(registration, subOperation);
|
|
6658
|
+
} else if (typeof value === "string" && ["increment", "decrement", "set"].includes(value)) {
|
|
6659
|
+
resolved[key] = { __effect: value };
|
|
6660
|
+
} else if (typeof value === "object" && value !== null) {
|
|
6661
|
+
resolved[key] = await this.resolveNestedData(registration, value, tableName);
|
|
6351
6662
|
}
|
|
6352
|
-
task.respondsTo(...intents.map((intent) => intent.name));
|
|
6353
6663
|
}
|
|
6664
|
+
return resolved;
|
|
6665
|
+
}
|
|
6666
|
+
async executeSubOperation(registration, operation) {
|
|
6667
|
+
const targetTableName = operation.table;
|
|
6668
|
+
if (!registration.schema.tables[targetTableName]) {
|
|
6669
|
+
throw new Error(
|
|
6670
|
+
`Sub-operation table '${targetTableName}' does not exist in actor schema`
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
const pool = this.getPoolOrThrow(registration);
|
|
6674
|
+
const safetyPolicy = this.resolveSafetyPolicy(registration);
|
|
6675
|
+
return this.executeWithTransaction(pool, true, async (client) => {
|
|
6676
|
+
if (operation.subOperation === "insert") {
|
|
6677
|
+
const resolvedData = await this.resolveNestedData(
|
|
6678
|
+
registration,
|
|
6679
|
+
operation.data,
|
|
6680
|
+
operation.table
|
|
6681
|
+
);
|
|
6682
|
+
const row = ensurePlainObject(resolvedData, "sub-operation insert data");
|
|
6683
|
+
const keys = Object.keys(row);
|
|
6684
|
+
const params = Object.values(row);
|
|
6685
|
+
const sql2 = `INSERT INTO ${operation.table} (${keys.map((key) => (0, import_lodash_es.snakeCase)(key)).join(", ")}) VALUES (${params.map((_, index) => `$${index + 1}`).join(", ")}) ON CONFLICT DO NOTHING RETURNING ${operation.return ?? "*"}`;
|
|
6686
|
+
const result2 = await this.withTimeout(
|
|
6687
|
+
() => client.query(sql2, params),
|
|
6688
|
+
safetyPolicy.statementTimeoutMs,
|
|
6689
|
+
`Sub-operation insert timeout on table ${operation.table}`
|
|
6690
|
+
);
|
|
6691
|
+
const returnKey2 = operation.return ?? "uuid";
|
|
6692
|
+
if (result2.rows[0]?.[returnKey2] !== void 0) {
|
|
6693
|
+
return result2.rows[0][returnKey2];
|
|
6694
|
+
}
|
|
6695
|
+
return row[returnKey2] ?? row.uuid ?? {};
|
|
6696
|
+
}
|
|
6697
|
+
const queryParams = [];
|
|
6698
|
+
const whereClause = this.buildWhereClause(operation.filter || {}, queryParams);
|
|
6699
|
+
const sql = `SELECT ${(operation.fields ?? ["*"]).map((field) => field === "*" ? field : (0, import_lodash_es.snakeCase)(field)).join(", ")} FROM ${operation.table} ${whereClause} LIMIT 1`;
|
|
6700
|
+
const result = await this.withTimeout(
|
|
6701
|
+
() => client.query(sql, queryParams),
|
|
6702
|
+
safetyPolicy.statementTimeoutMs,
|
|
6703
|
+
`Sub-operation query timeout on table ${operation.table}`
|
|
6704
|
+
);
|
|
6705
|
+
const returnKey = operation.return ?? "uuid";
|
|
6706
|
+
return result.rows[0]?.[returnKey] ?? {};
|
|
6707
|
+
});
|
|
6354
6708
|
}
|
|
6355
6709
|
getInputSchema(op, tableName, table) {
|
|
6356
6710
|
const inputSchema = {
|
|
@@ -6369,66 +6723,43 @@ var DatabaseController = class _DatabaseController {
|
|
|
6369
6723
|
}
|
|
6370
6724
|
inputSchema.properties.transaction = getTransactionSchema();
|
|
6371
6725
|
inputSchema.properties.queryData.properties.transaction = inputSchema.properties.transaction;
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
|
|
6410
|
-
inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
|
|
6411
|
-
inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
|
|
6412
|
-
break;
|
|
6413
|
-
case "update":
|
|
6414
|
-
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
6415
|
-
table,
|
|
6416
|
-
tableName
|
|
6417
|
-
);
|
|
6418
|
-
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
6419
|
-
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
|
|
6420
|
-
table,
|
|
6421
|
-
tableName
|
|
6422
|
-
);
|
|
6423
|
-
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
6424
|
-
break;
|
|
6425
|
-
case "delete":
|
|
6426
|
-
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
6427
|
-
table,
|
|
6428
|
-
tableName
|
|
6429
|
-
);
|
|
6430
|
-
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
6431
|
-
break;
|
|
6726
|
+
if (op === "insert" || op === "update") {
|
|
6727
|
+
inputSchema.properties.data = getInsertDataSchemaFromTable(table, tableName);
|
|
6728
|
+
inputSchema.properties.queryData.properties.data = inputSchema.properties.data;
|
|
6729
|
+
}
|
|
6730
|
+
if (op === "insert") {
|
|
6731
|
+
inputSchema.properties.batch = getQueryBatchSchemaFromTable();
|
|
6732
|
+
inputSchema.properties.queryData.properties.batch = inputSchema.properties.batch;
|
|
6733
|
+
inputSchema.properties.onConflict = getQueryOnConflictSchemaFromTable(
|
|
6734
|
+
table,
|
|
6735
|
+
tableName
|
|
6736
|
+
);
|
|
6737
|
+
inputSchema.properties.queryData.properties.onConflict = inputSchema.properties.onConflict;
|
|
6738
|
+
}
|
|
6739
|
+
if (op === "query" || op === "update" || op === "delete") {
|
|
6740
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(table, tableName);
|
|
6741
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
6742
|
+
}
|
|
6743
|
+
if (op === "query") {
|
|
6744
|
+
inputSchema.properties.queryMode = getQueryModeSchema();
|
|
6745
|
+
inputSchema.properties.queryData.properties.queryMode = inputSchema.properties.queryMode;
|
|
6746
|
+
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(table, tableName);
|
|
6747
|
+
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
6748
|
+
inputSchema.properties.joins = getQueryJoinsSchemaFromTable(table, tableName);
|
|
6749
|
+
inputSchema.properties.queryData.properties.joins = inputSchema.properties.joins;
|
|
6750
|
+
inputSchema.properties.sort = getQuerySortSchemaFromTable(table, tableName);
|
|
6751
|
+
inputSchema.properties.queryData.properties.sort = inputSchema.properties.sort;
|
|
6752
|
+
inputSchema.properties.aggregates = getQueryAggregatesSchemaFromTable(
|
|
6753
|
+
table,
|
|
6754
|
+
tableName
|
|
6755
|
+
);
|
|
6756
|
+
inputSchema.properties.queryData.properties.aggregates = inputSchema.properties.aggregates;
|
|
6757
|
+
inputSchema.properties.groupBy = getQueryGroupBySchemaFromTable(table, tableName);
|
|
6758
|
+
inputSchema.properties.queryData.properties.groupBy = inputSchema.properties.groupBy;
|
|
6759
|
+
inputSchema.properties.limit = getQueryLimitSchemaFromTable();
|
|
6760
|
+
inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
|
|
6761
|
+
inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
|
|
6762
|
+
inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
|
|
6432
6763
|
}
|
|
6433
6764
|
return inputSchema;
|
|
6434
6765
|
}
|
|
@@ -6438,13 +6769,13 @@ function getInsertDataSchemaFromTable(table, tableName) {
|
|
|
6438
6769
|
type: "object",
|
|
6439
6770
|
properties: {
|
|
6440
6771
|
...Object.fromEntries(
|
|
6441
|
-
Object.entries(table.fields).map((field) => {
|
|
6772
|
+
Object.entries(table.fields).map(([fieldName, field]) => {
|
|
6442
6773
|
return [
|
|
6443
|
-
|
|
6774
|
+
fieldName,
|
|
6444
6775
|
{
|
|
6445
6776
|
value: {
|
|
6446
|
-
type: tableFieldTypeToSchemaType(field
|
|
6447
|
-
description: `Inferred from field '${
|
|
6777
|
+
type: tableFieldTypeToSchemaType(field.type),
|
|
6778
|
+
description: `Inferred from field '${fieldName}' of type [${field.type}] on table ${tableName}.`
|
|
6448
6779
|
},
|
|
6449
6780
|
effect: {
|
|
6450
6781
|
type: "string",
|
|
@@ -6493,7 +6824,7 @@ function getInsertDataSchemaFromTable(table, tableName) {
|
|
|
6493
6824
|
})
|
|
6494
6825
|
)
|
|
6495
6826
|
},
|
|
6496
|
-
required: Object.entries(table.fields).filter((field) => field
|
|
6827
|
+
required: Object.entries(table.fields).filter(([, field]) => field.required || field.primary).map(([fieldName]) => fieldName),
|
|
6497
6828
|
strict: true
|
|
6498
6829
|
};
|
|
6499
6830
|
return {
|
|
@@ -6509,18 +6840,18 @@ function getQueryFilterSchemaFromTable(table, tableName) {
|
|
|
6509
6840
|
type: "object",
|
|
6510
6841
|
properties: {
|
|
6511
6842
|
...Object.fromEntries(
|
|
6512
|
-
Object.entries(table.fields).map((field) => {
|
|
6843
|
+
Object.entries(table.fields).map(([fieldName, field]) => {
|
|
6513
6844
|
return [
|
|
6514
|
-
|
|
6845
|
+
fieldName,
|
|
6515
6846
|
{
|
|
6516
6847
|
value: {
|
|
6517
|
-
type: tableFieldTypeToSchemaType(field
|
|
6518
|
-
description: `Inferred from field '${
|
|
6848
|
+
type: tableFieldTypeToSchemaType(field.type),
|
|
6849
|
+
description: `Inferred from field '${fieldName}' of type [${field.type}] on table ${tableName}.`
|
|
6519
6850
|
},
|
|
6520
6851
|
in: {
|
|
6521
6852
|
type: "array",
|
|
6522
6853
|
items: {
|
|
6523
|
-
type: tableFieldTypeToSchemaType(field
|
|
6854
|
+
type: tableFieldTypeToSchemaType(field.type)
|
|
6524
6855
|
}
|
|
6525
6856
|
}
|
|
6526
6857
|
}
|
|
@@ -6529,7 +6860,7 @@ function getQueryFilterSchemaFromTable(table, tableName) {
|
|
|
6529
6860
|
)
|
|
6530
6861
|
},
|
|
6531
6862
|
strict: true,
|
|
6532
|
-
description: `Inferred from table '${tableName}' on
|
|
6863
|
+
description: `Inferred from table '${tableName}' on postgres actor table contract.`
|
|
6533
6864
|
};
|
|
6534
6865
|
}
|
|
6535
6866
|
function getQueryFieldsSchemaFromTable(table, tableName) {
|
|
@@ -6541,106 +6872,100 @@ function getQueryFieldsSchemaFromTable(table, tableName) {
|
|
|
6541
6872
|
oneOf: Object.keys(table.fields)
|
|
6542
6873
|
}
|
|
6543
6874
|
},
|
|
6544
|
-
description: `Inferred from table '${tableName}'
|
|
6875
|
+
description: `Inferred field projection from table '${tableName}'.`
|
|
6545
6876
|
};
|
|
6546
6877
|
}
|
|
6547
|
-
function
|
|
6878
|
+
function getQueryModeSchema() {
|
|
6548
6879
|
return {
|
|
6549
|
-
type: "
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
},
|
|
6583
|
-
required: ["on", "fields"],
|
|
6584
|
-
strict: true
|
|
6585
|
-
}
|
|
6586
|
-
];
|
|
6587
|
-
})
|
|
6588
|
-
)
|
|
6880
|
+
type: "string",
|
|
6881
|
+
constraints: {
|
|
6882
|
+
oneOf: ["rows", "count", "exists", "one", "aggregate"]
|
|
6883
|
+
}
|
|
6884
|
+
};
|
|
6885
|
+
}
|
|
6886
|
+
function getQueryAggregatesSchemaFromTable(table, tableName) {
|
|
6887
|
+
return {
|
|
6888
|
+
type: "array",
|
|
6889
|
+
items: {
|
|
6890
|
+
type: "object",
|
|
6891
|
+
properties: {
|
|
6892
|
+
fn: {
|
|
6893
|
+
type: "string",
|
|
6894
|
+
constraints: {
|
|
6895
|
+
oneOf: ["count", "sum", "avg", "min", "max"]
|
|
6896
|
+
}
|
|
6897
|
+
},
|
|
6898
|
+
field: {
|
|
6899
|
+
type: "string",
|
|
6900
|
+
constraints: {
|
|
6901
|
+
oneOf: Object.keys(table.fields)
|
|
6902
|
+
}
|
|
6903
|
+
},
|
|
6904
|
+
as: {
|
|
6905
|
+
type: "string"
|
|
6906
|
+
},
|
|
6907
|
+
distinct: {
|
|
6908
|
+
type: "boolean"
|
|
6909
|
+
}
|
|
6910
|
+
},
|
|
6911
|
+
required: ["fn"],
|
|
6912
|
+
strict: true
|
|
6589
6913
|
},
|
|
6590
|
-
|
|
6591
|
-
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
6914
|
+
description: `Aggregate definitions inferred from table '${tableName}'.`
|
|
6592
6915
|
};
|
|
6593
6916
|
}
|
|
6594
|
-
function
|
|
6917
|
+
function getQueryGroupBySchemaFromTable(table, tableName) {
|
|
6595
6918
|
return {
|
|
6596
|
-
type: "
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
{
|
|
6603
|
-
type: "string",
|
|
6604
|
-
constraints: {
|
|
6605
|
-
oneOf: ["asc", "desc"]
|
|
6606
|
-
}
|
|
6607
|
-
}
|
|
6608
|
-
];
|
|
6609
|
-
})
|
|
6610
|
-
)
|
|
6919
|
+
type: "array",
|
|
6920
|
+
items: {
|
|
6921
|
+
type: "string",
|
|
6922
|
+
constraints: {
|
|
6923
|
+
oneOf: Object.keys(table.fields)
|
|
6924
|
+
}
|
|
6611
6925
|
},
|
|
6612
|
-
|
|
6613
|
-
|
|
6926
|
+
description: `Group by fields inferred from table '${tableName}'.`
|
|
6927
|
+
};
|
|
6928
|
+
}
|
|
6929
|
+
function getQueryJoinsSchemaFromTable(_table, tableName) {
|
|
6930
|
+
return {
|
|
6931
|
+
type: "object",
|
|
6932
|
+
description: `Join definitions for table '${tableName}'.`
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6935
|
+
function getQuerySortSchemaFromTable(_table, tableName) {
|
|
6936
|
+
return {
|
|
6937
|
+
type: "object",
|
|
6938
|
+
strict: false,
|
|
6939
|
+
description: `Sort definition for table '${tableName}'. Keys are validated at runtime against allowlists and aggregate aliases.`
|
|
6614
6940
|
};
|
|
6615
6941
|
}
|
|
6616
6942
|
function getQueryLimitSchemaFromTable() {
|
|
6617
6943
|
return {
|
|
6618
6944
|
type: "number",
|
|
6619
6945
|
constraints: {
|
|
6620
|
-
min:
|
|
6621
|
-
|
|
6622
|
-
|
|
6946
|
+
min: 0,
|
|
6947
|
+
max: 1e3
|
|
6948
|
+
}
|
|
6623
6949
|
};
|
|
6624
6950
|
}
|
|
6625
6951
|
function getQueryOffsetSchemaFromTable() {
|
|
6626
6952
|
return {
|
|
6627
6953
|
type: "number",
|
|
6628
6954
|
constraints: {
|
|
6629
|
-
min: 0
|
|
6630
|
-
|
|
6631
|
-
|
|
6955
|
+
min: 0,
|
|
6956
|
+
max: 1e6
|
|
6957
|
+
}
|
|
6632
6958
|
};
|
|
6633
6959
|
}
|
|
6634
|
-
function
|
|
6960
|
+
function getQueryBatchSchemaFromTable() {
|
|
6635
6961
|
return {
|
|
6636
|
-
type: "boolean"
|
|
6637
|
-
description: "Whether to run the query in a transaction"
|
|
6962
|
+
type: "boolean"
|
|
6638
6963
|
};
|
|
6639
6964
|
}
|
|
6640
|
-
function
|
|
6965
|
+
function getTransactionSchema() {
|
|
6641
6966
|
return {
|
|
6642
6967
|
type: "boolean",
|
|
6643
|
-
description: "
|
|
6968
|
+
description: "Execute the operation in a transaction."
|
|
6644
6969
|
};
|
|
6645
6970
|
}
|
|
6646
6971
|
function getQueryOnConflictSchemaFromTable(table, tableName) {
|
|
@@ -6666,55 +6991,43 @@ function getQueryOnConflictSchemaFromTable(table, tableName) {
|
|
|
6666
6991
|
}
|
|
6667
6992
|
},
|
|
6668
6993
|
set: {
|
|
6669
|
-
type: "object"
|
|
6670
|
-
properties: {
|
|
6671
|
-
...Object.fromEntries(
|
|
6672
|
-
Object.entries(table.fields).map((field) => {
|
|
6673
|
-
return [
|
|
6674
|
-
field[0],
|
|
6675
|
-
{
|
|
6676
|
-
type: tableFieldTypeToSchemaType(field[1].type),
|
|
6677
|
-
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
6678
|
-
}
|
|
6679
|
-
];
|
|
6680
|
-
})
|
|
6681
|
-
)
|
|
6682
|
-
}
|
|
6994
|
+
type: "object"
|
|
6683
6995
|
},
|
|
6684
6996
|
where: {
|
|
6685
6997
|
type: "string"
|
|
6686
6998
|
}
|
|
6687
|
-
}
|
|
6688
|
-
required: ["do"]
|
|
6999
|
+
}
|
|
6689
7000
|
}
|
|
6690
7001
|
},
|
|
6691
|
-
|
|
6692
|
-
|
|
7002
|
+
strict: true,
|
|
7003
|
+
description: `Conflict strategy for inserts on table '${tableName}'.`
|
|
6693
7004
|
};
|
|
6694
7005
|
}
|
|
6695
7006
|
function tableFieldTypeToSchemaType(type) {
|
|
6696
7007
|
switch (type) {
|
|
6697
7008
|
case "varchar":
|
|
6698
7009
|
case "text":
|
|
6699
|
-
case "jsonb":
|
|
6700
7010
|
case "uuid":
|
|
7011
|
+
case "timestamp":
|
|
6701
7012
|
case "date":
|
|
6702
7013
|
case "geo_point":
|
|
6703
|
-
case "bytea":
|
|
6704
7014
|
return "string";
|
|
6705
7015
|
case "int":
|
|
6706
7016
|
case "bigint":
|
|
6707
7017
|
case "decimal":
|
|
6708
|
-
case "timestamp":
|
|
6709
7018
|
return "number";
|
|
6710
7019
|
case "boolean":
|
|
6711
7020
|
return "boolean";
|
|
6712
7021
|
case "array":
|
|
6713
7022
|
return "array";
|
|
6714
7023
|
case "object":
|
|
7024
|
+
case "jsonb":
|
|
6715
7025
|
return "object";
|
|
7026
|
+
case "bytea":
|
|
7027
|
+
return "string";
|
|
7028
|
+
default:
|
|
7029
|
+
return "any";
|
|
6716
7030
|
}
|
|
6717
|
-
return "any";
|
|
6718
7031
|
}
|
|
6719
7032
|
|
|
6720
7033
|
// src/Cadenza.ts
|
|
@@ -7453,12 +7766,12 @@ var CadenzaService = class {
|
|
|
7453
7766
|
static bootstrap() {
|
|
7454
7767
|
if (this.isBootstrapped) return;
|
|
7455
7768
|
this.isBootstrapped = true;
|
|
7456
|
-
|
|
7457
|
-
this.signalBroker =
|
|
7458
|
-
this.inquiryBroker =
|
|
7459
|
-
this.runner =
|
|
7460
|
-
this.metaRunner =
|
|
7461
|
-
this.registry =
|
|
7769
|
+
import_core4.default.bootstrap();
|
|
7770
|
+
this.signalBroker = import_core4.default.signalBroker;
|
|
7771
|
+
this.inquiryBroker = import_core4.default.inquiryBroker;
|
|
7772
|
+
this.runner = import_core4.default.runner;
|
|
7773
|
+
this.metaRunner = import_core4.default.metaRunner;
|
|
7774
|
+
this.registry = import_core4.default.registry;
|
|
7462
7775
|
this.serviceRegistry = ServiceRegistry.instance;
|
|
7463
7776
|
SignalController.instance;
|
|
7464
7777
|
RestController.instance;
|
|
@@ -7501,7 +7814,7 @@ var CadenzaService = class {
|
|
|
7501
7814
|
* @return {void} Does not return any value.
|
|
7502
7815
|
*/
|
|
7503
7816
|
static validateName(name) {
|
|
7504
|
-
|
|
7817
|
+
import_core4.default.validateName(name);
|
|
7505
7818
|
}
|
|
7506
7819
|
/**
|
|
7507
7820
|
* Gets the current run strategy from the Cadenza configuration.
|
|
@@ -7509,7 +7822,7 @@ var CadenzaService = class {
|
|
|
7509
7822
|
* @return {Function} The run strategy function defined in the Cadenza configuration.
|
|
7510
7823
|
*/
|
|
7511
7824
|
static get runStrategy() {
|
|
7512
|
-
return
|
|
7825
|
+
return import_core4.default.runStrategy;
|
|
7513
7826
|
}
|
|
7514
7827
|
/**
|
|
7515
7828
|
* Sets the mode for the Cadenza application.
|
|
@@ -7518,7 +7831,7 @@ var CadenzaService = class {
|
|
|
7518
7831
|
* @return {void} This method does not return a value.
|
|
7519
7832
|
*/
|
|
7520
7833
|
static setMode(mode) {
|
|
7521
|
-
|
|
7834
|
+
import_core4.default.setMode(mode);
|
|
7522
7835
|
}
|
|
7523
7836
|
/**
|
|
7524
7837
|
* Emits a signal with the specified data using the associated broker.
|
|
@@ -7536,16 +7849,16 @@ var CadenzaService = class {
|
|
|
7536
7849
|
* ```
|
|
7537
7850
|
*/
|
|
7538
7851
|
static emit(signal, data = {}, options = {}) {
|
|
7539
|
-
|
|
7852
|
+
import_core4.default.emit(signal, data, options);
|
|
7540
7853
|
}
|
|
7541
7854
|
static debounce(signal, context = {}, delayMs = 500) {
|
|
7542
|
-
|
|
7855
|
+
import_core4.default.debounce(signal, context, delayMs);
|
|
7543
7856
|
}
|
|
7544
7857
|
static schedule(signal, context, timeoutMs, exactDateTime) {
|
|
7545
|
-
|
|
7858
|
+
import_core4.default.schedule(signal, context, timeoutMs, exactDateTime);
|
|
7546
7859
|
}
|
|
7547
7860
|
static interval(signal, context, intervalMs, leading = false, startDateTime) {
|
|
7548
|
-
|
|
7861
|
+
import_core4.default.interval(signal, context, intervalMs, leading, startDateTime);
|
|
7549
7862
|
}
|
|
7550
7863
|
static defineIntent(intent) {
|
|
7551
7864
|
this.inquiryBroker?.addIntent(intent);
|
|
@@ -7793,18 +8106,18 @@ var CadenzaService = class {
|
|
|
7793
8106
|
});
|
|
7794
8107
|
}
|
|
7795
8108
|
static get(taskName) {
|
|
7796
|
-
return
|
|
8109
|
+
return import_core4.default.get(taskName);
|
|
7797
8110
|
}
|
|
7798
8111
|
static getActor(actorName) {
|
|
7799
|
-
const cadenzaWithActors =
|
|
8112
|
+
const cadenzaWithActors = import_core4.default;
|
|
7800
8113
|
return cadenzaWithActors.getActor?.(actorName);
|
|
7801
8114
|
}
|
|
7802
8115
|
static getAllActors() {
|
|
7803
|
-
const cadenzaWithActors =
|
|
8116
|
+
const cadenzaWithActors = import_core4.default;
|
|
7804
8117
|
return cadenzaWithActors.getAllActors?.() ?? [];
|
|
7805
8118
|
}
|
|
7806
8119
|
static getRoutine(routineName) {
|
|
7807
|
-
return
|
|
8120
|
+
return import_core4.default.getRoutine(routineName);
|
|
7808
8121
|
}
|
|
7809
8122
|
/**
|
|
7810
8123
|
* Creates a new DeputyTask instance based on the provided routine name, service name, and options.
|
|
@@ -8290,16 +8603,16 @@ var CadenzaService = class {
|
|
|
8290
8603
|
this.createCadenzaService(serviceName, description, options);
|
|
8291
8604
|
}
|
|
8292
8605
|
/**
|
|
8293
|
-
* Creates and initializes a database service
|
|
8294
|
-
* This
|
|
8606
|
+
* Creates and initializes a PostgresActor-backed database service.
|
|
8607
|
+
* This is the canonical API for schema-driven postgres setup in cadenza-service.
|
|
8295
8608
|
*
|
|
8296
|
-
* @param {string} name -
|
|
8297
|
-
* @param {DatabaseSchemaDefinition} schema -
|
|
8298
|
-
* @param {string} [description=""] -
|
|
8299
|
-
* @param {ServerOptions & DatabaseOptions} [options={}] -
|
|
8300
|
-
* @return {void}
|
|
8609
|
+
* @param {string} name - Logical actor/service name.
|
|
8610
|
+
* @param {DatabaseSchemaDefinition} schema - Database schema definition.
|
|
8611
|
+
* @param {string} [description=""] - Optional human-readable description.
|
|
8612
|
+
* @param {ServerOptions & DatabaseOptions} [options={}] - Server/database runtime options.
|
|
8613
|
+
* @return {void}
|
|
8301
8614
|
*/
|
|
8302
|
-
static
|
|
8615
|
+
static createPostgresActor(name, schema, description = "", options = {}) {
|
|
8303
8616
|
if (isBrowser) {
|
|
8304
8617
|
console.warn(
|
|
8305
8618
|
"Database service creation is not supported in the browser. Use the CadenzaDB service instead."
|
|
@@ -8309,7 +8622,7 @@ var CadenzaService = class {
|
|
|
8309
8622
|
if (this.serviceCreated) return;
|
|
8310
8623
|
this.bootstrap();
|
|
8311
8624
|
this.serviceRegistry.serviceName = name;
|
|
8312
|
-
DatabaseController.instance;
|
|
8625
|
+
const databaseController = DatabaseController.instance;
|
|
8313
8626
|
options = {
|
|
8314
8627
|
loadBalance: true,
|
|
8315
8628
|
useSocket: true,
|
|
@@ -8330,14 +8643,23 @@ var CadenzaService = class {
|
|
|
8330
8643
|
isDatabase: true,
|
|
8331
8644
|
...options
|
|
8332
8645
|
};
|
|
8333
|
-
|
|
8646
|
+
const registration = databaseController.createPostgresActor(
|
|
8647
|
+
name,
|
|
8648
|
+
schema,
|
|
8649
|
+
options
|
|
8650
|
+
);
|
|
8651
|
+
console.log("Creating PostgresActor", {
|
|
8652
|
+
serviceName: name,
|
|
8653
|
+
actorName: registration.actorName,
|
|
8654
|
+
options
|
|
8655
|
+
});
|
|
8334
8656
|
this.emit("meta.database_init_requested", {
|
|
8335
8657
|
schema,
|
|
8336
8658
|
databaseName: options.databaseName,
|
|
8337
8659
|
options
|
|
8338
8660
|
});
|
|
8339
8661
|
this.createMetaTask("Set database connection", () => {
|
|
8340
|
-
this.createMetaTask("Insert database service", (_, emit) => {
|
|
8662
|
+
this.createMetaTask("Insert database service bridge", (_, emit) => {
|
|
8341
8663
|
emit("global.meta.created_database_service", {
|
|
8342
8664
|
data: {
|
|
8343
8665
|
service_name: name,
|
|
@@ -8348,12 +8670,33 @@ var CadenzaService = class {
|
|
|
8348
8670
|
});
|
|
8349
8671
|
this.log("Database service created", {
|
|
8350
8672
|
name,
|
|
8351
|
-
isMeta: options.isMeta
|
|
8673
|
+
isMeta: options.isMeta,
|
|
8674
|
+
actorName: registration.actorName
|
|
8352
8675
|
});
|
|
8353
8676
|
}).doOn("meta.service_registry.service_inserted");
|
|
8354
8677
|
this.createCadenzaService(name, description, options);
|
|
8355
8678
|
}).doOn("meta.database.setup_done");
|
|
8356
8679
|
}
|
|
8680
|
+
/**
|
|
8681
|
+
* Creates a meta PostgresActor service.
|
|
8682
|
+
*
|
|
8683
|
+
* @param {string} name - Logical actor/service name.
|
|
8684
|
+
* @param {DatabaseSchemaDefinition} schema - Database schema definition.
|
|
8685
|
+
* @param {string} [description=""] - Optional description.
|
|
8686
|
+
* @param {ServerOptions & DatabaseOptions} [options={}] - Optional server/database options.
|
|
8687
|
+
* @return {void}
|
|
8688
|
+
*/
|
|
8689
|
+
static createMetaPostgresActor(name, schema, description = "", options = {}) {
|
|
8690
|
+
this.bootstrap();
|
|
8691
|
+
options.isMeta = true;
|
|
8692
|
+
this.createPostgresActor(name, schema, description, options);
|
|
8693
|
+
}
|
|
8694
|
+
/**
|
|
8695
|
+
* Legacy compatibility wrapper. Prefer {@link createPostgresActor}.
|
|
8696
|
+
*/
|
|
8697
|
+
static createDatabaseService(name, schema, description = "", options = {}) {
|
|
8698
|
+
this.createPostgresActor(name, schema, description, options);
|
|
8699
|
+
}
|
|
8357
8700
|
/**
|
|
8358
8701
|
* Creates a meta database service with the specified configuration.
|
|
8359
8702
|
*
|
|
@@ -8364,17 +8707,15 @@ var CadenzaService = class {
|
|
|
8364
8707
|
* @return {void} - This method does not return a value.
|
|
8365
8708
|
*/
|
|
8366
8709
|
static createMetaDatabaseService(name, schema, description = "", options = {}) {
|
|
8367
|
-
this.
|
|
8368
|
-
options.isMeta = true;
|
|
8369
|
-
this.createDatabaseService(name, schema, description, options);
|
|
8710
|
+
this.createMetaPostgresActor(name, schema, description, options);
|
|
8370
8711
|
}
|
|
8371
8712
|
static createActor(spec, options = {}) {
|
|
8372
8713
|
this.bootstrap();
|
|
8373
|
-
return
|
|
8714
|
+
return import_core4.default.createActor(spec, options);
|
|
8374
8715
|
}
|
|
8375
8716
|
static createActorFromDefinition(definition, options = {}) {
|
|
8376
8717
|
this.bootstrap();
|
|
8377
|
-
return
|
|
8718
|
+
return import_core4.default.createActorFromDefinition(definition, options);
|
|
8378
8719
|
}
|
|
8379
8720
|
/**
|
|
8380
8721
|
* Creates and registers a new task with the provided name, function, and optional details.
|
|
@@ -8448,7 +8789,7 @@ var CadenzaService = class {
|
|
|
8448
8789
|
*/
|
|
8449
8790
|
static createTask(name, func, description, options = {}) {
|
|
8450
8791
|
this.bootstrap();
|
|
8451
|
-
return
|
|
8792
|
+
return import_core4.default.createTask(name, func, description, options);
|
|
8452
8793
|
}
|
|
8453
8794
|
/**
|
|
8454
8795
|
* Creates a meta task with the specified name, functionality, description, and options.
|
|
@@ -8464,7 +8805,7 @@ var CadenzaService = class {
|
|
|
8464
8805
|
*/
|
|
8465
8806
|
static createMetaTask(name, func, description, options = {}) {
|
|
8466
8807
|
this.bootstrap();
|
|
8467
|
-
return
|
|
8808
|
+
return import_core4.default.createMetaTask(name, func, description, options);
|
|
8468
8809
|
}
|
|
8469
8810
|
/**
|
|
8470
8811
|
* Creates a unique task by wrapping the provided task function with a uniqueness constraint.
|
|
@@ -8514,7 +8855,7 @@ var CadenzaService = class {
|
|
|
8514
8855
|
*/
|
|
8515
8856
|
static createUniqueTask(name, func, description, options = {}) {
|
|
8516
8857
|
this.bootstrap();
|
|
8517
|
-
return
|
|
8858
|
+
return import_core4.default.createUniqueTask(name, func, description, options);
|
|
8518
8859
|
}
|
|
8519
8860
|
/**
|
|
8520
8861
|
* Creates a unique meta task with the specified name, function, description, and options.
|
|
@@ -8528,7 +8869,7 @@ var CadenzaService = class {
|
|
|
8528
8869
|
*/
|
|
8529
8870
|
static createUniqueMetaTask(name, func, description, options = {}) {
|
|
8530
8871
|
this.bootstrap();
|
|
8531
|
-
return
|
|
8872
|
+
return import_core4.default.createUniqueMetaTask(name, func, description, options);
|
|
8532
8873
|
}
|
|
8533
8874
|
/**
|
|
8534
8875
|
* Creates a throttled task with a concurrency limit of 1, ensuring that only one instance of the task can run at a time for a specific throttle tag.
|
|
@@ -8561,7 +8902,7 @@ var CadenzaService = class {
|
|
|
8561
8902
|
*/
|
|
8562
8903
|
static createThrottledTask(name, func, throttledIdGetter = () => "default", description, options = {}) {
|
|
8563
8904
|
this.bootstrap();
|
|
8564
|
-
return
|
|
8905
|
+
return import_core4.default.createThrottledTask(
|
|
8565
8906
|
name,
|
|
8566
8907
|
func,
|
|
8567
8908
|
throttledIdGetter,
|
|
@@ -8582,7 +8923,7 @@ var CadenzaService = class {
|
|
|
8582
8923
|
*/
|
|
8583
8924
|
static createThrottledMetaTask(name, func, throttledIdGetter = () => "default", description, options = {}) {
|
|
8584
8925
|
this.bootstrap();
|
|
8585
|
-
return
|
|
8926
|
+
return import_core4.default.createThrottledMetaTask(
|
|
8586
8927
|
name,
|
|
8587
8928
|
func,
|
|
8588
8929
|
throttledIdGetter,
|
|
@@ -8625,7 +8966,7 @@ var CadenzaService = class {
|
|
|
8625
8966
|
*/
|
|
8626
8967
|
static createDebounceTask(name, func, description, debounceTime = 1e3, options = {}) {
|
|
8627
8968
|
this.bootstrap();
|
|
8628
|
-
return
|
|
8969
|
+
return import_core4.default.createDebounceTask(
|
|
8629
8970
|
name,
|
|
8630
8971
|
func,
|
|
8631
8972
|
description,
|
|
@@ -8646,7 +8987,7 @@ var CadenzaService = class {
|
|
|
8646
8987
|
*/
|
|
8647
8988
|
static createDebounceMetaTask(name, func, description, debounceTime = 1e3, options = {}) {
|
|
8648
8989
|
this.bootstrap();
|
|
8649
|
-
return
|
|
8990
|
+
return import_core4.default.createDebounceMetaTask(
|
|
8650
8991
|
name,
|
|
8651
8992
|
func,
|
|
8652
8993
|
description,
|
|
@@ -8716,7 +9057,7 @@ var CadenzaService = class {
|
|
|
8716
9057
|
*/
|
|
8717
9058
|
static createEphemeralTask(name, func, description, options = {}) {
|
|
8718
9059
|
this.bootstrap();
|
|
8719
|
-
return
|
|
9060
|
+
return import_core4.default.createEphemeralTask(name, func, description, options);
|
|
8720
9061
|
}
|
|
8721
9062
|
/**
|
|
8722
9063
|
* Creates an ephemeral meta task with the specified name, function, description, and options.
|
|
@@ -8730,7 +9071,7 @@ var CadenzaService = class {
|
|
|
8730
9071
|
*/
|
|
8731
9072
|
static createEphemeralMetaTask(name, func, description, options = {}) {
|
|
8732
9073
|
this.bootstrap();
|
|
8733
|
-
return
|
|
9074
|
+
return import_core4.default.createEphemeralMetaTask(name, func, description, options);
|
|
8734
9075
|
}
|
|
8735
9076
|
/**
|
|
8736
9077
|
* Creates a new routine with the specified name, tasks, and an optional description.
|
|
@@ -8762,7 +9103,7 @@ var CadenzaService = class {
|
|
|
8762
9103
|
*/
|
|
8763
9104
|
static createRoutine(name, tasks, description = "") {
|
|
8764
9105
|
this.bootstrap();
|
|
8765
|
-
return
|
|
9106
|
+
return import_core4.default.createRoutine(name, tasks, description);
|
|
8766
9107
|
}
|
|
8767
9108
|
/**
|
|
8768
9109
|
* Creates a meta routine with a given name, tasks, and optional description.
|
|
@@ -8777,10 +9118,10 @@ var CadenzaService = class {
|
|
|
8777
9118
|
*/
|
|
8778
9119
|
static createMetaRoutine(name, tasks, description = "") {
|
|
8779
9120
|
this.bootstrap();
|
|
8780
|
-
return
|
|
9121
|
+
return import_core4.default.createMetaRoutine(name, tasks, description);
|
|
8781
9122
|
}
|
|
8782
9123
|
static reset() {
|
|
8783
|
-
|
|
9124
|
+
import_core4.default.reset();
|
|
8784
9125
|
this.serviceRegistry.reset();
|
|
8785
9126
|
}
|
|
8786
9127
|
};
|
|
@@ -8789,7 +9130,7 @@ CadenzaService.serviceCreated = false;
|
|
|
8789
9130
|
CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
|
|
8790
9131
|
|
|
8791
9132
|
// src/index.ts
|
|
8792
|
-
var
|
|
9133
|
+
var import_core5 = require("@cadenza.io/core");
|
|
8793
9134
|
var index_default = CadenzaService;
|
|
8794
9135
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8795
9136
|
0 && (module.exports = {
|