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.
Files changed (39) hide show
  1. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts +4 -0
  2. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts.map +1 -0
  3. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js +65 -0
  4. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js.map +1 -0
  5. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  6. package/dist/DboBuilder/ViewHandler/subscribe.js +5 -5
  7. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  8. package/dist/JSONBValidation/JSONBValidation.d.ts.map +1 -1
  9. package/dist/JSONBValidation/JSONBValidation.js +105 -13
  10. package/dist/JSONBValidation/JSONBValidation.js.map +1 -1
  11. package/dist/JSONBValidation/JSONBValidation.spec.js +17 -3
  12. package/dist/JSONBValidation/JSONBValidation.spec.js.map +1 -1
  13. package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts +1 -0
  14. package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts.map +1 -1
  15. package/dist/JSONBValidation/getJSONBSchemaTSTypes.js +97 -96
  16. package/dist/JSONBValidation/getJSONBSchemaTSTypes.js.map +1 -1
  17. package/dist/PubSubManager/PubSubManager.d.ts +7 -23
  18. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  19. package/dist/PubSubManager/PubSubManager.js +1 -1
  20. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  21. package/dist/PubSubManager/addSub.d.ts +1 -1
  22. package/dist/PubSubManager/addSub.d.ts.map +1 -1
  23. package/dist/PubSubManager/addSub.js +17 -21
  24. package/dist/PubSubManager/addSub.js.map +1 -1
  25. package/dist/PubSubManager/notifListener.d.ts.map +1 -1
  26. package/dist/PubSubManager/notifListener.js +9 -4
  27. package/dist/PubSubManager/notifListener.js.map +1 -1
  28. package/dist/PubSubManager/pushSubData.js +1 -1
  29. package/dist/PubSubManager/pushSubData.js.map +1 -1
  30. package/lib/DboBuilder/ViewHandler/getValidatedSubscribeOptions.ts +72 -0
  31. package/lib/DboBuilder/ViewHandler/subscribe.ts +9 -6
  32. package/lib/JSONBValidation/JSONBValidation.spec.ts +38 -3
  33. package/lib/JSONBValidation/JSONBValidation.ts +102 -15
  34. package/lib/JSONBValidation/getJSONBSchemaTSTypes.ts +110 -110
  35. package/lib/PubSubManager/PubSubManager.ts +9 -8
  36. package/lib/PubSubManager/addSub.ts +20 -24
  37. package/lib/PubSubManager/notifListener.ts +16 -4
  38. package/lib/PubSubManager/pushSubData.ts +1 -1
  39. 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
- key: string,
55
- val: any,
54
+ value: any,
56
55
  rawFieldType: JSONB.FieldType,
57
56
  path: string[] = []
