prostgles-server 4.2.625 → 4.2.626

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 (109) hide show
  1. package/dist/Audit/AuditTable.d.ts +2 -1
  2. package/dist/Audit/AuditTable.d.ts.map +1 -1
  3. package/dist/Audit/AuditTable.js.map +1 -1
  4. package/dist/Audit/getAuditTableRules.d.ts.map +1 -1
  5. package/dist/Audit/getAuditTableRules.js +3 -3
  6. package/dist/Audit/getAuditTableRules.js.map +1 -1
  7. package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.d.ts.map +1 -1
  8. package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js +7 -1
  9. package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js.map +1 -1
  10. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts +3 -1
  11. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
  12. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +19 -5
  13. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
  14. package/dist/DboBuilder/TableHandler/updateFile.d.ts +2 -39
  15. package/dist/DboBuilder/TableHandler/updateFile.d.ts.map +1 -1
  16. package/dist/DboBuilder/TableHandler/updateFile.js +1 -0
  17. package/dist/DboBuilder/TableHandler/updateFile.js.map +1 -1
  18. package/dist/DboBuilder/TableHandler/uploadFile.d.ts +2 -1
  19. package/dist/DboBuilder/TableHandler/uploadFile.d.ts.map +1 -1
  20. package/dist/DboBuilder/TableHandler/uploadFile.js +2 -1
  21. package/dist/DboBuilder/TableHandler/uploadFile.js.map +1 -1
  22. package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts.map +1 -1
  23. package/dist/DboBuilder/ViewHandler/getRawInfo.js +6 -2
  24. package/dist/DboBuilder/ViewHandler/getRawInfo.js.map +1 -1
  25. package/dist/DboBuilder/getColumns.d.ts.map +1 -1
  26. package/dist/DboBuilder/getColumns.js +3 -0
  27. package/dist/DboBuilder/getColumns.js.map +1 -1
  28. package/dist/ProstglesTypes.d.ts +10 -0
  29. package/dist/ProstglesTypes.d.ts.map +1 -1
  30. package/dist/ProstglesTypes.js.map +1 -1
  31. package/dist/PublishParser/PublishParser.d.ts.map +1 -1
  32. package/dist/PublishParser/PublishParser.js +7 -0
  33. package/dist/PublishParser/PublishParser.js.map +1 -1
  34. package/dist/PublishParser/getFileTableRules.d.ts.map +1 -1
  35. package/dist/PublishParser/getFileTableRules.js +4 -1
  36. package/dist/PublishParser/getFileTableRules.js.map +1 -1
  37. package/dist/PublishParser/getFileVersionTableRules.d.ts +7 -0
  38. package/dist/PublishParser/getFileVersionTableRules.d.ts.map +1 -0
  39. package/dist/PublishParser/getFileVersionTableRules.js +77 -0
  40. package/dist/PublishParser/getFileVersionTableRules.js.map +1 -0
  41. package/dist/PublishParser/getFileVersionTableRules.spec.d.ts +2 -0
  42. package/dist/PublishParser/getFileVersionTableRules.spec.d.ts.map +1 -0
  43. package/dist/PublishParser/getFileVersionTableRules.spec.js +50 -0
  44. package/dist/PublishParser/getFileVersionTableRules.spec.js.map +1 -0
  45. package/dist/PublishParser/getPublishedTableNames.d.ts.map +1 -1
  46. package/dist/PublishParser/getPublishedTableNames.js +11 -4
  47. package/dist/PublishParser/getPublishedTableNames.js.map +1 -1
  48. package/dist/StorageClient/deleteUnreferencedFile.d.ts.map +1 -1
  49. package/dist/StorageClient/deleteUnreferencedFile.js +17 -1
  50. package/dist/StorageClient/deleteUnreferencedFile.js.map +1 -1
  51. package/dist/StorageClient/fileTableDefinitions.d.ts +54 -0
  52. package/dist/StorageClient/fileTableDefinitions.d.ts.map +1 -0
  53. package/dist/StorageClient/fileTableDefinitions.js +38 -0
  54. package/dist/StorageClient/fileTableDefinitions.js.map +1 -0
  55. package/dist/StorageClient/fileVersionUtils.d.ts +18 -0
  56. package/dist/StorageClient/fileVersionUtils.d.ts.map +1 -0
  57. package/dist/StorageClient/fileVersionUtils.js +22 -0
  58. package/dist/StorageClient/fileVersionUtils.js.map +1 -0
  59. package/dist/StorageClient/getFileStorageKey.d.ts +3 -1
  60. package/dist/StorageClient/getFileStorageKey.d.ts.map +1 -1
  61. package/dist/StorageClient/getFileStorageKey.js +9 -1
  62. package/dist/StorageClient/getFileStorageKey.js.map +1 -1
  63. package/dist/StorageClient/getFileTableConfig.d.ts +1 -23
  64. package/dist/StorageClient/getFileTableConfig.d.ts.map +1 -1
  65. package/dist/StorageClient/getFileTableConfig.js +63 -25
  66. package/dist/StorageClient/getFileTableConfig.js.map +1 -1
  67. package/dist/StorageClient/getFileTableConfig.spec.js +6 -1
  68. package/dist/StorageClient/getFileTableConfig.spec.js.map +1 -1
  69. package/dist/StorageClient/saveFileVersion.d.ts +7 -0
  70. package/dist/StorageClient/saveFileVersion.d.ts.map +1 -0
  71. package/dist/StorageClient/saveFileVersion.js +71 -0
  72. package/dist/StorageClient/saveFileVersion.js.map +1 -0
  73. package/dist/StorageClient/setupFileServeHandler.d.ts.map +1 -1
  74. package/dist/StorageClient/setupFileServeHandler.js +38 -21
  75. package/dist/StorageClient/setupFileServeHandler.js.map +1 -1
  76. package/dist/TableConfig/TableRowFromColumnDefinitions.d.ts +11 -4
  77. package/dist/TableConfig/TableRowFromColumnDefinitions.d.ts.map +1 -1
  78. package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
  79. package/dist/TableConfig/initTableConfig.js +9 -6
  80. package/dist/TableConfig/initTableConfig.js.map +1 -1
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js.map +1 -1
  84. package/lib/Audit/AuditTable.ts +4 -1
  85. package/lib/Audit/getAuditTableRules.ts +5 -1
  86. package/lib/DBSchemaBuilder/getClientDBGeneratedSchemas.ts +9 -1
  87. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +31 -5
  88. package/lib/DboBuilder/TableHandler/updateFile.ts +1 -0
  89. package/lib/DboBuilder/TableHandler/uploadFile.ts +3 -0
  90. package/lib/DboBuilder/ViewHandler/getRawInfo.ts +6 -2
  91. package/lib/DboBuilder/getColumns.ts +2 -0
  92. package/lib/ProstglesTypes.ts +11 -0
  93. package/lib/PublishParser/PublishParser.ts +25 -7
  94. package/lib/PublishParser/getFileTableRules.ts +7 -1
  95. package/lib/PublishParser/getFileVersionTableRules.spec.ts +57 -0
  96. package/lib/PublishParser/getFileVersionTableRules.ts +106 -0
  97. package/lib/PublishParser/getPublishedTableNames.ts +14 -5
  98. package/lib/StorageClient/deleteUnreferencedFile.ts +20 -2
  99. package/lib/StorageClient/fileTableDefinitions.ts +52 -0
  100. package/lib/StorageClient/fileVersionUtils.ts +37 -0
  101. package/lib/StorageClient/getFileStorageKey.ts +11 -2
  102. package/lib/StorageClient/getFileTableConfig.spec.ts +6 -1
  103. package/lib/StorageClient/getFileTableConfig.ts +85 -30
  104. package/lib/StorageClient/saveFileVersion.ts +82 -0
  105. package/lib/StorageClient/setupFileServeHandler.ts +51 -28
  106. package/lib/TableConfig/TableRowFromColumnDefinitions.ts +37 -13
  107. package/lib/TableConfig/initTableConfig.ts +10 -6
  108. package/lib/index.ts +2 -1
  109. package/package.json +1 -1
