prostgles-server 4.2.265 → 4.2.267
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DboBuilder/TableHandler/DataValidator.js +5 -5
- package/dist/DboBuilder/TableHandler/DataValidator.js.map +1 -1
- package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js +1 -2
- package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js.map +1 -1
- package/dist/DboBuilder/TableHandler/insertTest.js +2 -2
- package/dist/DboBuilder/TableHandler/insertTest.js.map +1 -1
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +1 -2
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
- package/dist/DboBuilder/TableHandler/updateFile.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/updateFile.js +1 -2
- package/dist/DboBuilder/TableHandler/updateFile.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
- package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts +4 -0
- package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts.map +1 -0
- package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js +65 -0
- package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js.map +1 -0
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.js +6 -8
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/subscribe.js +5 -5
- package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
- package/dist/DboBuilder/dboBuilderUtils.d.ts.map +1 -1
- package/dist/DboBuilder/dboBuilderUtils.js +1 -2
- package/dist/DboBuilder/dboBuilderUtils.js.map +1 -1
- package/dist/DboBuilder/getSubscribeRelatedTables.d.ts.map +1 -1
- package/dist/DboBuilder/getSubscribeRelatedTables.js +2 -2
- package/dist/DboBuilder/getSubscribeRelatedTables.js.map +1 -1
- package/dist/JSONBValidation/JSONBValidation.d.ts.map +1 -1
- package/dist/JSONBValidation/JSONBValidation.js +105 -13
- package/dist/JSONBValidation/JSONBValidation.js.map +1 -1
- package/dist/JSONBValidation/JSONBValidation.spec.js +17 -3
- package/dist/JSONBValidation/JSONBValidation.spec.js.map +1 -1
- package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts +1 -0
- package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts.map +1 -1
- package/dist/JSONBValidation/getJSONBSchemaTSTypes.js +98 -97
- package/dist/JSONBValidation/getJSONBSchemaTSTypes.js.map +1 -1
- package/dist/JSONBValidation/validate_jsonb_schema_sql.js +3 -3
- package/dist/JSONBValidation/validate_jsonb_schema_sql.js.map +1 -1
- package/dist/Logging.d.ts +2 -1
- package/dist/Logging.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +7 -44
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js +7 -38
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PubSubManager/PubSubManagerUtils.d.ts +22 -0
- package/dist/PubSubManager/PubSubManagerUtils.d.ts.map +1 -0
- package/dist/PubSubManager/PubSubManagerUtils.js +33 -0
- package/dist/PubSubManager/PubSubManagerUtils.js.map +1 -0
- package/dist/PubSubManager/addSub.d.ts +1 -1
- package/dist/PubSubManager/addSub.d.ts.map +1 -1
- package/dist/PubSubManager/addSub.js +20 -24
- package/dist/PubSubManager/addSub.js.map +1 -1
- package/dist/PubSubManager/addSync.d.ts.map +1 -1
- package/dist/PubSubManager/addSync.js +3 -3
- package/dist/PubSubManager/addSync.js.map +1 -1
- package/dist/PubSubManager/addTrigger.d.ts +1 -1
- package/dist/PubSubManager/addTrigger.d.ts.map +1 -1
- package/dist/PubSubManager/addTrigger.js +9 -9
- package/dist/PubSubManager/addTrigger.js.map +1 -1
- package/dist/PubSubManager/deleteOrphanedTriggers.d.ts.map +1 -1
- package/dist/PubSubManager/deleteOrphanedTriggers.js +2 -2
- package/dist/PubSubManager/deleteOrphanedTriggers.js.map +1 -1
- package/dist/PubSubManager/getPubSubManagerInitQuery.d.ts.map +1 -1
- package/dist/PubSubManager/getPubSubManagerInitQuery.js +26 -23
- package/dist/PubSubManager/getPubSubManagerInitQuery.js.map +1 -1
- package/dist/PubSubManager/initPubSubManager.d.ts +1 -1
- package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/initPubSubManager.js +3 -3
- package/dist/PubSubManager/initPubSubManager.js.map +1 -1
- package/dist/PubSubManager/initialiseEventTriggers.d.ts +1 -1
- package/dist/PubSubManager/initialiseEventTriggers.d.ts.map +1 -1
- package/dist/PubSubManager/initialiseEventTriggers.js +2 -2
- package/dist/PubSubManager/initialiseEventTriggers.js.map +1 -1
- package/dist/PubSubManager/notifListener.d.ts.map +1 -1
- package/dist/PubSubManager/notifListener.js +20 -14
- package/dist/PubSubManager/notifListener.js.map +1 -1
- package/dist/PubSubManager/orphanTriggerCheck.d.ts.map +1 -1
- package/dist/PubSubManager/orphanTriggerCheck.js +2 -2
- package/dist/PubSubManager/orphanTriggerCheck.js.map +1 -1
- package/dist/PubSubManager/pushSubData.d.ts +1 -1
- package/dist/PubSubManager/pushSubData.d.ts.map +1 -1
- package/dist/PubSubManager/pushSubData.js +3 -3
- package/dist/PubSubManager/pushSubData.js.map +1 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.js +2 -2
- package/dist/PublishParser/getTableRulesWithoutFileTable.js.map +1 -1
- package/dist/SchemaWatch/SchemaWatch.d.ts.map +1 -1
- package/dist/SchemaWatch/SchemaWatch.js +4 -5
- package/dist/SchemaWatch/SchemaWatch.js.map +1 -1
- package/dist/SyncReplication.d.ts +1 -1
- package/dist/SyncReplication.d.ts.map +1 -1
- package/dist/SyncReplication.js +7 -7
- package/dist/SyncReplication.js.map +1 -1
- package/dist/TableConfig/applyTableConfig.js +2 -2
- package/dist/TableConfig/applyTableConfig.js.map +1 -1
- package/dist/TableConfig/getColumnDefinitionQuery.js +7 -7
- package/dist/TableConfig/getColumnDefinitionQuery.js.map +1 -1
- package/dist/TableConfig/getConstraintDefinitionQueries.js +4 -4
- package/dist/TableConfig/getConstraintDefinitionQueries.js.map +1 -1
- package/dist/TableConfig/getSchemaDiffQueries.js +2 -2
- package/dist/TableConfig/getSchemaDiffQueries.js.map +1 -1
- package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
- package/dist/TableConfig/initTableConfig.js +5 -5
- package/dist/TableConfig/initTableConfig.js.map +1 -1
- package/dist/TableConfig/runMigrations.js +2 -2
- package/dist/TableConfig/runMigrations.js.map +1 -1
- package/dist/TableConfig/tableConfigSchemaUtils.js +3 -3
- package/dist/TableConfig/tableConfigSchemaUtils.js.map +1 -1
- package/lib/DboBuilder/TableHandler/DataValidator.ts +1 -1
- package/lib/DboBuilder/TableHandler/insert/insertNestedRecords.ts +1 -1
- package/lib/DboBuilder/TableHandler/insertTest.ts +1 -1
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +1 -2
- package/lib/DboBuilder/TableHandler/updateFile.ts +1 -2
- package/lib/DboBuilder/ViewHandler/getValidatedSubscribeOptions.ts +72 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +7 -13
- package/lib/DboBuilder/ViewHandler/subscribe.ts +9 -6
- package/lib/DboBuilder/dboBuilderUtils.ts +1 -1
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +2 -1
- package/lib/JSONBValidation/JSONBValidation.spec.ts +38 -3
- package/lib/JSONBValidation/JSONBValidation.ts +102 -15
- package/lib/JSONBValidation/getJSONBSchemaTSTypes.ts +111 -111
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +3 -3
- package/lib/Logging.ts +2 -1
- package/lib/PubSubManager/PubSubManager.ts +11 -50
- package/lib/PubSubManager/PubSubManagerUtils.ts +37 -0
- package/lib/PubSubManager/addSub.ts +21 -30
- package/lib/PubSubManager/addSync.ts +2 -7
- package/lib/PubSubManager/addTrigger.ts +3 -2
- package/lib/PubSubManager/deleteOrphanedTriggers.ts +2 -1
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +29 -39
- package/lib/PubSubManager/initPubSubManager.ts +2 -1
- package/lib/PubSubManager/initialiseEventTriggers.ts +3 -2
- package/lib/PubSubManager/notifListener.ts +20 -6
- package/lib/PubSubManager/orphanTriggerCheck.ts +2 -1
- package/lib/PubSubManager/pushSubData.ts +3 -2
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +1 -1
- package/lib/SchemaWatch/SchemaWatch.ts +2 -5
- package/lib/SyncReplication.ts +3 -2
- package/lib/TableConfig/applyTableConfig.ts +2 -2
- package/lib/TableConfig/getColumnDefinitionQuery.ts +1 -1
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +1 -1
- package/lib/TableConfig/getSchemaDiffQueries.ts +1 -1
- package/lib/TableConfig/initTableConfig.ts +7 -4
- package/lib/TableConfig/runMigrations.ts +2 -2
- package/lib/TableConfig/tableConfigSchemaUtils.ts +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isDefined, isEmpty, type SubscribeOptions } from "prostgles-types";
|
|
2
|
+
import { getJSONBObjectSchemaValidationError } from "../../JSONBValidation/JSONBValidation";
|
|
3
|
+
import { type Required_ish, type SubscribeRule } from "../../PublishParser/PublishParser";
|
|
4
|
+
|
|
5
|
+
export const getValidatedSubscribeOptions = (
|
|
6
|
+
rawVal: Required_ish<SubscribeOptions>,
|
|
7
|
+
subscribeRule: SubscribeRule | undefined
|
|
8
|
+
): Required_ish<SubscribeOptions> => {
|
|
9
|
+
const { data, error } = getJSONBObjectSchemaValidationError(
|
|
10
|
+
{
|
|
11
|
+
throttle: { type: "integer", optional: true },
|
|
12
|
+
throttleOpts: {
|
|
13
|
+
type: {
|
|
14
|
+
skipFirst: { type: "boolean", optional: true },
|
|
15
|
+
},
|
|
16
|
+
optional: true,
|
|
17
|
+
},
|
|
18
|
+
skipFirst: { type: "boolean", optional: true },
|
|
19
|
+
actions: {
|
|
20
|
+
oneOf: [
|
|
21
|
+
{
|
|
22
|
+
record: {
|
|
23
|
+
keysEnum: ["insert", "update", "delete"],
|
|
24
|
+
partial: true,
|
|
25
|
+
values: {
|
|
26
|
+
enum: [true],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
record: {
|
|
32
|
+
keysEnum: ["insert", "update", "delete"],
|
|
33
|
+
partial: true,
|
|
34
|
+
values: {
|
|
35
|
+
enum: [false],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
optional: true,
|
|
41
|
+
},
|
|
42
|
+
} as const,
|
|
43
|
+
rawVal,
|
|
44
|
+
"subscribeParams"
|
|
45
|
+
);
|
|
46
|
+
if (error !== undefined) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const publishedThrottle = subscribeRule?.throttle || 0;
|
|
51
|
+
const { actions, throttleOpts, skipFirst, throttle = publishedThrottle } = data;
|
|
52
|
+
if (actions && isEmpty(actions)) {
|
|
53
|
+
throw `addSub: actions cannot be empty`;
|
|
54
|
+
}
|
|
55
|
+
if (
|
|
56
|
+
publishedThrottle &&
|
|
57
|
+
Number.isInteger(publishedThrottle) &&
|
|
58
|
+
publishedThrottle > 0 &&
|
|
59
|
+
throttle < publishedThrottle
|
|
60
|
+
) {
|
|
61
|
+
throw `addSub: throttle ${throttle} is less than the minimum allowed throttle ${publishedThrottle}`;
|
|
62
|
+
}
|
|
63
|
+
if (isDefined(throttle) && !Number.isInteger(throttle)) {
|
|
64
|
+
throw `addSub: throttle ${throttle} must be an integer`;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
actions: actions,
|
|
68
|
+
skipFirst,
|
|
69
|
+
throttle,
|
|
70
|
+
throttleOpts,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { FUNCTIONS, parseFunction } from "../QueryBuilder/Functions";
|
|
8
8
|
import { asNameAlias, parseFunctionObject } from "../QueryBuilder/QueryBuilder";
|
|
9
9
|
import { TableSchemaColumn } from "../DboBuilderTypes";
|
|
10
|
-
import { asValue } from "../../PubSubManager/
|
|
10
|
+
import { asValue } from "../../PubSubManager/PubSubManagerUtils";
|
|
11
11
|
|
|
12
12
|
const allowedComparators = FILTER_OPERANDS; //[">", "<", "=", "<=", ">=", "<>", "!="]
|
|
13
13
|
type Args = {
|
|
@@ -73,32 +73,26 @@ export const parseComplexFilter = ({
|
|
|
73
73
|
const leftVal = getFuncQuery(leftFilter);
|
|
74
74
|
let result = leftVal;
|
|
75
75
|
if (comparator) {
|
|
76
|
-
if (
|
|
77
|
-
typeof comparator !== "string" ||
|
|
78
|
-
!allowedComparators.includes(comparator as any)
|
|
79
|
-
) {
|
|
76
|
+
if (typeof comparator !== "string" || !allowedComparators.includes(comparator as any)) {
|
|
80
77
|
throw `Invalid $filter. comparator ${JSON.stringify(comparator)} is not valid. Expecting one of: ${allowedComparators}`;
|
|
81
78
|
}
|
|
82
79
|
if (!rightFilterOrValue) {
|
|
83
80
|
throw "Invalid $filter. Expecting a value or function after the comparator";
|
|
84
81
|
}
|
|
85
|
-
const maybeValidComparator =
|
|
86
|
-
comparator as keyof typeof FILTER_OPERAND_TO_SQL_OPERAND;
|
|
82
|
+
const maybeValidComparator = comparator as keyof typeof FILTER_OPERAND_TO_SQL_OPERAND;
|
|
87
83
|
const sqlOperand = FILTER_OPERAND_TO_SQL_OPERAND[maybeValidComparator];
|
|
88
84
|
if (!sqlOperand) {
|
|
89
85
|
throw `Invalid $filter. comparator ${comparator} is not valid. Expecting one of: ${allowedComparators}`;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
let rightVal =
|
|
93
|
-
|
|
88
|
+
let rightVal =
|
|
89
|
+
isObject(rightFilterOrValue) ?
|
|
90
|
+
getFuncQuery(rightFilterOrValue)
|
|
94
91
|
: parseFilterRightValue(rightFilterOrValue, {
|
|
95
92
|
selectItem: undefined,
|
|
96
93
|
expect: ["$in", "$nin"].includes(comparator) ? "csv" : undefined,
|
|
97
94
|
});
|
|
98
|
-
if (
|
|
99
|
-
maybeValidComparator === "$between" ||
|
|
100
|
-
maybeValidComparator === "$notBetween"
|
|
101
|
-
) {
|
|
95
|
+
if (maybeValidComparator === "$between" || maybeValidComparator === "$notBetween") {
|
|
102
96
|
if (!Array.isArray(rightVal) || rightVal.length !== 2) {
|
|
103
97
|
throw "Between filter expects an array of two values";
|
|
104
98
|
}
|
|
@@ -2,13 +2,14 @@ import { AnyObject, SubscribeParams, SubscriptionChannels } from "prostgles-type
|
|
|
2
2
|
import { ParsedTableRule } from "../../PublishParser/PublishParser";
|
|
3
3
|
import {
|
|
4
4
|
Filter,
|
|
5
|
-
LocalParams,
|
|
6
5
|
getErrorAsObject,
|
|
7
6
|
getSerializedClientErrorFromPGError,
|
|
7
|
+
LocalParams,
|
|
8
8
|
} from "../DboBuilder";
|
|
9
9
|
import { getSubscribeRelatedTables } from "../getSubscribeRelatedTables";
|
|
10
10
|
import { NewQuery } from "../QueryBuilder/QueryBuilder";
|
|
11
11
|
import { ViewHandler } from "./ViewHandler";
|
|
12
|
+
import { getValidatedSubscribeOptions } from "./getValidatedSubscribeOptions";
|
|
12
13
|
|
|
13
14
|
type OnData = (items: AnyObject[]) => any;
|
|
14
15
|
export type LocalFuncs =
|
|
@@ -80,7 +81,7 @@ async function subscribe(
|
|
|
80
81
|
throw " Cannot have localFunc AND socket ";
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
const { throttle
|
|
84
|
+
const { throttle, throttleOpts, skipFirst, actions, ...selectParams } = params;
|
|
84
85
|
|
|
85
86
|
/** Ensure request is valid */
|
|
86
87
|
await this.find(filter, { ...selectParams, limit: 0 }, undefined, table_rules, localParams);
|
|
@@ -107,10 +108,12 @@ async function subscribe(
|
|
|
107
108
|
condition: newQuery.whereOpts.condition,
|
|
108
109
|
table_name: this.name,
|
|
109
110
|
filter: { ...filter },
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
selectParams: { ...selectParams },
|
|
112
|
+
subscribeOptions: getValidatedSubscribeOptions(
|
|
113
|
+
{ actions, skipFirst, throttle, throttleOpts },
|
|
114
|
+
table_rules?.subscribe
|
|
115
|
+
),
|
|
116
|
+
lastPushed: 0,
|
|
114
117
|
} as const;
|
|
115
118
|
|
|
116
119
|
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
getKeys,
|
|
12
12
|
isObject,
|
|
13
13
|
omitKeys,
|
|
14
|
+
pickKeys,
|
|
14
15
|
} from "prostgles-types";
|
|
15
16
|
import { DB } from "../Prostgles";
|
|
16
|
-
import { pickKeys } from "../PubSubManager/PubSubManager";
|
|
17
17
|
import { LocalParams, SortItem, pgp } from "./DboBuilderTypes";
|
|
18
18
|
import { asNameAlias } from "./QueryBuilder/QueryBuilder";
|
|
19
19
|
import { ViewHandler } from "./ViewHandler/ViewHandler";
|
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
SubscribeParams,
|
|
7
7
|
} from "prostgles-types";
|
|
8
8
|
import { ParsedTableRule } from "../PublishParser/PublishParser";
|
|
9
|
-
import {
|
|
9
|
+
import { ViewSubscriptionOptions } from "../PubSubManager/PubSubManager";
|
|
10
10
|
import { Filter, getSerializedClientErrorFromPGError, LocalParams } from "./DboBuilder";
|
|
11
11
|
import { NewQuery } from "./QueryBuilder/QueryBuilder";
|
|
12
12
|
import { ViewHandler } from "./ViewHandler/ViewHandler";
|
|
13
|
+
import { log } from "../PubSubManager/PubSubManagerUtils";
|
|
13
14
|
|
|
14
15
|
type Args = {
|
|
15
16
|
selectParams: Omit<SubscribeParams, "throttle">;
|
|
@@ -45,7 +45,7 @@ void describe("JSONBValidation", async () => {
|
|
|
45
45
|
assert.deepStrictEqual(
|
|
46
46
|
getJSONBObjectSchemaValidationError(schema.type, { ...obj, age: 22.2 }, "test"),
|
|
47
47
|
{
|
|
48
|
-
error: "age is of invalid type. Expecting integer",
|
|
48
|
+
error: "age is of invalid type. Expecting null | integer",
|
|
49
49
|
}
|
|
50
50
|
);
|
|
51
51
|
assert.deepStrictEqual(
|
|
@@ -64,7 +64,7 @@ void describe("JSONBValidation", async () => {
|
|
|
64
64
|
{ ...obj, address: { ...obj.address, street_number: 22.22 } },
|
|
65
65
|
"test"
|
|
66
66
|
),
|
|
67
|
-
{ error: "address.street_number is of invalid type. Expecting integer" }
|
|
67
|
+
{ error: "address.street_number is of invalid type. Expecting undefined | integer" }
|
|
68
68
|
);
|
|
69
69
|
assert.deepStrictEqual(
|
|
70
70
|
getJSONBObjectSchemaValidationError(
|
|
@@ -88,7 +88,42 @@ void describe("JSONBValidation", async () => {
|
|
|
88
88
|
{ ...obj, address: { ...obj.address, t: 2 } },
|
|
89
89
|
"test"
|
|
90
90
|
),
|
|
91
|
-
{ error: 'address.t is of invalid type. Expecting
|
|
91
|
+
{ error: 'address.t is of invalid type. Expecting undefined | "a" | "b" | "c"' }
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
await test("getJSONBObjectSchemaValidationError oneOf record", () => {
|
|
95
|
+
assert.deepStrictEqual(
|
|
96
|
+
getJSONBObjectSchemaValidationError(
|
|
97
|
+
{
|
|
98
|
+
d: { record: { keysEnum: ["a", "b"], values: "boolean" } },
|
|
99
|
+
o: { optional: true, oneOf: ["number", "string[]"] },
|
|
100
|
+
},
|
|
101
|
+
{ d: { a: true, b: 1 } },
|
|
102
|
+
"test"
|
|
103
|
+
),
|
|
104
|
+
{ error: "d.b is of invalid type. Expecting boolean" }
|
|
105
|
+
);
|
|
106
|
+
assert.deepStrictEqual(
|
|
107
|
+
getJSONBObjectSchemaValidationError(
|
|
108
|
+
{
|
|
109
|
+
d: { record: { keysEnum: ["a", "b"], values: "boolean" } },
|
|
110
|
+
o: { optional: true, oneOf: ["number", "string[]"] },
|
|
111
|
+
},
|
|
112
|
+
{ d: { a: true, b: true }, o: false },
|
|
113
|
+
"test"
|
|
114
|
+
),
|
|
115
|
+
{ error: "o is of invalid type. Expecting undefined | number | string[]" }
|
|
116
|
+
);
|
|
117
|
+
assert.deepStrictEqual(
|
|
118
|
+
getJSONBObjectSchemaValidationError(
|
|
119
|
+
{
|
|
120
|
+
d: { record: { keysEnum: ["a", "b"], values: "boolean" } },
|
|
121
|
+
o: { optional: true, oneOf: ["number", "string[]"] },
|
|
122
|
+
},
|
|
123
|
+
{ d: { a: true, b: true }, o: ["str"] },
|
|
124
|
+
"test"
|
|
125
|
+
),
|
|
126
|
+
{ data: { d: { a: true, b: true }, o: ["str"] } }
|
|
92
127
|
);
|
|
93
128
|
});
|
|
94
129
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getKeys, getObjectEntries, isObject, JSONB } from "prostgles-types";
|
|
1
|
+
import { getKeys, getObjectEntries, isEmpty, isObject, JSONB } from "prostgles-types";
|
|
2
2
|
|
|
3
3
|
export const getFieldTypeObj = (rawFieldType: JSONB.FieldType): JSONB.FieldTypeObj => {
|
|
4
4
|
if (typeof rawFieldType === "string") return { type: rawFieldType };
|
|
@@ -51,36 +51,35 @@ const getValidator = (type: Extract<DataType, string>) => {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
const getPropertyValidationError = (
|
|
54
|
-
|
|
55
|
-
val: any,
|
|
54
|
+
value: any,
|
|
56
55
|
rawFieldType: JSONB.FieldType,
|
|
57
56
|
path: string[] = []
|
|
58
57
|
): string | undefined => {
|
|
59
|
-
|
|
58
|
+
const err = `${path.join(".")} is of invalid type. Expecting ${getTypeDescription(rawFieldType).replaceAll("\n", "")}`;
|
|
60
59
|
const fieldType = getFieldTypeObj(rawFieldType);
|
|
60
|
+
|
|
61
61
|
const { type, allowedValues, nullable, optional } = fieldType;
|
|
62
|
-
if (nullable &&
|
|
63
|
-
if (optional &&
|
|
62
|
+
if (nullable && value === null) return;
|
|
63
|
+
if (optional && value === undefined) return;
|
|
64
64
|
if (allowedValues) {
|
|
65
65
|
throw new Error(`Allowed values are not supported for validation`);
|
|
66
66
|
}
|
|
67
67
|
if (type) {
|
|
68
68
|
if (isObject(type)) {
|
|
69
|
-
if (!isObject(
|
|
70
|
-
return
|
|
69
|
+
if (!isObject(value)) {
|
|
70
|
+
return err;
|
|
71
71
|
}
|
|
72
72
|
for (const [subKey, subSchema] of getObjectEntries(type)) {
|
|
73
|
-
const error = getPropertyValidationError(
|
|
74
|
-
if (error) {
|
|
73
|
+
const error = getPropertyValidationError(value[subKey], subSchema, [...path, subKey]);
|
|
74
|
+
if (error !== undefined) {
|
|
75
75
|
return error;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
|
-
err += type;
|
|
81
80
|
|
|
82
81
|
const { validator } = getValidator(type);
|
|
83
|
-
const isValid = validator(
|
|
82
|
+
const isValid = validator(value);
|
|
84
83
|
if (!isValid) {
|
|
85
84
|
return err;
|
|
86
85
|
}
|
|
@@ -91,14 +90,102 @@ const getPropertyValidationError = (
|
|
|
91
90
|
const otherOptions = [];
|
|
92
91
|
if (fieldType.nullable) otherOptions.push(null);
|
|
93
92
|
if (fieldType.optional) otherOptions.push(undefined);
|
|
94
|
-
err += `one of: ${JSON.stringify([...fieldType.enum, ...otherOptions]).slice(1, -1)}`;
|
|
93
|
+
// err += `one of: ${JSON.stringify([...fieldType.enum, ...otherOptions]).slice(1, -1)}`;
|
|
95
94
|
|
|
96
|
-
if (!fieldType.enum.includes(
|
|
95
|
+
if (!fieldType.enum.includes(value)) return err;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (fieldType.oneOf) {
|
|
99
|
+
if (!fieldType.oneOf.length) {
|
|
100
|
+
return err + "to not be empty";
|
|
101
|
+
}
|
|
102
|
+
let firstError: string | undefined;
|
|
103
|
+
const validMember = fieldType.oneOf.find((member) => {
|
|
104
|
+
const error = getPropertyValidationError(value, member, path);
|
|
105
|
+
firstError ??= error;
|
|
106
|
+
return error === undefined;
|
|
107
|
+
});
|
|
108
|
+
if (validMember) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
return err;
|
|
112
|
+
}
|
|
113
|
+
if (fieldType.record) {
|
|
114
|
+
const { keysEnum, partial, values: valuesSchema } = fieldType.record;
|
|
115
|
+
if (!isObject(value)) {
|
|
116
|
+
return err + "object";
|
|
117
|
+
}
|
|
118
|
+
if (partial && isEmpty(value)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const valueKeys = getKeys(value);
|
|
122
|
+
const missingKey = partial ? undefined : keysEnum?.find((key) => !valueKeys.includes(key));
|
|
123
|
+
if (missingKey !== undefined) {
|
|
124
|
+
return `${err} to have key ${missingKey}`;
|
|
125
|
+
}
|
|
126
|
+
const extraKeys = valueKeys.filter((key) => !keysEnum?.includes(key));
|
|
127
|
+
if (extraKeys.length) {
|
|
128
|
+
return `${err} has extra keys: ${extraKeys}`;
|
|
129
|
+
}
|
|
130
|
+
if (valuesSchema) {
|
|
131
|
+
for (const [propKey, propValue] of Object.entries(value)) {
|
|
132
|
+
const valError = getPropertyValidationError(propValue, valuesSchema, [...path, propKey]);
|
|
133
|
+
if (valError !== undefined) {
|
|
134
|
+
return `${valError}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
97
138
|
return;
|
|
98
139
|
}
|
|
99
140
|
return `Could not validate field type: ${JSON.stringify(fieldType)}`;
|
|
100
141
|
};
|
|
101
142
|
|
|
143
|
+
const getTypeDescription = (schema: JSONB.FieldType): string => {
|
|
144
|
+
const schemaObj = getFieldTypeObj(schema);
|
|
145
|
+
const { type, nullable, optional, oneOf, record } = schemaObj;
|
|
146
|
+
const allowedTypes: any[] = [];
|
|
147
|
+
if (nullable) allowedTypes.push("null");
|
|
148
|
+
if (optional) allowedTypes.push("undefined");
|
|
149
|
+
if (typeof type === "string") {
|
|
150
|
+
allowedTypes.push(type);
|
|
151
|
+
} else if (type) {
|
|
152
|
+
if (isObject(type)) {
|
|
153
|
+
const keyOpts: string[] = [];
|
|
154
|
+
Object.entries(type).forEach(([key, value]) => {
|
|
155
|
+
keyOpts.push(`${key}: ${getTypeDescription(value)}`);
|
|
156
|
+
});
|
|
157
|
+
allowedTypes.push(`{ ${keyOpts.join("; ")} }`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
schemaObj.enum?.forEach((v) => {
|
|
161
|
+
if (v === null) {
|
|
162
|
+
allowedTypes.push("null");
|
|
163
|
+
} else if (v === undefined) {
|
|
164
|
+
allowedTypes.push("undefined");
|
|
165
|
+
} else if (typeof v === "string") {
|
|
166
|
+
allowedTypes.push(JSON.stringify(v));
|
|
167
|
+
} else {
|
|
168
|
+
allowedTypes.push(v);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
oneOf?.forEach((v) => {
|
|
172
|
+
const type = getTypeDescription(v);
|
|
173
|
+
allowedTypes.push(type);
|
|
174
|
+
});
|
|
175
|
+
if (record) {
|
|
176
|
+
const { keysEnum, partial, values } = record;
|
|
177
|
+
const optional = partial ? "?" : "";
|
|
178
|
+
const valueType = !values ? "any" : getTypeDescription(values);
|
|
179
|
+
if (keysEnum) {
|
|
180
|
+
allowedTypes.push(`{ [${keysEnum.join(" | ")}]${optional}: ${valueType} }`);
|
|
181
|
+
} else {
|
|
182
|
+
allowedTypes.push(`{ [key: string]${optional}: ${valueType} }`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return allowedTypes.join(" | ");
|
|
187
|
+
};
|
|
188
|
+
|
|
102
189
|
export const getJSONBObjectSchemaValidationError = <S extends JSONB.ObjectType["type"]>(
|
|
103
190
|
schema: S,
|
|
104
191
|
obj: any,
|
|
@@ -110,7 +197,7 @@ export const getJSONBObjectSchemaValidationError = <S extends JSONB.ObjectType["
|
|
|
110
197
|
return { error: `Expecting ${objName} to be an object` };
|
|
111
198
|
}
|
|
112
199
|
for (const [k, objSchema] of Object.entries(schema)) {
|
|
113
|
-
const error = getPropertyValidationError(
|
|
200
|
+
const error = getPropertyValidationError(obj[k], objSchema, [k]);
|
|
114
201
|
if (error) {
|
|
115
202
|
return { error };
|
|
116
203
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getKeys, isObject, type JSONB, type TableSchema } from "prostgles-types";
|
|
2
2
|
import { postgresToTsType } from "../DboBuilder/DboBuilder";
|
|
3
|
-
import { asValue } from "../PubSubManager/
|
|
3
|
+
import { asValue } from "../PubSubManager/PubSubManagerUtils";
|
|
4
4
|
import { getFieldTypeObj } from "./JSONBValidation";
|
|
5
5
|
|
|
6
6
|
type ColOpts = { nullable?: boolean };
|
|
@@ -11,124 +11,124 @@ export function getJSONBSchemaTSTypes(
|
|
|
11
11
|
outerLeading = "",
|
|
12
12
|
tables: TableSchema[]
|
|
13
13
|
): string {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
const nullType = fieldType.nullable ? `null | ` : "";
|
|
14
|
+
return getJSONBTSTypes(
|
|
15
|
+
tables,
|
|
16
|
+
{ ...(schema as JSONB.FieldTypeObj), nullable: colOpts.nullable },
|
|
17
|
+
undefined,
|
|
18
|
+
outerLeading
|
|
19
|
+
);
|
|
20
|
+
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
export const getJSONBTSTypes = (
|
|
23
|
+
tables: TableSchema[],
|
|
24
|
+
rawFieldType: JSONB.FieldType,
|
|
25
|
+
isOneOf = false,
|
|
26
|
+
innerLeading = "",
|
|
27
|
+
depth = 0
|
|
28
|
+
): string => {
|
|
29
|
+
const fieldType = getFieldTypeObj(rawFieldType);
|
|
30
|
+
const nullType = fieldType.nullable ? `null | ` : "";
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
/** Primitives */
|
|
33
|
+
if (typeof fieldType.type === "string") {
|
|
34
|
+
const correctType = fieldType.type
|
|
35
|
+
.replace("integer", "number")
|
|
36
|
+
.replace("time", "string")
|
|
37
|
+
.replace("timestamp", "string")
|
|
38
|
+
.replace("Date", "string");
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const spacing = isOneOf ? " " : " ";
|
|
43
|
-
let objDef =
|
|
44
|
-
` {${spacing}` +
|
|
45
|
-
getKeys(type)
|
|
46
|
-
.map((key) => {
|
|
47
|
-
const fieldType = getFieldTypeObj(type[key]!);
|
|
48
|
-
const escapedKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
49
|
-
return (
|
|
50
|
-
`${spacing}${escapedKey}${fieldType.optional ? "?" : ""}: ` +
|
|
51
|
-
addSemicolonIfMissing(getFieldType(fieldType, true, undefined, depth + 1))
|
|
52
|
-
);
|
|
53
|
-
})
|
|
54
|
-
.join(" ") +
|
|
55
|
-
`${spacing}}`;
|
|
56
|
-
if (!isOneOf) {
|
|
57
|
-
objDef = addSemicolonIfMissing(objDef);
|
|
58
|
-
}
|
|
40
|
+
if (fieldType.allowedValues && fieldType.type.endsWith("[]")) {
|
|
41
|
+
return nullType + ` (${fieldType.allowedValues.map((v) => JSON.stringify(v)).join(" | ")})[]`;
|
|
42
|
+
}
|
|
43
|
+
return nullType + correctType;
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
45
|
+
/** Object */
|
|
46
|
+
} else if (isObject(fieldType.type)) {
|
|
47
|
+
const addSemicolonIfMissing = (v: string) => (v.trim().endsWith(";") ? v : v.trim() + ";");
|
|
48
|
+
const { type } = fieldType;
|
|
49
|
+
const spacing = isOneOf ? " " : " ";
|
|
50
|
+
let objDef =
|
|
51
|
+
` {${spacing}` +
|
|
52
|
+
getKeys(type)
|
|
53
|
+
.map((key) => {
|
|
54
|
+
const fieldType = getFieldTypeObj(type[key]!);
|
|
55
|
+
const escapedKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
56
|
+
return (
|
|
57
|
+
`${spacing}${escapedKey}${fieldType.optional ? "?" : ""}: ` +
|
|
58
|
+
addSemicolonIfMissing(getJSONBTSTypes(tables, fieldType, true, undefined, depth + 1))
|
|
59
|
+
);
|
|
60
|
+
})
|
|
61
|
+
.join(" ") +
|
|
62
|
+
`${spacing}}`;
|
|
63
|
+
if (!isOneOf) {
|
|
64
|
+
objDef = addSemicolonIfMissing(objDef);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Keep single line */
|
|
68
|
+
if (isOneOf) {
|
|
69
|
+
objDef = objDef.split("\n").join("");
|
|
70
|
+
}
|
|
71
|
+
return nullType + objDef;
|
|
72
|
+
} else if (fieldType.enum) {
|
|
73
|
+
return nullType + fieldType.enum.map((v) => asValue(v)).join(" | ");
|
|
74
|
+
} else if (fieldType.oneOf || fieldType.oneOfType) {
|
|
75
|
+
const oneOf = fieldType.oneOf || fieldType.oneOfType.map((type) => ({ type }));
|
|
76
|
+
return (
|
|
77
|
+
(fieldType.nullable ? `\n${innerLeading} | null` : "") +
|
|
78
|
+
oneOf
|
|
79
|
+
.map((v) => `\n${innerLeading} | ` + getJSONBTSTypes(tables, v, true, undefined, depth + 1))
|
|
80
|
+
.join("")
|
|
81
|
+
);
|
|
82
|
+
} else if (fieldType.arrayOf || fieldType.arrayOfType) {
|
|
83
|
+
const arrayOf = fieldType.arrayOf || { type: fieldType.arrayOfType };
|
|
84
|
+
return `${fieldType.nullable ? `null | ` : ""} ( ${getJSONBTSTypes(tables, arrayOf, true, undefined, depth + 1)} )[]`;
|
|
85
|
+
} else if (fieldType.record) {
|
|
86
|
+
const { keysEnum, values, partial } = fieldType.record;
|
|
87
|
+
// TODO: ensure props with undefined values are not allowed in the TS type (strict union)
|
|
88
|
+
const getRecord = (v: string) => (partial ? `Partial<Record<${v}>>` : `Record<${v}>`);
|
|
89
|
+
return `${fieldType.nullable ? `null |` : ""} ${getRecord(`${keysEnum?.map((v) => asValue(v)).join(" | ") ?? "string"}, ${!values ? "any" : getJSONBTSTypes(tables, values, true, undefined, depth + 1)}`)}`;
|
|
90
|
+
} else if (fieldType.lookup) {
|
|
91
|
+
const l = fieldType.lookup;
|
|
92
|
+
if (l.type === "data-def") {
|
|
93
|
+
return `${fieldType.nullable ? `null |` : ""} ${getJSONBTSTypes(tables, {
|
|
94
|
+
type: {
|
|
95
|
+
table: "string",
|
|
96
|
+
column: "string",
|
|
97
|
+
filter: { record: {}, optional: true },
|
|
98
|
+
isArray: { type: "boolean", optional: true },
|
|
99
|
+
searchColumns: { type: "string[]", optional: true },
|
|
100
|
+
isFullRow: {
|
|
101
|
+
optional: true,
|
|
102
|
+
type: {
|
|
103
|
+
displayColumns: { type: "string[]", optional: true },
|
|
98
104
|
},
|
|
99
|
-
showInRowCard: { optional: true, record: {} },
|
|
100
105
|
},
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
showInRowCard: { optional: true, record: {} },
|
|
107
|
+
},
|
|
108
|
+
})}`;
|
|
109
|
+
}
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
111
|
+
const isSChema = l.type === "schema";
|
|
112
|
+
let type =
|
|
113
|
+
isSChema ?
|
|
114
|
+
l.object === "table" ?
|
|
115
|
+
"string"
|
|
116
|
+
: `{ "table": string; "column": string; }`
|
|
117
|
+
: "";
|
|
118
|
+
if (!isSChema) {
|
|
119
|
+
const cols = tables.find((t) => t.name === l.table)?.columns;
|
|
120
|
+
if (!l.isFullRow) {
|
|
121
|
+
type = postgresToTsType(cols?.find((c) => c.name === l.column)?.udt_name ?? "text");
|
|
122
|
+
} else {
|
|
123
|
+
type =
|
|
124
|
+
!cols ? "any" : (
|
|
125
|
+
`{ ${cols.map((c) => `${JSON.stringify(c.name)}: ${c.is_nullable ? "null | " : ""} ${postgresToTsType(c.udt_name)}; `).join(" ")} }`
|
|
126
|
+
);
|
|
121
127
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return getFieldType(
|
|
127
|
-
{ ...(schema as JSONB.FieldTypeObj), nullable: colOpts.nullable },
|
|
128
|
-
undefined,
|
|
129
|
-
outerLeading
|
|
130
|
-
);
|
|
131
|
-
}
|
|
128
|
+
}
|
|
129
|
+
return `${fieldType.nullable ? `null | ` : ""}${type}${l.isArray ? "[]" : ""}`;
|
|
130
|
+
} else throw "Unexpected getSchemaTSTypes: " + JSON.stringify({ fieldType }, null, 2);
|
|
131
|
+
};
|
|
132
132
|
|
|
133
133
|
const isValidIdentifier = (str: string) => {
|
|
134
134
|
const identifierRegex = /^[A-Za-z$_][A-Za-z0-9$_]*$/;
|