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.
Files changed (148) hide show
  1. package/dist/DboBuilder/TableHandler/DataValidator.js +5 -5
  2. package/dist/DboBuilder/TableHandler/DataValidator.js.map +1 -1
  3. package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.d.ts.map +1 -1
  4. package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js +1 -2
  5. package/dist/DboBuilder/TableHandler/insert/insertNestedRecords.js.map +1 -1
  6. package/dist/DboBuilder/TableHandler/insertTest.js +2 -2
  7. package/dist/DboBuilder/TableHandler/insertTest.js.map +1 -1
  8. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
  9. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +1 -2
  10. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
  11. package/dist/DboBuilder/TableHandler/updateFile.d.ts.map +1 -1
  12. package/dist/DboBuilder/TableHandler/updateFile.js +1 -2
  13. package/dist/DboBuilder/TableHandler/updateFile.js.map +1 -1
  14. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
  15. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts +4 -0
  16. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.d.ts.map +1 -0
  17. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js +65 -0
  18. package/dist/DboBuilder/ViewHandler/getValidatedSubscribeOptions.js.map +1 -0
  19. package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts.map +1 -1
  20. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js +6 -8
  21. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js.map +1 -1
  22. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  23. package/dist/DboBuilder/ViewHandler/subscribe.js +5 -5
  24. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  25. package/dist/DboBuilder/dboBuilderUtils.d.ts.map +1 -1
  26. package/dist/DboBuilder/dboBuilderUtils.js +1 -2
  27. package/dist/DboBuilder/dboBuilderUtils.js.map +1 -1
  28. package/dist/DboBuilder/getSubscribeRelatedTables.d.ts.map +1 -1
  29. package/dist/DboBuilder/getSubscribeRelatedTables.js +2 -2
  30. package/dist/DboBuilder/getSubscribeRelatedTables.js.map +1 -1
  31. package/dist/JSONBValidation/JSONBValidation.d.ts.map +1 -1
  32. package/dist/JSONBValidation/JSONBValidation.js +105 -13
  33. package/dist/JSONBValidation/JSONBValidation.js.map +1 -1
  34. package/dist/JSONBValidation/JSONBValidation.spec.js +17 -3
  35. package/dist/JSONBValidation/JSONBValidation.spec.js.map +1 -1
  36. package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts +1 -0
  37. package/dist/JSONBValidation/getJSONBSchemaTSTypes.d.ts.map +1 -1
  38. package/dist/JSONBValidation/getJSONBSchemaTSTypes.js +98 -97
  39. package/dist/JSONBValidation/getJSONBSchemaTSTypes.js.map +1 -1
  40. package/dist/JSONBValidation/validate_jsonb_schema_sql.js +3 -3
  41. package/dist/JSONBValidation/validate_jsonb_schema_sql.js.map +1 -1
  42. package/dist/Logging.d.ts +2 -1
  43. package/dist/Logging.d.ts.map +1 -1
  44. package/dist/PubSubManager/PubSubManager.d.ts +7 -44
  45. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  46. package/dist/PubSubManager/PubSubManager.js +7 -38
  47. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  48. package/dist/PubSubManager/PubSubManagerUtils.d.ts +22 -0
  49. package/dist/PubSubManager/PubSubManagerUtils.d.ts.map +1 -0
  50. package/dist/PubSubManager/PubSubManagerUtils.js +33 -0
  51. package/dist/PubSubManager/PubSubManagerUtils.js.map +1 -0
  52. package/dist/PubSubManager/addSub.d.ts +1 -1
  53. package/dist/PubSubManager/addSub.d.ts.map +1 -1
  54. package/dist/PubSubManager/addSub.js +20 -24
  55. package/dist/PubSubManager/addSub.js.map +1 -1
  56. package/dist/PubSubManager/addSync.d.ts.map +1 -1
  57. package/dist/PubSubManager/addSync.js +3 -3
  58. package/dist/PubSubManager/addSync.js.map +1 -1
  59. package/dist/PubSubManager/addTrigger.d.ts +1 -1
  60. package/dist/PubSubManager/addTrigger.d.ts.map +1 -1
  61. package/dist/PubSubManager/addTrigger.js +9 -9
  62. package/dist/PubSubManager/addTrigger.js.map +1 -1
  63. package/dist/PubSubManager/deleteOrphanedTriggers.d.ts.map +1 -1
  64. package/dist/PubSubManager/deleteOrphanedTriggers.js +2 -2
  65. package/dist/PubSubManager/deleteOrphanedTriggers.js.map +1 -1
  66. package/dist/PubSubManager/getPubSubManagerInitQuery.d.ts.map +1 -1
  67. package/dist/PubSubManager/getPubSubManagerInitQuery.js +26 -23
  68. package/dist/PubSubManager/getPubSubManagerInitQuery.js.map +1 -1
  69. package/dist/PubSubManager/initPubSubManager.d.ts +1 -1
  70. package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
  71. package/dist/PubSubManager/initPubSubManager.js +3 -3
  72. package/dist/PubSubManager/initPubSubManager.js.map +1 -1
  73. package/dist/PubSubManager/initialiseEventTriggers.d.ts +1 -1
  74. package/dist/PubSubManager/initialiseEventTriggers.d.ts.map +1 -1
  75. package/dist/PubSubManager/initialiseEventTriggers.js +2 -2
  76. package/dist/PubSubManager/initialiseEventTriggers.js.map +1 -1
  77. package/dist/PubSubManager/notifListener.d.ts.map +1 -1
  78. package/dist/PubSubManager/notifListener.js +20 -14
  79. package/dist/PubSubManager/notifListener.js.map +1 -1
  80. package/dist/PubSubManager/orphanTriggerCheck.d.ts.map +1 -1
  81. package/dist/PubSubManager/orphanTriggerCheck.js +2 -2
  82. package/dist/PubSubManager/orphanTriggerCheck.js.map +1 -1
  83. package/dist/PubSubManager/pushSubData.d.ts +1 -1
  84. package/dist/PubSubManager/pushSubData.d.ts.map +1 -1
  85. package/dist/PubSubManager/pushSubData.js +3 -3
  86. package/dist/PubSubManager/pushSubData.js.map +1 -1
  87. package/dist/PublishParser/getTableRulesWithoutFileTable.js +2 -2
  88. package/dist/PublishParser/getTableRulesWithoutFileTable.js.map +1 -1
  89. package/dist/SchemaWatch/SchemaWatch.d.ts.map +1 -1
  90. package/dist/SchemaWatch/SchemaWatch.js +4 -5
  91. package/dist/SchemaWatch/SchemaWatch.js.map +1 -1
  92. package/dist/SyncReplication.d.ts +1 -1
  93. package/dist/SyncReplication.d.ts.map +1 -1
  94. package/dist/SyncReplication.js +7 -7
  95. package/dist/SyncReplication.js.map +1 -1
  96. package/dist/TableConfig/applyTableConfig.js +2 -2
  97. package/dist/TableConfig/applyTableConfig.js.map +1 -1
  98. package/dist/TableConfig/getColumnDefinitionQuery.js +7 -7
  99. package/dist/TableConfig/getColumnDefinitionQuery.js.map +1 -1
  100. package/dist/TableConfig/getConstraintDefinitionQueries.js +4 -4
  101. package/dist/TableConfig/getConstraintDefinitionQueries.js.map +1 -1
  102. package/dist/TableConfig/getSchemaDiffQueries.js +2 -2
  103. package/dist/TableConfig/getSchemaDiffQueries.js.map +1 -1
  104. package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
  105. package/dist/TableConfig/initTableConfig.js +5 -5
  106. package/dist/TableConfig/initTableConfig.js.map +1 -1
  107. package/dist/TableConfig/runMigrations.js +2 -2
  108. package/dist/TableConfig/runMigrations.js.map +1 -1
  109. package/dist/TableConfig/tableConfigSchemaUtils.js +3 -3
  110. package/dist/TableConfig/tableConfigSchemaUtils.js.map +1 -1
  111. package/lib/DboBuilder/TableHandler/DataValidator.ts +1 -1
  112. package/lib/DboBuilder/TableHandler/insert/insertNestedRecords.ts +1 -1
  113. package/lib/DboBuilder/TableHandler/insertTest.ts +1 -1
  114. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +1 -2
  115. package/lib/DboBuilder/TableHandler/updateFile.ts +1 -2
  116. package/lib/DboBuilder/ViewHandler/getValidatedSubscribeOptions.ts +72 -0
  117. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +7 -13
  118. package/lib/DboBuilder/ViewHandler/subscribe.ts +9 -6
  119. package/lib/DboBuilder/dboBuilderUtils.ts +1 -1
  120. package/lib/DboBuilder/getSubscribeRelatedTables.ts +2 -1
  121. package/lib/JSONBValidation/JSONBValidation.spec.ts +38 -3
  122. package/lib/JSONBValidation/JSONBValidation.ts +102 -15
  123. package/lib/JSONBValidation/getJSONBSchemaTSTypes.ts +111 -111
  124. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +3 -3
  125. package/lib/Logging.ts +2 -1
  126. package/lib/PubSubManager/PubSubManager.ts +11 -50
  127. package/lib/PubSubManager/PubSubManagerUtils.ts +37 -0
  128. package/lib/PubSubManager/addSub.ts +21 -30
  129. package/lib/PubSubManager/addSync.ts +2 -7
  130. package/lib/PubSubManager/addTrigger.ts +3 -2
  131. package/lib/PubSubManager/deleteOrphanedTriggers.ts +2 -1
  132. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +29 -39
  133. package/lib/PubSubManager/initPubSubManager.ts +2 -1
  134. package/lib/PubSubManager/initialiseEventTriggers.ts +3 -2
  135. package/lib/PubSubManager/notifListener.ts +20 -6
  136. package/lib/PubSubManager/orphanTriggerCheck.ts +2 -1
  137. package/lib/PubSubManager/pushSubData.ts +3 -2
  138. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +1 -1
  139. package/lib/SchemaWatch/SchemaWatch.ts +2 -5
  140. package/lib/SyncReplication.ts +3 -2
  141. package/lib/TableConfig/applyTableConfig.ts +2 -2
  142. package/lib/TableConfig/getColumnDefinitionQuery.ts +1 -1
  143. package/lib/TableConfig/getConstraintDefinitionQueries.ts +1 -1
  144. package/lib/TableConfig/getSchemaDiffQueries.ts +1 -1
  145. package/lib/TableConfig/initTableConfig.ts +7 -4
  146. package/lib/TableConfig/runMigrations.ts +2 -2
  147. package/lib/TableConfig/tableConfigSchemaUtils.ts +2 -2
  148. 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/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 = isObject(rightFilterOrValue)
93
- ? getFuncQuery(rightFilterOrValue)
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 = 0, throttleOpts, ...selectParams } = params;
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
- params: { ...selectParams },
111
- throttle,
112
- throttleOpts,
113
- last_throttled: 0,
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 { log, ViewSubscriptionOptions } from "../PubSubManager/PubSubManager";
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 one of: "a","b","c",null' }
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
- 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
  }
@@ -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/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
- 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$_]*$/;