@@ -1,37 +1,27 @@
1
- import { omitKeys } from "prostgles-types";
1
+ import { asName, omitKeys } from "prostgles-types";
2
+ import { md5 } from "prostgles-types/dist/md5";
2
3
  import { onDeleteFromFileTable } from "../DboBuilder/TableHandler/onDeleteFromFileTable";
3
4
  import { updateFile } from "../DboBuilder/TableHandler/updateFile";
4
5
  import { assertFileObjectValid, uploadFile } from "../DboBuilder/TableHandler/uploadFile";
5
6
  import type { Prostgles } from "../Prostgles";
6
- import type { BeforeEachTsTrigger } from "../PublishParser/publishTypesAndUtils";
7
+ import type {
8
+ AfterEachTsTrigger,
9
+ BeforeEachTsTrigger,
10
+ } from "../PublishParser/publishTypesAndUtils";
7
11
  import type { TableConfig } from "../TableConfig/TableConfigTypes";
8
- import type { TableRowFromColumnDefinitions } from "../TableConfig/TableRowFromColumnDefinitions";
9
12
  import type { TableHooks } from "../TableHooks/TableHooks";
10
13
  import { setupFileServeHandler } from "./setupFileServeHandler";
11
14
  import type { TableHandlers } from "../DboBuilder/DboBuilder";
