prostgles-server 4.2.642 → 4.2.644

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 (173) hide show
  1. package/debug/caught-request-fix-build.log +4 -0
  2. package/debug/caught-request-fix-test-build.log +0 -0
  3. package/debug/caught-request-subscription-failure.log +603 -0
  4. package/debug/caught-validation-build.log +4 -0
  5. package/debug/caught-validation-fix-build.log +4 -0
  6. package/debug/caught-validation-fix-test-build.log +0 -0
  7. package/debug/caught-validation-test-build.log +0 -0
  8. package/debug/client-db-tx-build.log +4 -0
  9. package/debug/client-db-tx-test-server.log +3248 -0
  10. package/debug/client-db-tx-tests-build.log +0 -0
  11. package/debug/client-handler-shape-build.log +4 -0
  12. package/debug/client-handler-shape-test-server.log +544 -0
  13. package/debug/client-handler-shape-tests-build.log +0 -0
  14. package/debug/column-definitions-test-server.log +152 -164
  15. package/debug/fixes-review-build.log +4 -0
  16. package/debug/fixes-review-client-schema.backup +745 -0
  17. package/debug/fixes-review-generated-schema.backup +489 -0
  18. package/debug/fixes-review-probes-build.log +0 -0
  19. package/debug/fixes-review-probes.patch +43 -0
  20. package/debug/fixes-review-restore-build.log +0 -0
  21. package/debug/fixes-review-tests-build.log +0 -0
  22. package/debug/fixes-review-typecheck.log +8 -0
  23. package/debug/function-client-db-tx-build.log +4 -0
  24. package/debug/function-client-db-tx-tests-build.log +0 -0
  25. package/debug/review-current-fixes-build.log +4 -0
  26. package/debug/review-current-fixes-client.backup +745 -0
  27. package/debug/review-current-fixes-probes-build.log +0 -0
  28. package/debug/review-current-fixes-probes.patch +55 -0
  29. package/debug/review-current-fixes-restore-build.log +0 -0
  30. package/debug/review-current-fixes-rls.backup +478 -0
  31. package/debug/review-current-fixes-schema.backup +489 -0
  32. package/debug/review-current-fixes-test-build.log +0 -0
  33. package/debug/review-current-fixes-typecheck.log +8 -0
  34. package/debug/review-user-identity-build.log +4 -0
  35. package/debug/review-user-identity-probes-build.log +0 -0
  36. package/debug/review-user-identity-probes.log +558 -0
  37. package/debug/review-user-identity-probes.patch +45 -0
  38. package/debug/review-user-identity-restore-build.log +0 -0
  39. package/debug/review-user-identity-rls.backup +463 -0
  40. package/debug/review-user-identity-schema.backup +720 -0
  41. package/debug/review-user-identity-server.log +3248 -0
  42. package/debug/review-user-identity-test-build.log +0 -0
  43. package/debug/review-user-identity-typecheck.log +8 -0
  44. package/debug/schema-types-focused-strict-check.log +0 -0
  45. package/debug/schema-types-strict-check.log +38 -0
  46. package/debug/schema-types-tests-build.log +0 -0
  47. package/debug/security-review-baseline-tests.log +3250 -0
  48. package/debug/security-review-build.log +4 -0
  49. package/debug/security-review-checkfilter-repro.log +545 -0
  50. package/debug/security-review-clientSchemaTypes.spec.ts.backup +553 -0
  51. package/debug/security-review-repro-build.log +0 -0
  52. package/debug/security-review-repro.spec.ts +591 -0
  53. package/debug/security-review-restore-build.log +0 -0
  54. package/debug/security-review-server-tests.log +3254 -0
  55. package/debug/security-review-test-build.log +0 -0
  56. package/debug/server-user-lint.log +4 -0
  57. package/debug/server-user-test-server.log +538 -0
  58. package/debug/server-user-typecheck.log +8 -0
  59. package/debug/test-check-eslint.log +6 -0
  60. package/debug/test-check-lint-baseline.json +1 -0
  61. package/debug/test-check-npm-test.log +789 -0
  62. package/debug/test-check-types.log +8 -0
  63. package/debug/test-config-npm-test.log +223 -0
  64. package/debug/test-config-strict-null-check.log +50 -0
  65. package/debug/test-config-typecheck.log +8 -0
  66. package/debug/tryRunP-test-quick.log +78 -0
  67. package/debug/tryRunP-typecheck.log +42 -0
  68. package/debug/user-identity-direct-call-build.log +0 -0
  69. package/debug/user-identity-direct-call-schema.backup +489 -0
  70. package/debug/user-identity-regressions-build.log +4 -0
  71. package/debug/user-identity-regressions-generated-schema.backup +489 -0
  72. package/debug/user-identity-regressions-test-build.log +0 -0
  73. package/dist/Auth/AuthHandler.js +1 -1
  74. package/dist/Auth/AuthHandler.js.map +1 -1
  75. package/dist/Auth/AuthTypes.d.ts +11 -9
  76. package/dist/Auth/AuthTypes.d.ts.map +1 -1
  77. package/dist/Auth/AuthTypes.js.map +1 -1
  78. package/dist/Auth/utils/getClientRequestIPsInfo.d.ts +2 -1
  79. package/dist/Auth/utils/getClientRequestIPsInfo.d.ts.map +1 -1
  80. package/dist/Auth/utils/getClientRequestIPsInfo.js +8 -1
  81. package/dist/Auth/utils/getClientRequestIPsInfo.js.map +1 -1
  82. package/dist/Auth/utils/getSessionUser.d.ts +4 -3
  83. package/dist/Auth/utils/getSessionUser.d.ts.map +1 -1
  84. package/dist/Auth/utils/getSessionUser.js +4 -2
  85. package/dist/Auth/utils/getSessionUser.js.map +1 -1
  86. package/dist/Auth/utils/getSessionUser.spec.js +2 -4
  87. package/dist/Auth/utils/getSessionUser.spec.js.map +1 -1
  88. package/dist/Auth/utils/handleGetUser.d.ts.map +1 -1
  89. package/dist/Auth/utils/handleGetUser.js +16 -0
  90. package/dist/Auth/utils/handleGetUser.js.map +1 -1
  91. package/dist/Auth/utils/serverSideRequest.d.ts +17 -0
  92. package/dist/Auth/utils/serverSideRequest.d.ts.map +1 -0
  93. package/dist/Auth/utils/serverSideRequest.js +20 -0
  94. package/dist/Auth/utils/serverSideRequest.js.map +1 -0
  95. package/dist/DboBuilder/DboBuilder.d.ts.map +1 -1
  96. package/dist/DboBuilder/DboBuilder.js +4 -2
  97. package/dist/DboBuilder/DboBuilder.js.map +1 -1
  98. package/dist/DboBuilder/TableHandler/TableHandler.d.ts +1 -1
  99. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +0 -4
  100. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts.map +1 -1
  101. package/dist/DboBuilder/ViewHandler/ViewHandler.js +5 -8
  102. package/dist/DboBuilder/ViewHandler/ViewHandler.js.map +1 -1
  103. package/dist/DboBuilder/ViewHandler/count.d.ts.map +1 -1
  104. package/dist/DboBuilder/ViewHandler/count.js +2 -1
  105. package/dist/DboBuilder/ViewHandler/count.js.map +1 -1
  106. package/dist/DboBuilder/ViewHandler/find.d.ts.map +1 -1
  107. package/dist/DboBuilder/ViewHandler/find.js +2 -1
  108. package/dist/DboBuilder/ViewHandler/find.js.map +1 -1
  109. package/dist/DboBuilder/ViewHandler/getDbHandlerWithAbort.d.ts +9 -2
  110. package/dist/DboBuilder/ViewHandler/getDbHandlerWithAbort.d.ts.map +1 -1
  111. package/dist/DboBuilder/ViewHandler/getDbHandlerWithAbort.js +57 -23
  112. package/dist/DboBuilder/ViewHandler/getDbHandlerWithAbort.js.map +1 -1
  113. package/dist/DboBuilder/ViewHandler/size.d.ts.map +1 -1
  114. package/dist/DboBuilder/ViewHandler/size.js +2 -1
  115. package/dist/DboBuilder/ViewHandler/size.js.map +1 -1
  116. package/dist/DboBuilder/dboBuilderUtils.d.ts +1 -0
  117. package/dist/DboBuilder/dboBuilderUtils.d.ts.map +1 -1
  118. package/dist/DboBuilder/dboBuilderUtils.js +2 -1
  119. package/dist/DboBuilder/dboBuilderUtils.js.map +1 -1
  120. package/dist/PublishParser/PublishParser.d.ts.map +1 -1
  121. package/dist/PublishParser/PublishParser.js +2 -1
  122. package/dist/PublishParser/PublishParser.js.map +1 -1
  123. package/dist/PublishParser/defineServerFunction.d.ts +2 -0
  124. package/dist/PublishParser/defineServerFunction.d.ts.map +1 -1
  125. package/dist/PublishParser/defineServerFunction.js.map +1 -1
  126. package/dist/PublishParser/getPublishedTableNames.d.ts.map +1 -1
  127. package/dist/PublishParser/getPublishedTableNames.js +2 -1
  128. package/dist/PublishParser/getPublishedTableNames.js.map +1 -1
  129. package/dist/PublishParser/publishTypesAndUtils.d.ts +1 -1
  130. package/dist/PublishParser/publishTypesAndUtils.d.ts.map +1 -1
  131. package/dist/StorageClient/StorageClientTypes.d.ts +5 -4
  132. package/dist/StorageClient/StorageClientTypes.d.ts.map +1 -1
  133. package/dist/WebsocketAPI/getClientHandlers.d.ts +5 -0
  134. package/dist/WebsocketAPI/getClientHandlers.d.ts.map +1 -1
  135. package/dist/WebsocketAPI/getClientHandlers.js +33 -14
  136. package/dist/WebsocketAPI/getClientHandlers.js.map +1 -1
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +3 -1
  140. package/dist/index.js.map +1 -1
  141. package/dist/initProstgles.d.ts +12 -2
  142. package/dist/initProstgles.d.ts.map +1 -1
  143. package/dist/initProstgles.js +10 -1
  144. package/dist/initProstgles.js.map +1 -1
  145. package/dist/runClientRequest.d.ts +2 -1
  146. package/dist/runClientRequest.d.ts.map +1 -1
  147. package/dist/runClientRequest.js +7 -4
  148. package/dist/runClientRequest.js.map +1 -1
  149. package/eslint.config.mjs +21 -1
  150. package/lib/Auth/AuthHandler.ts +1 -1
  151. package/lib/Auth/AuthTypes.ts +12 -8
  152. package/lib/Auth/utils/getClientRequestIPsInfo.ts +11 -2
  153. package/lib/Auth/utils/getSessionUser.spec.ts +4 -6
  154. package/lib/Auth/utils/getSessionUser.ts +8 -5
  155. package/lib/Auth/utils/handleGetUser.ts +13 -0
  156. package/lib/Auth/utils/serverSideRequest.ts +27 -0
  157. package/lib/DboBuilder/DboBuilder.ts +7 -2
  158. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +11 -11
  159. package/lib/DboBuilder/ViewHandler/count.ts +2 -1
  160. package/lib/DboBuilder/ViewHandler/find.ts +2 -1
  161. package/lib/DboBuilder/ViewHandler/getDbHandlerWithAbort.ts +72 -29
  162. package/lib/DboBuilder/ViewHandler/size.ts +2 -1
  163. package/lib/DboBuilder/dboBuilderUtils.ts +2 -0
  164. package/lib/PublishParser/PublishParser.ts +2 -1
  165. package/lib/PublishParser/defineServerFunction.ts +3 -5
  166. package/lib/PublishParser/getPublishedTableNames.ts +2 -1
  167. package/lib/PublishParser/publishTypesAndUtils.ts +1 -1
  168. package/lib/StorageClient/StorageClientTypes.ts +5 -4
  169. package/lib/WebsocketAPI/getClientHandlers.ts +63 -29
  170. package/lib/index.ts +3 -3
  171. package/lib/initProstgles.ts +31 -5
  172. package/lib/runClientRequest.ts +10 -3
  173. package/package.json +5 -3
