@terreno/api 0.11.6 → 0.11.8

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.
@@ -110,9 +110,9 @@ let globalConfig: OpenApiValidatorConfig = {
110
110
  * Check whether `configureOpenApiValidator()` has been called.
111
111
  * Validation middleware is a no-op when this returns false.
112
112
  */
113
- export function isOpenApiValidatorConfigured(): boolean {
113
+ export const isOpenApiValidatorConfigured = (): boolean => {
114
114
  return isConfigured;
115
- }
115
+ };
116
116
 
117
117
  /**
118
118
  * Configure the global OpenAPI validator settings.
@@ -131,28 +131,28 @@ export function isOpenApiValidatorConfigured(): boolean {
131
131
  * });
132
132
  * ```
133
133
  */
134
- export function configureOpenApiValidator(config: Partial<OpenApiValidatorConfig> = {}): void {
134
+ export const configureOpenApiValidator = (config: Partial<OpenApiValidatorConfig> = {}): void => {
135
135
  isConfigured = true;
136
136
  globalConfig = {...globalConfig, ...config};
137
137
  // Clear cached AJV instances so new config takes effect
138
138
  ajvCache.clear();
139
139
  validatorCache.clear();
140
140
  logger.debug(`OpenAPI validator configured: ${JSON.stringify(globalConfig)}`);
141
- }
141
+ };
142
142
 
143
143
  /**
144
144
  * Get the current global validator configuration.
145
145
  */
146
- export function getOpenApiValidatorConfig(): OpenApiValidatorConfig {
146
+ export const getOpenApiValidatorConfig = (): OpenApiValidatorConfig => {
147
147
  return {...globalConfig};
148
- }
148
+ };
149
149
 
150
150
  /**
151
151
  * Reset the global validator configuration to defaults.
152
152
  * Also resets `isConfigured` to false.
153
153
  * Useful for testing.
154
154
  */
155
- export function resetOpenApiValidatorConfig(): void {
155
+ export const resetOpenApiValidatorConfig = (): void => {
156
156
  isConfigured = false;
157
157
  globalConfig = {
158
158
  coerceTypes: true,
@@ -163,7 +163,7 @@ export function resetOpenApiValidatorConfig(): void {
163
163
  };
164
164
  ajvCache.clear();
165
165
  validatorCache.clear();
166
- }
166
+ };
167
167
 
168
168
  // Lazy AJV instance cache keyed by coerceTypes + removeAdditional
169
169
  const ajvCache = new Map<string, Ajv>();
@@ -171,7 +171,7 @@ const ajvCache = new Map<string, Ajv>();
171
171
  /**
172
172
  * Get or create an AJV instance with the current config settings.
173
173
  */
174
- function getAjvInstance(): Ajv {
174
+ const getAjvInstance = (): Ajv => {
175
175
  const key = `coerce:${globalConfig.coerceTypes ?? true},remove:${globalConfig.removeAdditional ?? true}`;
176
176
  let instance = ajvCache.get(key);
177
177
 
@@ -189,7 +189,7 @@ function getAjvInstance(): Ajv {
189
189
  }
190
190
 
191
191
  return instance;
192
- }
192
+ };
193
193
 
194
194
  // Cache compiled validators by schema hash + config key
195
195
  const validatorCache = new Map<string, ValidateFunction>();
@@ -197,9 +197,9 @@ const validatorCache = new Map<string, ValidateFunction>();
197
197
  /**
198
198
  * Generate a simple hash for a schema to use as a cache key.
199
199
  */
200
- function hashSchema(schema: OpenApiSchema): string {
200
+ const hashSchema = (schema: OpenApiSchema): string => {
201
201
  return JSON.stringify(schema);
202
- }
202
+ };
203
203
 
204
204
  const VALID_JSON_SCHEMA_TYPES = new Set([
205
205
  "string",
@@ -221,7 +221,7 @@ const MONGOOSE_TYPE_MAP: Record<string, {type: string; format?: string}> = {
221
221
  * Recursively replace non-standard mongoose-to-swagger types with valid JSON Schema types
222
222
  * so AJV can compile the schema.
223
223
  */
224
- function sanitizeSchemaForAjv(schema: Record<string, unknown>): Record<string, unknown> {
224
+ const sanitizeSchemaForAjv = (schema: Record<string, unknown>): Record<string, unknown> => {
225
225
  if (!schema || typeof schema !== "object") {
226
226
  return schema;
227
227
  }
@@ -262,7 +262,7 @@ function sanitizeSchemaForAjv(schema: Record<string, unknown>): Record<string, u
262
262
  }
263
263
 
264
264
  return result;
265
- }
265
+ };
266
266
 
267
267
  /**
268
268
  * Get or create a compiled validator for a schema.
@@ -270,7 +270,7 @@ function sanitizeSchemaForAjv(schema: Record<string, unknown>): Record<string, u
270
270
  * Sanitizes non-standard mongoose-to-swagger types before compilation.
271
271
  * Returns null if the schema still cannot be compiled after sanitization.
272
272
  */
273
- function getValidator(schema: OpenApiSchema): ValidateFunction | null {
273
+ const getValidator = (schema: OpenApiSchema): ValidateFunction | null => {
274
274
  const ajv = getAjvInstance();
275
275
  const configKey = `coerce:${globalConfig.coerceTypes ?? true},remove:${globalConfig.removeAdditional ?? true}`;
276
276
  const hash = `${configKey}:${hashSchema(schema)}`;
@@ -293,12 +293,12 @@ function getValidator(schema: OpenApiSchema): ValidateFunction | null {
293
293
  validatorCache.set(hash, null as unknown as ValidateFunction);
294
294
  return null;
295
295
  }
296
- }
296
+ };
297
297
 
298
298
  /**
299
299
  * Format AJV errors into a human-readable string.
300
300
  */
301
- function formatValidationErrors(errors: ErrorObject[]): string {
301
+ const formatValidationErrors = (errors: ErrorObject[]): string => {
302
302
  return errors
303
303
  .map((err) => {
304
304
  const path = err.instancePath || "/";
@@ -306,17 +306,17 @@ function formatValidationErrors(errors: ErrorObject[]): string {
306
306
  return `${path}: ${message}`;
307
307
  })
308
308
  .join("; ");
309
- }
309
+ };
310
310
 
311
311
  /**
312
312
  * Convert OpenApiSchemaProperty to a full OpenApiSchema suitable for AJV.
313
313
  * Strips `required` from individual properties (OpenAPI-style) and moves it
314
314
  * to the schema-level `required` array (JSON Schema-style) for AJV compatibility.
315
315
  */
316
- function propertiesToSchema(
316
+ const propertiesToSchema = (
317
317
  properties: Record<string, OpenApiSchemaProperty>,
318
318
  requiredFields?: string[]
319
- ): OpenApiSchema {
319
+ ): OpenApiSchema => {
320
320
  // Extract required fields from properties that have required: true
321
321
  const autoRequired = Object.entries(properties)
322
322
  .filter(([_, prop]) => prop.required)
@@ -344,7 +344,7 @@ function propertiesToSchema(
344
344
  }
345
345
 
346
346
  return schema;
347
- }
347
+ };
348
348
 
349
349
  /**
350
350
  * Options for the request body validator middleware.
@@ -388,10 +388,10 @@ export interface RequestBodyValidatorOptions {
388
388
  * @param options - Optional configuration for this validator
389
389
  * @returns Express middleware function
390
390
  */
391
- export function validateRequestBody(
391
+ export const validateRequestBody = (
392
392
  schema: Record<string, OpenApiSchemaProperty>,
393
393
  options?: RequestBodyValidatorOptions
394
- ): (req: Request, res: Response, next: NextFunction) => void {
394
+ ): ((req: Request, res: Response, next: NextFunction) => void) => {
395
395
  const fullSchema = propertiesToSchema(schema, options?.required);
396
396
 
397
397
  return (req: Request, _res: Response, next: NextFunction): void => {
@@ -491,7 +491,7 @@ export function validateRequestBody(
491
491
 
492
492
  next();
493
493
  };
494
- }
494
+ };
495
495
 
496
496
  /**
497
497
  * Options for the query parameter validator middleware.
@@ -515,10 +515,10 @@ export interface QueryValidatorOptions {
515
515
  * @param options - Optional configuration for this validator
516
516
  * @returns Express middleware function
517
517
  */
518
- export function validateQueryParams(
518
+ export const validateQueryParams = (
519
519
  schema: Record<string, OpenApiSchemaProperty>,
520
520
  options?: QueryValidatorOptions
521
- ): (req: Request, res: Response, next: NextFunction) => void {
521
+ ): ((req: Request, res: Response, next: NextFunction) => void) => {
522
522
  const fullSchema = propertiesToSchema(schema);
523
523
 
524
524
  return (req: Request, _res: Response, next: NextFunction): void => {
@@ -591,7 +591,7 @@ export function validateQueryParams(
591
591
 
592
592
  next();
593
593
  };
594
- }
594
+ };
595
595
 
596
596
  /**
597
597
  * Options for creating a combined validation middleware.
@@ -630,9 +630,9 @@ export interface CreateValidatorOptions {
630
630
  * ], handler);
631
631
  * ```
632
632
  */
633
- export function createValidator(
633
+ export const createValidator = (
634
634
  options: CreateValidatorOptions
635
- ): (req: Request, res: Response, next: NextFunction) => void {
635
+ ): ((req: Request, res: Response, next: NextFunction) => void) => {
636
636
  const bodyValidator = options.body
637
637
  ? validateRequestBody(options.body, {enabled: options.enabled})
638
638
  : null;
@@ -663,7 +663,7 @@ export function createValidator(
663
663
  next();
664
664
  }
665
665
  };
666
- }
666
+ };
667
667
 
668
668
  /**
669
669
  * Validates response data against a schema.
@@ -673,10 +673,10 @@ export function createValidator(
673
673
  * @param schema - The expected schema
674
674
  * @returns Object with valid flag and any errors
675
675
  */
676
- export function validateResponseData(
676
+ export const validateResponseData = (
677
677
  data: unknown,
678
678
  schema: Record<string, OpenApiSchemaProperty>
679
- ): {valid: boolean; errors?: ErrorObject[]} {
679
+ ): {valid: boolean; errors?: ErrorObject[]} => {
680
680
  if (!globalConfig.validateResponses) {
681
681
  return {valid: true};
682
682
  }
@@ -698,7 +698,7 @@ export function validateResponseData(
698
698
  }
699
699
 
700
700
  return {valid: true};
701
- }
701
+ };
702
702
 
703
703
  const m2sOptions = {
704
704
  props: ["readOnly", "required", "enum", "default"],
@@ -712,19 +712,19 @@ const m2sOptions = {
712
712
  * @param model - A Mongoose model
713
713
  * @returns Schema properties suitable for validation
714
714
  */
715
- export function getSchemaFromModel<T>(model: Model<T>): Record<string, OpenApiSchemaProperty> {
715
+ export const getSchemaFromModel = <T>(model: Model<T>): Record<string, OpenApiSchemaProperty> => {
716
716
  const modelSwagger = m2s(model, m2sOptions);
717
717
  fixMixedFields((model as any).schema, modelSwagger.properties);
718
718
  return modelSwagger.properties as Record<string, OpenApiSchemaProperty>;
719
- }
719
+ };
720
720
 
721
721
  /**
722
722
  * Extract required field names from a Mongoose model's swagger schema.
723
723
  */
724
- function getRequiredFieldsFromModel<T>(model: Model<T>): string[] {
724
+ const getRequiredFieldsFromModel = <T>(model: Model<T>): string[] => {
725
725
  const modelSwagger = m2s(model, m2sOptions);
726
726
  return (modelSwagger.required as string[]) ?? [];
727
- }
727
+ };
728
728
 
729
729
  /**
730
730
  * Creates a request body validator middleware from a Mongoose model.
@@ -734,10 +734,10 @@ function getRequiredFieldsFromModel<T>(model: Model<T>): string[] {
734
734
  * @param options - Optional configuration for the validator
735
735
  * @returns Express middleware function
736
736
  */
737
- export function validateModelRequestBody<T>(
737
+ export const validateModelRequestBody = <T>(
738
738
  model: Model<T>,
739
739
  options?: RequestBodyValidatorOptions
740
- ): (req: Request, res: Response, next: NextFunction) => void {
740
+ ): ((req: Request, res: Response, next: NextFunction) => void) => {
741
741
  let schema = getSchemaFromModel(model);
742
742
  let requiredFields = getRequiredFieldsFromModel(model);
743
743
 
@@ -751,7 +751,7 @@ export function validateModelRequestBody<T>(
751
751
  ...options,
752
752
  required: [...(options?.required ?? []), ...requiredFields],
753
753
  });
754
- }
754
+ };
755
755
 
756
756
  /**
757
757
  * Options for creating validation middleware for a modelRouter.
@@ -805,13 +805,13 @@ export interface ModelRouterValidationOptions {
805
805
  * @param options - Configuration options
806
806
  * @returns Object with create and update validation middleware
807
807
  */
808
- export function createModelValidators<T>(
808
+ export const createModelValidators = <T>(
809
809
  model: Model<T>,
810
810
  options?: ModelRouterValidationOptions
811
811
  ): {
812
812
  create: (req: Request, res: Response, next: NextFunction) => void;
813
813
  update: (req: Request, res: Response, next: NextFunction) => void;
814
- } {
814
+ } => {
815
815
  const schema = getSchemaFromModel(model);
816
816
 
817
817
  return {
@@ -826,7 +826,7 @@ export function createModelValidators<T>(
826
826
  onError: options?.onError,
827
827
  }),
828
828
  };
829
- }
829
+ };
830
830
 
831
831
  /**
832
832
  * Build a query parameter schema from a model's Mongoose schema and queryFields array.
@@ -836,10 +836,10 @@ export function createModelValidators<T>(
836
836
  * @param queryFields - Array of field names allowed for querying
837
837
  * @returns Schema properties suitable for query validation
838
838
  */
839
- export function buildQuerySchemaFromFields<T>(
839
+ export const buildQuerySchemaFromFields = <T>(
840
840
  model: Model<T>,
841
841
  queryFields: string[] = []
842
- ): Record<string, OpenApiSchemaProperty> {
842
+ ): Record<string, OpenApiSchemaProperty> => {
843
843
  const modelSchema = getSchemaFromModel(model);
844
844
  const querySchema: Record<string, OpenApiSchemaProperty> = {
845
845
  limit: {type: "number"},
@@ -859,4 +859,4 @@ export function buildQuerySchemaFromFields<T>(
859
859
  }
860
860
 
861
861
  return querySchema;
862
- }
862
+ };
@@ -8,10 +8,12 @@ import {addAuthRoutes, setupAuth} from "./auth";
8
8
  import type {APIErrorConstructor} from "./errors";
9
9
  import {Permissions} from "./permissions";
10
10
  import {
11
+ baseUserPlugin,
11
12
  createdUpdatedPlugin,
12
13
  DateOnly,
13
14
  findExactlyOne,
14
15
  findOneOrNone,
16
+ firebaseJWTPlugin,
15
17
  type IsDeleted,
16
18
  isDeletedPlugin,
17
19
  upsertPlugin,
@@ -52,6 +54,33 @@ stuffSchema.plugin(createdUpdatedPlugin);
52
54
 
53
55
  const StuffModel = model<Stuff>("Stuff", stuffSchema) as unknown as StuffModelType;
54
56
 
57
+ describe("baseUserPlugin", () => {
58
+ it("adds admin and email fields to the schema", () => {
59
+ const testSchema = new Schema({});
60
+ // biome-ignore lint/suspicious/noExplicitAny: test schema
61
+ baseUserPlugin(testSchema as Schema<any, any, any, any>);
62
+
63
+ const adminPath = testSchema.path("admin");
64
+ expect(adminPath).toBeDefined();
65
+ expect((adminPath as unknown as {options: {default: boolean}}).options.default).toBe(false);
66
+
67
+ const emailPath = testSchema.path("email");
68
+ expect(emailPath).toBeDefined();
69
+ expect((emailPath as unknown as {options: {index: boolean}}).options.index).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe("firebaseJWTPlugin", () => {
74
+ it("adds firebaseId field to the schema", () => {
75
+ const testSchema = new Schema({});
76
+ firebaseJWTPlugin(testSchema);
77
+
78
+ const firebaseIdPath = testSchema.path("firebaseId");
79
+ expect(firebaseIdPath).toBeDefined();
80
+ expect((firebaseIdPath as unknown as {options: {index: boolean}}).options.index).toBe(true);
81
+ });
82
+ });
83
+
55
84
  describe("createdUpdate", () => {
56
85
  it("sets created and updated on save", async () => {
57
86
  setSystemTime(new Date("2022-12-17T03:24:00.000Z"));
@@ -42,7 +42,7 @@ interface BackgroundTaskLog {
42
42
  message: string;
43
43
  }
44
44
 
45
- export type BackgroundTaskMethods = {
45
+ export interface BackgroundTaskMethods {
46
46
  addLog: (
47
47
  this: BackgroundTaskDocument,
48
48
  level: "info" | "warn" | "error",
@@ -54,35 +54,31 @@ export type BackgroundTaskMethods = {
54
54
  stage?: string,
55
55
  message?: string
56
56
  ) => Promise<void>;
57
- };
57
+ }
58
+
59
+ export interface BackgroundTaskDocument extends Document, BackgroundTaskMethods {
60
+ taskType: string;
61
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
62
+ progress?: BackgroundTaskProgress;
63
+ createdBy?: mongoose.Types.ObjectId;
64
+ isDryRun: boolean;
65
+ result?: string[];
66
+ error?: string;
67
+ logs: BackgroundTaskLog[];
68
+ startedAt?: Date;
69
+ completedAt?: Date;
70
+ created: Date;
71
+ updated: Date;
72
+ deleted: boolean;
73
+ }
58
74
 
59
- export type BackgroundTaskDocument = Document &
60
- BackgroundTaskMethods & {
61
- taskType: string;
62
- status: "pending" | "running" | "completed" | "failed" | "cancelled";
63
- progress?: BackgroundTaskProgress;
64
- createdBy?: mongoose.Types.ObjectId;
65
- isDryRun: boolean;
66
- result?: string[];
67
- error?: string;
68
- logs: BackgroundTaskLog[];
69
- startedAt?: Date;
70
- completedAt?: Date;
71
- created: Date;
72
- updated: Date;
73
- deleted: boolean;
74
- };
75
-
76
- export type BackgroundTaskStatics = {
75
+ export interface BackgroundTaskStatics {
77
76
  checkCancellation: (taskId: string) => Promise<void>;
78
- };
77
+ }
79
78
 
80
- export type BackgroundTaskModel = Model<
81
- BackgroundTaskDocument,
82
- Record<string, never>,
83
- BackgroundTaskMethods
84
- > &
85
- BackgroundTaskStatics;
79
+ export interface BackgroundTaskModel
80
+ extends Model<BackgroundTaskDocument, Record<string, never>, BackgroundTaskMethods>,
81
+ BackgroundTaskStatics {}
86
82
 
87
83
  const progressSchema = new Schema(
88
84
  {
@@ -1,6 +1,16 @@
1
1
  import type {SecretProvider} from "./configurationPlugin";
2
+ import {APIError} from "./errors";
2
3
  import {logger} from "./logger";
3
4
 
5
+ interface SecretManagerClient {
6
+ accessSecretVersion(request: {name: string}): Promise<[{payload?: {data?: string | Uint8Array}}]>;
7
+ }
8
+
9
+ interface SecretManagerModule {
10
+ SecretManagerServiceClient?: new () => SecretManagerClient;
11
+ default?: {SecretManagerServiceClient?: new () => SecretManagerClient};
12
+ }
13
+
4
14
  /**
5
15
  * Secret provider that reads secrets from environment variables.
6
16
  * Useful for local development and testing.
@@ -53,26 +63,34 @@ export interface GcpSecretProviderOptions {
53
63
  export class GcpSecretProvider implements SecretProvider {
54
64
  name = "gcp";
55
65
  private projectId: string;
56
- private client: any = null;
66
+ private client: SecretManagerClient | null = null;
57
67
 
58
68
  constructor(options: GcpSecretProviderOptions) {
59
69
  this.projectId = options.projectId;
60
70
  }
61
71
 
62
- private async getClient(): Promise<any> {
72
+ private async getClient(): Promise<SecretManagerClient> {
63
73
  if (!this.client) {
74
+ let mod: SecretManagerModule;
64
75
  try {
65
76
  // Dynamic import — @google-cloud/secret-manager is an optional peer dependency
66
77
  const moduleName = "@google-cloud/secret-manager";
67
- const mod: any = await import(/* webpackIgnore: true */ moduleName);
68
- const SecretManagerServiceClient =
69
- mod.SecretManagerServiceClient ?? mod.default?.SecretManagerServiceClient;
70
- this.client = new SecretManagerServiceClient();
78
+ mod = await import(/* webpackIgnore: true */ moduleName);
71
79
  } catch {
80
+ throw new APIError({
81
+ status: 500,
82
+ title:
83
+ "GcpSecretProvider requires @google-cloud/secret-manager. Install it with: bun add @google-cloud/secret-manager",
84
+ });
85
+ }
86
+ const SecretManagerServiceClient =
87
+ mod.SecretManagerServiceClient ?? mod.default?.SecretManagerServiceClient;
88
+ if (!SecretManagerServiceClient) {
72
89
  throw new Error(
73
- "GcpSecretProvider requires @google-cloud/secret-manager. Install it with: bun add @google-cloud/secret-manager"
90
+ "SecretManagerServiceClient not found in @google-cloud/secret-manager module"
74
91
  );
75
92
  }
93
+ this.client = new SecretManagerServiceClient();
76
94
  }
77
95
  return this.client;
78
96
  }
@@ -97,8 +115,8 @@ export class GcpSecretProvider implements SecretProvider {
97
115
  return null;
98
116
  }
99
117
  return typeof payload === "string" ? payload : new TextDecoder().decode(payload);
100
- } catch (error: any) {
101
- if (error?.code === 5) {
118
+ } catch (error: unknown) {
119
+ if (error instanceof Error && "code" in error && (error as {code: number}).code === 5) {
102
120
  // NOT_FOUND
103
121
  logger.warn(`GcpSecretProvider: secret ${secretName} not found`);
104
122
  return null;