15
+ import { getFileTableHandler, getFileVersionTableName } from "./fileVersionUtils";
16
+ import { backfillFileVersions, saveFileVersion } from "./saveFileVersion";
17
+ import {
18
+ FILE_TABLE_COLUMN_DEFINITIONS,
19
+ FILE_TABLE_VERSION_COLUMN_DEFINITIONS,
20
+ FILE_VERSION_TABLE_COLUMN_DEFINITIONS,
21
+ type FileTableRow,
22
+ } from "./fileTableDefinitions";
12
23
 
13
- const FILE_TABLE_COLUMN_DEFINITIONS = {
14
- id: `UUID PRIMARY KEY DEFAULT gen_random_uuid()`,
15
- storage_key: `UUID UNIQUE`, // NULL for legacy objects stored under their file ID.
16
- extension: `TEXT NOT NULL DEFAULT ''`,
17
- content_type: `TEXT NOT NULL DEFAULT ''`,
18
- content_length: `BIGINT NOT NULL DEFAULT 0`,
19
- etag: `TEXT NOT NULL DEFAULT ''`,
20
- original_name: `TEXT NOT NULL`,
21
- original_last_modified: `TIMESTAMPTZ`,
22
- description: `TEXT`,
23
- url: `TEXT NOT NULL DEFAULT ''`,
24
- cloud_url: `TEXT`,
25
- signed_url: `TEXT`,
26
- signed_url_expires: `BIGINT`,
27
- added: `TIMESTAMP NOT NULL DEFAULT NOW()`,
28
- updated: `TIMESTAMP NOT NULL DEFAULT NOW()`,
29
- deleted: `TIMESTAMPTZ`,
30
- deleted_from_storage: `TIMESTAMPTZ`,
31
- data: `BYTEA NOT NULL CHECK (data = decode('01', 'hex'))`, // Used as a placeholder to ensure insert types are correct. Actual data is uploaded to storageClient and not stored in the DB
32
- } as const;
33
-
34
- export type FileTableRow = TableRowFromColumnDefinitions<typeof FILE_TABLE_COLUMN_DEFINITIONS>;
24
+ export type { FileTableInsertRow, FileTableRow } from "./fileTableDefinitions";
35
25
 
