@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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import pg, { PoolConfig, PoolClient } from 'pg';
1
+ import pg, { Pool, PoolConfig, PoolClient } from 'pg';
2
2
 
3
3
  type LogFunction = (message: string | unknown, meta?: Record<string, unknown>) => void;
4
4
  type Loggable = {
@@ -523,6 +523,14 @@ declare class Server {
523
523
  tenants: Tenants;
524
524
  auth: Auth;
525
525
  constructor(config?: NileConfig);
526
+ /**
527
+ * Query the database with the current context
528
+ */
529
+ query: Pool['query'];
530
+ /**
531
+ * Return a db object that can be used to talk to the database
532
+ * Does not have a context by default
533
+ */
526
534
  get db(): pg.Pool & {
527
535
  clearConnections: () => void;
528
536
  };
@@ -534,14 +542,16 @@ declare class Server {
534
542
  get handlers(): NileHandlers;
535
543
  get paths(): ConfigurablePaths;
536
544
  set paths(paths: ConfigurablePaths);
537
- /** Allows setting of context outside of the request lifecycle
538
- * Basically means you want to disregard cookies and do everything manually
539
- * If we elect to DDL, we don't want to use tenant id or user id, so remove those.
545
+ /**
546
+ * Sets the context for a particular set of requests or db calls to be sure the context is fully managed for the entire lifecycle
540
547
  */
541
- withContext(context?: PartialContext): Promise<this>;
542
- withContext<T>(context: PartialContext, fn: (sdk: this) => Promise<T>): Promise<T>;
548
+ withContext(): Promise<this>;
549
+ withContext<T>(context: PartialContext, fn: AsyncCallback<this, T>): Promise<T>;
550
+ withContext(context: PartialContext): Promise<this>;
551
+ withContext<T>(fn: AsyncCallback<this, T>): Promise<T>;
543
552
  /**
544
553
  * Creates a context without a user id and a tenant id, but keeps the headers around for auth at least.
554
+ * This is useful for DDL/DML, since most extensions will set the context by default
545
555
  */
546
556
  noContext(): Promise<this>;
547
557
  noContext<T>(fn: (sdk: this) => Promise<T>): Promise<T>;
@@ -552,6 +562,7 @@ declare class Server {
552
562
  getContext(): Context;
553
563
  }
554
564
  declare function create<T = Server>(config?: NileConfig): T;
565
+ type AsyncCallback<TInstance, TResult> = (sdk: TInstance) => Promise<TResult>;
555
566
 
556
567
  type Opts = {
557
568
  basePath?: string;
@@ -561,13 +572,12 @@ type Context = {
561
572
  headers: Headers;
562
573
  tenantId: string | undefined | null;
563
574
  userId: string | undefined | null;
564
- preserveHeaders: boolean;
565
575
  };
566
576
  type PartialContext = {
567
577
  headers?: null | Headers;
568
578
  tenantId?: string | undefined | null;
569
579
  userId?: string | undefined | null;
570
- preserveHeaders?: boolean;
580
+ useLastContext?: boolean;
571
581
  };
572
582
  type CTX = {
573
583
  run: <T>(ctx: Partial<Context>, fn: () => T) => T;
@@ -583,6 +593,8 @@ type ExtensionResult<TParams> = {
583
593
  onResponse?: (params: TParams, ctx: CTX) => void | Promise<void>;
584
594
  onHandleRequest?: (params?: TParams) => RouteReturn | Promise<RouteReturn>;
585
595
  onConfigure?: (params?: TParams) => void;
596
+ withUserId?: () => string;
597
+ withTenantId?: () => string;
586
598
  replace?: {
587
599
  handlers: (handlers: NileHandlers) => Any;
588
600
  };
@@ -595,7 +607,9 @@ declare enum ExtensionState {
595
607
  onHandleRequest = "onHandleRequest",
596
608
  onRequest = "onRequest",
597
609
  onResponse = "onResponse",
598
- withContext = "withContext"
610
+ withContext = "withContext",
611
+ withTenantId = "withTenantId",
612
+ withUserId = "withUserId"
599
613
  }
600
614
  type NilePoolConfig = PoolConfig & {
601
615
  afterCreate?: AfterCreate;
@@ -692,9 +706,9 @@ type NileConfig = {
692
706
  /** Hooks executed before and after each request. */
693
707
  extensions?: Extension[];
694
708
  /**
695
- * Preserve incoming request headers when running extensions.
709
+ * Re-use the last set context
696
710
  */
697
- preserveHeaders?: boolean;
711
+ useLastContext?: boolean;
698
712
  };
699
713
  type NileDb = NilePoolConfig & {
700
714
  tenantId?: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import pg, { PoolConfig, PoolClient } from 'pg';
1
+ import pg, { Pool, PoolConfig, PoolClient } from 'pg';
2
2
 
3
3
  type LogFunction = (message: string | unknown, meta?: Record<string, unknown>) => void;
4
4
  type Loggable = {
@@ -523,6 +523,14 @@ declare class Server {
523
523
  tenants: Tenants;
524
524
  auth: Auth;
525
525
  constructor(config?: NileConfig);
526
+ /**
527
+ * Query the database with the current context
528
+ */
529
+ query: Pool['query'];
530
+ /**
531
+ * Return a db object that can be used to talk to the database
532
+ * Does not have a context by default
533
+ */
526
534
  get db(): pg.Pool & {
527
535
  clearConnections: () => void;
528
536
  };
@@ -534,14 +542,16 @@ declare class Server {
534
542
  get handlers(): NileHandlers;
535
543
  get paths(): ConfigurablePaths;
536
544
  set paths(paths: ConfigurablePaths);
537
- /** Allows setting of context outside of the request lifecycle
538
- * Basically means you want to disregard cookies and do everything manually
539
- * If we elect to DDL, we don't want to use tenant id or user id, so remove those.
545
+ /**
546
+ * Sets the context for a particular set of requests or db calls to be sure the context is fully managed for the entire lifecycle
540
547
  */
541
- withContext(context?: PartialContext): Promise<this>;
542
- withContext<T>(context: PartialContext, fn: (sdk: this) => Promise<T>): Promise<T>;
548
+ withContext(): Promise<this>;
549
+ withContext<T>(context: PartialContext, fn: AsyncCallback<this, T>): Promise<T>;
550
+ withContext(context: PartialContext): Promise<this>;
551
+ withContext<T>(fn: AsyncCallback<this, T>): Promise<T>;
543
552
  /**
544
553
  * Creates a context without a user id and a tenant id, but keeps the headers around for auth at least.
554
+ * This is useful for DDL/DML, since most extensions will set the context by default
545
555
  */
546
556
  noContext(): Promise<this>;
547
557
  noContext<T>(fn: (sdk: this) => Promise<T>): Promise<T>;
@@ -552,6 +562,7 @@ declare class Server {
552
562
  getContext(): Context;
553
563
  }
554
564
  declare function create<T = Server>(config?: NileConfig): T;
565
+ type AsyncCallback<TInstance, TResult> = (sdk: TInstance) => Promise<TResult>;
555
566
 
556
567
  type Opts = {
557
568
  basePath?: string;
@@ -561,13 +572,12 @@ type Context = {
561
572
  headers: Headers;
562
573
  tenantId: string | undefined | null;
563
574
  userId: string | undefined | null;
564
- preserveHeaders: boolean;
565
575
  };
566
576
  type PartialContext = {
567
577
  headers?: null | Headers;
568
578
  tenantId?: string | undefined | null;
569
579
  userId?: string | undefined | null;
570
- preserveHeaders?: boolean;
580
+ useLastContext?: boolean;
571
581
  };
572
582
  type CTX = {
573
583
  run: <T>(ctx: Partial<Context>, fn: () => T) => T;
@@ -583,6 +593,8 @@ type ExtensionResult<TParams> = {
583
593
  onResponse?: (params: TParams, ctx: CTX) => void | Promise<void>;
584
594
  onHandleRequest?: (params?: TParams) => RouteReturn | Promise<RouteReturn>;
585
595
  onConfigure?: (params?: TParams) => void;
596
+ withUserId?: () => string;
597
+ withTenantId?: () => string;
586
598
  replace?: {
587
599
  handlers: (handlers: NileHandlers) => Any;
588
600
  };
@@ -595,7 +607,9 @@ declare enum ExtensionState {
595
607
  onHandleRequest = "onHandleRequest",
596
608
  onRequest = "onRequest",
597
609
  onResponse = "onResponse",
598
- withContext = "withContext"
610
+ withContext = "withContext",
611
+ withTenantId = "withTenantId",
612
+ withUserId = "withUserId"
599
613
  }
600
614
  type NilePoolConfig = PoolConfig & {
601
615
  afterCreate?: AfterCreate;
@@ -692,9 +706,9 @@ type NileConfig = {
692
706
  /** Hooks executed before and after each request. */
693
707
  extensions?: Extension[];
694
708
  /**
695
- * Preserve incoming request headers when running extensions.
709
+ * Re-use the last set context
696
710
  */
697
- preserveHeaders?: boolean;
711
+ useLastContext?: boolean;
698
712
  };
699
713
  type NileDb = NilePoolConfig & {
700
714
  tenantId?: string;
package/dist/index.js CHANGED
@@ -14,6 +14,8 @@ var ExtensionState = /* @__PURE__ */ ((ExtensionState2) => {
14
14
  ExtensionState2["onRequest"] = "onRequest";
15
15
  ExtensionState2["onResponse"] = "onResponse";
16
16
  ExtensionState2["withContext"] = "withContext";
17
+ ExtensionState2["withTenantId"] = "withTenantId";
18
+ ExtensionState2["withUserId"] = "withUserId";
17
19
  return ExtensionState2;
18
20
  })(ExtensionState || {});
19
21
  var APIErrorErrorCodeEnum = {
@@ -226,6 +228,14 @@ function bindRunExtensions(instance) {
226
228
  if (extensionConfig.disableExtensions?.includes(ext.id)) {
227
229
  continue;
228
230
  }
231
+ if (ext.withTenantId && toRun === "withTenantId" /* withTenantId */) {
232
+ ctx.set({
233
+ tenantId: await ext.withTenantId()
234
+ });
235
+ }
236
+ if (ext.withUserId && toRun === "withUserId" /* withUserId */) {
237
+ ctx.set({ userId: await ext.withUserId() });
238
+ }
229
239
  if (ext.withContext && toRun === "withContext" /* withContext */) {
230
240
  await ext.withContext(ctx);
231
241
  }
@@ -241,10 +251,6 @@ function bindRunExtensions(instance) {
241
251
  const [param] = Array.isArray(params) ? params : [params];
242
252
  if (ext.onRequest && toRun === "onRequest" /* onRequest */) {
243
253
  const { ...previousContext } = ctx.get();
244
- const preserveHeaders = previousContext.preserveHeaders;
245
- if (preserveHeaders) {
246
- ctx.set({ preserveHeaders: false });
247
- }
248
254
  if (!_init) {
249
255
  continue;
250
256
  }
@@ -255,7 +261,7 @@ function bindRunExtensions(instance) {
255
261
  const cookie = updatedContext.headers.get("cookie");
256
262
  if (cookie && param.headers) {
257
263
  const updatedCookies = mergeCookies(
258
- preserveHeaders ? previousHeaders?.get("cookie") : null,
264
+ previousHeaders?.get("cookie"),
259
265
  updatedContext.headers.get("cookie")
260
266
  );
261
267
  param.headers.set("cookie", updatedCookies);
@@ -300,6 +306,11 @@ function mergeCookies(...cookieStrings) {
300
306
  }
301
307
  async function runExtensionContext(config) {
302
308
  await config?.extensionCtx?.runExtensions("withContext" /* withContext */, config);
309
+ await config?.extensionCtx?.runExtensions(
310
+ "withTenantId" /* withTenantId */,
311
+ config
312
+ );
313
+ await config?.extensionCtx?.runExtensions("withUserId" /* withUserId */, config);
303
314
  }
304
315
 
305
316
  // src/api/utils/request-context.ts
@@ -308,8 +319,7 @@ var storage = new async_hooks.AsyncLocalStorage();
308
319
  var defaultContext = {
309
320
  headers: new Headers(),
310
321
  tenantId: void 0,
311
- userId: void 0,
312
- preserveHeaders: false
322
+ userId: void 0
313
323
  };
314
324
  var lastUsedContext = defaultContext;
315
325
  var ctx = {
@@ -359,8 +369,6 @@ var ctx = {
359
369
  }
360
370
  if ("tenantId" in partial) store.tenantId = partial.tenantId;
361
371
  if ("userId" in partial) store.userId = partial.userId;
362
- if ("preserveHeaders" in partial)
363
- store.preserveHeaders = Boolean(partial.preserveHeaders);
364
372
  silly(`[SET] ${serializeContext(store)}`);
365
373
  lastUsedContext = { ...store };
366
374
  },
@@ -378,8 +386,7 @@ function withNileContext(config, fn, name = "unknown") {
378
386
  const context2 = {
379
387
  headers: mergedHeaders,
380
388
  tenantId: existing.tenantId,
381
- userId: existing.userId,
382
- preserveHeaders: existing.preserveHeaders ?? false
389
+ userId: existing.userId
383
390
  };
384
391
  silly(`${name} [INITIAL - Request] ${serializeContext(context2)}`);
385
392
  return ctx.run(context2, fn);
@@ -392,12 +399,10 @@ function withNileContext(config, fn, name = "unknown") {
392
399
  }
393
400
  const hasTenantId = "tenantId" in initialContext;
394
401
  const hasUserId = "userId" in initialContext;
395
- const hasPreserveHeaders = "preserveHeaders" in initialContext;
396
402
  const context = {
397
403
  headers: mergedHeaders,
398
404
  tenantId: hasTenantId ? initialContext.tenantId : existing.tenantId,
399
- userId: hasUserId ? initialContext.userId : existing.userId,
400
- preserveHeaders: hasPreserveHeaders ? Boolean(initialContext.preserveHeaders) : existing.preserveHeaders ?? false
405
+ userId: hasUserId ? initialContext.userId : existing.userId
401
406
  };
402
407
  silly(`${name} [INITIAL - Partial<Context>] ${serializeContext(context)}`);
403
408
  return ctx.run(context, async () => {
@@ -414,8 +419,7 @@ function serializeContext(context) {
414
419
  return JSON.stringify({
415
420
  headers,
416
421
  tenantId: context.tenantId,
417
- userId: context.userId,
418
- preserveHeaders: context.preserveHeaders
422
+ userId: context.userId
419
423
  });
420
424
  }
421
425
  function parseCookieHeader(header) {
@@ -2020,8 +2024,7 @@ var Config = class {
2020
2024
  this.context = {
2021
2025
  tenantId: config?.tenantId,
2022
2026
  userId: config?.userId,
2023
- headers: config?.headers ? new Headers(config.headers) : new Headers(),
2024
- preserveHeaders: false
2027
+ headers: config?.headers ? new Headers(config.headers) : new Headers()
2025
2028
  };
2026
2029
  this.routes = {
2027
2030
  ...appRoutes(config?.routePrefix),
@@ -2113,17 +2116,17 @@ var updateHeaders = (val) => {
2113
2116
  var watchHeaders = (cb) => eventer.subscribe("headers" /* Headers */, cb);
2114
2117
 
2115
2118
  // src/db/PoolProxy.ts
2116
- function createProxyForPool(pool, config) {
2117
- const { info, error } = config.logger("[pool]");
2119
+ function createProxyForPool(pool, config, logger, context) {
2120
+ const { info, error } = logger("[pool]");
2118
2121
  return new Proxy(pool, {
2119
2122
  get(target, property) {
2120
2123
  if (property === "query") {
2121
- if (!config.db.connectionString) {
2122
- if (!config.db.user || !config.db.password) {
2124
+ if (!config.connectionString) {
2125
+ if (!config.user || !config.password) {
2123
2126
  error(
2124
2127
  "Cannot connect to the database. User and/or password are missing. Generate them at https://console.thenile.dev"
2125
2128
  );
2126
- } else if (!config.db.database) {
2129
+ } else if (!config.database) {
2127
2130
  error(
2128
2131
  "Unable to obtain database name. Is process.env.NILEDB_POSTGRES_URL set?"
2129
2132
  );
@@ -2132,7 +2135,7 @@ function createProxyForPool(pool, config) {
2132
2135
  const caller = target[property];
2133
2136
  return function query(...args) {
2134
2137
  let log = "[QUERY]";
2135
- const { userId, tenantId } = config.context;
2138
+ const [tenantId, userId] = context;
2136
2139
  if (tenantId) {
2137
2140
  log = `${log}[TENANT:${tenantId}]`;
2138
2141
  }
@@ -2155,39 +2158,44 @@ var NileDatabase = class {
2155
2158
  tenantId;
2156
2159
  userId;
2157
2160
  id;
2158
- config;
2161
+ logger;
2159
2162
  timer;
2160
- constructor(config, id) {
2161
- const { warn: warn2, info, debug } = config.logger("[NileInstance]");
2163
+ config;
2164
+ constructor(config, logger, id) {
2165
+ this.logger = logger("[NileInstance]");
2162
2166
  this.id = id;
2163
2167
  const poolConfig = {
2164
2168
  min: 0,
2165
2169
  max: 10,
2166
2170
  idleTimeoutMillis: 3e4,
2167
- ...config.db
2171
+ ...config
2168
2172
  };
2169
2173
  const { afterCreate, ...remaining } = poolConfig;
2170
- config.db = poolConfig;
2171
- this.config = config;
2172
- const cloned = { ...this.config.db };
2174
+ this.config = remaining;
2175
+ const cloned = { ...config };
2173
2176
  cloned.password = "***";
2174
- debug(`Connection pool config ${JSON.stringify(cloned)}`);
2175
- this.pool = createProxyForPool(new pg__default.default.Pool(remaining), this.config);
2177
+ this.logger.debug(`Connection pool config ${JSON.stringify(cloned)}`);
2178
+ this.pool = createProxyForPool(
2179
+ new pg__default.default.Pool(remaining),
2180
+ this.config,
2181
+ logger,
2182
+ id === "base" ? [] : id.split(":")
2183
+ );
2176
2184
  if (typeof afterCreate === "function") {
2177
- warn2(
2185
+ this.logger.warn(
2178
2186
  "Providing an pool configuration will stop automatic tenant context setting."
2179
2187
  );
2180
2188
  }
2181
2189
  this.startTimeout();
2182
2190
  this.pool.on("connect", async (client) => {
2183
- debug(`pool connected ${this.id}`);
2191
+ this.logger.debug(`pool connected ${this.id}`);
2184
2192
  this.startTimeout();
2185
2193
  const afterCreate2 = makeAfterCreate(
2186
- config,
2187
- `${this.id}-${this.timer}`
2194
+ logger,
2195
+ `${this.id}|${this.timer}`
2188
2196
  );
2189
2197
  afterCreate2(client, (err) => {
2190
- const { error } = config.logger("[after create callback]");
2198
+ const { error } = logger("[after create callback]");
2191
2199
  if (err) {
2192
2200
  clearTimeout(this.timer);
2193
2201
  error("after create failed", {
@@ -2200,7 +2208,7 @@ var NileDatabase = class {
2200
2208
  });
2201
2209
  this.pool.on("error", (err) => {
2202
2210
  clearTimeout(this.timer);
2203
- info(`pool ${this.id} failed`, {
2211
+ this.logger.info(`pool ${this.id} failed`, {
2204
2212
  message: err.message,
2205
2213
  stack: err.stack
2206
2214
  });
@@ -2210,27 +2218,27 @@ var NileDatabase = class {
2210
2218
  if (destroy) {
2211
2219
  clearTimeout(this.timer);
2212
2220
  evictPool(this.id);
2213
- debug(`destroying pool ${this.id}`);
2221
+ this.logger.debug(`destroying pool ${this.id}`);
2214
2222
  }
2215
2223
  });
2216
2224
  }
2217
2225
  startTimeout() {
2218
- const { debug } = this.config.logger("[NileInstance]");
2226
+ const { debug } = this.logger;
2219
2227
  if (this.timer) {
2220
2228
  clearTimeout(this.timer);
2221
2229
  }
2222
2230
  this.timer = setTimeout(() => {
2223
2231
  debug(
2224
- `Pool reached idleTimeoutMillis. ${this.id} evicted after ${Number(this.config.db.idleTimeoutMillis) ?? 3e4}ms`
2232
+ `Pool reached idleTimeoutMillis. ${this.id} evicted after ${Number(this.config.idleTimeoutMillis) ?? 3e4}ms`
2225
2233
  );
2226
2234
  this.pool.end(() => {
2227
2235
  clearTimeout(this.timer);
2228
2236
  evictPool(this.id);
2229
2237
  });
2230
- }, Number(this.config.db.idleTimeoutMillis) ?? 3e4);
2238
+ }, Number(this.config.idleTimeoutMillis) ?? 3e4);
2231
2239
  }
2232
2240
  shutdown() {
2233
- const { debug } = this.config.logger("[NileInstance]");
2241
+ const { debug } = this.logger;
2234
2242
  debug(`attempting to shut down ${this.id}`);
2235
2243
  clearTimeout(this.timer);
2236
2244
  this.pool.end(() => {
@@ -2239,8 +2247,8 @@ var NileDatabase = class {
2239
2247
  }
2240
2248
  };
2241
2249
  var NileInstance_default = NileDatabase;
2242
- function makeAfterCreate(config, id) {
2243
- const { error, warn: warn2, debug } = config.logger("[afterCreate]");
2250
+ function makeAfterCreate(logger, id) {
2251
+ const { error, warn: warn2, debug } = logger("[afterCreate]");
2244
2252
  return (conn, done) => {
2245
2253
  conn.on("error", function errorHandler(e) {
2246
2254
  error(`Connection ${id} was terminated by server`, {
@@ -2249,8 +2257,9 @@ function makeAfterCreate(config, id) {
2249
2257
  });
2250
2258
  done(e, conn);
2251
2259
  });
2252
- const { tenantId, userId } = ctx.getLastUsed();
2253
- if (tenantId) {
2260
+ const [context] = id.split("|");
2261
+ const [tenantId, userId] = context.split(":");
2262
+ if (tenantId !== "base") {
2254
2263
  const query = [`SET nile.tenant_id = '${tenantId}'`];
2255
2264
  if (userId) {
2256
2265
  if (!tenantId) {
@@ -2315,9 +2324,9 @@ var DBManager = class {
2315
2324
  warn2(`missed eviction of ${id}`);
2316
2325
  }
2317
2326
  };
2318
- getConnection = (config) => {
2327
+ getConnection = (config, noContext = false) => {
2319
2328
  const { info } = Logger(config)("[DBManager]");
2320
- const { tenantId, userId } = ctx.getLastUsed();
2329
+ const { tenantId, userId } = noContext ? {} : ctx.getLastUsed();
2321
2330
  const id = this.makeId(tenantId, userId);
2322
2331
  const existing = this.connections.get(id);
2323
2332
  info(`# of instances: ${this.connections.size}`);
@@ -2326,7 +2335,7 @@ var DBManager = class {
2326
2335
  existing.startTimeout();
2327
2336
  return existing.pool;
2328
2337
  }
2329
- const newOne = new NileInstance_default(config, id);
2338
+ const newOne = new NileInstance_default(config.db, config.logger, id);
2330
2339
  this.connections.set(id, newOne);
2331
2340
  info(`created new ${id}`);
2332
2341
  info(`# of instances: ${this.connections.size}`);
@@ -2733,7 +2742,7 @@ var Auth = class {
2733
2742
  ].filter(Boolean).join("; ");
2734
2743
  const uHeaders = new Headers({ cookie });
2735
2744
  updateHeaders(uHeaders);
2736
- ctx.set({ headers: uHeaders, preserveHeaders: true });
2745
+ ctx.set({ headers: uHeaders });
2737
2746
  } else {
2738
2747
  error("Unable to set context after sign in", {
2739
2748
  headers: signInRes.headers
@@ -2855,7 +2864,7 @@ async function obtainCsrf(config, rawResponse = false) {
2855
2864
  parseToken(res.headers)
2856
2865
  ].filter(Boolean).join("; ");
2857
2866
  headers.set("cookie", cookie);
2858
- ctx.set({ headers, preserveHeaders: true });
2867
+ ctx.set({ headers });
2859
2868
  updateHeaders(headers);
2860
2869
  }
2861
2870
  if (!rawResponse) {
@@ -2874,7 +2883,7 @@ async function obtainCsrf(config, rawResponse = false) {
2874
2883
  }
2875
2884
  const cookie = cookieParts.filter(Boolean).join("; ");
2876
2885
  headers.set("cookie", cookie);
2877
- ctx.set({ headers, preserveHeaders: true });
2886
+ ctx.set({ headers });
2878
2887
  updateHeaders(new Headers({ cookie }));
2879
2888
  }
2880
2889
  if (rawResponse) {
@@ -3164,12 +3173,12 @@ var Tenants = class {
3164
3173
  try {
3165
3174
  const json = await me.json();
3166
3175
  if ("id" in json) {
3167
- ctx.set({ userId: json.id, preserveHeaders: true });
3176
+ ctx.set({ userId: json.id });
3168
3177
  }
3169
3178
  } catch {
3170
3179
  }
3171
3180
  if (typeof req === "string") {
3172
- ctx.set({ tenantId: req, preserveHeaders: true });
3181
+ ctx.set({ tenantId: req });
3173
3182
  } else {
3174
3183
  this.#handleContext(req);
3175
3184
  }
@@ -3179,7 +3188,7 @@ var Tenants = class {
3179
3188
  async addMember(req, rawResponse) {
3180
3189
  return withNileContext(this.#config, async () => {
3181
3190
  if (typeof req === "string") {
3182
- ctx.set({ userId: req, preserveHeaders: true });
3191
+ ctx.set({ userId: req });
3183
3192
  } else {
3184
3193
  this.#handleContext(req);
3185
3194
  }
@@ -3197,7 +3206,7 @@ var Tenants = class {
3197
3206
  return withNileContext(this.#config, async () => {
3198
3207
  this.#handleContext(req);
3199
3208
  if (typeof req === "string") {
3200
- ctx.set({ userId: req, preserveHeaders: true });
3209
+ ctx.set({ userId: req });
3201
3210
  }
3202
3211
  const res = await fetchTenantUser(this.#config, "DELETE");
3203
3212
  return responseHandler(res, rawResponse);
@@ -3320,10 +3329,10 @@ var Tenants = class {
3320
3329
  #handleContext(req) {
3321
3330
  if (typeof req === "object") {
3322
3331
  if ("tenantId" in req) {
3323
- ctx.set({ tenantId: req.tenantId, preserveHeaders: true });
3332
+ ctx.set({ tenantId: req.tenantId });
3324
3333
  }
3325
3334
  if ("userId" in req) {
3326
- ctx.set({ userId: req.userId, preserveHeaders: true });
3335
+ ctx.set({ userId: req.userId });
3327
3336
  }
3328
3337
  }
3329
3338
  }
@@ -3410,7 +3419,7 @@ function updateConfig(response, config) {
3410
3419
  ...config,
3411
3420
  origin,
3412
3421
  headers: headers ?? void 0,
3413
- preserveHeaders: true
3422
+ useLastContext: true
3414
3423
  };
3415
3424
  }
3416
3425
 
@@ -3442,7 +3451,6 @@ var Server = class {
3442
3451
  watchHeaders((headers) => {
3443
3452
  if (headers) {
3444
3453
  this.#config.context.headers = new Headers(headers);
3445
- this.#config.context.preserveHeaders = true;
3446
3454
  this.#reset();
3447
3455
  }
3448
3456
  });
@@ -3450,7 +3458,6 @@ var Server = class {
3450
3458
  ...this.#config.handlers,
3451
3459
  withContext: handlersWithContext(this.#config)
3452
3460
  };
3453
- this.#config.context.preserveHeaders = config?.preserveHeaders ?? false;
3454
3461
  this.#config.context.tenantId = getTenantId({ config: this.#config });
3455
3462
  this.#manager = new DBManager(this.#config);
3456
3463
  this.#handleHeaders(config);
@@ -3476,9 +3483,21 @@ var Server = class {
3476
3483
  }
3477
3484
  }
3478
3485
  }
3479
- get db() {
3486
+ /**
3487
+ * Query the database with the current context
3488
+ */
3489
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3490
+ query = (queryStream, values) => {
3480
3491
  this.#config.context = { ...this.getContext() };
3481
3492
  const pool = this.#manager.getConnection(this.#config);
3493
+ return pool.query(queryStream, values);
3494
+ };
3495
+ /**
3496
+ * Return a db object that can be used to talk to the database
3497
+ * Does not have a context by default
3498
+ */
3499
+ get db() {
3500
+ const pool = this.#manager.getConnection(this.#config, true);
3482
3501
  return Object.assign(pool, {
3483
3502
  clearConnections: () => {
3484
3503
  this.#manager.clear(this.#config);
@@ -3516,18 +3535,18 @@ var Server = class {
3516
3535
  set paths(paths) {
3517
3536
  this.#config.paths = paths;
3518
3537
  }
3519
- async withContext(context, fn) {
3520
- const { ...initialContext } = context ?? defaultContext;
3521
- this.#config.context = { ...initialContext };
3522
- const preserve = (context && "preserveHeaders" in context && context.preserveHeaders) ?? true;
3538
+ async withContext(contextOrFn, maybeFn) {
3539
+ const isFn = typeof contextOrFn === "function";
3540
+ const context = isFn ? {} : contextOrFn ?? {};
3541
+ const fn = isFn ? contextOrFn : maybeFn;
3542
+ const preserve = "useLastContext" in context ? context.useLastContext : true;
3523
3543
  if (preserve) {
3524
3544
  this.#config.context = { ...this.getContext(), ...context };
3545
+ } else {
3546
+ this.#config.context = { ...defaultContext, ...context };
3525
3547
  }
3526
3548
  return withNileContext(this.#config, async () => {
3527
- if (fn) {
3528
- return fn(this);
3529
- }
3530
- return this;
3549
+ return fn ? fn(this) : this;
3531
3550
  });
3532
3551
  }
3533
3552
  async noContext(fn) {