(instance: T): T => {
if (instance === null || typeof instance !== 'object') {
return instance;
}
return new Proxy(instance as object, {
get(target, prop) {
const value = Reflect.get(target, prop, target);
return typeof value === 'function'
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(value as (...a: any[]) => unknown).bind(target)
: value;
},
}) as T;
};
export const extension = {
// -- Config access -----------------------------------------------
//
// Lazy proxy to the server config. Populated by PuterServer during
// boot, so extensions can read it at request time (not import time).
get config(): IConfig {
return configContainer;
},
// -- Event subscription -------------------------------------------
on: (
key: P,
callback: (
key: MatchingEvents
,
data: EventMap[MatchingEvents
],
meta: EventMetadata,
) => Promise | void,
) => {
if (!extensionStore.events[key]) {
extensionStore.events[key] = [];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extensionStore.events[key].push(callback as any);
},
// -- Registry writers ---------------------------------------------
registerClient: (
name: string,
client: IPuterClientRegistry[keyof IPuterClientRegistry],
) => {
extensionStore.clients[name] = client;
},
registerStore: (
name: string,
store: IPuterStoreRegistry[keyof IPuterStoreRegistry],
) => {
extensionStore.stores[name] = store;
},
registerService: (
name: string,
service: IPuterServiceRegistry[keyof IPuterServiceRegistry],
) => {
extensionStore.services[name] = service;
},
registerController: (
name: string,
controller: IPuterControllerRegistry[keyof IPuterControllerRegistry],
) => {
extensionStore.controllers[name] = controller;
},
registerDriver: (
name: string,
driver: IPuterDriverRegistry[keyof IPuterDriverRegistry],
) => {
extensionStore.drivers[name] = driver;
},
registerGlobalMiddleware: (middleware: RequestHandler) => {
extensionStore.globalMiddlewares.push(middleware);
},
// -- Route registration -------------------------------------------
//
// Supports two call shapes per verb:
// extension.get('/path', handler)
// extension.get('/path', options, handler)
//
// The `options` object is the same `RouteOptions` shape controllers use,
// so everything that works on a controller route (subdomain, requireAuth,
// requireUserActor, adminOnly, allowedAppIds, middleware, bodyJson,
// bodyRaw, bodyText, bodyUrlencoded) works here identically.
get: makeRouteFn('get'),
post: makeRouteFn('post'),
put: makeRouteFn('put'),
delete: makeRouteFn('delete'),
patch: makeRouteFn('patch'),
head: makeRouteFn('head'),
options: makeRouteFn('options'),
all: makeRouteFn('all'),
use: makeUseFn(),
// -- Import proxy -------------------------------------------------
import: (
name: S,
): S extends 'client' | 'clients'
? LayerInstances & IExtensionClientInstances
: S extends 'store' | 'stores'
? LayerInstances & IExtensionStoreInstances
: S extends 'service' | 'services'
? LayerInstances & IExtensionServiceInstances
: S extends 'controller' | 'controllers'
? LayerInstances &
IExtensionControllerInstances
: S extends 'driver' | 'drivers'
? LayerInstances &
IExtensionDriverInstances
: never => {
switch (name) {
case 'clients':
case 'client': {
const proxyHandler = {
get: (_target: object, prop: string) => {
const proxiedObj = clientsContainers[prop];
if (!proxiedObj) {
const proxyProxyHandler = {
get: (_target2: object, prop2: string) => {
const proxiedObj2 =
// @ts-expect-error any type needed
clientsContainers[prop][prop2];
if (!proxiedObj2) {
throw new Error(
`extension.import('client:${prop}') missing property '${prop2}'`,
);
}
return proxiedObj2;
},
};
return new Proxy({}, proxyProxyHandler) as object;
}
return bindLayerMethods(proxiedObj);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy({}, proxyHandler) as any;
}
case 'stores':
case 'store': {
const proxyHandler = {
get: (_target: object, prop: string) => {
const proxiedObj = storesContainers[prop];
if (!proxiedObj) {
const proxyProxyHandler = {
get: (_target2: object, prop2: string) => {
const proxiedObj2 =
// @ts-expect-error any type needed
clientsContainers[prop][prop2];
if (!proxiedObj2) {
throw new Error(
`extension.import('client:${prop}') missing property '${prop2}'`,
);
}
return proxiedObj2;
},
};
return new Proxy({}, proxyProxyHandler) as object;
}
return bindLayerMethods(proxiedObj);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy({}, proxyHandler) as any;
}
case 'services':
case 'service': {
const proxyHandler = {
get: (_target: object, prop: string) => {
const proxiedObj = servicesContainers[prop];
if (!proxiedObj) {
const proxyProxyHandler = {
get: (_target2: object, prop2: string) => {
const proxiedObj2 =
// @ts-expect-error any type needed
clientsContainers[prop][prop2];
if (!proxiedObj2) {
throw new Error(
`extension.import('client:${prop}') missing property '${prop2}'`,
);
}
return proxiedObj2;
},
};
return new Proxy({}, proxyProxyHandler) as object;
}
return bindLayerMethods(proxiedObj);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy({}, proxyHandler) as any;
}
case 'controllers':
case 'controller': {
const proxyHandler = {
get: (_target: object, prop: string) => {
const proxiedObj = controllersContainers[prop];
if (!proxiedObj) {
const proxyProxyHandler = {
get: (_target2: object, prop2: string) => {
const proxiedObj2 =
// @ts-expect-error any type needed
clientsContainers[prop][prop2];
if (!proxiedObj2) {
throw new Error(
`extension.import('client:${prop}') missing property '${prop2}'`,
);
}
return proxiedObj2;
},
};
return new Proxy({}, proxyProxyHandler) as object;
}
return bindLayerMethods(proxiedObj);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy({}, proxyHandler) as any;
}
case 'drivers':
case 'driver': {
const proxyHandler = {
get: (_target: object, prop: string) => {
const proxiedObj = driversContainers[prop];
if (!proxiedObj) {
const proxyProxyHandler = {
get: (_target2: object, prop2: string) => {
const proxiedObj2 =
// @ts-expect-error any type needed
clientsContainers[prop][prop2];
if (!proxiedObj2) {
throw new Error(
`extension.import('client:${prop}') missing property '${prop2}'`,
);
}
return proxiedObj2;
},
};
return new Proxy({}, proxyProxyHandler) as object;
}
return bindLayerMethods(proxiedObj);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy({}, proxyHandler) as any;
}
default:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return undefined as any;
}
},
};