36
26
  export const getFileTableConfig = (
37
27
  prg: Prostgles,
@@ -44,7 +34,30 @@ export const getFileTableConfig = (
44
34
  const { expressApp } = fileTable;
45
35
 
46
36
  const { tableName: fileTableName, storageClient } = fileTable;
37
+ const versionTableName = getFileVersionTableName(fileTable);
38
+ const versionConstraintName = `prostgles_file_version_${md5(JSON.stringify([versionTableName, fileTableName]))}`;
39
+ const versionIndexName = `prostgles_file_version_${md5(versionTableName)}_key`;
40
+ if (fileTable.versioning) {
41
+ if (versionTableName === fileTableName) {
42
+ throw new Error("File version table name must differ from the file table name");
43
+ }
44
+ if (
45
+ fileTable.versioning.maxVersions !== undefined &&
46
+ (!Number.isInteger(fileTable.versioning.maxVersions) || fileTable.versioning.maxVersions < 1)
47
+ ) {
48
+ throw new Error("fileTable.versioning.maxVersions must be a positive integer");
49
+ }
50
+ if (tableConfig?.[versionTableName]) {
51
+ throw new Error(
52
+ `File version table name (${versionTableName}) is managed and cannot be configured in tableConfig`,
53
+ );
54
+ }
55
+ }
47
56
 
57
+ const fileColumnDefinitions = {
58
+ ...FILE_TABLE_COLUMN_DEFINITIONS,
59
+ ...(fileTable.versioning && FILE_TABLE_VERSION_COLUMN_DEFINITIONS),
60
+ };
48
61
  const userFileTableConfig = tableConfig?.[fileTableName];
49
62
  if (userFileTableConfig) {
50
63
  if ("isLookupTable" in userFileTableConfig) {
@@ -59,12 +72,21 @@ export const getFileTableConfig = (
59
72
  }
60
73
  if ("columns" in userFileTableConfig && userFileTableConfig.columns) {
61
74
  const userCols = new Set(Object.keys(userFileTableConfig.columns));
62
- const clashingColumns = Object.keys(FILE_TABLE_COLUMN_DEFINITIONS).filter((col) =>
75
+ const clashingFileColumns = Object.keys(fileColumnDefinitions).filter((col) =>
63
76
  userCols.has(col),
64
77
  );
65
- if (clashingColumns.length) {
78
+ if (clashingFileColumns.length) {
79
+ throw new Error(
80
+ `FileManager table name (${fileTableName}) has clashing column names in tableConfig: ${clashingFileColumns}`,
81
+ );
82
+ }
83
+
84
+ const clashingFileRevisionColumns = Object.keys(FILE_VERSION_TABLE_COLUMN_DEFINITIONS).filter(
85
+ (col) => userCols.has(col),
86
+ );
87
+ if (clashingFileRevisionColumns.length) {
66
88
  throw new Error(
67
- `FileManager table name (${fileTableName}) has clashing column names in tableConfig: ${clashingColumns}`,
89
+ `FileManager table name (${fileTableName}) has clashing column names with file revision table in tableConfig: ${clashingFileRevisionColumns}`,
68
90
  );
69
91
  }
70
92
  }
@@ -74,7 +96,7 @@ export const getFileTableConfig = (
74
96
  [fileTableName]: {
75
97
  ...userFileTableConfig,
76
98
  columns: {
77
- ...FILE_TABLE_COLUMN_DEFINITIONS,
99
+ ...fileColumnDefinitions,
78
100
  ...userFileTableConfig?.columns,
79
101
  },
80
102
  onMount: ({ _db }) => {
@@ -89,6 +111,24 @@ export const getFileTableConfig = (
89
111
  };
90
112
  },
91
113
  },
114
+ ...(fileTable.versioning && {
115
+ [versionTableName]: {
116
+ /** Mirror user columns in the file revision table */
117
+ columns: { ...FILE_VERSION_TABLE_COLUMN_DEFINITIONS, ...userFileTableConfig?.columns },
118
+ constraints: {
119
+ [versionConstraintName]: `FOREIGN KEY (file_id) REFERENCES ${asName(fileTableName)}(id) ON DELETE CASCADE`,
120
+ },
121
+ indexes: {
122
+ [versionIndexName]: {
123
+ unique: true,
124
+ columns: "file_id, version",
125
+ },
126
+ },
127
+ onMount: async ({ dbo }) => {
128
+ await backfillFileVersions(dbo, fileTable);
129
+ },
130
+ },
131
+ }),
92
132
  ...omitKeys(tableConfig ?? {}, [fileTableName]),
93
133
  };
94
134
 
@@ -156,9 +196,24 @@ export const getFileTableConfig = (
156
196
  } satisfies BeforeEachTsTrigger<FileTableRow, TableHandlers>,
157
197
  ...(userFileTableHooks?.beforeEach || []),
158
198
  ],
159
- onInsteadOfDelete: async ({ onCommit, tx, returningQuery, isOneOrNone, filterOpts }) => {
199
+ afterEach: [
200
+ ...(userFileTableHooks?.afterEach || []),
201
+ {
202
+ commands: { insert: 1, update: 1 },
203
+ validate: async ({ row, dbx, onCommit }) => {
204
+ const fileRow = await getFileTableHandler(dbx, fileTable).findOne({ id: row.id });
205
+ if (!fileRow) throw new Error(`File not found after write: ${row.id}`);
206
+ await saveFileVersion(fileTable, fileRow, dbx, onCommit);
207
+ },
208
+ } satisfies AfterEachTsTrigger<FileTableRow, TableHandlers> as AfterEachTsTrigger<
209
+ any,
210
+ TableHandlers
211
+ >,
212
+ ],
213
+ onInsteadOfDelete: async ({ onCommit, dbx, tx, returningQuery, isOneOrNone, filterOpts }) => {
160
214
  return onDeleteFromFileTable(fileTable, {
161
215
  onCommit,
216
+ dbx,
162
217
  t: tx,
163
218
  returningQuery,
164
219
  isOneOrNone,
@@ -0,0 +1,82 @@
1
+ import { pickKeys } from "prostgles-types";
2
+ import type { TableHandlers } from "../DboBuilder/DboBuilder";
3
+ import type { TableHandler } from "../DboBuilder/TableHandler/TableHandler";
4
+ import type { FileTableConfig } from "../ProstglesTypes";
5
+ import type { OnCommit } from "../PublishParser/publishTypesAndUtils";
6
+ import { deleteUnreferencedFile } from "./deleteUnreferencedFile";
7
+ import type { FileTableRow, FileVersionTableInsertRow } from "./fileTableDefinitions";
8
+ import { getFileRevisionsTableHandler, getFileTableHandler } from "./fileVersionUtils";
9
+ import { getFileStorageKey } from "./getFileStorageKey";
10
+
11
+ export const saveFileVersion = async (
12
+ config: FileTableConfig,
13
+ row: FileTableRow,
14
+ dbx: TableHandlers,
15
+ onCommit: OnCommit,
16
+ ) => {
17
+ if (!config.versioning) return;
18
+ const version = Number(row.version);
19
+ if (!Number.isInteger(version) || version < 1) {
20
+ throw new Error(`Invalid file version: ${row.version}`);
21
+ }
22
+
23
+ const versions = getFileRevisionsTableHandler(dbx, config);
24
+ const versionColumns = (versions as TableHandler).column_names;
25
+ await versions.insert(getFileVersionRow(row, versionColumns), {
26
+ onConflict: {
27
+ action: "DoNothing",
28
+ conflictColumns: ["file_id", "version"],
29
+ },
30
+ });
31
+
32
+ const { maxVersions } = config.versioning;
33
+ if (maxVersions === undefined) return;
34
+ const removed = (await versions.find(
35
+ { file_id: row.id },
36
+ {
37
+ select: { id: 1, storage_key: 1 },
38
+ orderBy: { version: -1 },
39
+ offset: maxVersions,
40
+ limit: null,
41
+ },
42
+ )) as { id: string; storage_key: string }[];
43
+ if (!removed.length) return;
44
+
45
+ await versions.delete({ id: { $in: removed.map(({ id }) => id) } });
46
+ for (const storageKey of new Set(removed.map(({ storage_key }) => storage_key))) {
47
+ onCommit(({ db }) => deleteUnreferencedFile(db, config, storageKey));
48
+ }
49
+ };
50
+
51
+ export const backfillFileVersions = async (dbo: TableHandlers, config: FileTableConfig) => {
52
+ if (!config.versioning) return;
53
+ const files = getFileTableHandler(dbo, config);
54
+ const fileRows = await files.find({}, { limit: null });
55
+ if (!fileRows.length) return;
56
+ console.log(`Backfilling file versions for ${fileRows.length} files`);
57
+ const fileRevisionsTableHandler = getFileRevisionsTableHandler(dbo, config);
58
+ const fileRevisionColumns = (fileRevisionsTableHandler as TableHandler).column_names;
59
+ await fileRevisionsTableHandler.insertMany(
60
+ fileRows.map((fileRow) => getFileVersionRow(fileRow, fileRevisionColumns)),
61
+ {
62
+ onConflict: {
63
+ action: "DoNothing",
64
+ conflictColumns: ["file_id", "version"],
65
+ },
66
+ },
67
+ );
68
+ };
69
+
70
+ /** Must ensure we copy over any columns that might have been created by the user */
71
+ const getFileVersionRow = (row: FileTableRow, cols: string[]): FileVersionTableInsertRow => {
72
+ const { id, ...insertRow } = pickKeys(row, cols as (keyof FileTableRow)[]);
73
+ const version = Number(row.version ?? 1);
74
+ return {
75
+ ...insertRow,
76
+ file_id: id,
77
+ version,
78
+ storage_key: getFileStorageKey(row),
79
+ url: `${row.url}?version=${version}`,
80
+ created: row.updated,
81
+ };
82
+ };
@@ -6,9 +6,11 @@ import type { DB } from "../initProstgles";
6
6
  import type { Prostgles } from "../Prostgles";
7
7
  import type { FileTableConfig } from "../ProstglesTypes";
8
8
  import { runClientRequest } from "../runClientRequest";
9
- import type { StorageClient } from "./StorageClientTypes";
10
- import type { FileTableRow } from "./getFileTableConfig";
9
+ import type { FileVersionTableRow } from "./fileTableDefinitions";
10
+ import { getFileVersionTableName } from "./fileVersionUtils";
11
11
  import { getFileStorageKey } from "./getFileStorageKey";
12
+ import type { FileTableRow } from "./getFileTableConfig";
13
+ import type { StorageClient } from "./StorageClientTypes";
12
14
 
13
15
  export const getFileServeRoute = (config: FileTableConfig) => {
14
16
  const fileTableName = config.tableName;
@@ -36,22 +38,39 @@ export const setupFileServeHandler = (
36
38
  if (typeof id !== "string" || !id) {
37
39
  throw "Invalid media name";
38
40
  }
41
+ const versionParam = req.query.version;
42
+ const version = typeof versionParam === "string" ? Number(versionParam) : undefined;
43
+ if (
44
+ versionParam !== undefined &&
45
+ (typeof versionParam !== "string" ||
46
+ !Number.isInteger(version) ||
47
+ version! < 1 ||
48
+ String(version) !== versionParam)
49
+ ) {
50
+ throw "Invalid file version";
51
+ }
52
+
53
+ if (version && !config.versioning) {
54
+ throw "File version requested but versioning is not enabled";
55
+ }
56
+
57
+ const select = {
58
+ id: 1,
59
+ storage_key: 1,
60
+ signed_url: 1,
61
+ signed_url_expires: 1,
62
+ content_type: 1,
63
+ } satisfies
64
+ Partial<Record<keyof FileTableRow, 1>> | Partial<Record<keyof FileVersionTableRow, 1>>;
39
65
 
40
- const selectParams = {
41
- select: {
42
- id: 1,
43
- storage_key: 1,
44
- signed_url: 1,
45
- signed_url_expires: 1,
46
- content_type: 1,
47
- },
48
- };
49
66
  const file = (await runClientRequest.bind(prg)(
50
67
  {
51
68
  command: "findOne",
52
- tableName: fileTableName,
53
- param1: { id },
54
- param2: selectParams,
69
+ tableName: version === undefined ? fileTableName : getFileVersionTableName(config),
70
+ param1: version === undefined ? { id } : { file_id: id, version },
71
+ param2: {
72
+ select,
73
+ },
55
74
  param3: undefined,
56
75
  },
57
76
  {
@@ -59,16 +78,18 @@ export const setupFileServeHandler = (
59
78
  httpReq: req,
60
79
  },
61
80
  undefined,
62
- )) as FileTableRow | undefined;
81
+ )) as Pick<FileTableRow, keyof typeof select> | undefined;
63
82
 
64
83
  if (!file) {
65
- res.status(HTTP_FAIL_CODES.NOT_FOUND).send("File not found or not allowed");
84
+ res
85
+ .status(HTTP_FAIL_CODES.NOT_FOUND)
86
+ .send(version === undefined ? "File not found or not allowed" : "File version not found");
66
87
  return;
67
88
  }
68
89
 
69
90
  if (storageClient.type === "cloud") {
70
- let url = file.signed_url;
71
- const expires = +(file.signed_url_expires || 0);
91
+ let url = version === undefined ? file.signed_url : undefined;
92
+ const expires = version === undefined ? +(file.signed_url_expires || 0) : 0;
72
93
 
73
94
  const HOUR = 3600 * 1000;
74
95
  const EXPIRES = Date.now() + HOUR;
@@ -76,20 +97,22 @@ export const setupFileServeHandler = (
76
97
  url = await storageClient.getSignedUrlForDownload(getFileStorageKey(file), 60 * 60);
77
98
 
78
99
  // Match the storage key to avoid caching a stale URL after replacement; NULL matches legacy files.
79
- await db.any(
80
- "UPDATE ${fileTableName:name} \
100
+ if (version === undefined) {
101
+ await db.any(
102
+ "UPDATE ${fileTableName:name} \
81
103
  SET signed_url = ${signed_url}, \
82
104
  signed_url_expires = ${signed_url_expires} \
83
105
  WHERE id = ${id} \
84
106
  AND storage_key IS NOT DISTINCT FROM ${storage_key}::uuid",
85
- {
86
- fileTableName,
87
- id: file.id,
88
- storage_key: file.storage_key,
89
- signed_url: url,
90
- signed_url_expires: EXPIRES,
91
- },
92
- );
107
+ {
108
+ fileTableName,
109
+ id: file.id,
110
+ storage_key: file.storage_key,
111
+ signed_url: url,
112
+ signed_url_expires: EXPIRES,
113
+ },
114
+ );
115
+ }
93
116
  }
94
117
 
95
118
  res.redirect(url);
@@ -1,25 +1,49 @@
1
1
  import type { JSONB } from "prostgles-types";
2
2
 
3
+ type ColumnDefinition =
4
+ | string
5
+ | { enum: readonly (string | number)[]; nullable?: boolean }
6
+ | { jsonbSchema: JSONB.JSONBSchema; nullable?: boolean };
7
+
3
8
  /** Row types for the column definitions used by managed tables. */
4
9
  export type TableRowFromColumnDefinitions<
5
- T extends Record<
6
- string,
7
- | string
8
- | { enum: readonly (string | number)[]; nullable?: boolean }
9
- | { jsonbSchema: JSONB.JSONBSchema; nullable?: boolean }
10
- >,
11
- > = {
12
- [K in keyof T]: T[K] extends { jsonbSchema: infer S extends JSONB.JSONBSchema }
10
+ T extends Record<string, ColumnDefinition>,
11
+ > = Omit<TableRow<T>, GeneratedColumnNames<T>> &
12
+ Partial<Pick<TableRow<T>, GeneratedColumnNames<T>>>;
13
+
14
+ type TableRow<T extends Record<string, ColumnDefinition>> = {
15
+ [K in keyof T]: T[K] extends {
16
+ jsonbSchema: infer S extends JSONB.JSONBSchema;
17
+ }
13
18
  ? JSONB.GetType<S> | (T[K] extends { nullable: true } ? null : never)
14
19
  : T[K] extends { enum: readonly (infer V)[] }
15
20
  ? V | (T[K] extends { nullable: true } ? null : never)
16
- : | (T[K] extends `BYTEA${string}`
17
- ? Buffer
18
- : T[K] extends `JSONB${string}`
19
- ? Record<string, unknown>
20
- : string)
21
+ : | ColumnValue<T[K]>
21
22
  | (T[K] extends
22
23
  `${string} NOT NULL${string}` | `${string}PRIMARY KEY${string}`
23
24
  ? never
24
25
  : null);
25
26
  };
27
+
28
+ type GeneratedColumnNames<T extends Record<string, ColumnDefinition>> = {
29
+ [K in keyof T]: T[K] extends string
30
+ ? T[K] extends
31
+ | `${string}DEFAULT${string}`
32
+ | `${string}SERIAL${string}`
33
+ | `${string}GENERATED${string}AS IDENTITY${string}`
34
+ ? K
35
+ : never
36
+ : never;
37
+ }[keyof T];
38
+
39
+ type ColumnValue<T> = T extends `BYTEA${string}`
40
+ ? Buffer
41
+ : T extends `JSONB${string}`
42
+ ? Record<string, unknown>
43
+ : T extends `${"SMALLINT" | "INTEGER" | "SERIAL"}${string}`
44
+ ? number
45
+ : T extends `${"BIGINT" | "BIGSERIAL" | "NUMERIC" | "DECIMAL"}${string}`
46
+ ? string | number
47
+ : T extends `${"REAL" | "DOUBLE PRECISION"}${string}`
48
+ ? number
49
+ : string;
@@ -183,6 +183,8 @@ export const initTableConfig = async function (this: TableConfigurator) {
183
183
  });
184
184
  if (tableColumnDefinition?.isCreate) {
185
185
  queries.push(...(constraintDefs?.map((c) => c.alterQuery) ?? []));
186
+ await runQueries(queries);
187
+ await this.prostgles.rebuildDBO();
186
188
  } else if (tableColumnDefinition) {
187
189
  const fullSchema = await getFutureTableSchema({
188
190
  db: this.db,
@@ -215,17 +217,19 @@ export const initTableConfig = async function (this: TableConfigurator) {
215
217
 
216
218
  /** Add missing named constraints */
217
219
  constraintDefs?.forEach((c) => {
218
- if (c.name && !currCons.some((cc) => cc.name === c.name)) {
219
- const fc = futureCons.find((nc) => nc.name === c.name);
220
- if (fc) {
221
- queries.push(`${ALTER_TABLE_Q} ADD CONSTRAINT ${asName(c.name)} ${c.content};`);
222
- }
220
+ if (!c.name) return;
221
+ const fc = futureCons.find((nc) => nc.name === c.name);
222
+ if (
223
+ fc &&
224
+ !currCons.some((cc) => cc.name === c.name && cc.definition === fc.definition)
225
+ ) {
226
+ queries.push(`${ALTER_TABLE_Q} ADD CONSTRAINT ${asName(c.name)} ${c.content};`);
223
227
  }
224
228
  });
225
229
 
226
230
  /** Add remaining missing constraints */
227
231
  futureCons
228
- .filter((nc) => !currCons.some((c) => c.definition === nc.definition))
232
+ .filter((nc) => !nc.isNamed && !currCons.some((c) => c.definition === nc.definition))
229
233
  .forEach((c) => {
230
234
  queries.push(`${ALTER_TABLE_Q} ADD ${c.definition};`);
231
235
  });
package/lib/index.ts CHANGED
@@ -52,11 +52,12 @@ export type * from "./TableHooks/TableHooks";
52
52
  export * from "./Auth/utils/upsertNamedExpressMiddleware";
53
53
  export type { RequestWithUser } from "./Auth/middleware/userContextMiddleware";
54
54
  export default prostgles;
55
- export type { FileTableRow } from "./StorageClient/getFileTableConfig";
55
+ export type { FileTableInsertRow, FileTableRow } from "./StorageClient/getFileTableConfig";
56
56
  export { defineJoin };
57
57
  export {
58
58
  AUDIT_TABLE_COLUMN_DEFINITIONS,
59
59
  AUDIT_TABLE_COLUMN_NAMES,
60
60
  AUDIT_TABLE_COLUMNS,
61
+ type AuditTableInsertRow,
61
62
  type AuditTableRow,
62
63
  } from "./Audit/AuditTable";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-server",
3
- "version": "4.2.625",
3
+ "version": "4.2.626",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",