@niledatabase/server 5.0.0-alpha.31 → 5.0.0-alpha.33

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.mjs CHANGED
@@ -8,6 +8,8 @@ var ExtensionState = /* @__PURE__ */ ((ExtensionState2) => {
8
8
  ExtensionState2["onRequest"] = "onRequest";
9
9
  ExtensionState2["onResponse"] = "onResponse";
10
10
  ExtensionState2["withContext"] = "withContext";
11
+ ExtensionState2["withTenantId"] = "withTenantId";
12
+ ExtensionState2["withUserId"] = "withUserId";
11
13
  return ExtensionState2;
12
14
  })(ExtensionState || {});
13
15
  var APIErrorErrorCodeEnum = {
@@ -220,6 +222,14 @@ function bindRunExtensions(instance) {
220
222
  if (extensionConfig.disableExtensions?.includes(ext.id)) {
221
223
  continue;
222
224
  }
225
+ if (ext.withTenantId && toRun === "withTenantId" /* withTenantId */) {
226
+ ctx.set({
227
+ tenantId: await ext.withTenantId()
228
+ });
229
+ }
230
+ if (ext.withUserId && toRun === "withUserId" /* withUserId */) {
231
+ ctx.set({ userId: await ext.withUserId() });
232
+ }
223
233
  if (ext.withContext && toRun === "withContext" /* withContext */) {
224
234
  await ext.withContext(ctx);
225
235
  }
@@ -235,10 +245,6 @@ function bindRunExtensions(instance) {
235
245
  const [param] = Array.isArray(params) ? params : [params];
236
246
  if (ext.onRequest && toRun === "onRequest" /* onRequest */) {
237
247
  const { ...previousContext } = ctx.get();
238
- const preserveHeaders = previousContext.preserveHeaders;
239
- if (preserveHeaders) {
240
- ctx.set({ preserveHeaders: false });
241
- }
242
248
  if (!_init) {
243
249
  continue;
244
250
  }
@@ -249,7 +255,7 @@ function bindRunExtensions(instance) {
249
255
  const cookie = updatedContext.headers.get("cookie");
250
256
  if (cookie && param.headers) {
251
257
  const updatedCookies = mergeCookies(
252
- preserveHeaders ? previousHeaders?.get("cookie") : null,
258
+ previousHeaders?.get("cookie"),
253
259
  updatedContext.headers.get("cookie")
254
260
  );
255
261
  param.headers.set("cookie", updatedCookies);
@@ -294,6 +300,11 @@ function mergeCookies(...cookieStrings) {
294
300
  }
295
301
  async function runExtensionContext(config) {
296
302
  await config?.extensionCtx?.runExtensions("withContext" /* withContext */, config);
303
+ await config?.extensionCtx?.runExtensions(
304
+ "withTenantId" /* withTenantId */,
305
+ config
306
+ );
307
+ await config?.extensionCtx?.runExtensions("withUserId" /* withUserId */, config);
297
308
  }
298
309
 
299
310
  // src/api/utils/request-context.ts
@@ -302,8 +313,7 @@ var storage = new AsyncLocalStorage();
302
313
  var defaultContext = {
303
314
  headers: new Headers(),
304
315
  tenantId: void 0,
305
- userId: void 0,
306
- preserveHeaders: false
316
+ userId: void 0
307
317
  };
308
318
  var lastUsedContext = defaultContext;
309
319
  var ctx = {
@@ -353,8 +363,6 @@ var ctx = {
353
363
  }
354
364
  if ("tenantId" in partial) store.tenantId = partial.tenantId;
355
365
  if ("userId" in partial) store.userId = partial.userId;
356
- if ("preserveHeaders" in partial)
357
- store.preserveHeaders = Boolean(partial.preserveHeaders);
358
366
  silly(`[SET] ${serializeContext(store)}`);
359
367
  lastUsedContext = { ...store };
360
368
  },
@@ -372,8 +380,7 @@ function withNileContext(config, fn, name = "unknown") {
372
380
  const context2 = {
373
381
  headers: mergedHeaders,
374
382
  tenantId: existing.tenantId,
375
- userId: existing.userId,
376
- preserveHeaders: existing.preserveHeaders ?? false
383
+ userId: existing.userId
377
384
  };
378
385
  silly(`${name} [INITIAL - Request] ${serializeContext(context2)}`);
379
386
  return ctx.run(context2, fn);
@@ -386,12 +393,10 @@ function withNileContext(config, fn, name = "unknown") {
386
393
  }
387
394
  const hasTenantId = "tenantId" in initialContext;
388
395
  const hasUserId = "userId" in initialContext;
389
- const hasPreserveHeaders = "preserveHeaders" in initialContext;
390
396
  const context = {
391
397
  headers: mergedHeaders,
392
398
  tenantId: hasTenantId ? initialContext.tenantId : existing.tenantId,
393
- userId: hasUserId ? initialContext.userId : existing.userId,
394
- preserveHeaders: hasPreserveHeaders ? Boolean(initialContext.preserveHeaders) : existing.preserveHeaders ?? false
399
+ userId: hasUserId ? initialContext.userId : existing.userId
395
400
  };
396
401
  silly(`${name} [INITIAL - Partial<Context>] ${serializeContext(context)}`);
397
402
  return ctx.run(context, async () => {
@@ -408,8 +413,7 @@ function serializeContext(context) {
408
413
  return JSON.stringify({
409
414
  headers,
410
415
  tenantId: context.tenantId,
411
- userId: context.userId,
412
- preserveHeaders: context.preserveHeaders
416
+ userId: context.userId
413
417
  });
414
418
  }
415
419
  function parseCookieHeader(header) {
@@ -2014,8 +2018,7 @@ var Config = class {
2014
2018
  this.context = {
2015
2019
  tenantId: config?.tenantId,
2016
2020
  userId: config?.userId,
2017
- headers: config?.headers ? new Headers(config.headers) : new Headers(),
2018
- preserveHeaders: false
2021
+ headers: config?.headers ? new Headers(config.headers) : new Headers()
2019
2022
  };
2020
2023
  this.routes = {
2021
2024
  ...appRoutes(config?.routePrefix),
@@ -2107,17 +2110,17 @@ var updateHeaders = (val) => {
2107
2110
  var watchHeaders = (cb) => eventer.subscribe("headers" /* Headers */, cb);
2108
2111
 
2109
2112
  // src/db/PoolProxy.ts
2110
- function createProxyForPool(pool, config) {
2111
- const { info, error } = config.logger("[pool]");
2113
+ function createProxyForPool(pool, config, logger, context) {
2114
+ const { info, error } = logger("[pool]");
2112
2115
  return new Proxy(pool, {
2113
2116
  get(target, property) {
2114
2117
  if (property === "query") {
2115
- if (!config.db.connectionString) {
2116
- if (!config.db.user || !config.db.password) {
2118
+ if (!config.connectionString) {
2119
+ if (!config.user || !config.password) {
2117
2120
  error(
2118
2121
  "Cannot connect to the database. User and/or password are missing. Generate them at https://console.thenile.dev"
2119
2122
  );
2120
- } else if (!config.db.database) {
2123
+ } else if (!config.database) {
2121
2124
  error(
2122
2125
  "Unable to obtain database name. Is process.env.NILEDB_POSTGRES_URL set?"
2123
2126
  );
@@ -2126,7 +2129,7 @@ function createProxyForPool(pool, config) {
2126
2129
  const caller = target[property];
2127
2130
  return function query(...args) {
2128
2131
  let log = "[QUERY]";
2129
- const { userId, tenantId } = config.context;
2132
+ const [tenantId, userId] = context;
2130
2133
  if (tenantId) {
2131
2134
  log = `${log}[TENANT:${tenantId}]`;
2132
2135
  }
@@ -2149,39 +2152,44 @@ var NileDatabase = class {
2149
2152
  tenantId;
2150
2153
  userId;
2151
2154
  id;
2152
- config;
2155
+ logger;
2153
2156
  timer;
2154
- constructor(config, id) {
2155
- const { warn: warn2, info, debug } = config.logger("[NileInstance]");
2157
+ config;
2158
+ constructor(config, logger, id) {
2159
+ this.logger = logger("[NileInstance]");
2156
2160
  this.id = id;
2157
2161
  const poolConfig = {
2158
2162
  min: 0,
2159
2163
  max: 10,
2160
2164
  idleTimeoutMillis: 3e4,
2161
- ...config.db
2165
+ ...config
2162
2166
  };
2163
2167
  const { afterCreate, ...remaining } = poolConfig;
2164
- config.db = poolConfig;
2165
- this.config = config;
2166
- const cloned = { ...this.config.db };
2168
+ this.config = remaining;
2169
+ const cloned = { ...config };
2167
2170
  cloned.password = "***";
2168
- debug(`Connection pool config ${JSON.stringify(cloned)}`);
2169
- this.pool = createProxyForPool(new pg.Pool(remaining), this.config);
2171
+ this.logger.debug(`Connection pool config ${JSON.stringify(cloned)}`);
2172
+ this.pool = createProxyForPool(
2173
+ new pg.Pool(remaining),
2174
+ this.config,
2175
+ logger,
2176
+ id === "base" ? [] : id.split(":")
2177
+ );
2170
2178
  if (typeof afterCreate === "function") {
2171
- warn2(
2179
+ this.logger.warn(
2172
2180
  "Providing an pool configuration will stop automatic tenant context setting."
2173
2181
  );
2174
2182
  }
2175
2183
  this.startTimeout();
2176
2184
  this.pool.on("connect", async (client) => {
2177
- debug(`pool connected ${this.id}`);
2185
+ this.logger.debug(`pool connected ${this.id}`);
2178
2186
  this.startTimeout();
2179
2187
  const afterCreate2 = makeAfterCreate(
2180
- config,
2181
- `${this.id}-${this.timer}`
2188
+ logger,
2189
+ `${this.id}|${this.timer}`
2182
2190
  );
2183
2191
  afterCreate2(client, (err) => {
2184
- const { error } = config.logger("[after create callback]");
2192
+ const { error } = logger("[after create callback]");
2185
2193
  if (err) {
2186
2194
  clearTimeout(this.timer);
2187
2195
  error("after create failed", {
@@ -2194,7 +2202,7 @@ var NileDatabase = class {
2194
2202
  });
2195
2203
  this.pool.on("error", (err) => {
2196
2204
  clearTimeout(this.timer);
2197
- info(`pool ${this.id} failed`, {
2205
+ this.logger.info(`pool ${this.id} failed`, {
2198
2206
  message: err.message,
2199
2207
  stack: err.stack
2200
2208
  });
@@ -2204,27 +2212,27 @@ var NileDatabase = class {
2204
2212
  if (destroy) {
2205
2213
  clearTimeout(this.timer);
2206
2214
  evictPool(this.id);
2207
- debug(`destroying pool ${this.id}`);
2215
+ this.logger.debug(`destroying pool ${this.id}`);
2208
2216
  }
2209
2217
  });
2210
2218
  }
2211
2219
  startTimeout() {
2212
- const { debug } = this.config.logger("[NileInstance]");
2220
+ const { debug } = this.logger;
2213
2221
  if (this.timer) {
2214
2222
  clearTimeout(this.timer);
2215
2223
  }
2216
2224
  this.timer = setTimeout(() => {
2217
2225
  debug(
2218
- `Pool reached idleTimeoutMillis. ${this.id} evicted after ${Number(this.config.db.idleTimeoutMillis) ?? 3e4}ms`
2226
+ `Pool reached idleTimeoutMillis. ${this.id} evicted after ${Number(this.config.idleTimeoutMillis) ?? 3e4}ms`
2219
2227
  );
2220
2228
  this.pool.end(() => {
2221
2229
  clearTimeout(this.timer);
2222
2230
  evictPool(this.id);
2223
2231
  });
2224
- }, Number(this.config.db.idleTimeoutMillis) ?? 3e4);
2232
+ }, Number(this.config.idleTimeoutMillis) ?? 3e4);
2225
2233
  }
2226
2234
  shutdown() {
2227
- const { debug } = this.config.logger("[NileInstance]");
2235
+ const { debug } = this.logger;
2228
2236
  debug(`attempting to shut down ${this.id}`);
2229
2237
  clearTimeout(this.timer);
2230
2238
  this.pool.end(() => {
@@ -2233,8 +2241,8 @@ var NileDatabase = class {
2233
2241
  }
2234
2242
  };
2235
2243
  var NileInstance_default = NileDatabase;
2236
- function makeAfterCreate(config, id) {
2237
- const { error, warn: warn2, debug } = config.logger("[afterCreate]");
2244
+ function makeAfterCreate(logger, id) {
2245
+ const { error, warn: warn2, debug } = logger("[afterCreate]");
2238
2246
  return (conn, done) => {
2239
2247
  conn.on("error", function errorHandler(e) {
2240
2248
  error(`Connection ${id} was terminated by server`, {
@@ -2243,8 +2251,9 @@ function makeAfterCreate(config, id) {
2243
2251
  });
2244
2252
  done(e, conn);
2245
2253
  });
2246
- const { tenantId, userId } = ctx.getLastUsed();
2247
- if (tenantId) {
2254
+ const [context] = id.split("|");
2255
+ const [tenantId, userId] = context.split(":");
2256
+ if (tenantId !== "base") {
2248
2257
  const query = [`SET nile.tenant_id = '${tenantId}'`];
2249
2258
  if (userId) {
2250
2259
  if (!tenantId) {
@@ -2309,9 +2318,9 @@ var DBManager = class {
2309
2318
  warn2(`missed eviction of ${id}`);
2310
2319
  }
2311
2320
  };
2312
- getConnection = (config) => {
2321
+ getConnection = (config, noContext = false) => {
2313
2322
  const { info } = Logger(config)("[DBManager]");
2314
- const { tenantId, userId } = ctx.getLastUsed();
2323
+ const { tenantId, userId } = noContext ? {} : ctx.getLastUsed();
2315
2324
  const id = this.makeId(tenantId, userId);
2316
2325
  const existing = this.connections.get(id);
2317
2326
  info(`# of instances: ${this.connections.size}`);
@@ -2320,7 +2329,7 @@ var DBManager = class {
2320
2329
  existing.startTimeout();
2321
2330
  return existing.pool;
2322
2331
  }
2323
- const newOne = new NileInstance_default(config, id);
2332
+ const newOne = new NileInstance_default(config.db, config.logger, id);
2324
2333
  this.connections.set(id, newOne);
2325
2334
  info(`created new ${id}`);
2326
2335
  info(`# of instances: ${this.connections.size}`);
@@ -2727,7 +2736,7 @@ var Auth = class {
2727
2736
  ].filter(Boolean).join("; ");
2728
2737
  const uHeaders = new Headers({ cookie });
2729
2738
  updateHeaders(uHeaders);
2730
- ctx.set({ headers: uHeaders, preserveHeaders: true });
2739
+ ctx.set({ headers: uHeaders });
2731
2740
  } else {
2732
2741
  error("Unable to set context after sign in", {
2733
2742
  headers: signInRes.headers
@@ -2849,7 +2858,7 @@ async function obtainCsrf(config, rawResponse = false) {
2849
2858
  parseToken(res.headers)
2850
2859
  ].filter(Boolean).join("; ");
2851
2860
  headers.set("cookie", cookie);
2852
- ctx.set({ headers, preserveHeaders: true });
2861
+ ctx.set({ headers });
2853
2862
  updateHeaders(headers);
2854
2863
  }
2855
2864
  if (!rawResponse) {
@@ -2868,7 +2877,7 @@ async function obtainCsrf(config, rawResponse = false) {
2868
2877
  }
2869
2878
  const cookie = cookieParts.filter(Boolean).join("; ");
2870
2879
  headers.set("cookie", cookie);
2871
- ctx.set({ headers, preserveHeaders: true });
2880
+ ctx.set({ headers });
2872
2881
  updateHeaders(new Headers({ cookie }));
2873
2882
  }
2874
2883
  if (rawResponse) {
@@ -3158,12 +3167,12 @@ var Tenants = class {
3158
3167
  try {
3159
3168
  const json = await me.json();
3160
3169
  if ("id" in json) {
3161
- ctx.set({ userId: json.id, preserveHeaders: true });
3170
+ ctx.set({ userId: json.id });
3162
3171
  }
3163
3172
  } catch {
3164
3173
  }
3165
3174
  if (typeof req === "string") {
3166
- ctx.set({ tenantId: req, preserveHeaders: true });
3175
+ ctx.set({ tenantId: req });
3167
3176
  } else {
3168
3177
  this.#handleContext(req);
3169
3178
  }
@@ -3173,7 +3182,7 @@ var Tenants = class {
3173
3182
  async addMember(req, rawResponse) {
3174
3183
  return withNileContext(this.#config, async () => {
3175
3184
  if (typeof req === "string") {
3176
- ctx.set({ userId: req, preserveHeaders: true });
3185
+ ctx.set({ userId: req });
3177
3186
  } else {
3178
3187
  this.#handleContext(req);
3179
3188
  }
@@ -3191,7 +3200,7 @@ var Tenants = class {
3191
3200
  return withNileContext(this.#config, async () => {
3192
3201
  this.#handleContext(req);
3193
3202
  if (typeof req === "string") {
3194
- ctx.set({ userId: req, preserveHeaders: true });
3203
+ ctx.set({ userId: req });
3195
3204
  }
3196
3205
  const res = await fetchTenantUser(this.#config, "DELETE");
3197
3206
  return responseHandler(res, rawResponse);
@@ -3314,10 +3323,10 @@ var Tenants = class {
3314
3323
  #handleContext(req) {
3315
3324
  if (typeof req === "object") {
3316
3325
  if ("tenantId" in req) {
3317
- ctx.set({ tenantId: req.tenantId, preserveHeaders: true });
3326
+ ctx.set({ tenantId: req.tenantId });
3318
3327
  }
3319
3328
  if ("userId" in req) {
3320
- ctx.set({ userId: req.userId, preserveHeaders: true });
3329
+ ctx.set({ userId: req.userId });
3321
3330
  }
3322
3331
  }
3323
3332
  }
@@ -3404,7 +3413,7 @@ function updateConfig(response, config) {
3404
3413
  ...config,
3405
3414
  origin,
3406
3415
  headers: headers ?? void 0,
3407
- preserveHeaders: true
3416
+ useLastContext: true
3408
3417
  };
3409
3418
  }
3410
3419
 
@@ -3436,7 +3445,6 @@ var Server = class {
3436
3445
  watchHeaders((headers) => {
3437
3446
  if (headers) {
3438
3447
  this.#config.context.headers = new Headers(headers);
3439
- this.#config.context.preserveHeaders = true;
3440
3448
  this.#reset();
3441
3449
  }
3442
3450
  });
@@ -3444,7 +3452,6 @@ var Server = class {
3444
3452
  ...this.#config.handlers,
3445
3453
  withContext: handlersWithContext(this.#config)
3446
3454
  };
3447
- this.#config.context.preserveHeaders = config?.preserveHeaders ?? false;
3448
3455
  this.#config.context.tenantId = getTenantId({ config: this.#config });
3449
3456
  this.#manager = new DBManager(this.#config);
3450
3457
  this.#handleHeaders(config);
@@ -3470,9 +3477,21 @@ var Server = class {
3470
3477
  }
3471
3478
  }
3472
3479
  }
3473
- get db() {
3480
+ /**
3481
+ * Query the database with the current context
3482
+ */
3483
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3484
+ query = (queryStream, values) => {
3474
3485
  this.#config.context = { ...this.getContext() };
3475
3486
  const pool = this.#manager.getConnection(this.#config);
3487
+ return pool.query(queryStream, values);
3488
+ };
3489
+ /**
3490
+ * Return a db object that can be used to talk to the database
3491
+ * Does not have a context by default
3492
+ */
3493
+ get db() {
3494
+ const pool = this.#manager.getConnection(this.#config, true);
3476
3495
  return Object.assign(pool, {
3477
3496
  clearConnections: () => {
3478
3497
  this.#manager.clear(this.#config);
@@ -3510,18 +3529,18 @@ var Server = class {
3510
3529
  set paths(paths) {
3511
3530
  this.#config.paths = paths;
3512
3531
  }
3513
- async withContext(context, fn) {
3514
- const { ...initialContext } = context ?? defaultContext;
3515
- this.#config.context = { ...initialContext };
3516
- const preserve = (context && "preserveHeaders" in context && context.preserveHeaders) ?? true;
3532
+ async withContext(contextOrFn, maybeFn) {
3533
+ const isFn = typeof contextOrFn === "function";
3534
+ const context = isFn ? {} : contextOrFn ?? {};
3535
+ const fn = isFn ? contextOrFn : maybeFn;
3536
+ const preserve = "useLastContext" in context ? context.useLastContext : true;
3517
3537
  if (preserve) {
3518
3538
  this.#config.context = { ...this.getContext(), ...context };
3539
+ } else {
3540
+ this.#config.context = { ...defaultContext, ...context };
3519
3541
  }
3520
3542
  return withNileContext(this.#config, async () => {
3521
- if (fn) {
3522
- return fn(this);
3523
- }
3524
- return this;
3543
+ return fn ? fn(this) : this;
3525
3544
  });
3526
3545
  }
3527
3546
  async noContext(fn) {