@terreno/api 0.3.1 → 0.4.2
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/api.js +9 -8
- package/dist/betterAuthSetup.js +1 -1
- package/dist/configuration.test.d.ts +1 -0
- package/dist/configuration.test.js +699 -0
- package/dist/configurationApp.d.ts +91 -0
- package/dist/configurationApp.js +407 -0
- package/dist/configurationPlugin.d.ts +102 -0
- package/dist/configurationPlugin.js +285 -0
- package/dist/configurationPlugin.test.d.ts +1 -0
- package/dist/configurationPlugin.test.js +509 -0
- package/dist/example.js +1 -1
- package/dist/expressServer.js +5 -1
- package/dist/githubAuth.js +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/openApiCompat.d.ts +23 -0
- package/dist/openApiCompat.js +198 -0
- package/dist/scriptRunner.d.ts +52 -0
- package/dist/scriptRunner.js +231 -0
- package/dist/secretProviders.d.ts +47 -0
- package/dist/secretProviders.js +214 -0
- package/dist/terrenoApp.d.ts +25 -0
- package/dist/terrenoApp.js +49 -2
- package/dist/tests.d.ts +27 -9
- package/dist/tests.js +10 -1
- package/package.json +13 -13
- package/src/api.ts +9 -8
- package/src/betterAuthSetup.ts +2 -2
- package/src/configuration.test.ts +398 -0
- package/src/configurationApp.ts +359 -0
- package/src/configurationPlugin.test.ts +299 -0
- package/src/configurationPlugin.ts +288 -0
- package/src/example.ts +1 -1
- package/src/expressServer.ts +6 -1
- package/src/githubAuth.ts +4 -4
- package/src/index.ts +5 -0
- package/src/openApiCompat.ts +147 -0
- package/src/permissions.ts +1 -1
- package/src/scriptRunner.ts +219 -0
- package/src/secretProviders.ts +109 -0
- package/src/terrenoApp.ts +44 -2
- package/src/tests.ts +12 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
45
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
46
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
47
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
48
|
+
function step(op) {
|
|
49
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
50
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
51
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
52
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
53
|
+
switch (op[0]) {
|
|
54
|
+
case 0: case 1: t = op; break;
|
|
55
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
56
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
57
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
58
|
+
default:
|
|
59
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
60
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
61
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
62
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
63
|
+
if (t[2]) _.ops.pop();
|
|
64
|
+
_.trys.pop(); continue;
|
|
65
|
+
}
|
|
66
|
+
op = body.call(thisArg, _);
|
|
67
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
68
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
72
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
73
|
+
if (!m) return o;
|
|
74
|
+
var i = m.call(o), r, ar = [], e;
|
|
75
|
+
try {
|
|
76
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
77
|
+
}
|
|
78
|
+
catch (error) { e = { error: error }; }
|
|
79
|
+
finally {
|
|
80
|
+
try {
|
|
81
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
82
|
+
}
|
|
83
|
+
finally { if (e) throw e.error; }
|
|
84
|
+
}
|
|
85
|
+
return ar;
|
|
86
|
+
};
|
|
87
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
88
|
+
exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
|
|
89
|
+
var logger_1 = require("./logger");
|
|
90
|
+
/**
|
|
91
|
+
* Secret provider that reads secrets from environment variables.
|
|
92
|
+
* Useful for local development and testing.
|
|
93
|
+
*
|
|
94
|
+
* Maps secret names to environment variable names by converting to SCREAMING_SNAKE_CASE.
|
|
95
|
+
* e.g., "openai-api-key" → process.env.OPENAI_API_KEY
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const provider = new EnvSecretProvider();
|
|
100
|
+
* // reads process.env.OPENAI_API_KEY
|
|
101
|
+
* const key = await provider.getSecret("openai-api-key");
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
var EnvSecretProvider = /** @class */ (function () {
|
|
105
|
+
function EnvSecretProvider() {
|
|
106
|
+
this.name = "env";
|
|
107
|
+
}
|
|
108
|
+
EnvSecretProvider.prototype.getSecret = function (secretName) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
110
|
+
var envKey, value;
|
|
111
|
+
var _a;
|
|
112
|
+
return __generator(this, function (_b) {
|
|
113
|
+
envKey = secretName.replace(/[-.]/g, "_").toUpperCase();
|
|
114
|
+
value = (_a = process.env[envKey]) !== null && _a !== void 0 ? _a : null;
|
|
115
|
+
if (value === null) {
|
|
116
|
+
logger_1.logger.debug("EnvSecretProvider: no env var found for ".concat(secretName, " (tried ").concat(envKey, ")"));
|
|
117
|
+
}
|
|
118
|
+
return [2 /*return*/, value];
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
return EnvSecretProvider;
|
|
123
|
+
}());
|
|
124
|
+
exports.EnvSecretProvider = EnvSecretProvider;
|
|
125
|
+
/**
|
|
126
|
+
* Secret provider that reads secrets from Google Cloud Secret Manager.
|
|
127
|
+
*
|
|
128
|
+
* Requires `@google-cloud/secret-manager` to be installed.
|
|
129
|
+
* Resolves short names like "openai-api-key" to the full resource path
|
|
130
|
+
* `projects/{projectId}/secrets/{secretName}/versions/latest`.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const provider = new GcpSecretProvider({ projectId: "my-project" });
|
|
135
|
+
* const key = await provider.getSecret("openai-api-key");
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
var GcpSecretProvider = /** @class */ (function () {
|
|
139
|
+
function GcpSecretProvider(options) {
|
|
140
|
+
this.name = "gcp";
|
|
141
|
+
this.client = null;
|
|
142
|
+
this.projectId = options.projectId;
|
|
143
|
+
}
|
|
144
|
+
GcpSecretProvider.prototype.getClient = function () {
|
|
145
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
146
|
+
var moduleName, mod, SecretManagerServiceClient, _a;
|
|
147
|
+
var _b, _c;
|
|
148
|
+
return __generator(this, function (_d) {
|
|
149
|
+
switch (_d.label) {
|
|
150
|
+
case 0:
|
|
151
|
+
if (!!this.client) return [3 /*break*/, 4];
|
|
152
|
+
_d.label = 1;
|
|
153
|
+
case 1:
|
|
154
|
+
_d.trys.push([1, 3, , 4]);
|
|
155
|
+
moduleName = "@google-cloud/secret-manager";
|
|
156
|
+
return [4 /*yield*/, Promise.resolve("".concat(/* webpackIgnore: true */ moduleName)).then(function (s) { return __importStar(require(s)); })];
|
|
157
|
+
case 2:
|
|
158
|
+
mod = _d.sent();
|
|
159
|
+
SecretManagerServiceClient = (_b = mod.SecretManagerServiceClient) !== null && _b !== void 0 ? _b : (_c = mod.default) === null || _c === void 0 ? void 0 : _c.SecretManagerServiceClient;
|
|
160
|
+
this.client = new SecretManagerServiceClient();
|
|
161
|
+
return [3 /*break*/, 4];
|
|
162
|
+
case 3:
|
|
163
|
+
_a = _d.sent();
|
|
164
|
+
throw new Error("GcpSecretProvider requires @google-cloud/secret-manager. Install it with: bun add @google-cloud/secret-manager");
|
|
165
|
+
case 4: return [2 /*return*/, this.client];
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
GcpSecretProvider.prototype.getSecret = function (secretName) {
|
|
171
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
172
|
+
var client, resourceName, _a, version, payload, error_1;
|
|
173
|
+
var _b;
|
|
174
|
+
return __generator(this, function (_c) {
|
|
175
|
+
switch (_c.label) {
|
|
176
|
+
case 0: return [4 /*yield*/, this.getClient()];
|
|
177
|
+
case 1:
|
|
178
|
+
client = _c.sent();
|
|
179
|
+
if (secretName.startsWith("projects/")) {
|
|
180
|
+
resourceName = secretName.endsWith("/versions/latest")
|
|
181
|
+
? secretName
|
|
182
|
+
: "".concat(secretName, "/versions/latest");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/latest");
|
|
186
|
+
}
|
|
187
|
+
_c.label = 2;
|
|
188
|
+
case 2:
|
|
189
|
+
_c.trys.push([2, 4, , 5]);
|
|
190
|
+
return [4 /*yield*/, client.accessSecretVersion({ name: resourceName })];
|
|
191
|
+
case 3:
|
|
192
|
+
_a = __read.apply(void 0, [_c.sent(), 1]), version = _a[0];
|
|
193
|
+
payload = (_b = version.payload) === null || _b === void 0 ? void 0 : _b.data;
|
|
194
|
+
if (!payload) {
|
|
195
|
+
logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " has no payload"));
|
|
196
|
+
return [2 /*return*/, null];
|
|
197
|
+
}
|
|
198
|
+
return [2 /*return*/, typeof payload === "string" ? payload : new TextDecoder().decode(payload)];
|
|
199
|
+
case 4:
|
|
200
|
+
error_1 = _c.sent();
|
|
201
|
+
if ((error_1 === null || error_1 === void 0 ? void 0 : error_1.code) === 5) {
|
|
202
|
+
// NOT_FOUND
|
|
203
|
+
logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " not found"));
|
|
204
|
+
return [2 /*return*/, null];
|
|
205
|
+
}
|
|
206
|
+
throw error_1;
|
|
207
|
+
case 5: return [2 /*return*/];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
return GcpSecretProvider;
|
|
213
|
+
}());
|
|
214
|
+
exports.GcpSecretProvider = GcpSecretProvider;
|
package/dist/terrenoApp.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as Sentry from "@sentry/bun";
|
|
|
2
2
|
import express from "express";
|
|
3
3
|
import type { ModelRouterRegistration } from "./api";
|
|
4
4
|
import { type UserModel as UserMongooseModel } from "./auth";
|
|
5
|
+
import { type ConfigurationAppOptions } from "./configurationApp";
|
|
5
6
|
import { type AuthOptions } from "./expressServer";
|
|
6
7
|
import { type GitHubAuthOptions } from "./githubAuth";
|
|
7
8
|
import { type LoggingOptions } from "./logger";
|
|
@@ -94,6 +95,7 @@ export declare class TerrenoApp {
|
|
|
94
95
|
private options;
|
|
95
96
|
private registrations;
|
|
96
97
|
private middlewareFns;
|
|
98
|
+
private configurationApp;
|
|
97
99
|
/**
|
|
98
100
|
* Create a new TerrenoApp builder.
|
|
99
101
|
*
|
|
@@ -139,6 +141,29 @@ export declare class TerrenoApp {
|
|
|
139
141
|
* ```
|
|
140
142
|
*/
|
|
141
143
|
addMiddleware(fn: express.RequestHandler | ((app: express.Application) => void)): this;
|
|
144
|
+
/**
|
|
145
|
+
* Register a configuration model with the application.
|
|
146
|
+
*
|
|
147
|
+
* Adds configuration management endpoints that expose the model's schema
|
|
148
|
+
* as metadata, and provide GET/PATCH endpoints for reading and updating
|
|
149
|
+
* the singleton configuration document. Nested subschemas become separate
|
|
150
|
+
* sections in the admin UI.
|
|
151
|
+
*
|
|
152
|
+
* All configuration endpoints require admin authentication.
|
|
153
|
+
*
|
|
154
|
+
* @param model - Mongoose model with configurationPlugin applied
|
|
155
|
+
* @param options - Optional configuration (basePath, fieldOverrides)
|
|
156
|
+
* @returns This TerrenoApp instance for method chaining
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const app = new TerrenoApp({ userModel: User })
|
|
161
|
+
* .configure(AppConfig)
|
|
162
|
+
* .register(todoRouter)
|
|
163
|
+
* .start();
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
configure(model: import("mongoose").Model<any>, options?: Omit<ConfigurationAppOptions, "model">): this;
|
|
142
167
|
/**
|
|
143
168
|
* Build the Express application without starting the server.
|
|
144
169
|
*
|
package/dist/terrenoApp.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
14
|
if (k2 === undefined) k2 = k;
|
|
4
15
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -54,10 +65,12 @@ var cors_1 = __importDefault(require("cors"));
|
|
|
54
65
|
var express_1 = __importDefault(require("express"));
|
|
55
66
|
var qs_1 = __importDefault(require("qs"));
|
|
56
67
|
var auth_1 = require("./auth");
|
|
68
|
+
var configurationApp_1 = require("./configurationApp");
|
|
57
69
|
var errors_1 = require("./errors");
|
|
58
70
|
var expressServer_1 = require("./expressServer");
|
|
59
71
|
var githubAuth_1 = require("./githubAuth");
|
|
60
72
|
var logger_1 = require("./logger");
|
|
73
|
+
var openApiCompat_1 = require("./openApiCompat");
|
|
61
74
|
var openApiEtag_1 = require("./openApiEtag");
|
|
62
75
|
/**
|
|
63
76
|
* Fluent API for building Express applications with Terreno framework.
|
|
@@ -128,6 +141,7 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
128
141
|
function TerrenoApp(options) {
|
|
129
142
|
this.registrations = [];
|
|
130
143
|
this.middlewareFns = [];
|
|
144
|
+
this.configurationApp = null;
|
|
131
145
|
this.options = options;
|
|
132
146
|
}
|
|
133
147
|
/**
|
|
@@ -175,6 +189,32 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
175
189
|
this.middlewareFns.push(fn);
|
|
176
190
|
return this;
|
|
177
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Register a configuration model with the application.
|
|
194
|
+
*
|
|
195
|
+
* Adds configuration management endpoints that expose the model's schema
|
|
196
|
+
* as metadata, and provide GET/PATCH endpoints for reading and updating
|
|
197
|
+
* the singleton configuration document. Nested subschemas become separate
|
|
198
|
+
* sections in the admin UI.
|
|
199
|
+
*
|
|
200
|
+
* All configuration endpoints require admin authentication.
|
|
201
|
+
*
|
|
202
|
+
* @param model - Mongoose model with configurationPlugin applied
|
|
203
|
+
* @param options - Optional configuration (basePath, fieldOverrides)
|
|
204
|
+
* @returns This TerrenoApp instance for method chaining
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* const app = new TerrenoApp({ userModel: User })
|
|
209
|
+
* .configure(AppConfig)
|
|
210
|
+
* .register(todoRouter)
|
|
211
|
+
* .start();
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
TerrenoApp.prototype.configure = function (model, options) {
|
|
215
|
+
this.configurationApp = new configurationApp_1.ConfigurationApp(__assign({ model: model }, options));
|
|
216
|
+
return this;
|
|
217
|
+
};
|
|
178
218
|
/**
|
|
179
219
|
* Build the Express application without starting the server.
|
|
180
220
|
*
|
|
@@ -204,6 +244,8 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
204
244
|
(0, logger_1.setupLogging)(this.options.loggingOptions);
|
|
205
245
|
var app = (0, express_1.default)();
|
|
206
246
|
var options = this.options;
|
|
247
|
+
// Record mount paths on layers for Express 5 → OpenAPI compat
|
|
248
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
207
249
|
app.set("query parser", function (str) { var _a; return qs_1.default.parse(str, { arrayLimit: (_a = options.arrayLimit) !== null && _a !== void 0 ? _a : 200 }); });
|
|
208
250
|
app.use((0, cors_1.default)({ credentials: true, origin: (_c = options.corsOrigin) !== null && _c !== void 0 ? _c : "*" }));
|
|
209
251
|
try {
|
|
@@ -227,7 +269,7 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
227
269
|
}
|
|
228
270
|
finally { if (e_1) throw e_1.error; }
|
|
229
271
|
}
|
|
230
|
-
app.use(express_1.default.json());
|
|
272
|
+
app.use(express_1.default.json({ limit: "50mb" }));
|
|
231
273
|
// Auth routes (login/signup/refresh_token) before JWT middleware
|
|
232
274
|
(0, auth_1.addAuthRoutes)(app, options.userModel, options.authOptions);
|
|
233
275
|
(0, auth_1.setupAuth)(app, options.userModel);
|
|
@@ -240,7 +282,7 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
240
282
|
next();
|
|
241
283
|
});
|
|
242
284
|
// Sentry scopes
|
|
243
|
-
app.
|
|
285
|
+
app.use(function (req, _res, next) {
|
|
244
286
|
var _a;
|
|
245
287
|
var transactionId = req.header("X-Transaction-ID");
|
|
246
288
|
var sessionId = req.header("X-Session-ID");
|
|
@@ -256,6 +298,7 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
256
298
|
next();
|
|
257
299
|
});
|
|
258
300
|
// OpenAPI
|
|
301
|
+
app.use(openApiCompat_1.openApiCompatMiddleware);
|
|
259
302
|
app.use(openApiEtag_1.openApiEtagMiddleware);
|
|
260
303
|
var oapi = (0, openapi_1.default)({
|
|
261
304
|
info: {
|
|
@@ -275,6 +318,10 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
275
318
|
(0, githubAuth_1.setupGitHubAuth)(app, options.userModel, options.githubAuth);
|
|
276
319
|
(0, githubAuth_1.addGitHubAuthRoutes)(app, options.userModel, options.githubAuth, options.authOptions);
|
|
277
320
|
}
|
|
321
|
+
// Mount configuration app if configured
|
|
322
|
+
if (this.configurationApp) {
|
|
323
|
+
this.configurationApp.register(app);
|
|
324
|
+
}
|
|
278
325
|
try {
|
|
279
326
|
// Mount registered model routers and plugins
|
|
280
327
|
for (var _f = __values(this.registrations), _g = _f.next(); !_g.done; _g = _f.next()) {
|
package/dist/tests.d.ts
CHANGED
|
@@ -46,36 +46,54 @@ export interface Food {
|
|
|
46
46
|
likes: boolean;
|
|
47
47
|
}[];
|
|
48
48
|
}
|
|
49
|
-
export declare const UserModel: mongoose.Model<User, {}, {}, {}, mongoose.Document<unknown, {}, User, {},
|
|
49
|
+
export declare const UserModel: mongoose.Model<User, {}, {}, {}, mongoose.Document<unknown, {}, User, {}, mongoose.DefaultSchemaOptions> & User & {
|
|
50
50
|
_id: mongoose.Types.ObjectId;
|
|
51
51
|
} & {
|
|
52
52
|
__v: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
53
|
+
} & {
|
|
54
|
+
id: string;
|
|
55
|
+
}, any, User>;
|
|
56
|
+
export declare const SuperUserModel: mongoose.Model<User & SuperUser, {}, {}, {
|
|
57
|
+
id: string;
|
|
58
|
+
}, mongoose.Document<unknown, {}, User & SuperUser, {
|
|
59
|
+
id: string;
|
|
60
|
+
}, mongoose.DefaultSchemaOptions> & Omit<User & SuperUser & {
|
|
55
61
|
_id: mongoose.Types.ObjectId;
|
|
56
62
|
} & {
|
|
57
63
|
__v: number;
|
|
58
|
-
},
|
|
59
|
-
|
|
64
|
+
}, "id"> & {
|
|
65
|
+
id: string;
|
|
66
|
+
}, any, User & SuperUser>;
|
|
67
|
+
export declare const StaffUserModel: mongoose.Model<User & StaffUser, {}, {}, {
|
|
68
|
+
id: string;
|
|
69
|
+
}, mongoose.Document<unknown, {}, User & StaffUser, {
|
|
70
|
+
id: string;
|
|
71
|
+
}, mongoose.DefaultSchemaOptions> & Omit<User & StaffUser & {
|
|
60
72
|
_id: mongoose.Types.ObjectId;
|
|
61
73
|
} & {
|
|
62
74
|
__v: number;
|
|
63
|
-
},
|
|
75
|
+
}, "id"> & {
|
|
76
|
+
id: string;
|
|
77
|
+
}, any, User & StaffUser>;
|
|
64
78
|
export declare const FoodModel: Model<Food>;
|
|
65
79
|
interface RequiredField {
|
|
66
80
|
name: string;
|
|
67
81
|
about?: string;
|
|
68
82
|
}
|
|
69
|
-
export declare const RequiredModel: mongoose.Model<RequiredField, {}, {}, {}, mongoose.Document<unknown, {}, RequiredField, {},
|
|
83
|
+
export declare const RequiredModel: mongoose.Model<RequiredField, {}, {}, {}, mongoose.Document<unknown, {}, RequiredField, {}, mongoose.DefaultSchemaOptions> & RequiredField & {
|
|
70
84
|
_id: mongoose.Types.ObjectId;
|
|
71
85
|
} & {
|
|
72
86
|
__v: number;
|
|
73
|
-
}
|
|
87
|
+
} & {
|
|
88
|
+
id: string;
|
|
89
|
+
}, any, RequiredField>;
|
|
74
90
|
export declare function getBaseServer(): Express;
|
|
75
91
|
export declare function authAsUser(app: express.Application, type: "admin" | "notAdmin"): Promise<TestAgent>;
|
|
76
|
-
export declare function setupDb(): Promise<(mongoose.Document<unknown, {}, User, {},
|
|
92
|
+
export declare function setupDb(): Promise<(mongoose.Document<unknown, {}, User, {}, mongoose.DefaultSchemaOptions> & User & {
|
|
77
93
|
_id: mongoose.Types.ObjectId;
|
|
78
94
|
} & {
|
|
79
95
|
__v: number;
|
|
96
|
+
} & {
|
|
97
|
+
id: string;
|
|
80
98
|
})[]>;
|
|
81
99
|
export {};
|
package/dist/tests.js
CHANGED
|
@@ -95,8 +95,10 @@ exports.setupDb = setupDb;
|
|
|
95
95
|
var express_1 = __importDefault(require("express"));
|
|
96
96
|
var mongoose_1 = __importStar(require("mongoose"));
|
|
97
97
|
var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
|
|
98
|
+
var qs_1 = __importDefault(require("qs"));
|
|
98
99
|
var supertest_1 = __importDefault(require("supertest"));
|
|
99
100
|
var logger_1 = require("./logger");
|
|
101
|
+
var openApiCompat_1 = require("./openApiCompat");
|
|
100
102
|
var plugins_1 = require("./plugins");
|
|
101
103
|
var userSchema = new mongoose_1.Schema({
|
|
102
104
|
admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
|
|
@@ -189,7 +191,14 @@ var requiredSchema = new mongoose_1.Schema({
|
|
|
189
191
|
exports.RequiredModel = (0, mongoose_1.model)("Required", requiredSchema);
|
|
190
192
|
function getBaseServer() {
|
|
191
193
|
var app = (0, express_1.default)();
|
|
192
|
-
app.
|
|
194
|
+
app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
|
|
195
|
+
// Express 5 defaults to 'simple' query parser (Node querystring) which doesn't
|
|
196
|
+
// support nested bracket notation like name[$regex]=Green. Use qs to match
|
|
197
|
+
// what setupServer() configures.
|
|
198
|
+
app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
|
|
199
|
+
// Record mount paths on layers for Express 5 → OpenAPI compat
|
|
200
|
+
(0, openApiCompat_1.patchAppUse)(app);
|
|
201
|
+
app.use(function (req, res, next) {
|
|
193
202
|
res.header("Access-Control-Allow-Origin", "*");
|
|
194
203
|
res.header("Access-Control-Allow-Headers", "*");
|
|
195
204
|
// intercepts OPTIONS method
|
package/package.json
CHANGED
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"axios": "^1.13.2",
|
|
15
15
|
"cors": "^2.8.5",
|
|
16
16
|
"cron": "^4.3.4",
|
|
17
|
-
"expo-server-sdk": "^
|
|
18
|
-
"express": "^
|
|
17
|
+
"expo-server-sdk": "^6.0.0",
|
|
18
|
+
"express": "^5.2.1",
|
|
19
19
|
"generaterr": "^1.5.0",
|
|
20
20
|
"jsonwebtoken": "^9.0.2",
|
|
21
21
|
"lodash": "^4.17.23",
|
|
22
22
|
"luxon": "^3.7.2",
|
|
23
|
-
"mongoose": "
|
|
23
|
+
"mongoose": "9.2.3",
|
|
24
24
|
"mongoose-to-swagger": "^1.4.0",
|
|
25
25
|
"ms": "^2.1.3",
|
|
26
26
|
"on-finished": "^2.3.0",
|
|
@@ -37,28 +37,28 @@
|
|
|
37
37
|
"description": "Styled after the Django & Django REST Framework, a batteries-include framework for building REST APIs with Node/Express/Mongoose.",
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@biomejs/biome": "^2.3.6",
|
|
40
|
-
"@types/bcrypt": "^
|
|
40
|
+
"@types/bcrypt": "^6.0.0",
|
|
41
41
|
"@types/bun": "^1.2.4",
|
|
42
42
|
"@types/cors": "^2.8.17",
|
|
43
43
|
"@types/cron": "^2.4.3",
|
|
44
|
-
"@types/express": "^
|
|
44
|
+
"@types/express": "^5.0.6",
|
|
45
45
|
"@types/jsonwebtoken": "^9.0.9",
|
|
46
46
|
"@types/lodash": "^4.17.15",
|
|
47
47
|
"@types/mongodb": "^4.0.7",
|
|
48
|
-
"@types/node": "^
|
|
48
|
+
"@types/node": "^25.3.0",
|
|
49
49
|
"@types/on-finished": "^2.3.4",
|
|
50
50
|
"@types/passport": "^1.0.17",
|
|
51
51
|
"@types/passport-anonymous": "^1.0.5",
|
|
52
52
|
"@types/passport-github2": "^1.2.9",
|
|
53
53
|
"@types/passport-jwt": "^4.0.1",
|
|
54
54
|
"@types/passport-local": "^1.0.38",
|
|
55
|
-
"@types/sinon": "^
|
|
55
|
+
"@types/sinon": "^21.0.0",
|
|
56
56
|
"@types/supertest": "^6.0.2",
|
|
57
57
|
"mongodb-memory-server": "^11.0.1",
|
|
58
|
-
"sinon": "^
|
|
58
|
+
"sinon": "^21.0.1",
|
|
59
59
|
"supertest": "^7.0.0",
|
|
60
|
-
"typedoc": "~0.
|
|
61
|
-
"typescript": "5.
|
|
60
|
+
"typedoc": "~0.28.17",
|
|
61
|
+
"typescript": "5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"homepage": "https://github.com/FlourishHealth/terreno#readme",
|
|
64
64
|
"keywords": [
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"main": "dist/index.js",
|
|
72
72
|
"name": "@terreno/api",
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"mongoose": "^
|
|
74
|
+
"mongoose": "^9.0.0"
|
|
75
75
|
},
|
|
76
76
|
"repository": {
|
|
77
77
|
"type": "git",
|
|
@@ -88,10 +88,10 @@
|
|
|
88
88
|
"lint": "biome check ./src",
|
|
89
89
|
"lint:fix": "biome check --write ./src",
|
|
90
90
|
"lint:unsafefix": "biome check --fix --unsafe ./src",
|
|
91
|
-
"test": "bun test --preload ./src/tests/bunSetup.ts",
|
|
91
|
+
"test": "bun test --preload ./src/tests/bunSetup.ts --update-snapshots",
|
|
92
92
|
"test:ci": "bun test --preload ./src/tests/bunSetup.ts",
|
|
93
93
|
"updateSnapshot": "bun test --update-snapshots"
|
|
94
94
|
},
|
|
95
95
|
"types": "dist/index.d.ts",
|
|
96
|
-
"version": "0.
|
|
96
|
+
"version": "0.4.2"
|
|
97
97
|
}
|
package/src/api.ts
CHANGED
|
@@ -992,9 +992,12 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
992
992
|
});
|
|
993
993
|
}
|
|
994
994
|
|
|
995
|
+
const field = req.params.field as string;
|
|
996
|
+
const itemId = req.params.itemId as string;
|
|
997
|
+
|
|
995
998
|
// We apply the operation *before* the hooks. As far as the callers are concerned, this should
|
|
996
999
|
// be like PATCHing the field and replacing the whole thing.
|
|
997
|
-
if (operation !== "DELETE" && req.body[
|
|
1000
|
+
if (operation !== "DELETE" && req.body[field] === undefined) {
|
|
998
1001
|
throw new APIError({
|
|
999
1002
|
status: 400,
|
|
1000
1003
|
title: `Malformed body, array operations should have a single, top level key, got: ${Object.keys(
|
|
@@ -1003,28 +1006,26 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1003
1006
|
});
|
|
1004
1007
|
}
|
|
1005
1008
|
|
|
1006
|
-
const field = req.params.field;
|
|
1007
|
-
|
|
1008
1009
|
const array = [...doc[field]];
|
|
1009
1010
|
if (operation === "POST") {
|
|
1010
1011
|
array.push(req.body[field]);
|
|
1011
1012
|
} else if (operation === "PATCH" || operation === "DELETE") {
|
|
1012
1013
|
// Check for subschema vs String array:
|
|
1013
1014
|
let index;
|
|
1014
|
-
if (isValidObjectId(
|
|
1015
|
-
index = array.findIndex((x: any) => x.id ===
|
|
1015
|
+
if (isValidObjectId(itemId)) {
|
|
1016
|
+
index = array.findIndex((x: any) => x.id === itemId);
|
|
1016
1017
|
} else {
|
|
1017
|
-
index = array.findIndex((x: string) => x ===
|
|
1018
|
+
index = array.findIndex((x: string) => x === itemId);
|
|
1018
1019
|
}
|
|
1019
1020
|
if (index === -1) {
|
|
1020
1021
|
throw new APIError({
|
|
1021
1022
|
status: 404,
|
|
1022
|
-
title: `Could not find ${field}/${
|
|
1023
|
+
title: `Could not find ${field}/${itemId}`,
|
|
1023
1024
|
});
|
|
1024
1025
|
}
|
|
1025
1026
|
// For PATCHing an item by ID, we need to merge the objects so we don't override the _id or
|
|
1026
1027
|
// other parts of the subdocument.
|
|
1027
|
-
if (operation === "PATCH" && isValidObjectId(
|
|
1028
|
+
if (operation === "PATCH" && isValidObjectId(itemId)) {
|
|
1028
1029
|
Object.assign(array[index], req.body[field]);
|
|
1029
1030
|
} else if (operation === "PATCH") {
|
|
1030
1031
|
// For PATCHing a string array, we can replace the whole object.
|
package/src/betterAuthSetup.ts
CHANGED
|
@@ -87,7 +87,7 @@ export const createBetterAuth = (options: CreateBetterAuthOptions): BetterAuthIn
|
|
|
87
87
|
trustedOrigins: config.trustedOrigins ?? [],
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
return auth;
|
|
90
|
+
return auth as any;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -205,7 +205,7 @@ export const mountBetterAuthRoutes = (
|
|
|
205
205
|
const handler = toNodeHandler(auth);
|
|
206
206
|
|
|
207
207
|
// Mount at the base path with wildcard
|
|
208
|
-
app.all(`${basePath}
|
|
208
|
+
app.all(`${basePath}/*path`, (req, res) => {
|
|
209
209
|
return handler(req, res);
|
|
210
210
|
});
|
|
211
211
|
|