@celerity-sdk/core 0.2.1 → 0.3.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @celerity-sdk/core
2
2
 
3
- Core SDK for building Celerity applications decorators, dependency injection, layers, guards, handler adapters, and the application factory.
3
+ Core SDK for building Celerity applications - decorators, dependency injection, layers, guards, handler adapters, and the application factory.
4
4
 
5
5
  ## Installation
6
6
 
@@ -101,12 +101,27 @@ class OrdersHandler {
101
101
  import { CelerityFactory } from "@celerity-sdk/core";
102
102
 
103
103
  const app = await CelerityFactory.create(AppModule);
104
- // Auto-detects platform from CELERITY_RUNTIME_PLATFORM env var
104
+ // Auto-detects platform from CELERITY_PLATFORM env var
105
105
  ```
106
106
 
107
+ ## Handler Resolution
108
+
109
+ When a handler is invoked by ID (e.g. from a blueprint's `spec.handler` field), the SDK resolves it using a multi-step strategy:
110
+
111
+ 1. **Direct registry ID match** - looks up the handler by its explicit `id` (set via decorator or `createHttpHandler`).
112
+ 2. **Module resolution fallback** - if the direct lookup fails, the ID is treated as a module reference and dynamically imported:
113
+ - `"handlers.hello"` - named export `hello` from module `handlers`
114
+ - `"handlers"` - default export from module `handlers`
115
+ - `"app.module"` - tries named export split first (`module: "app"`, `export: "module"`), then falls back to default export from module `app.module`
116
+ 3. **Path/method routing** - if both ID-based lookups fail, falls back to matching the incoming request's HTTP method and path against the registry.
117
+
118
+ Module resolution matches the imported function against the registry by reference (`===`). Once matched, the handler ID is assigned and subsequent invocations use the direct lookup without repeated imports.
119
+
120
+ This is primarily relevant for blueprint-first function handlers where `spec.handler` references like `"handlers.hello"` map to exported functions that have no routing information in code.
121
+
107
122
  ## Guards
108
123
 
109
- Guards are declarative they annotate handlers with protection requirements but do not execute in the Node.js process. Guard enforcement happens at the Rust runtime layer (containers) or API Gateway (serverless).
124
+ Guards are declarative. They annotate handlers with protection requirements but do not execute in the Node.js process. Guard enforcement happens at the Rust runtime layer (containers) or API Gateway (serverless).
110
125
 
111
126
  ## Testing
112
127
 
@@ -117,6 +132,18 @@ const app = new TestingApplication(AppModule);
117
132
  const response = await app.handle(mockRequest({ method: "GET", path: "/orders/1" }));
118
133
  ```
119
134
 
135
+ ## Advanced Exports
136
+
137
+ The following exports are available for adapter authors building custom serverless or runtime adapters:
138
+
139
+ | Export | Purpose |
140
+ |---|---|
141
+ | `resolveHandlerByModuleRef(id, registry, baseDir)` | Resolve a handler ID as a module reference via dynamic import |
142
+ | `executeHandlerPipeline(handler, request, options)` | Execute the full layer + handler pipeline |
143
+ | `HandlerRegistry` | Handler registry class with route and ID-based lookups |
144
+ | `bootstrapForRuntime(modulePath?, systemLayers?)` | Bootstrap for the Celerity runtime host |
145
+ | `mapRuntimeRequest` / `mapToRuntimeResponse` | Runtime request/response mappers |
146
+
120
147
  ## Part of the Celerity Framework
121
148
 
122
149
  See [celerityframework.io](https://celerityframework.io) for full documentation.
package/dist/index.cjs CHANGED
@@ -112,6 +112,7 @@ __export(index_exports, {
112
112
  mapToRuntimeResponse: () => mapToRuntimeResponse,
113
113
  mockRequest: () => mockRequest,
114
114
  registerModuleGraph: () => registerModuleGraph,
115
+ resolveHandlerByModuleRef: () => resolveHandlerByModuleRef,
115
116
  runLayerPipeline: () => runLayerPipeline,
116
117
  startRuntime: () => startRuntime,
117
118
  tokenToString: () => tokenToString,
@@ -1805,23 +1806,96 @@ function httpDelete(path, handlerOrOptions, maybeHandler) {
1805
1806
  }
1806
1807
  __name(httpDelete, "httpDelete");
1807
1808
 
1808
- // src/bootstrap/discovery.ts
1809
+ // src/handlers/module-resolver.ts
1809
1810
  var import_node_path = require("path");
1810
1811
  var import_debug9 = __toESM(require("debug"), 1);
1811
- var debug9 = (0, import_debug9.default)("celerity:core:bootstrap");
1812
+ var debug9 = (0, import_debug9.default)("celerity:core:module-resolver");
1813
+ async function resolveHandlerByModuleRef(handlerId, registry, baseDir) {
1814
+ const lastDot = handlerId.lastIndexOf(".");
1815
+ if (lastDot > 0) {
1816
+ const moduleName = handlerId.slice(0, lastDot);
1817
+ const exportName = handlerId.slice(lastDot + 1);
1818
+ const result = await tryResolveExport(baseDir, moduleName, exportName, handlerId, registry);
1819
+ if (result) return result;
1820
+ }
1821
+ return tryResolveExport(baseDir, handlerId, "default", handlerId, registry);
1822
+ }
1823
+ __name(resolveHandlerByModuleRef, "resolveHandlerByModuleRef");
1824
+ async function tryResolveExport(baseDir, moduleName, exportName, handlerId, registry) {
1825
+ const handlerModulePath = (0, import_node_path.resolve)(baseDir, moduleName);
1826
+ let mod;
1827
+ try {
1828
+ mod = await import(handlerModulePath);
1829
+ } catch {
1830
+ try {
1831
+ mod = await import(`${handlerModulePath}.js`);
1832
+ } catch {
1833
+ return null;
1834
+ }
1835
+ }
1836
+ const exported = mod[exportName];
1837
+ if (!exported) return null;
1838
+ const isFnDef = typeof exported === "object" && exported !== null && exported.__celerity_handler;
1839
+ const handlerFn = isFnDef ? exported.handler : exported;
1840
+ if (typeof handlerFn !== "function") return null;
1841
+ const match = registry.getAllHandlers().find((h) => h.handlerFn === handlerFn);
1842
+ if (match) {
1843
+ match.id = handlerId;
1844
+ debug9("matched '%s' to registry handler", handlerId);
1845
+ return match;
1846
+ }
1847
+ debug9("'%s' not in registry, wrapping directly", handlerId);
1848
+ return buildResolvedFromExport(handlerId, handlerFn, isFnDef ? exported : null);
1849
+ }
1850
+ __name(tryResolveExport, "tryResolveExport");
1851
+ function buildResolvedFromExport(handlerId, handlerFn, fnDef) {
1852
+ if (fnDef) {
1853
+ const meta = fnDef.metadata;
1854
+ return {
1855
+ id: handlerId,
1856
+ protectedBy: [],
1857
+ layers: [
1858
+ ...meta.layers ?? []
1859
+ ],
1860
+ isPublic: false,
1861
+ paramMetadata: [],
1862
+ customMetadata: meta.customMetadata ?? {},
1863
+ handlerFn,
1864
+ isFunctionHandler: true,
1865
+ injectTokens: meta.inject ?? []
1866
+ };
1867
+ }
1868
+ return {
1869
+ id: handlerId,
1870
+ protectedBy: [],
1871
+ layers: [],
1872
+ isPublic: false,
1873
+ paramMetadata: [],
1874
+ customMetadata: {},
1875
+ handlerFn,
1876
+ isFunctionHandler: true,
1877
+ injectTokens: []
1878
+ };
1879
+ }
1880
+ __name(buildResolvedFromExport, "buildResolvedFromExport");
1881
+
1882
+ // src/bootstrap/discovery.ts
1883
+ var import_node_path2 = require("path");
1884
+ var import_debug10 = __toESM(require("debug"), 1);
1885
+ var debug10 = (0, import_debug10.default)("celerity:core:bootstrap");
1812
1886
  async function discoverModule(modulePath) {
1813
1887
  const resolved = modulePath ?? process.env.CELERITY_MODULE_PATH;
1814
1888
  if (!resolved) {
1815
1889
  throw new Error("Cannot discover module: set CELERITY_MODULE_PATH environment variable or pass modulePath");
1816
1890
  }
1817
- const absolutePath = (0, import_node_path.resolve)(resolved);
1818
- debug9("discoverModule: importing %s", absolutePath);
1891
+ const absolutePath = (0, import_node_path2.resolve)(resolved);
1892
+ debug10("discoverModule: importing %s", absolutePath);
1819
1893
  const imported = await import(absolutePath);
1820
1894
  const rootModule = imported.default ?? findModuleExport(imported);
1821
1895
  if (!rootModule || typeof rootModule !== "function") {
1822
1896
  throw new Error(`No module class found in "${resolved}"`);
1823
1897
  }
1824
- debug9("discoverModule: found %s", rootModule.name);
1898
+ debug10("discoverModule: found %s", rootModule.name);
1825
1899
  return rootModule;
1826
1900
  }
1827
1901
  __name(discoverModule, "discoverModule");
@@ -1877,8 +1951,11 @@ function mapToRuntimeResponse(response) {
1877
1951
  __name(mapToRuntimeResponse, "mapToRuntimeResponse");
1878
1952
 
1879
1953
  // src/bootstrap/runtime-entry.ts
1954
+ var import_node_path3 = require("path");
1880
1955
  async function bootstrapForRuntime(modulePath, systemLayers) {
1881
1956
  const layers = systemLayers ?? await createDefaultSystemLayers();
1957
+ const resolvedModulePath = modulePath ?? process.env.CELERITY_MODULE_PATH;
1958
+ const moduleDir = resolvedModulePath ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(resolvedModulePath)) : process.cwd();
1882
1959
  const rootModule = await discoverModule(modulePath);
1883
1960
  const { container, registry } = await bootstrap(rootModule);
1884
1961
  function buildCallback(handler) {
@@ -1899,8 +1976,12 @@ async function bootstrapForRuntime(modulePath, systemLayers) {
1899
1976
  createRouteCallback(path, method) {
1900
1977
  return buildCallback(registry.getHandler(path, method));
1901
1978
  },
1902
- createRouteCallbackById(handlerId) {
1903
- return buildCallback(registry.getHandlerById(handlerId));
1979
+ async createRouteCallbackById(handlerId, codeLocation) {
1980
+ const fromRegistry = registry.getHandlerById(handlerId);
1981
+ if (fromRegistry) return buildCallback(fromRegistry);
1982
+ const baseDir = codeLocation ? (0, import_node_path3.resolve)(codeLocation) : moduleDir;
1983
+ const resolved = await resolveHandlerByModuleRef(handlerId, registry, baseDir);
1984
+ return resolved ? buildCallback(resolved) : null;
1904
1985
  }
1905
1986
  };
1906
1987
  }
@@ -1915,7 +1996,7 @@ async function startRuntime(options) {
1915
1996
  const appConfig = app.setup();
1916
1997
  const result = await bootstrapForRuntime();
1917
1998
  for (const def of appConfig.api?.http?.handlers ?? []) {
1918
- const callback = result.createRouteCallback(def.path, def.method) ?? result.createRouteCallbackById(def.handler);
1999
+ const callback = result.createRouteCallback(def.path, def.method) ?? await result.createRouteCallbackById(def.handler, def.location);
1919
2000
  if (callback) {
1920
2001
  app.registerHttpHandler(def.path, def.method, def.timeout, callback);
1921
2002
  }
@@ -2006,6 +2087,7 @@ __name(startRuntime, "startRuntime");
2006
2087
  mapToRuntimeResponse,
2007
2088
  mockRequest,
2008
2089
  registerModuleGraph,
2090
+ resolveHandlerByModuleRef,
2009
2091
  runLayerPipeline,
2010
2092
  startRuntime,
2011
2093
  tokenToString,