@niledatabase/server 5.0.3-alpha.0 → 5.0.3

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
@@ -298,8 +298,13 @@ function mergeCookies(...cookieStrings) {
298
298
  }
299
299
  return [...cookieMap.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
300
300
  }
301
- async function runExtensionContext(config) {
302
- await config?.extensionCtx?.runExtensions("withContext" /* withContext */, config);
301
+ async function runExtensionContext(config, options) {
302
+ if (!options?.skipWithContext) {
303
+ await config?.extensionCtx?.runExtensions(
304
+ "withContext" /* withContext */,
305
+ config
306
+ );
307
+ }
303
308
  await config?.extensionCtx?.runExtensions(
304
309
  "withTenantId" /* withTenantId */,
305
310
  config
@@ -369,38 +374,55 @@ var ctx = {
369
374
  // for convenience only
370
375
  getLastUsed: () => lastUsedContext
371
376
  };
372
- function withNileContext(config, fn, name = "unknown") {
377
+ async function withNileContext(config, fn, name = "unknown") {
373
378
  const initialContext = config.context;
374
379
  const existing = ctx.get();
375
- const mergedHeaders = new Headers(existing.headers);
380
+ const { overrides: extensionOverrides, ran: extensionRan } = await resolveExtensionOverrides(config, existing);
381
+ let mergedHeaders = new Headers(existing.headers);
382
+ let tenantId = existing.tenantId;
383
+ let userId = existing.userId;
376
384
  if (initialContext instanceof Request) {
377
385
  initialContext.headers.forEach((value, key17) => {
378
386
  mergedHeaders.set(key17, value);
379
387
  });
380
- const context2 = {
381
- headers: mergedHeaders,
382
- tenantId: existing.tenantId,
383
- userId: existing.userId
384
- };
385
- silly(`${name} [INITIAL - Request] ${serializeContext(context2)}`);
386
- return ctx.run(context2, fn);
388
+ } else {
389
+ if (initialContext.headers === null) {
390
+ mergedHeaders = new Headers();
391
+ } else if (initialContext.headers) {
392
+ const incoming = initialContext.headers instanceof Headers ? initialContext.headers : new Headers(initialContext.headers);
393
+ incoming.forEach((value, key17) => {
394
+ mergedHeaders.set(key17, value);
395
+ });
396
+ }
397
+ if ("tenantId" in initialContext) {
398
+ tenantId = initialContext.tenantId;
399
+ }
400
+ if ("userId" in initialContext) {
401
+ userId = initialContext.userId;
402
+ }
387
403
  }
388
- if (initialContext.headers) {
389
- const incoming = initialContext.headers instanceof Headers ? initialContext.headers : new Headers(initialContext.headers);
390
- incoming.forEach((value, key17) => {
404
+ if (extensionOverrides?.headers) {
405
+ for (const key17 of extensionOverrides.headers.removed) {
406
+ mergedHeaders.delete(key17);
407
+ }
408
+ for (const [key17, value] of extensionOverrides.headers.set) {
391
409
  mergedHeaders.set(key17, value);
392
- });
410
+ }
411
+ }
412
+ if (extensionOverrides?.tenantId) {
413
+ tenantId = extensionOverrides.tenantId.value;
414
+ }
415
+ if (extensionOverrides?.userId) {
416
+ userId = extensionOverrides.userId.value;
393
417
  }
394
- const hasTenantId = "tenantId" in initialContext;
395
- const hasUserId = "userId" in initialContext;
396
418
  const context = {
397
419
  headers: mergedHeaders,
398
- tenantId: hasTenantId ? initialContext.tenantId : existing.tenantId,
399
- userId: hasUserId ? initialContext.userId : existing.userId
420
+ tenantId,
421
+ userId
400
422
  };
401
- silly(`${name} [INITIAL - Partial<Context>] ${serializeContext(context)}`);
423
+ silly(`${name} [INITIAL] ${serializeContext(context)}`);
402
424
  return ctx.run(context, async () => {
403
- await runExtensionContext(config);
425
+ await runExtensionContext(config, { skipWithContext: extensionRan });
404
426
  return fn();
405
427
  });
406
428
  }
@@ -426,10 +448,81 @@ function parseCookieHeader(header) {
426
448
  function serializeCookies(cookies) {
427
449
  return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
428
450
  }
451
+ async function resolveExtensionOverrides(config, existing) {
452
+ if (!config.extensions?.length || !config.extensionCtx) {
453
+ return { ran: false };
454
+ }
455
+ let updated;
456
+ await ctx.run(
457
+ {
458
+ headers: new Headers(existing.headers),
459
+ tenantId: existing.tenantId,
460
+ userId: existing.userId
461
+ },
462
+ async () => {
463
+ await config.extensionCtx?.runExtensions(
464
+ "withContext" /* withContext */,
465
+ config
466
+ );
467
+ updated = ctx.get();
468
+ }
469
+ );
470
+ if (!updated) {
471
+ return { ran: true };
472
+ }
473
+ const diff = diffContext(existing, updated);
474
+ return { overrides: diff, ran: true };
475
+ }
476
+ function diffContext(before, after) {
477
+ const headers = diffHeaders(before.headers, after.headers);
478
+ const tenantChanged = before.tenantId !== after.tenantId;
479
+ const userChanged = before.userId !== after.userId;
480
+ if (!headers && !tenantChanged && !userChanged) {
481
+ return void 0;
482
+ }
483
+ const overrides = {};
484
+ if (headers) {
485
+ overrides.headers = headers;
486
+ }
487
+ if (tenantChanged) {
488
+ overrides.tenantId = { value: after.tenantId };
489
+ }
490
+ if (userChanged) {
491
+ overrides.userId = { value: after.userId };
492
+ }
493
+ return overrides;
494
+ }
495
+ function diffHeaders(before, after) {
496
+ const beforeMap = headersToMap(before);
497
+ const afterMap = headersToMap(after);
498
+ const set = [];
499
+ const removed = [];
500
+ for (const [key17, value] of afterMap.entries()) {
501
+ if (beforeMap.get(key17) !== value) {
502
+ set.push([key17, value]);
503
+ }
504
+ }
505
+ for (const key17 of beforeMap.keys()) {
506
+ if (!afterMap.has(key17)) {
507
+ removed.push(key17);
508
+ }
509
+ }
510
+ if (set.length === 0 && removed.length === 0) {
511
+ return void 0;
512
+ }
513
+ return { set, removed };
514
+ }
515
+ function headersToMap(headers) {
516
+ const map = /* @__PURE__ */ new Map();
517
+ headers.forEach((value, key17) => {
518
+ map.set(key17.toLowerCase(), value);
519
+ });
520
+ return map;
521
+ }
429
522
 
430
523
  // src/api/utils/request.ts
431
524
  async function request(url, _init, config) {
432
- const { debug, info, error, warn: warn2 } = config.logger("[REQUEST]");
525
+ const { debug, info, error } = config.logger("[REQUEST]");
433
526
  const { request: request2, ...init } = _init;
434
527
  const requestUrl = new URL(request2.url);
435
528
  const updatedHeaders = new Headers({});
@@ -2405,79 +2498,89 @@ var Auth = class {
2405
2498
  * from the internal configuration once the request completes.
2406
2499
  */
2407
2500
  async signOut() {
2408
- return withNileContext(this.#config, async () => {
2409
- const csrfRes = await this.getCsrf();
2410
- if (!("csrfToken" in csrfRes)) {
2411
- throw new Error("Unable to obtain CSRF token. Sign out failed.");
2412
- }
2413
- const body = JSON.stringify({
2414
- csrfToken: csrfRes.csrfToken,
2415
- json: true
2416
- });
2417
- const res = await fetchSignOut(this.#config, body);
2418
- updateHeaders(new Headers({}));
2419
- ctx.set({ headers: null });
2420
- return res;
2421
- });
2501
+ return withNileContext(
2502
+ this.#config,
2503
+ async () => {
2504
+ const csrfRes = await this.getCsrf();
2505
+ if (!("csrfToken" in csrfRes)) {
2506
+ throw new Error("Unable to obtain CSRF token. Sign out failed.");
2507
+ }
2508
+ const body = JSON.stringify({
2509
+ csrfToken: csrfRes.csrfToken,
2510
+ json: true
2511
+ });
2512
+ const res = await fetchSignOut(this.#config, body);
2513
+ updateHeaders(new Headers({}));
2514
+ ctx.set({ headers: null });
2515
+ return res;
2516
+ },
2517
+ "signout"
2518
+ );
2422
2519
  }
2423
2520
  async signUp(payload, rawResponse) {
2424
- return withNileContext(this.#config, async () => {
2425
- ctx.set({ headers: null });
2426
- const { email, password, ...params } = payload;
2427
- if (!email || !password) {
2428
- throw new Error(
2429
- "Server side sign up requires a user email and password."
2430
- );
2431
- }
2432
- const providers = await this.listProviders();
2433
- const { credentials } = providers ?? {};
2434
- if (!credentials) {
2435
- throw new Error(
2436
- "Unable to obtain credential provider. Aborting server side sign up."
2437
- );
2438
- }
2439
- const csrf = await obtainCsrf(this.#config);
2440
- let csrfToken;
2441
- if ("csrfToken" in csrf) {
2442
- csrfToken = csrf.csrfToken;
2443
- } else {
2444
- throw new Error("Unable to obtain parse CSRF. Request blocked.");
2445
- }
2446
- const body = JSON.stringify({
2447
- email,
2448
- password,
2449
- csrfToken,
2450
- callbackUrl: credentials.callbackUrl
2451
- });
2452
- const res = await fetchSignUp(this.#config, { body, params });
2453
- if (res.status > 299) {
2454
- this.#logger.error(await res.clone().text());
2455
- return void 0;
2456
- }
2457
- const token = parseToken(res.headers);
2458
- if (!token) {
2459
- throw new Error("Server side sign up failed. Session token not found");
2460
- }
2461
- const { headers } = ctx.get();
2462
- headers?.append("cookie", token);
2463
- ctx.set({ headers });
2464
- updateHeaders(headers);
2465
- if (rawResponse) {
2466
- return res;
2467
- }
2468
- try {
2469
- const json = await res.clone().json();
2470
- if (json && typeof json === "object" && "tenants" in json) {
2471
- const tenantId = json.tenants[0];
2472
- if (tenantId) {
2473
- updateTenantId(tenantId);
2521
+ return withNileContext(
2522
+ this.#config,
2523
+ async () => {
2524
+ ctx.set({ headers: null });
2525
+ const { email, password, ...params } = payload;
2526
+ if (!email || !password) {
2527
+ throw new Error(
2528
+ "Server side sign up requires a user email and password."
2529
+ );
2530
+ }
2531
+ const providers = await this.listProviders();
2532
+ const { credentials } = providers ?? {};
2533
+ if (!credentials) {
2534
+ throw new Error(
2535
+ "Unable to obtain credential provider. Aborting server side sign up."
2536
+ );
2537
+ }
2538
+ const csrf = await obtainCsrf(this.#config);
2539
+ let csrfToken;
2540
+ if ("csrfToken" in csrf) {
2541
+ csrfToken = csrf.csrfToken;
2542
+ } else {
2543
+ throw new Error("Unable to obtain parse CSRF. Request blocked.");
2544
+ }
2545
+ const body = JSON.stringify({
2546
+ email,
2547
+ password,
2548
+ csrfToken,
2549
+ callbackUrl: credentials.callbackUrl
2550
+ });
2551
+ const res = await fetchSignUp(this.#config, { body, params });
2552
+ if (res.status > 299) {
2553
+ this.#logger.error(await res.clone().text());
2554
+ return void 0;
2555
+ }
2556
+ const token = parseToken(res.headers);
2557
+ if (!token) {
2558
+ throw new Error(
2559
+ "Server side sign up failed. Session token not found"
2560
+ );
2561
+ }
2562
+ const { headers } = ctx.get();
2563
+ headers?.append("cookie", token);
2564
+ ctx.set({ headers });
2565
+ updateHeaders(headers);
2566
+ if (rawResponse) {
2567
+ return res;
2568
+ }
2569
+ try {
2570
+ const json = await res.clone().json();
2571
+ if (json && typeof json === "object" && "tenants" in json) {
2572
+ const tenantId = json.tenants[0];
2573
+ if (tenantId) {
2574
+ updateTenantId(tenantId);
2575
+ }
2474
2576
  }
2577
+ return json;
2578
+ } catch {
2579
+ return res;
2475
2580
  }
2476
- return json;
2477
- } catch {
2478
- return res;
2479
- }
2480
- });
2581
+ },
2582
+ "signup"
2583
+ );
2481
2584
  }
2482
2585
  /**
2483
2586
  * Request a password reset email.
@@ -2960,17 +3063,21 @@ var Users = class {
2960
3063
  });
2961
3064
  }
2962
3065
  async getSelf(rawResponse) {
2963
- return withNileContext(this.#config, async () => {
2964
- const res = await fetchMe(this.#config);
2965
- if (rawResponse) {
2966
- return res;
2967
- }
2968
- try {
2969
- return await res?.clone().json();
2970
- } catch {
2971
- return res;
2972
- }
2973
- });
3066
+ return withNileContext(
3067
+ this.#config,
3068
+ async () => {
3069
+ const res = await fetchMe(this.#config);
3070
+ if (rawResponse) {
3071
+ return res;
3072
+ }
3073
+ try {
3074
+ return await res?.clone().json();
3075
+ } catch {
3076
+ return res;
3077
+ }
3078
+ },
3079
+ "getSelf"
3080
+ );
2974
3081
  }
2975
3082
  async verifySelf(options, rawResponse = false) {
2976
3083
  return withNileContext(this.#config, async () => {
@@ -3534,8 +3641,11 @@ var Server = class {
3534
3641
  const context = isFn ? {} : contextOrFn ?? {};
3535
3642
  const fn = isFn ? contextOrFn : maybeFn;
3536
3643
  const preserve = "useLastContext" in context ? context.useLastContext : true;
3644
+ let hydrated;
3537
3645
  if (preserve) {
3538
- this.#config.context = { ...this.getContext(), ...context };
3646
+ hydrated = await this.#hydrateContextFromExtensions();
3647
+ const base = hydrated ?? this.#config.context;
3648
+ this.#config.context = { ...base, ...context };
3539
3649
  } else {
3540
3650
  this.#config.context = { ...defaultContext, ...context };
3541
3651
  }
@@ -3607,6 +3717,31 @@ var Server = class {
3607
3717
  }
3608
3718
  this.#config.logger("[handleHeaders]").debug(JSON.stringify(merged));
3609
3719
  }
3720
+ async #hydrateContextFromExtensions() {
3721
+ if (!this.#config.extensions || this.#config.extensions.length === 0) {
3722
+ return void 0;
3723
+ }
3724
+ let updated;
3725
+ await ctx.run({}, async () => {
3726
+ await this.#config.extensionCtx?.runExtensions(
3727
+ "withContext" /* withContext */,
3728
+ this.#config
3729
+ );
3730
+ updated = ctx.get();
3731
+ });
3732
+ if (!updated) {
3733
+ return void 0;
3734
+ }
3735
+ const hydrated = {
3736
+ headers: new Headers(updated.headers),
3737
+ tenantId: updated.tenantId,
3738
+ userId: updated.userId
3739
+ };
3740
+ this.#config.context.headers = new Headers(hydrated.headers);
3741
+ this.#config.context.tenantId = hydrated.tenantId;
3742
+ this.#config.context.userId = hydrated.userId;
3743
+ return hydrated;
3744
+ }
3610
3745
  /**
3611
3746
  * Allow some internal mutations to reset our config + headers
3612
3747
  */