58
57
  ): string | undefined => {
59
- let err = `${[...path, key].join(".")} is of invalid type. Expecting `;
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 && val === null) return;
63
- if (optional && val === undefined) return;
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(val)) {
70
- return `${[...path, key].join(".")} is of invalid type. Expecting object`;
69
+ if (!isObject(value)) {
70
+ return err;
71
71
  }
72
72
  for (const [subKey, subSchema] of getObjectEntries(type)) {
73
- const error = getPropertyValidationError(subKey, val[subKey], subSchema, [...path, key]);
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(val);
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(val)) return err;
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(k, obj[k], objSchema);
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
- const getFieldType = (
15
- rawFieldType: JSONB.FieldType,
16
- isOneOf = false,
17
- innerLeading = "",
18
- depth = 0
19
- ): string => {
20
- const fieldType = getFieldTypeObj(rawFieldType);
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
- /** Primitives */
24
- if (typeof fieldType.type === "string") {
25
- const correctType = fieldType.type
26
- .replace("integer", "number")
27
- .replace("time", "string")
28
- .replace("timestamp", "string")
29
- .replace("Date", "string");
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
- if (fieldType.allowedValues && fieldType.type.endsWith("[]")) {
32
- return (
33
- nullType + ` (${fieldType.allowedValues.map((v) => JSON.stringify(v)).join(" | ")})[]`
34
- );
35
- }
36
- return nullType + correctType;
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
- /** Object */
39
- } else if (isObject(fieldType.type)) {
40
- const addSemicolonIfMissing = (v: string) => (v.trim().endsWith(";") ? v : v.trim() + ";");
41
- const { type } = fieldType;
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
- /** Keep single line */
61
- if (isOneOf) {
62
- objDef = objDef.split("\n").join("");
63
- }
64
- return nullType + objDef;
65
- } else if (fieldType.enum) {
66
- return nullType + fieldType.enum.map((v) => asValue(v)).join(" | ");
67
- } else if (fieldType.oneOf || fieldType.oneOfType) {
68
- const oneOf = fieldType.oneOf || fieldType.oneOfType.map((type) => ({ type }));
69
- return (
70
- (fieldType.nullable ? `\n${innerLeading} | null` : "") +
71
- oneOf
72
- .map((v) => `\n${innerLeading} | ` + getFieldType(v, true, undefined, depth + 1))
73
- .join("")
74
- );
75
- } else if (fieldType.arrayOf || fieldType.arrayOfType) {
76
- const arrayOf = fieldType.arrayOf || { type: fieldType.arrayOfType };
77
- return `${fieldType.nullable ? `null | ` : ""} ( ${getFieldType(arrayOf, true, undefined, depth + 1)} )[]`;
78
- } else if (fieldType.record) {
79
- const { keysEnum, values, partial } = fieldType.record;
80
- // TODO: ensure props with undefined values are not allowed in the TS type (strict union)
81
- const getRecord = (v: string) => (partial ? `Partial<Record<${v}>>` : `Record<${v}>`);
82
- return `${fieldType.nullable ? `null |` : ""} ${getRecord(`${keysEnum?.map((v) => asValue(v)).join(" | ") ?? "string"}, ${!values ? "any" : getFieldType(values, true, undefined, depth + 1)}`)}`;
83
- } else if (fieldType.lookup) {
84
- const l = fieldType.lookup;
85
- if (l.type === "data-def") {
86
- return `${fieldType.nullable ? `null |` : ""} ${getFieldType({
87
- type: {
88
- table: "string",
89
- column: "string",
90
- filter: { record: {}, optional: true },
91
- isArray: { type: "boolean", optional: true },
92
- searchColumns: { type: "string[]", optional: true },
93
- isFullRow: {
94
- optional: true,
95
- type: {
96
- displayColumns: { type: "string[]", optional: true },
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
- const isSChema = l.type === "schema";
105
- let type =
106
- isSChema ?
107
- l.object === "table" ?
108
- "string"
109
- : `{ "table": string; "column": string; }`
110
- : "";
111
- if (!isSChema) {
112
- const cols = tables.find((t) => t.name === l.table)?.columns;
113
- if (!l.isFullRow) {
114
- type = postgresToTsType(cols?.find((c) => c.name === l.column)?.udt_name ?? "text");
115
- } else {
116
- type =
117
- !cols ? "any" : (
118
- `{ ${cols.map((c) => `${JSON.stringify(c.name)}: ${c.is_nullable ? "null | " : ""} ${postgresToTsType(c.udt_name)}; `).join(" ")} }`
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
- return `${fieldType.nullable ? `null | ` : ""}${type}${l.isArray ? "[]" : ""}`;
123
- } else throw "Unexpected getSchemaTSTypes: " + JSON.stringify({ fieldType, schema }, null, 2);
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 = Pick<SubscribeParams, "throttle" | "throttleOpts"> & {
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
- params: SelectParams;
128
+ selectParams: SelectParams;
129
+ subscribeOptions: SubscribeOptions;
128
130
 
129
131
  localFuncs?: LocalFuncs;
130
132
  socket: PRGLIOSocket | undefined;
131
133
 
132
- last_throttled: number;
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
- | "throttle"
141
+ | "selectParams"
142
+ | "subscribeOptions"
140
143
  | "is_throttling"
141
- | "last_throttled"
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
- params = {},
31
+ selectParams = {},
31
32
  condition = "",
32
- throttle = 0, //subOne = false,
33
33
  viewOptions,
34
34
  table_info,
35
- throttleOpts,
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
- let validated_throttle = subscriptionParams.throttle || 10;
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
- params,
67
- last_throttled: 0,
57
+ selectParams: selectParams,
58
+ lastPushed: 0,
68
59
  socket,
69
- throttleOpts,
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
- throttle: validated_throttle,
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
- if (!throttleOpts?.skipFirst && sub.last_throttled <= Date.now() - throttle) {
133
- sub.last_throttled = Date.now();
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.265",
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.161"
60
+ "prostgles-types": "^4.0.164"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@eslint/js": "^9.22.0",