@@ -29,6 +29,7 @@ import {
29
29
  getCanExecute,
30
30
  getConstraints,
31
31
  getSerializedClientErrorFromPGError,
32
+ TX_KEY,
32
33
  } from "./dboBuilderUtils";
33
34
  import { prepareShortestJoinPaths } from "./joins/prepareShortestJoinPaths";
34
35
  import { cacheDBTypes, runSQL } from "./runSql/runSQL";
@@ -304,10 +305,14 @@ export class DboBuilder {
304
305
  });
305
306
 
306
307
  if (this.prostgles.opts.transactions) {
307
- const txKey = "tx";
308
+ if (this.dboMap.has(TX_KEY)) {
309
+ throw new Error(
310
+ `Transaction key ${JSON.stringify(TX_KEY)} clashes with an existing table or view name`,
311
+ );
312
+ }
308
313
 
309
314
  //@ts-ignore
310
- this.dbo[txKey] = <R, TH extends DbTxTableHandlers & Pick<DBHandlerServer, "sql">>(
315
+ this.dbo[TX_KEY] = <R, TH extends DbTxTableHandlers & Pick<DBHandlerServer, "sql">>(
311
316
  cb: TxCB<Promise<R>, TH>,
312
317
  ) => this.getTX(cb);
313
318
  }
@@ -25,7 +25,7 @@ import { getColumns } from "../getColumns";
25
25
  import type { Graph } from "../joins/shortestPath";
26
26
  import { count } from "./count";
27
27
  import { find, type Param3 } from "./find";
28
- import { getDbHandlerWithAbort } from "./getDbHandlerWithAbort";
28
+ import { getAbortSignalKeys } from "./getDbHandlerWithAbort";
29
29
  import { getInfo } from "./getInfo";
30
30
  import { parseFieldFilter } from "./parseFieldFilter";
31
31
  import { size } from "./size";
@@ -121,20 +121,21 @@ export class ViewHandler {
121
121
  };
122
122
 
123
123
  abortRemoteQuery = (abortSignalId: string, localParams: LocalParams) => {
124
- const sid = this.dboBuilder.prostgles.authHandler.getSIDNoError(localParams.clientReq);
125
- if (!sid) {
126
- throw new Error(
127
- "Cannot get SID from client request. Ensure that the client is authenticated before using abortable queries.",
128
- );
129
- }
130
- const abortableQuery = this.activeQueries.get(abortSignalId);
131
- if (!abortableQuery || abortableQuery.sid !== sid) {
124
+ const abortSignalKeys = getAbortSignalKeys(
125
+ this.dboBuilder.prostgles,
126
+ { abortSignalId },
127
+ localParams,
128
+ );
129
+
130
+ const abortableQuery = this.activeQueries.get(abortSignalKeys.abortSignalKey);
131
+ if (!abortableQuery) {
132
132
  throw new Error(
133
133
  `No active query found with abortSignalId ${abortSignalId}. Ensure that the query was initiated with the correct abortSignalId.`,
134
134
  );
135
135
  }
136
+ this.abortRequests.add(abortSignalKeys.abortSignalKey);
136
137
  abortableQuery.abort();
137
- this.abortRequests.delete(abortSignalId);
138
+ this.abortRequests.delete(abortSignalKeys.abortSignalKey);
138
139
  };
139
140
 
140
141
  activeQueries = new Map<
@@ -148,7 +149,6 @@ export class ViewHandler {
148
149
  }
149
150
  >();
150
151
  abortRequests = new Set<string>();
151
- getDbHandlerWithAbort = getDbHandlerWithAbort.bind(this);
152
152
 
153
153
  _log = ({
154
154
  command,
@@ -9,6 +9,7 @@ import {
9
9
  import type { ViewHandler } from "./ViewHandler";
10
10
  import { getReturnTypeQuery } from "./getReturnTypeQuery";
11
11
  import type { Param3 } from "./find";
12
+ import { getDbHandlerWithAbort } from "./getDbHandlerWithAbort";
12
13
 
13
14
  export async function count(
14
15
  this: ViewHandler,
@@ -59,7 +60,7 @@ export async function count(
59
60
  return queryToReturn as unknown[];
60
61
  }
61
62
 
62
- const handler = this.getDbHandlerWithAbort(localParams, {
63
+ const handler = getDbHandlerWithAbort(this, localParams, {
63
64
  abortSignal: selectParams?.abortSignal,
64
65
  abortSignalId: param3?.abortSignalId,
65
66
  });
@@ -13,6 +13,7 @@ import { getSelectQuery } from "../QueryBuilder/getSelectQuery";
13
13
  import { getReturnTypeQuery } from "./getReturnTypeQuery";
14
14
  import { validateSelectParams } from "./validateSelectParams";
15
15
  import type { ViewHandler } from "./ViewHandler";
16
+ import { getDbHandlerWithAbort } from "./getDbHandlerWithAbort";
16
17
 
17
18
  export type Param3 = {
18
19
  abortSignalId?: string;
@@ -102,7 +103,7 @@ export const find = async function (
102
103
 
103
104
  const query = queryWithRLS;
104
105
  const isOneOrNone = returnType === "row" || returnType === "value";
105
- const dbHandler = this.getDbHandlerWithAbort(localParams, {
106
+ const dbHandler = getDbHandlerWithAbort(this, localParams, {
106
107
  abortSignal,
107
108
  abortSignalId: param3?.abortSignalId,
108
109
  });
@@ -2,12 +2,16 @@ import type { DB } from "../../initProstgles";
2
2
  import { QUERY_ID_PREFIX, type LocalParams } from "../DboBuilder";
3
3
  import type { ViewHandler } from "./ViewHandler";
4
4
  import type pgPromise from "pg-promise";
5
+ import { getServerSideUserId } from "../../Auth/utils/serverSideRequest";
6
+ import type { Prostgles } from "../../Prostgles";
5
7
 
6
- export function getDbHandlerWithAbort(
7
- this: ViewHandler,
8
+ type Params = { abortSignal: AbortSignal | undefined; abortSignalId: string | undefined };
9
+
10
+ export const getDbHandlerWithAbort = (
11
+ viewHandler: ViewHandler,
8
12
  localParams: LocalParams | undefined,
9
- params: { abortSignal: AbortSignal | undefined; abortSignalId: string | undefined },
10
- ): Pick<DB | pgPromise.ITask<{}>, "any" | "one" | "many" | "manyOrNone" | "none" | "oneOrNone"> {
13
+ params: Params,
14
+ ): Pick<DB | pgPromise.ITask<{}>, "any" | "one" | "many" | "manyOrNone" | "none" | "oneOrNone"> => {
11
15
  if (params.abortSignal && params.abortSignalId) {
12
16
  throw new Error("Cannot provide both abortSignal and abortSignalId");
13
17
  }
@@ -16,28 +20,29 @@ export function getDbHandlerWithAbort(
16
20
  throw new Error("Query aborted before execution");
17
21
  }
18
22
 
19
- const abortSignal =
20
- params.abortSignal ?? AbortSignal.timeout(localParams?.clientReq ? 7_000 : 120_000);
21
- const abortSignalId = params.abortSignalId ?? crypto.randomUUID();
22
-
23
- if (this.activeQueries.has(abortSignalId)) {
24
- throw new Error(
25
- `A query with abortSignalId ${params.abortSignalId} is already active. Ensure that each query has a unique abortSignalId.`,
26
- );
23
+ if (params.abortSignal !== undefined && !(params.abortSignal instanceof AbortSignal)) {
24
+ throw new Error("abortSignal must be an instance of AbortSignal");
27
25
  }
28
- const handler = this.getTransaction(localParams)?.t ?? this.db;
29
- const { adminClient } = this.dboBuilder.prostgles;
26
+
27
+ const { clientReq } = localParams ?? {};
28
+ const abortSignal = params.abortSignal ?? AbortSignal.timeout(clientReq ? 7_000 : 120_000);
29
+ const { prostgles } = viewHandler.dboBuilder;
30
+ const handler = viewHandler.getTransaction(localParams)?.t ?? viewHandler.db;
31
+ const { adminClient } = prostgles;
30
32
  if (!adminClient) {
31
33
  throw new Error(
32
34
  "adminClient not available. Ensure prostgles.adminClient is initialized before using abortable queries.",
33
35
  );
34
36
  }
35
- const sid = this.dboBuilder.prostgles.authHandler.getSIDNoError(localParams?.clientReq);
36
- if (!sid && localParams?.clientReq) {
37
+
38
+ const signalKeys = getAbortSignalKeys(prostgles, params, localParams);
39
+ const { abortSignalKey } = signalKeys;
40
+ if (viewHandler.activeQueries.has(abortSignalKey)) {
37
41
  throw new Error(
38
- "Cannot get SID from client request. Ensure that the client is authenticated before using abortable queries.",
42
+ `A query with abortSignalId ${params.abortSignalId} is already active. Ensure that each query has a unique abortSignalId.`,
39
43
  );
40
44
  }
45
+
41
46
  const withAbortQuery = <Args extends unknown[], R extends Promise<any>>(
42
47
  func: (query: string, ...args: Args) => R,
43
48
  ) => {
@@ -52,16 +57,17 @@ export function getDbHandlerWithAbort(
52
57
  }
53
58
 
54
59
  const abort = () => {
55
- void this._log({
56
- data: { query, abortSignalId },
60
+ void viewHandler._log({
61
+ data: { query, abortSignalId: signalKeys.abortSignalId },
57
62
  command: "abort",
58
63
  localParams,
59
- duration: Date.now() - (this.activeQueries.get(abortSignalId)?.start ?? Date.now()),
64
+ duration:
65
+ Date.now() - (viewHandler.activeQueries.get(abortSignalKey)?.start ?? Date.now()),
60
66
  error: new Error("Query aborted"),
61
67
  });
62
68
  /** Only terminate if there is exactly one matching query with a query id prefix */
63
69
 
64
- this.abortRequests.delete(abortSignalId);
70
+ viewHandler.abortRequests.delete(abortSignalKey);
65
71
  void adminClient
66
72
  .query(
67
73
  `
@@ -71,17 +77,18 @@ export function getDbHandlerWithAbort(
71
77
  `,
72
78
  [`${queryIdPrefix}%`],
73
79
  )
74
- .catch((err) => {
80
+ .catch((_err) => {
75
81
  // ignore error
76
82
  });
77
83
  };
78
84
 
79
- if (this.abortRequests.has(abortSignalId)) {
80
- this.abortRequests.delete(abortSignalId);
81
- throw new Error(`Abort requested`);
85
+ if (viewHandler.abortRequests.has(abortSignalKey)) {
86
+ viewHandler.abortRequests.delete(abortSignalKey);
87
+ throw new Error("Abort already requested");
82
88
  }
83
89
 
84
- this.activeQueries.set(abortSignalId, {
90
+ const sid = prostgles.authHandler.getSIDNoError(clientReq);
91
+ viewHandler.activeQueries.set(abortSignalKey, {
85
92
  query,
86
93
  start: Date.now(),
87
94
  sid,
@@ -91,8 +98,8 @@ export function getDbHandlerWithAbort(
91
98
  abortSignal.addEventListener("abort", abort);
92
99
  return func(query, ...args).finally(() => {
93
100
  abortSignal.removeEventListener("abort", abort);
94
- this.activeQueries.delete(abortSignalId);
95
- this.abortRequests.delete(abortSignalId);
101
+ viewHandler.activeQueries.delete(abortSignalKey);
102
+ viewHandler.abortRequests.delete(abortSignalKey);
96
103
  });
97
104
  };
98
105
  };
@@ -105,4 +112,40 @@ export function getDbHandlerWithAbort(
105
112
  none: withAbortQuery(handler.none.bind(handler)),
106
113
  many: withAbortQuery(handler.many.bind(handler)),
107
114
  };
108
- }
115
+ };
116
+
117
+ export const getAbortSignalKeys = (
118
+ prostgles: Prostgles,
119
+ params: Pick<Params, "abortSignalId">,
120
+ localParams: LocalParams | undefined,
121
+ ) => {
122
+ const clientIdentifier = (() => {
123
+ const { clientReq } = localParams ?? {};
124
+ if (!clientReq) {
125
+ if (params.abortSignalId !== undefined) {
126
+ throw new Error(
127
+ "abortSignalId must not be provided for local requests. Use abortSignal instead.",
128
+ );
129
+ }
130
+ return ["<local-request>", "local"];
131
+ }
132
+ const sid = prostgles.authHandler.getSIDNoError(clientReq);
133
+ if (sid) return [sid, "sid"];
134
+ const userId = getServerSideUserId(prostgles, clientReq);
135
+ if (userId) return [userId, "userId"];
136
+ throw new Error(
137
+ "Cannot get SID or userId from client request. Ensure that the client is authenticated before using abortable queries.",
138
+ );
139
+ })();
140
+ const abortSignalId = params.abortSignalId ?? crypto.randomUUID();
141
+
142
+ const MAX_LENGTH = 36;
143
+ if (abortSignalId.length > MAX_LENGTH) {
144
+ throw new Error(`abortSignalId length must not exceed ${MAX_LENGTH} characters`);
145
+ }
146
+ const abortSignalKey = JSON.stringify([...clientIdentifier, abortSignalId]);
147
+ return {
148
+ abortSignalId,
149
+ abortSignalKey,
150
+ };
151
+ };
@@ -9,6 +9,7 @@ import {
9
9
  import type { ViewHandler } from "./ViewHandler";
10
10
  import { getReturnTypeQuery } from "./getReturnTypeQuery";
11
11
  import type { Param3 } from "./find";
12
+ import { getDbHandlerWithAbort } from "./getDbHandlerWithAbort";
12
13
  export async function size(
13
14
  this: ViewHandler,
14
15
  _filter?: Filter,
@@ -62,7 +63,7 @@ export async function size(
62
63
  return queryToReturn as unknown[];
63
64
  }
64
65
 
65
- const handler = this.getDbHandlerWithAbort(localParams, {
66
+ const handler = getDbHandlerWithAbort(this, localParams, {
66
67
  abortSignal: selectParams?.abortSignal,
67
68
  abortSignalId: param3?.abortSignalId,
68
69
  });
@@ -203,3 +203,5 @@ export const withUserRLS = (
203
203
  const queryId = crypto.randomUUID();
204
204
  return [QUERY_ID_PREFIX + queryId, firstQuery, query].filter(Boolean).join("\n");
205
205
  };
206
+
207
+ export const TX_KEY = "tx" as const;
@@ -129,10 +129,11 @@ export class PublishParser {
129
129
  };
130
130
  return unrestrictedCtx;
131
131
  }
132
- const { clientDb } = await publishParams.getClientDBHandlers(undefined);
132
+ const { clientDb, withClientDbTx } = await publishParams.getClientDBHandlers(undefined);
133
133
  const { clientInfo, clientReq, tables } = publishParams;
134
134
  const restrictedCtx: RestrictedFunctionContext<void, SessionUser, unknown> = {
135
135
  dbo: clientDb,
136
+ withClientDbTx,
136
137
  user,
137
138
  clientInfo,
138
139
  clientReq,
@@ -11,6 +11,7 @@ import type { SessionUser } from "../Auth/AuthTypes";
11
11
  import type { DBOFullyTyped, DBOFullyTypedClient } from "../DBSchemaBuilder/DBSchemaBuilder";
12
12
  import type { DB } from "../initProstgles";
13
13
  import type { PublishParams } from "./publishTypesAndUtils";
14
+ import type { ClientHandlers } from "../WebsocketAPI/getClientHandlers";
14
15
 
15
16
  type FunctionContextBase<S, SUser extends SessionUser, Context> = Pick<
16
17
  PublishParams<S, SUser>,
@@ -30,6 +31,7 @@ export type RestrictedFunctionContext<
30
31
  Context = undefined,
31
32
  > = FunctionContextBase<S, SUser, Context> & {
32
33
  dbo: DBOFullyTypedClient<S>;
34
+ withClientDbTx: ClientHandlers<S>["withClientDbTx"];
33
35
  };
34
36
 
35
37
  export type UnrestrictedFunctionContext<
@@ -55,11 +57,7 @@ export type ServerFunctionDefinition = {
55
57
  };
56
58
 
57
59
  declare const serverFunctionContext: unique symbol;
58
- export type ServerFunctionContextMarker<
59
- S,
60
- SUser extends SessionUser,
61
- Context = undefined,
62
- > = {
60
+ export type ServerFunctionContextMarker<S, SUser extends SessionUser, Context = undefined> = {
63
61
  [serverFunctionContext]?: [S, SUser, [Context] extends [undefined] ? never : Context];
64
62
  };
65
63
 
@@ -2,12 +2,13 @@ import { isObject } from "prostgles-types";
2
2
  import type { PublishParser } from "./PublishParser";
3
3
  import { type PublishObject } from "./PublishParser";
4
4
  import { getFileVersionTableName, isFileVersionTable } from "../StorageClient/fileVersionUtils";
5
+ import { TX_KEY } from "../DboBuilder/dboBuilderUtils";
5
6
 
6
7
  export const getPublishedTableNames = (
7
8
  publishParserInstance: PublishParser,
8
9
  publishObject: PublishObject,
9
10
  ) => {
10
- const txKey = !publishParserInstance.prostgles.opts.transactions ? "" : "tx";
11
+ const txKey = !publishParserInstance.prostgles.opts.transactions ? "" : TX_KEY;
11
12
  if (txKey && txKey in publishObject) {
12
13
  throw new Error(
13
14
  `Transactions key ${JSON.stringify(txKey)} collides with a published table name`,
@@ -127,7 +127,7 @@ export type AfterEachTsTrigger<R, DBX, Context = undefined, InputDataType = R> =
127
127
  localParams: undefined | LocalParams;
128
128
  context: Context;
129
129
  },
130
- ) => Promise<void>;
130
+ ) => MaybePromise<void>;
131
131
  };
132
132
 
133
133
  export type ValidateRowsArgsCommon<R = AnyObject, DBX = DBHandlerServer> = {
@@ -1,3 +1,4 @@
1
+ import type { MaybePromise } from "prostgles-types";
1
2
  import type * as stream from "stream";
2
3
 
3
4
  export type LocalUploadedFileDetails = {
@@ -22,9 +23,9 @@ export type UploadFileOptions = {
22
23
  onProgress?: (bytesUploaded: number) => void;
23
24
  };
24
25
  export type StorageClientBase<T> = {
25
- upload: (file: UploadFileOptions) => Promise<T>;
26
- downloadAsStream: (name: string) => Promise<stream.Readable>;
27
- delete: (fileName: string) => Promise<void>;
26
+ upload: (file: UploadFileOptions) => MaybePromise<T>;
27
+ downloadAsStream: (name: string) => MaybePromise<stream.Readable>;
28
+ delete: (fileName: string) => MaybePromise<void>;
28
29
  };
29
30
  export type LocalStorageClient = StorageClientBase<LocalUploadedFileDetails> & {
30
31
  type: "local";
@@ -32,6 +33,6 @@ export type LocalStorageClient = StorageClientBase<LocalUploadedFileDetails> & {
32
33
  };
33
34
  export type CloudStorageClient = StorageClientBase<CloudUploadedFileDetails> & {
34
35
  type: "cloud";
35
- getSignedUrlForDownload: (fileName: string, expiresInSeconds: number) => Promise<string>;
36
+ getSignedUrlForDownload: (fileName: string, expiresInSeconds: number) => MaybePromise<string>;
36
37
  };
37
38
  export type StorageClient = LocalStorageClient | CloudStorageClient;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ getAllowedTableMethods,
2
3
  getKeys,
3
4
  type AnyObject,
4
5
  type ClientSchema,
@@ -8,6 +9,7 @@ import {
8
9
  } from "prostgles-types";
9
10
  import type { AuthClientRequest } from "../Auth/AuthTypes";
10
11
  import type { DBOFullyTypedClient } from "../DBSchemaBuilder/DBSchemaBuilder";
12
+ import type { DbTxTableHandlers } from "../DboBuilder/DboBuilderTypes";
11
13
  import type { Prostgles } from "../Prostgles";
12
14
  import type { ServerFunctionDefinition } from "../PublishParser/defineServerFunction";
13
15
  import type { PermissionScope } from "../PublishParser/publishTypesAndUtils";
@@ -17,6 +19,11 @@ import { getClientSchema } from "./getClientSchema";
17
19
  export type ClientHandlers<S = void> = {
18
20
  clientSql: SQLHandler;
19
21
  clientDb: DBOFullyTypedClient<S>;
22
+ /**
23
+ * Runs permission-checked table operations in a transaction. Requires transactions: true.
24
+ * Caught table request errors also roll back the transaction.
25
+ */
26
+ withClientDbTx: <R>(callback: (clientDb: DBOFullyTypedClient<S>) => R | Promise<R>) => Promise<R>;
20
27
  clientMethods: Record<string, ServerFunctionDefinition>;
21
28
  clientSchema: ClientSchema;
22
29
  };
@@ -27,47 +34,74 @@ export const getClientHandlers = async <S = void>(
27
34
  ): Promise<ClientHandlers<S>> => {
28
35
  prostgles.checkNotDestroyed();
29
36
  const clientSchema =
30
- clientReq.socket?.prostgles?.get(prostgles.appId) ??
37
+ (scope ? undefined : clientReq.socket?.prostgles?.get(prostgles.appId)) ??
31
38
  (await getClientSchema.bind(prostgles)(clientReq, scope));
39
+
32
40
  const sqlHandler: SQLHandler | undefined = ((
33
41
  query: string,
34
42
  params?: unknown,
35
43
  options?: SQLOptions,
36
44
  ) => runClientSqlRequest.bind(prostgles)({ query, params, options }, clientReq)) as SQLHandler;
37
- const tableHandlers = Object.fromEntries(
38
- prostgles.dboBuilder.tablesOrViews!.map((table) => {
39
- const methods = [...tableMethods];
40
- const handlers = Object.fromEntries(
41
- methods.map((command) => {
42
- const method = (param1: unknown, param2: unknown, param3: unknown) =>
43
- runClientRequest.bind(prostgles)(
44
- { command, tableName: table.name, param1, param2, param3 },
45
- clientReq,
46
- scope,
47
- );
48
- return [command, method];
49
- }),
50
- );
51
- return [table.name, handlers];
52
- }),
53
- );
54
45
 
55
- const txNotAllowed: {} = {
56
- tx: () => {
57
- throw new Error("Transactions are not allowed in client handlers");
58
- },
59
- };
46
+ const getTableHandlers = (
47
+ transactionHandlers?: DbTxTableHandlers,
48
+ onRequestError?: (error: unknown) => void,
49
+ ) =>
50
+ Object.fromEntries(
51
+ clientSchema.tableSchema.map((table) => {
52
+ const allowedMethods = getAllowedTableMethods(table);
53
+ const methods = tableMethods.filter((command) => allowedMethods.includes(command));
54
+ const handlers = Object.fromEntries(
55
+ methods.map((command) => {
56
+ const method = (param1: unknown, param2: unknown, param3: unknown) =>
57
+ runClientRequest
58
+ .bind(prostgles)(
59
+ { command, tableName: table.name, param1, param2, param3 },
60
+ clientReq,
61
+ scope,
62
+ transactionHandlers,
63
+ )
64
+ .catch((error: unknown) => {
65
+ onRequestError?.(error);
66
+ throw error;
67
+ });
68
+ return [command, method];
69
+ }),
70
+ );
71
+ return [table.name, handlers];
72
+ }),
73
+ );
74
+
60
75
  const clientSql = ((query: string, params?: AnyObject, options?: SQLOptions) => {
61
76
  if (scope && !scope.allowSql) {
62
- throw new Error("SQL is dissallowed by PermissionScope");
77
+ throw new Error("SQL is disallowed by PermissionScope");
63
78
  }
64
79
 
65
80
  return sqlHandler(query, params, options);
66
81
  }) as SQLHandler;
67
- const clientDb = {
68
- ...tableHandlers,
69
- ...txNotAllowed,
70
- } as DBOFullyTypedClient<S>;
82
+
83
+ const clientDb = getTableHandlers() as unknown as DBOFullyTypedClient<S>;
84
+
85
+ const withClientDbTx: ClientHandlers<S>["withClientDbTx"] = async (callback) => {
86
+ prostgles.checkNotDestroyed();
87
+ if (!prostgles.opts.transactions) {
88
+ throw new Error("Transactions are not enabled");
89
+ }
90
+ return prostgles.dboBuilder.getTX(async (transactionHandlers) => {
91
+ /**
92
+ * runClientRequest throws authorization errors.
93
+ * Prevent cached authorization errors from committing the transaction.
94
+ */
95
+ let failure: { error: unknown } | undefined;
96
+ const result = await callback({
97
+ ...getTableHandlers(transactionHandlers, (error) => {
98
+ failure ??= { error };
99
+ }),
100
+ } as unknown as DBOFullyTypedClient<S>);
101
+ if (failure) throw failure.error;
102
+ return result;
103
+ });
104
+ };
71
105
 
72
106
  const clientMethods: Record<string, ServerFunctionDefinition> = Object.fromEntries(
73
107
  clientSchema.methods.map(({ name, input, description, output }) => {
@@ -81,7 +115,7 @@ export const getClientHandlers = async <S = void>(
81
115
  }),
82
116
  );
83
117
 
84
- return { clientDb, clientSql, clientMethods, clientSchema };
118
+ return { clientDb, clientSql, clientMethods, clientSchema, withClientDbTx };
85
119
  };
86
120
 
87
121
  const tableMethods = getKeys({
package/lib/index.ts CHANGED
@@ -3,6 +3,7 @@ import type { SessionUser } from "./Auth/AuthTypes";
3
3
  import type { InitResult, OnReadyCallbackBasic } from "./initProstgles";
4
4
  import { Prostgles } from "./Prostgles";
5
5
  import type { ProstglesInitOptions } from "./ProstglesTypes";
6
+ export { DB_GENERATED_NAMES } from "./DBSchemaBuilder/constants";
6
7
 
7
8
  function prostgles<
8
9
  S = void,
@@ -22,9 +23,8 @@ export const createProstgles = <
22
23
  SUser extends SessionUser = SessionUser,
23
24
  ClientSchema = S,
24
25
  >() => {
25
- return <Context = undefined>(
26
- params: ProstglesInitOptions<S, SUser, Context>,
27
- ) => prostgles<S, SUser, Context, ClientSchema>(params);
26
+ return <Context = undefined>(params: ProstglesInitOptions<S, SUser, Context>) =>
27
+ prostgles<S, SUser, Context, ClientSchema>(params);
28
28
  };
29
29
  export * from "./PublishParser/defineServerFunction";
30
30
  export * from "./Auth/AuthTypes";
@@ -19,6 +19,17 @@ import { updateConfiguration, type clientOnlyUpdateKeys } from "./updateConfigur
19
19
  import { sleep } from "./utils/utils";
20
20
  import { getClientHandlers } from "./WebsocketAPI/getClientHandlers";
21
21
  import { getAdminClient } from "./DboBuilder/runSql/getAdminClient";
22
+ import { createServerSideRequest } from "./Auth/utils/serverSideRequest";
23
+
24
+ type ClientDBHandlerRequest =
25
+ | AuthClientRequest
26
+ | {
27
+ /** Trusted server-only identity, resolved through auth.findUser on every operation. */
28
+ userId: string;
29
+ socket?: never;
30
+ httpReq?: never;
31
+ res?: never;
32
+ };
22
33
 
23
34
  /**
24
35
  * Database connection details
@@ -125,9 +136,12 @@ export type InitResult<
125
136
  update: (newOpts: UpdatableOptions<S, SUser, Context>, force?: true) => Promise<void>;
126
137
  restart: () => Promise<InitResult<S, SUser, Context, ClientSchema>>;
127
138
  options: ProstglesInitOptions<S, SUser, Context>;
128
- /** Uses the configured client schema by default and accepts a publish-profile schema override. */
139
+ /**
140
+ * Uses the configured client schema by default and accepts a publish-profile schema override.
141
+ * Only pass userId from trusted server code; never forward client-supplied input here.
142
+ */
129
143
  getClientDBHandlers: <NarrowedClientSchema = ClientSchema>(
130
- clientReq: AuthClientRequest,
144
+ clientReq: ClientDBHandlerRequest,
131
145
  scope: PermissionScope | undefined,
132
146
  ) => ReturnType<typeof getClientHandlers<NarrowedClientSchema>>;
133
147
 
@@ -333,10 +347,22 @@ export const initProstgles = async function (
333
347
  await sleep(1000);
334
348
  return true;
335
349
  },
336
- getClientDBHandlers: <ClientSchema = void>(
337
- clientReq: AuthClientRequest,
350
+ getClientDBHandlers: async <ClientSchema = void>(
351
+ clientReq: ClientDBHandlerRequest,
338
352
  scope: PermissionScope | undefined,
339
- ) => getClientHandlers<ClientSchema>(this, clientReq, scope),
353
+ ) => {
354
+ if ("userId" in clientReq) {
355
+ if ("socket" in clientReq || "httpReq" in clientReq || "res" in clientReq) {
356
+ throw new Error("userId cannot be combined with a client request");
357
+ }
358
+ return getClientHandlers<ClientSchema>(
359
+ this,
360
+ createServerSideRequest(this, clientReq.userId),
361
+ scope,
362
+ );
363
+ }
364
+ return getClientHandlers<ClientSchema>(this, clientReq, scope);
365
+ },
340
366
  };
341
367
 
342
368
  return initResult;
@@ -9,11 +9,13 @@ import {
9
9
  import type { AuthClientRequest } from "./Auth/AuthTypes";
10
10
  import { getSessionUser } from "./Auth/utils/getSessionUser";
11
11
  import type { LocalParams } from "./DboBuilder/DboBuilder";
12
+ import type { DbTxTableHandlers } from "./DboBuilder/DboBuilderTypes";
12
13
  import type { TableHandler as TableHandlerServer } from "./DboBuilder/TableHandler/TableHandler";
13
14
  import { canRunSQL } from "./DboBuilder/runSql/runSQL";
14
15
  import type { Prostgles } from "./Prostgles";
15
16
  import type { ParsedTableRule } from "./PublishParser/publishTypesAndUtils";
16
17
  import { type PermissionScope } from "./PublishParser/publishTypesAndUtils";
18
+ import { getAbortSignalKeys } from "./DboBuilder/ViewHandler/getDbHandlerWithAbort";
17
19
 
18
20
  const TABLE_METHODS = {
19
21
  find: 1,
@@ -64,6 +66,7 @@ export const runClientRequest = async function (
64
66
  nonValidatedArgs: Args,
65
67
  clientReq: AuthClientRequest,
66
68
  scope: PermissionScope | undefined,
69
+ transactionHandlers?: DbTxTableHandlers,
67
70
  ) {
68
71
  this.checkNotDestroyed();
69
72
  /* Channel name will only include client-sent params so we ignore table_rules enforced params */
@@ -102,7 +105,12 @@ export const runClientRequest = async function (
102
105
  );
103
106
  }
104
107
 
105
- const tableHandler = this.dboBuilder.dboMap.get(tableName);
108
+ const tableHandler =
109
+ transactionHandlers ?
110
+ Object.hasOwn(transactionHandlers, tableName) ?
111
+ transactionHandlers[tableName]
112
+ : undefined
113
+ : this.dboBuilder.dboMap.get(tableName);
106
114
  if (!tableHandler) {
107
115
  throw `tableName ${tableName} is invalid or not allowed`;
108
116
  }
@@ -119,7 +127,7 @@ export const runClientRequest = async function (
119
127
  scope,
120
128
  });
121
129
 
122
- const sessionUser = getSessionUser(clientInfo);
130
+ const sessionUser = getSessionUser(clientInfo, this.opts.auth?.sessionFields);
123
131
  const localParams: LocalParams = {
124
132
  clientReq,
125
133
  isRemoteRequest: { user: sessionUser, clientInfo },
@@ -151,7 +159,6 @@ export const runClientRequest = async function (
151
159
  scope,
152
160
  );
153
161
 
154
- tableHandler.abortRequests.add(validation.data.param1.abortSignalId);
155
162
  return tableHandler.abortRemoteQuery(validation.data.param1.abortSignalId, localParams);
156
163
  } else {
157
164
  this.publishParser.validateRequestRule(