prostgles-server 4.2.265 → 4.2.266
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/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/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/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 +97 -96
- package/dist/JSONBValidation/getJSONBSchemaTSTypes.js.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +7 -23
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js +1 -1
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PubSubManager/addSub.d.ts +1 -1
- package/dist/PubSubManager/addSub.d.ts.map +1 -1
- package/dist/PubSubManager/addSub.js +17 -21
- package/dist/PubSubManager/addSub.js.map +1 -1
- package/dist/PubSubManager/notifListener.d.ts.map +1 -1
- package/dist/PubSubManager/notifListener.js +9 -4
- package/dist/PubSubManager/notifListener.js.map +1 -1
- package/dist/PubSubManager/pushSubData.js +1 -1
- package/dist/PubSubManager/pushSubData.js.map +1 -1
- package/lib/DboBuilder/ViewHandler/getValidatedSubscribeOptions.ts +72 -0
- package/lib/DboBuilder/ViewHandler/subscribe.ts +9 -6
- package/lib/JSONBValidation/JSONBValidation.spec.ts +38 -3
- package/lib/JSONBValidation/JSONBValidation.ts +102 -15
- package/lib/JSONBValidation/getJSONBSchemaTSTypes.ts +110 -110
- package/lib/PubSubManager/PubSubManager.ts +9 -8
- package/lib/PubSubManager/addSub.ts +20 -24
- package/lib/PubSubManager/notifListener.ts +16 -4
- package/lib/PubSubManager/pushSubData.ts +1 -1
- package/package.json +2 -2
|
@@ -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
|
}
|
|
@@ -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$_]*$/;
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
SelectParams,
|
|
30
30
|
SubscribeParams,
|
|
31
31
|
WAL,
|
|
32
|
+
type SubscribeOptions,
|
|
32
33
|
} from "prostgles-types";
|
|
33
34
|
|
|
34
35
|
import { find, pickKeys } from "prostgles-types/dist/util";
|
|
@@ -109,7 +110,7 @@ export type ViewSubscriptionOptions = (
|
|
|
109
110
|
}[];
|
|
110
111
|
};
|
|
111
112
|
|
|
112
|
-
export type SubscriptionParams =
|
|
113
|
+
export type SubscriptionParams = {
|
|
113
114
|
socket_id?: string;
|
|
114
115
|
channel_name: string;
|
|
115
116
|
|
|
@@ -124,22 +125,23 @@ export type SubscriptionParams = Pick<SubscribeParams, "throttle" | "throttleOpt
|
|
|
124
125
|
/* Used as input */
|
|
125
126
|
table_rules?: ParsedTableRule;
|
|
126
127
|
filter: object;
|
|
127
|
-
|
|
128
|
+
selectParams: SelectParams;
|
|
129
|
+
subscribeOptions: SubscribeOptions;
|
|
128
130
|
|
|
129
131
|
localFuncs?: LocalFuncs;
|
|
130
132
|
socket: PRGLIOSocket | undefined;
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
lastPushed: number;
|
|
133
135
|
is_throttling?: any;
|
|
134
136
|
is_ready?: boolean;
|
|
135
137
|
};
|
|
136
138
|
|
|
137
139
|
export type Subscription = Pick<
|
|
138
140
|
SubscriptionParams,
|
|
139
|
-
| "
|
|
141
|
+
| "selectParams"
|
|
142
|
+
| "subscribeOptions"
|
|
140
143
|
| "is_throttling"
|
|
141
|
-
| "
|
|
142
|
-
| "throttleOpts"
|
|
144
|
+
| "lastPushed"
|
|
143
145
|
| "channel_name"
|
|
144
146
|
| "is_ready"
|
|
145
147
|
| "localFuncs"
|
|
@@ -147,7 +149,6 @@ export type Subscription = Pick<
|
|
|
147
149
|
| "socket_id"
|
|
148
150
|
| "table_info"
|
|
149
151
|
| "filter"
|
|
150
|
-
| "params"
|
|
151
152
|
| "table_rules"
|
|
152
153
|
> & {
|
|
153
154
|
triggers: {
|
|
@@ -298,7 +299,7 @@ export class PubSubManager {
|
|
|
298
299
|
getSubData = async (
|
|
299
300
|
sub: Subscription
|
|
300
301
|
): Promise<{ data: any[]; err?: undefined } | { data?: undefined; err: any }> => {
|
|
301
|
-
const { table_info, filter, params, table_rules } = sub; //, subOne = false
|
|
302
|
+
const { table_info, filter, selectParams: params, table_rules } = sub; //, subOne = false
|
|
302
303
|
const { name: table_name } = table_info;
|
|
303
304
|
|
|
304
305
|
if (!this.dbo[table_name]?.find) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { SubscriptionChannels } from "prostgles-types";
|
|
2
|
+
import { VoidFunction } from "../SchemaWatch/SchemaWatch";
|
|
3
|
+
import { tout } from "./initPubSubManager";
|
|
2
4
|
import {
|
|
3
5
|
BasicCallback,
|
|
4
6
|
parseCondition,
|
|
@@ -6,7 +8,6 @@ import {
|
|
|
6
8
|
Subscription,
|
|
7
9
|
SubscriptionParams,
|
|
8
10
|
} from "./PubSubManager";
|
|
9
|
-
import { VoidFunction } from "../SchemaWatch/SchemaWatch";
|
|
10
11
|
|
|
11
12
|
type AddSubscriptionParams = SubscriptionParams & {
|
|
12
13
|
condition: string;
|
|
@@ -27,12 +28,11 @@ export async function addSub(
|
|
|
27
28
|
localFuncs,
|
|
28
29
|
table_rules,
|
|
29
30
|
filter = {},
|
|
30
|
-
|
|
31
|
+
selectParams = {},
|
|
31
32
|
condition = "",
|
|
32
|
-
throttle = 0, //subOne = false,
|
|
33
33
|
viewOptions,
|
|
34
34
|
table_info,
|
|
35
|
-
|
|
35
|
+
subscribeOptions,
|
|
36
36
|
} = subscriptionParams;
|
|
37
37
|
const table_name = table_info.name;
|
|
38
38
|
|
|
@@ -43,16 +43,7 @@ export async function addSub(
|
|
|
43
43
|
throw "addSub: cannot have socket AND func";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
const pubThrottle = table_rules?.subscribe?.throttle || 0;
|
|
48
|
-
if (pubThrottle && Number.isInteger(pubThrottle) && pubThrottle > 0) {
|
|
49
|
-
validated_throttle = pubThrottle;
|
|
50
|
-
}
|
|
51
|
-
if (throttle && Number.isInteger(throttle) && throttle >= pubThrottle) {
|
|
52
|
-
validated_throttle = throttle;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`;
|
|
46
|
+
const channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.${JSON.stringify(selectParams)}.${"m"}.sub`;
|
|
56
47
|
const mainTrigger = {
|
|
57
48
|
table_name: table_name,
|
|
58
49
|
condition: parseCondition(condition),
|
|
@@ -63,16 +54,16 @@ export async function addSub(
|
|
|
63
54
|
channel_name,
|
|
64
55
|
filter,
|
|
65
56
|
localFuncs,
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
selectParams: selectParams,
|
|
58
|
+
lastPushed: 0,
|
|
68
59
|
socket,
|
|
69
|
-
|
|
60
|
+
subscribeOptions,
|
|
70
61
|
table_info,
|
|
71
62
|
is_ready: true,
|
|
72
63
|
is_throttling: false,
|
|
73
64
|
socket_id: socket?.id,
|
|
74
65
|
table_rules,
|
|
75
|
-
|
|
66
|
+
|
|
76
67
|
triggers: [mainTrigger],
|
|
77
68
|
};
|
|
78
69
|
|
|
@@ -105,14 +96,21 @@ export async function addSub(
|
|
|
105
96
|
}
|
|
106
97
|
}
|
|
107
98
|
|
|
99
|
+
const { skipFirst, throttleOpts, throttle } = subscribeOptions;
|
|
100
|
+
const sendFirstData = async () => {
|
|
101
|
+
if (skipFirst) return;
|
|
102
|
+
if (throttleOpts?.skipFirst && throttle) {
|
|
103
|
+
await tout(throttle);
|
|
104
|
+
}
|
|
105
|
+
void this.pushSubData(newSub);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
108
|
if (localFuncs) {
|
|
109
109
|
/**
|
|
110
110
|
* Must ensure sub will start sending data after all triggers are set up.
|
|
111
111
|
* Socket clients are not affected as they need to confirm they are ready to receive data
|
|
112
112
|
*/
|
|
113
|
-
result.sendFirstData =
|
|
114
|
-
void this.pushSubData(newSub);
|
|
115
|
-
};
|
|
113
|
+
result.sendFirstData = sendFirstData;
|
|
116
114
|
} else if (socket) {
|
|
117
115
|
const removeListeners = () => {
|
|
118
116
|
socket.removeAllListeners(channel_name);
|
|
@@ -121,9 +119,7 @@ export async function addSub(
|
|
|
121
119
|
};
|
|
122
120
|
removeListeners();
|
|
123
121
|
|
|
124
|
-
socket.once(result.channelNameReady,
|
|
125
|
-
void this.pushSubData(newSub);
|
|
126
|
-
});
|
|
122
|
+
socket.once(result.channelNameReady, sendFirstData);
|
|
127
123
|
socket.once(result.channelNameUnsubscribe, (_data: any, cb: BasicCallback) => {
|
|
128
124
|
const res = "ok";
|
|
129
125
|
this.subs = this.subs.filter((s) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseFieldFilter } from "../DboBuilder/ViewHandler/parseFieldFilter";
|
|
1
2
|
import { log, NOTIF_TYPE, NotifTypeName, pickKeys, PubSubManager } from "./PubSubManager";
|
|
2
3
|
|
|
3
4
|
/* Relay relevant data to relevant subscriptions */
|
|
@@ -128,10 +129,22 @@ export async function notifListener(this: PubSubManager, data: { payload: string
|
|
|
128
129
|
)
|
|
129
130
|
);
|
|
130
131
|
activeAndReadySubs.forEach((sub) => {
|
|
131
|
-
const { throttle = 0, throttleOpts } = sub;
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
const { throttle = 0, throttleOpts, actions } = sub.subscribeOptions;
|
|
133
|
+
|
|
134
|
+
const commandLowerCase = (op_name?.toLowerCase() || "insert") as keyof NonNullable<
|
|
135
|
+
typeof actions
|
|
136
|
+
>;
|
|
137
|
+
|
|
138
|
+
const actionIsIgnored =
|
|
139
|
+
actions &&
|
|
140
|
+
!parseFieldFilter(actions, false, ["insert", "update", "delete"]).includes(
|
|
141
|
+
commandLowerCase as any
|
|
142
|
+
);
|
|
143
|
+
if (actionIsIgnored) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
134
146
|
|
|
147
|
+
if (!throttleOpts?.skipFirst && sub.lastPushed <= Date.now() - throttle) {
|
|
135
148
|
/* It is assumed the policy was checked before this point */
|
|
136
149
|
void this.pushSubData(sub);
|
|
137
150
|
} else if (!sub.is_throttling) {
|
|
@@ -139,7 +152,6 @@ export async function notifListener(this: PubSubManager, data: { payload: string
|
|
|
139
152
|
sub.is_throttling = setTimeout(() => {
|
|
140
153
|
log("throttling finished. pushSubData...");
|
|
141
154
|
sub.is_throttling = null;
|
|
142
|
-
sub.last_throttled = Date.now();
|
|
143
155
|
void this.pushSubData(sub);
|
|
144
156
|
}, throttle);
|
|
145
157
|
}
|
|
@@ -35,6 +35,7 @@ export async function pushSubData(this: PubSubManager, sub: Subscription, err?:
|
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
sub.lastPushed = Date.now();
|
|
38
39
|
return new Promise(async (resolve, reject) => {
|
|
39
40
|
/* TODO: Retire subOne -> it's redundant */
|
|
40
41
|
|
|
@@ -57,7 +58,6 @@ export async function pushSubData(this: PubSubManager, sub: Subscription, err?:
|
|
|
57
58
|
} else {
|
|
58
59
|
onLog("no client to push data to");
|
|
59
60
|
}
|
|
60
|
-
// sub.last_throttled = Date.now();
|
|
61
61
|
} else {
|
|
62
62
|
onLog("fetch data error");
|
|
63
63
|
const errObj = { _err_msg: err.toString(), err };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prostgles-server",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.266",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"pg": "^8.11.5",
|
|
58
58
|
"pg-cursor": "^2.11.0",
|
|
59
59
|
"pg-promise": "^11.9.1",
|
|
60
|
-
"prostgles-types": "^4.0.
|
|
60
|
+
"prostgles-types": "^4.0.164"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@eslint/js": "^9.22.0",
|