@stacksjs/sites 0.71.2 → 0.71.10

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.
@@ -0,0 +1 @@
1
+ import{AsyncLocalStorage}from"node:async_hooks";const SITE_STORAGE_KEY=Symbol.for("stacks.sites.contextStorage"),siteStorage=globalThis[SITE_STORAGE_KEY]??=new AsyncLocalStorage;export class SiteNotResolvedError extends Error{status=404;constructor(message="Unknown site"){super(message);this.name="SiteNotResolvedError"}}export function runWithSite(site,fn){return siteStorage.run(site,fn)}export function setCurrentSite(site){siteStorage.enterWith(site)}export function currentSite(){return siteStorage.getStore()??void 0}export function currentSiteId(){return currentSite()?.id}export function requireSite(){const site=currentSite();if(!site)throw new SiteNotResolvedError;return site}
package/dist/index.js CHANGED
@@ -1,2 +1 @@
1
- // @bun
2
- export{R as toSiteSnapshot,c as sitesOptions,n as siteResolver,C as siteOwnership,S as setCurrentSite,i as runWithSite,H as resolveSiteByHost,s as requireSite,d as requestHost,u as normalizeHost,h as isPlatformHost,v as forSite,x as databaseSiteStore,r as currentSiteId,o as currentSite,m as clearSiteCache,l as classifyHost,p as SiteNotResolvedError};
1
+ export{currentSite,currentSiteId,requireSite,runWithSite,setCurrentSite,SiteNotResolvedError}from"./context";export{default as siteResolver}from"./middleware";export{classifyHost,clearSiteCache,databaseSiteStore,isPlatformHost,normalizeHost,requestHost,resolveSiteByHost,sitesOptions}from"./resolver";export{forSite,siteOwnership}from"./scoping";export{toSiteSnapshot}from"./snapshot";
@@ -0,0 +1 @@
1
+ import{Middleware}from"@stacksjs/router";import{setCurrentSite,SiteNotResolvedError}from"./context";import{isPlatformHost,requestHost,resolveSiteByHost,sitesOptions}from"./resolver";export default new Middleware({name:"SiteResolver",priority:1,async handle(request){const options=sitesOptions();if(!options.enabled){setCurrentSite(null);return}const headers=request.headers instanceof Headers?request.headers:new Headers(request.headers??{}),host=requestHost(headers,options),site=await resolveSiteByHost(host,void 0,options);request.site=site;setCurrentSite(site);if(!site&&options.strict&&!isPlatformHost(host,options))throw new SiteNotResolvedError}});
@@ -0,0 +1 @@
1
+ import{config}from"@stacksjs/config";import{db}from"@stacksjs/database";export function normalizeHost(raw){if(!raw)return"";let host=raw.trim().toLowerCase();if(host.startsWith("[")){const close=host.indexOf("]");if(close!==-1)host=host.slice(0,close+1)}else{const colon=host.indexOf(":");if(colon!==-1)host=host.slice(0,colon)}return host.endsWith(".")?host.slice(0,-1):host}export function sitesOptions(){const raw=config.sites??{};return{enabled:raw.enabled??!1,baseDomain:normalizeHost(raw.baseDomain??""),platformHosts:(raw.platformHosts??[]).map(normalizeHost),strict:raw.strict??!1,trustProxyHost:raw.trustProxyHost??!0,cacheTtlSeconds:raw.cacheTtlSeconds??60}}export function classifyHost(host,options){if(!host||options.platformHosts.includes(host))return{kind:"platform"};const base=options.baseDomain;if(base&&host===base)return{kind:"platform"};if(base&&host.endsWith(`.${base}`)){const sub=host.slice(0,-(base.length+1));if(sub&&!sub.includes(".")&&sub!=="www")return{kind:"subdomain",subdomain:sub};return{kind:"platform"}}return{kind:"custom",domain:host}}export function requestHost(headers,options){const first=(options.trustProxyHost?headers.get("x-forwarded-host"):null)?.split(",")[0];return normalizeHost(first||headers.get("host"))}function rowToContext(row,host){let settings={};const rawSettings=row.settings;if(typeof rawSettings==="string"&&rawSettings)try{settings=JSON.parse(rawSettings)}catch{}else if(rawSettings&&typeof rawSettings==="object")settings=rawSettings;return{id:Number(row.id),uuid:String(row.uuid??""),name:String(row.name??""),subdomain:String(row.subdomain??""),host,teamId:row.team_id==null?null:Number(row.team_id),status:String(row.status??"active"),settings}}const SITE_COLUMNS=["id","uuid","name","subdomain","team_id","status","settings"];export const databaseSiteStore={async byDomain(domain){const link=await db.selectFrom("site_domains").where("domain","=",domain).where("verified_at","is not",null).select(["site_id"]).executeTakeFirst();if(!link)return null;const site=await db.selectFrom("sites").where("id","=",link.site_id).where("status","=","active").select([...SITE_COLUMNS]).executeTakeFirst();return site?rowToContext(site,domain):null},async bySubdomain(subdomain){const site=await db.selectFrom("sites").where("subdomain","=",subdomain).where("status","=","active").select([...SITE_COLUMNS]).executeTakeFirst();return site?rowToContext(site,`${subdomain}.${sitesOptions().baseDomain}`):null}};const CACHE_KEY=Symbol.for("stacks.sites.hostCache"),hostCache=globalThis[CACHE_KEY]??=new Map;export function clearSiteCache(){hostCache.clear()}export async function resolveSiteByHost(rawHost,store=databaseSiteStore,options=sitesOptions()){if(!options.enabled)return null;const host=normalizeHost(rawHost);if(!host)return null;const cached=hostCache.get(host);if(cached&&cached.expiresAt>Date.now())return cached.site;const kind=classifyHost(host,options);let site=null;if(kind.kind==="custom")site=await store.byDomain(kind.domain);else if(kind.kind==="subdomain")site=await store.bySubdomain(kind.subdomain);hostCache.set(host,{site,expiresAt:Date.now()+options.cacheTtlSeconds*1000});return site}export function isPlatformHost(rawHost,options=sitesOptions()){return classifyHost(normalizeHost(rawHost),options).kind==="platform"}
@@ -0,0 +1 @@
1
+ import{resolveAuthenticatedTeamId}from"@stacksjs/auth";import{db}from"@stacksjs/database";import{currentSiteId}from"./context";import{SiteNotResolvedError}from"./context";export function forSite(qb,column="site_id",siteId=currentSiteId()){if(siteId==null)throw new SiteNotResolvedError("No site in request context");return qb.where(column,"=",siteId)}export function siteOwnership(){return{field:"site_id",resolve:async(_user,req)=>{const teamId=await resolveAuthenticatedTeamId(req);if(!teamId)return null;return(await db.selectFrom("sites").where("team_id","=",teamId).select(["id"]).execute()).map((row)=>Number(row.id))}}}
@@ -0,0 +1 @@
1
+ export function toSiteSnapshot(site){if(!site)return null;return{id:site.id,uuid:site.uuid,name:site.name,subdomain:site.subdomain,settings:site.settings}}
package/dist/types.js ADDED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/sites",
3
3
  "type": "module",
4
- "version": "0.71.2",
4
+ "version": "0.71.10",
5
5
  "description": "The Stacks multi-site (request-level tenancy) functionality.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -56,11 +56,11 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "devDependencies": {
59
- "@stacksjs/auth": "0.71.2",
60
- "@stacksjs/config": "0.71.2",
61
- "@stacksjs/database": "0.71.2",
62
- "@stacksjs/error-handling": "0.71.2",
63
- "@stacksjs/router": "0.71.2",
59
+ "@stacksjs/auth": "0.71.10",
60
+ "@stacksjs/config": "0.71.10",
61
+ "@stacksjs/database": "0.71.10",
62
+ "@stacksjs/error-handling": "0.71.10",
63
+ "@stacksjs/router": "0.71.10",
64
64
  "better-dx": "^0.2.23"
65
65
  },
66
66
  "sideEffects": false