@restura/core 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { UUID } from 'crypto';
2
+ import { z } from 'zod';
2
3
  import * as express from 'express';
3
4
  import { IncomingHttpHeaders } from 'http2';
4
- import { z } from 'zod';
5
5
  import { QueryResultRow, QueryConfigValues, QueryResult, PoolConfig, Pool, ClientConfig, Client } from 'pg';
6
6
  import peg from 'pegjs';
7
7
 
@@ -18,51 +18,6 @@ interface ResturaLogger {
18
18
  declare const logger: ResturaLogger;
19
19
  declare function setLogger(impl: ResturaLogger): void;
20
20
 
21
- /**
22
- * @deprecated This is used for passing around until we finally get to the sending externally
23
- * // TODO: Remove once backwards compatibility is no longer needed
24
- */
25
- interface RsErrorInternalData<T extends Record<string, unknown> = Record<string, unknown>> {
26
- err: ErrorCode;
27
- msg: string;
28
- stack: string;
29
- status: number;
30
- options?: T;
31
- }
32
- declare enum HtmlStatusCodes {
33
- BAD_REQUEST = 400,
34
- UNAUTHORIZED = 401,
35
- PAYMENT_REQUIRED = 402,
36
- FORBIDDEN = 403,
37
- NOT_FOUND = 404,
38
- METHOD_NOT_ALLOWED = 405,
39
- REQUEST_TIMEOUT = 408,
40
- CONFLICT = 409,
41
- GONE = 410,
42
- PAYLOAD_TOO_LARGE = 413,
43
- UNSUPPORTED_MEDIA_TYPE = 415,
44
- UPGRADE_REQUIRED = 426,
45
- UNPROCESSABLE_ENTITY = 422,
46
- TOO_MANY_REQUESTS = 429,
47
- SERVER_ERROR = 500,
48
- NOT_IMPLEMENTED = 501,
49
- BAD_GATEWAY = 502,
50
- SERVICE_UNAVAILABLE = 503,
51
- GATEWAY_TIMEOUT = 504,
52
- NETWORK_CONNECT_TIMEOUT = 599
53
- }
54
- type ErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'PAYMENT_REQUIRED' | 'FORBIDDEN' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'REQUEST_TIMEOUT' | 'CONFLICT' | 'GONE' | 'PAYLOAD_TOO_LARGE' | 'UNSUPPORTED_MEDIA_TYPE' | 'UPGRADE_REQUIRED' | 'UNPROCESSABLE_ENTITY' | 'TOO_MANY_REQUESTS' | 'SERVER_ERROR' | 'NOT_IMPLEMENTED' | 'BAD_GATEWAY' | 'SERVICE_UNAVAILABLE' | 'GATEWAY_TIMEOUT' | 'NETWORK_CONNECT_TIMEOUT' | 'UNKNOWN_ERROR' | 'RATE_LIMIT_EXCEEDED' | 'INVALID_TOKEN' | 'INCORRECT_EMAIL_OR_PASSWORD' | 'DUPLICATE' | 'CONNECTION_ERROR' | 'SCHEMA_ERROR' | 'DATABASE_ERROR';
55
- declare class RsError<T extends Record<string, unknown> = Record<string, unknown>> extends Error {
56
- err: ErrorCode;
57
- msg: string;
58
- options?: T;
59
- status?: number;
60
- constructor(errCode: ErrorCode, message?: string, options?: T);
61
- toJSON(): Record<string, unknown>;
62
- static htmlStatus(code: ErrorCode): number;
63
- static isRsError(error: unknown): error is RsError;
64
- }
65
-
66
21
  declare const whereDataSchema: z.ZodObject<{
67
22
  tableName: z.ZodOptional<z.ZodString>;
68
23
  columnName: z.ZodOptional<z.ZodString>;
@@ -869,6 +824,51 @@ declare const resturaSchema: z.ZodObject<{
869
824
  type ResturaSchema = z.infer<typeof resturaSchema>;
870
825
  declare function isSchemaValid(schemaToCheck: unknown): Promise<boolean>;
871
826
 
827
+ /**
828
+ * @deprecated This is used for passing around until we finally get to the sending externally
829
+ * // TODO: Remove once backwards compatibility is no longer needed
830
+ */
831
+ interface RsErrorInternalData<T extends Record<string, unknown> = Record<string, unknown>> {
832
+ err: ErrorCode;
833
+ msg: string;
834
+ stack: string;
835
+ status: number;
836
+ options?: T;
837
+ }
838
+ declare enum HtmlStatusCodes {
839
+ BAD_REQUEST = 400,
840
+ UNAUTHORIZED = 401,
841
+ PAYMENT_REQUIRED = 402,
842
+ FORBIDDEN = 403,
843
+ NOT_FOUND = 404,
844
+ METHOD_NOT_ALLOWED = 405,
845
+ REQUEST_TIMEOUT = 408,
846
+ CONFLICT = 409,
847
+ GONE = 410,
848
+ PAYLOAD_TOO_LARGE = 413,
849
+ UNSUPPORTED_MEDIA_TYPE = 415,
850
+ UPGRADE_REQUIRED = 426,
851
+ UNPROCESSABLE_ENTITY = 422,
852
+ TOO_MANY_REQUESTS = 429,
853
+ SERVER_ERROR = 500,
854
+ NOT_IMPLEMENTED = 501,
855
+ BAD_GATEWAY = 502,
856
+ SERVICE_UNAVAILABLE = 503,
857
+ GATEWAY_TIMEOUT = 504,
858
+ NETWORK_CONNECT_TIMEOUT = 599
859
+ }
860
+ type ErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'PAYMENT_REQUIRED' | 'FORBIDDEN' | 'NOT_FOUND' | 'METHOD_NOT_ALLOWED' | 'REQUEST_TIMEOUT' | 'CONFLICT' | 'GONE' | 'PAYLOAD_TOO_LARGE' | 'UNSUPPORTED_MEDIA_TYPE' | 'UPGRADE_REQUIRED' | 'UNPROCESSABLE_ENTITY' | 'TOO_MANY_REQUESTS' | 'SERVER_ERROR' | 'NOT_IMPLEMENTED' | 'BAD_GATEWAY' | 'SERVICE_UNAVAILABLE' | 'GATEWAY_TIMEOUT' | 'NETWORK_CONNECT_TIMEOUT' | 'UNKNOWN_ERROR' | 'RATE_LIMIT_EXCEEDED' | 'INVALID_TOKEN' | 'INCORRECT_EMAIL_OR_PASSWORD' | 'DUPLICATE' | 'CONNECTION_ERROR' | 'SCHEMA_ERROR' | 'DATABASE_ERROR';
861
+ declare class RsError<T extends Record<string, unknown> = Record<string, unknown>> extends Error {
862
+ err: ErrorCode;
863
+ msg: string;
864
+ options?: T;
865
+ status?: number;
866
+ constructor(errCode: ErrorCode, message?: string, options?: T);
867
+ toJSON(): Record<string, unknown>;
868
+ static htmlStatus(code: ErrorCode): number;
869
+ static isRsError(error: unknown): error is RsError;
870
+ }
871
+
872
872
  interface SchemaChangeValue {
873
873
  name: string;
874
874
  changeType: 'NEW' | 'MODIFIED' | 'DELETED';
@@ -991,18 +991,23 @@ type TriggerResult = {
991
991
  previousRecord: DynamicObject;
992
992
  requesterId: number;
993
993
  };
994
- type QueryMetadata = RequesterDetails & {
995
- connectionInstanceId: UUID;
994
+ type QueryMetadata = Partial<RequesterDetails> & DynamicObject & {
995
+ connectionInstanceId?: UUID;
996
996
  };
997
+ type NotifyValidationMode = 'off' | 'warn' | 'strict';
998
+ interface FireActionOptions {
999
+ rethrowHandlerErrors?: boolean;
1000
+ }
997
1001
  declare class EventManager {
998
1002
  private actionHandlers;
999
1003
  addRowInsertHandler<T>(onInsert: (data: ActionRowInsertData<T>, queryMetadata: QueryMetadata) => Promise<void>, filter?: ActionRowInsertFilter): void;
1000
1004
  addColumnChangeHandler<T>(onUpdate: (data: ActionColumnChangeData<T>, queryMetadata: QueryMetadata) => Promise<void>, filter: ActionColumnChangeFilter): void;
1001
1005
  addRowDeleteHandler<T>(onDelete: (data: ActionRowDeleteData<T>, queryMetadata: QueryMetadata) => Promise<void>, filter?: ActionRowDeleteFilter): void;
1002
- fireActionFromDbTrigger(sqlMutationData: SqlMutationData, result: TriggerResult): Promise<void>;
1006
+ fireActionFromDbTrigger(sqlMutationData: SqlMutationData, result: TriggerResult, options?: FireActionOptions): Promise<void>;
1003
1007
  private fireInsertActions;
1004
1008
  private fireDeleteActions;
1005
1009
  private fireUpdateActions;
1010
+ validateHandlersAgainstSchema(schema: ResturaSchema, mode: NotifyValidationMode): void;
1006
1011
  private hasHandlersForEventType;
1007
1012
  }
1008
1013
  declare const eventManager: EventManager;
@@ -1021,12 +1026,32 @@ declare const resturaConfigSchema: z.ZodObject<{
1021
1026
  generatedTypesPath: z.ZodDefault<z.ZodString>;
1022
1027
  fileTempCachePath: z.ZodOptional<z.ZodString>;
1023
1028
  scratchDatabaseSuffix: z.ZodOptional<z.ZodString>;
1029
+ eventDelivery: z.ZodDefault<z.ZodEnum<{
1030
+ direct: "direct";
1031
+ outbox: "outbox";
1032
+ }>>;
1033
+ eventOutbox: z.ZodPrefault<z.ZodObject<{
1034
+ channel: z.ZodDefault<z.ZodString>;
1035
+ pollIntervalMs: z.ZodDefault<z.ZodNumber>;
1036
+ batchSize: z.ZodDefault<z.ZodNumber>;
1037
+ maxAttempts: z.ZodDefault<z.ZodNumber>;
1038
+ pruneAfterDays: z.ZodDefault<z.ZodNumber>;
1039
+ }, z.core.$strip>>;
1040
+ notifyValidation: z.ZodDefault<z.ZodEnum<{
1041
+ warn: "warn";
1042
+ off: "off";
1043
+ strict: "strict";
1044
+ }>>;
1045
+ queryMetadataKeys: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodArray<z.ZodString>]>>;
1024
1046
  }, z.core.$strip>;
1025
1047
  type ResturaConfigSchema = z.infer<typeof resturaConfigSchema>;
1026
1048
 
1027
1049
  declare abstract class PsqlConnection {
1050
+ private static queryMetadataKeys;
1051
+ static setQueryMetadataKeys(keys: string[] | 'ALL'): void;
1028
1052
  readonly instanceId: UUID;
1029
1053
  protected constructor(instanceId?: UUID);
1054
+ private buildQueryMetadata;
1030
1055
  protected abstract query<R extends QueryResultRow = QueryResultRow, T extends Array<unknown> = unknown[]>(query: string, values?: QueryConfigValues<T>): Promise<QueryResult<R>>;
1031
1056
  queryOne<T>(query: string, options: unknown[], requesterDetails: RequesterDetails): Promise<T>;
1032
1057
  queryOneSchema<T>(query: string, params: unknown[], requesterDetails: RequesterDetails, zodSchema: z.ZodSchema<T>): Promise<T>;
@@ -1167,27 +1192,97 @@ declare abstract class SqlEngine {
1167
1192
  protected abstract executeDeleteRequest(req: RsRequest<unknown>, routeData: StandardRouteData, schema: ResturaSchema): Promise<boolean>;
1168
1193
  }
1169
1194
 
1195
+ interface EventOutboxOptions {
1196
+ channel: string;
1197
+ pollIntervalMs: number;
1198
+ batchSize: number;
1199
+ maxAttempts: number;
1200
+ pruneAfterDays: number;
1201
+ }
1202
+ declare const defaultEventOutboxOptions: EventOutboxOptions;
1203
+ interface EventDeliveryConfig {
1204
+ mode: 'direct' | 'outbox';
1205
+ outbox?: Partial<EventOutboxOptions>;
1206
+ }
1207
+ interface OutboxRow {
1208
+ id: number;
1209
+ createdOn: string;
1210
+ tableName: string;
1211
+ operation: MutationType;
1212
+ recordId: number | null;
1213
+ record: DynamicObject | null;
1214
+ previousRecord: DynamicObject | null;
1215
+ queryMetadata: QueryMetadata | null;
1216
+ processedOn: string | null;
1217
+ attempts: number;
1218
+ nextAttemptOn: string | null;
1219
+ isDeadLetter: boolean;
1220
+ }
1221
+ declare class EventOutboxConsumer {
1222
+ private psqlConnectionPool;
1223
+ private options;
1224
+ private isDraining;
1225
+ private drainRequested;
1226
+ private isStopped;
1227
+ private pollTimer;
1228
+ private pruneTimer;
1229
+ private activeDrain;
1230
+ private lastDrainOn;
1231
+ constructor(psqlConnectionPool: PsqlPool, options?: Partial<EventOutboxOptions>);
1232
+ get channel(): string;
1233
+ start(): void;
1234
+ stop(): Promise<void>;
1235
+ /** Safe to call at any frequency; overlapping calls coalesce into one extra pass. */
1236
+ drain(): Promise<void>;
1237
+ getStats(): Promise<{
1238
+ pendingCount: number;
1239
+ deadLetterCount: number;
1240
+ lastDrainOn: string | null;
1241
+ }>;
1242
+ private processBatch;
1243
+ private processRow;
1244
+ private prune;
1245
+ }
1246
+
1247
+ interface EventListenerHealth {
1248
+ connected: boolean;
1249
+ lastHeartbeatOn: string | null;
1250
+ reconnectAttempts: number;
1251
+ }
1170
1252
  declare class PsqlEngine extends SqlEngine {
1171
1253
  private psqlConnectionPool;
1172
1254
  setupTriggerListeners: Promise<void> | undefined;
1173
1255
  private triggerClient;
1174
1256
  private scratchDbName;
1175
1257
  private reconnectAttempts;
1176
- private readonly MAX_RECONNECT_ATTEMPTS;
1177
1258
  private readonly INITIAL_RECONNECT_DELAY;
1178
- constructor(psqlConnectionPool: PsqlPool, shouldListenForDbTriggers?: boolean, scratchDatabaseSuffix?: string);
1259
+ private readonly MAX_RECONNECT_DELAY;
1260
+ private readonly HEARTBEAT_INTERVAL_MS;
1261
+ private eventDelivery;
1262
+ private outboxConsumer;
1263
+ private heartbeatTimer;
1264
+ private isListenerConnected;
1265
+ private isReconnecting;
1266
+ private isClosed;
1267
+ private lastHeartbeatOn;
1268
+ constructor(psqlConnectionPool: PsqlPool, shouldListenForDbTriggers?: boolean, scratchDatabaseSuffix?: string, eventDelivery?: EventDeliveryConfig);
1179
1269
  close(): Promise<void>;
1270
+ getEventListenerHealth(): EventListenerHealth;
1271
+ getOutboxConsumer(): EventOutboxConsumer | undefined;
1180
1272
  /**
1181
1273
  * Setup the return types for the PostgreSQL connection.
1182
1274
  * For example return DATE as a string instead of a Date object and BIGINT as a number instead of a string.
1183
1275
  */
1184
1276
  private setupPgReturnTypes;
1185
1277
  private reconnectTriggerClient;
1278
+ private startHeartbeat;
1186
1279
  private listenForDbTriggers;
1280
+ private warnIfOutboxBacklogInDirectMode;
1187
1281
  private handleTrigger;
1188
1282
  createDatabaseFromSchema(schema: ResturaSchema, connection: PsqlPool): Promise<string>;
1189
1283
  generateDatabaseSchemaFromSchema(schema: ResturaSchema): string;
1190
1284
  diffDatabaseToSchema(schema: ResturaSchema): Promise<string>;
1285
+ private schemaGenerationOptions;
1191
1286
  protected createNestedSelect(req: RsRequest<unknown>, schema: ResturaSchema, item: ResponseData, routeData: StandardRouteData, sqlParams: string[]): string;
1192
1287
  protected executeCreateRequest(req: RsRequest<unknown>, routeData: StandardRouteData, schema: ResturaSchema): Promise<DynamicObject>;
1193
1288
  protected executeGetRequest(req: RsRequest<unknown>, routeData: StandardRouteData, schema: ResturaSchema): Promise<DynamicObject | any[]>;
@@ -1256,12 +1351,26 @@ declare function diffSchemaToDatabase(schema: ResturaSchema, snapshot: DbSnapsho
1256
1351
 
1257
1352
  declare const systemUser: RequesterDetails;
1258
1353
  declare function schemaToPsqlType(column: ColumnData): string;
1259
- declare function createInsertTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify']): string;
1260
- declare function createUpdateTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify']): string;
1261
- declare function createDeleteTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify']): string;
1262
- declare function generateDatabaseSchemaFromSchema(schema: ResturaSchema): string;
1354
+ declare const OUTBOX_TABLE_NAME = "dbEventOutbox";
1355
+ declare const DEFAULT_OUTBOX_CHANNEL = "restura_outbox";
1356
+ declare function isSensitiveColumnName(columnName: string): boolean;
1357
+ interface TriggerSqlOptions {
1358
+ delivery?: 'direct' | 'outbox';
1359
+ channel?: string;
1360
+ tableColumns?: string[];
1361
+ }
1362
+ interface SchemaGenerationOptions {
1363
+ eventDelivery?: 'direct' | 'outbox';
1364
+ outboxChannel?: string;
1365
+ }
1366
+ declare function createOutboxTableSql(): string;
1367
+ declare function createInsertTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify'], options?: TriggerSqlOptions): string;
1368
+ declare function createUpdateTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify'], options?: TriggerSqlOptions): string;
1369
+ declare function createDeleteTriggerSql(tableName: string, notify: ResturaSchema['database'][0]['notify'], options?: TriggerSqlOptions): string;
1370
+ declare function generateNotifyTriggersSql(schema: ResturaSchema, options?: SchemaGenerationOptions): string[];
1371
+ declare function generateDatabaseSchemaFromSchema(schema: ResturaSchema, options?: SchemaGenerationOptions): string;
1263
1372
  declare function getNewPublicSchemaAndScratchPool(targetPool: PsqlPool, scratchDbName: string): Promise<PsqlPool>;
1264
- declare function diffDatabaseToSchema(schema: ResturaSchema, targetPool: PsqlPool, scratchDbName: string): Promise<string>;
1373
+ declare function diffDatabaseToSchema(schema: ResturaSchema, targetPool: PsqlPool, scratchDbName: string, options?: SchemaGenerationOptions): Promise<string>;
1265
1374
 
1266
1375
  declare class PsqlTransaction extends PsqlConnection {
1267
1376
  clientConfig: ClientConfig;
@@ -1340,4 +1449,4 @@ declare function SQL(strings: TemplateStringsArray, ...values: unknown[]): strin
1340
1449
  */
1341
1450
  declare function toSqlLiteral(value: unknown): string;
1342
1451
 
1343
- export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticatedRequesterDetails, type ConjunctionTypes, type DatabaseActionData, type DbCheckConstraint, type DbColumn, type DbForeignKey, type DbIndex, type DbSnapshot, type DbTable, type DynamicObject, type ErrorCode, type EventType, HtmlStatusCodes, type LogLevel, type MatchTypes, type MutationType, type OnValidAuthenticationCallback, type PageQuery, PsqlConnection, PsqlEngine, PsqlPool, PsqlTransaction, type QueryMetadata, type RequesterDetails, type ResturaConfigSchema, type ResturaLogger, type ResturaSchema, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaPreview, type SqlMutationData, type StandardOrderTypes, type TriggerResult, apiGenerator, createDeleteTriggerSql, createInsertTriggerSql, createUpdateTriggerSql, diffDatabaseToSchema, diffSchemaToDatabase, escapeColumnName, eventManager, filterPsqlParser, generateDatabaseSchemaFromSchema, getNewPublicSchemaAndScratchPool, insertObjectQuery, introspectDatabase, isSchemaValid, isValueNumber, logger, modelGenerator, questionMarksToOrderedParams, restura, resturaGlobalTypesGenerator, resturaSchema, schemaToPsqlType, setLogger, systemUser, toSqlLiteral, updateObjectQuery };
1452
+ export { type ActionColumnChangeData, type ActionColumnChangeFilter, type ActionRowDeleteData, type ActionRowDeleteFilter, type ActionRowInsertData, type ActionRowInsertFilter, type ApiMethod, type AsyncExpressApplication, type AuthenticateHandler, type AuthenticatedRequesterDetails, type ConjunctionTypes, DEFAULT_OUTBOX_CHANNEL, type DatabaseActionData, type DbCheckConstraint, type DbColumn, type DbForeignKey, type DbIndex, type DbSnapshot, type DbTable, type DynamicObject, type ErrorCode, type EventDeliveryConfig, type EventListenerHealth, EventManager, EventOutboxConsumer, type EventOutboxOptions, type EventType, type FireActionOptions, HtmlStatusCodes, type LogLevel, type MatchTypes, type MutationType, type NotifyValidationMode, OUTBOX_TABLE_NAME, type OnValidAuthenticationCallback, type OutboxRow, type PageQuery, PsqlConnection, PsqlEngine, PsqlPool, PsqlTransaction, type QueryMetadata, type RequesterDetails, type ResturaConfigSchema, type ResturaLogger, type ResturaSchema, RsError, type RsErrorData, type RsErrorInternalData, type RsHeaders, type RsPagedResponseData, type RsRequest, type RsResponse, type RsResponseData, type RsRouteHandler, SQL, type SchemaChangeValue, type SchemaGenerationOptions, type SchemaPreview, type SqlMutationData, type StandardOrderTypes, type TriggerResult, type TriggerSqlOptions, apiGenerator, createDeleteTriggerSql, createInsertTriggerSql, createOutboxTableSql, createUpdateTriggerSql, defaultEventOutboxOptions, diffDatabaseToSchema, diffSchemaToDatabase, escapeColumnName, eventManager, filterPsqlParser, generateDatabaseSchemaFromSchema, generateNotifyTriggersSql, getNewPublicSchemaAndScratchPool, insertObjectQuery, introspectDatabase, isSchemaValid, isSensitiveColumnName, isValueNumber, logger, modelGenerator, questionMarksToOrderedParams, restura, resturaGlobalTypesGenerator, resturaSchema, schemaToPsqlType, setLogger, systemUser, toSqlLiteral, updateObjectQuery };