@celerity-sdk/core 0.2.0 → 0.3.0

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,
@@ -1171,6 +1172,11 @@ var HandlerRegistry = class {
1171
1172
  debug4("getHandler %s %s \u2192 %s", method, path, found ? "matched" : "not found");
1172
1173
  return found;
1173
1174
  }
1175
+ getHandlerById(id) {
1176
+ const found = this.handlers.find((h) => h.id !== void 0 && h.id === id);
1177
+ debug4("getHandlerById %s \u2192 %s", id, found ? "matched" : "not found");
1178
+ return found;
1179
+ }
1174
1180
  getAllHandlers() {
1175
1181
  return [
1176
1182
  ...this.handlers
@@ -1256,8 +1262,9 @@ var HandlerRegistry = class {
1256
1262
  layers.unshift(validate(schemas));
1257
1263
  }
1258
1264
  }
1259
- debug4("registerFunctionHandler: %s", meta.method && meta.path ? `${meta.method} ${meta.path}` : "(no route)");
1265
+ debug4("registerFunctionHandler: %s", definition.id ?? (meta.method && meta.path ? `${meta.method} ${meta.path}` : "(no route)"));
1260
1266
  this.handlers.push({
1267
+ id: definition.id,
1261
1268
  path: meta.path,
1262
1269
  method: meta.method,
1263
1270
  protectedBy: [],
@@ -1799,23 +1806,96 @@ function httpDelete(path, handlerOrOptions, maybeHandler) {
1799
1806
  }
1800
1807
  __name(httpDelete, "httpDelete");
1801
1808
 
1802
- // src/bootstrap/discovery.ts
1809
+ // src/handlers/module-resolver.ts
1803
1810
  var import_node_path = require("path");
1804
1811
  var import_debug9 = __toESM(require("debug"), 1);
1805
- 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");
1806
1886
  async function discoverModule(modulePath) {
1807
1887
  const resolved = modulePath ?? process.env.CELERITY_MODULE_PATH;
1808
1888
  if (!resolved) {
1809
1889
  throw new Error("Cannot discover module: set CELERITY_MODULE_PATH environment variable or pass modulePath");
1810
1890
  }
1811
- const absolutePath = (0, import_node_path.resolve)(resolved);
1812
- debug9("discoverModule: importing %s", absolutePath);
1891
+ const absolutePath = (0, import_node_path2.resolve)(resolved);
1892
+ debug10("discoverModule: importing %s", absolutePath);
1813
1893
  const imported = await import(absolutePath);
1814
1894
  const rootModule = imported.default ?? findModuleExport(imported);
1815
1895
  if (!rootModule || typeof rootModule !== "function") {
1816
1896
  throw new Error(`No module class found in "${resolved}"`);
1817
1897
  }
1818
- debug9("discoverModule: found %s", rootModule.name);
1898
+ debug10("discoverModule: found %s", rootModule.name);
1819
1899
  return rootModule;
1820
1900
  }
1821
1901
  __name(discoverModule, "discoverModule");
@@ -1871,24 +1951,37 @@ function mapToRuntimeResponse(response) {
1871
1951
  __name(mapToRuntimeResponse, "mapToRuntimeResponse");
1872
1952
 
1873
1953
  // src/bootstrap/runtime-entry.ts
1954
+ var import_node_path3 = require("path");
1874
1955
  async function bootstrapForRuntime(modulePath, systemLayers) {
1875
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();
1876
1959
  const rootModule = await discoverModule(modulePath);
1877
1960
  const { container, registry } = await bootstrap(rootModule);
1961
+ function buildCallback(handler) {
1962
+ if (!handler) return null;
1963
+ return async (_err, request) => {
1964
+ const httpRequest = mapRuntimeRequest(request);
1965
+ const httpResponse = await executeHandlerPipeline(handler, httpRequest, {
1966
+ container,
1967
+ systemLayers: layers
1968
+ });
1969
+ return mapToRuntimeResponse(httpResponse);
1970
+ };
1971
+ }
1972
+ __name(buildCallback, "buildCallback");
1878
1973
  return {
1879
1974
  registry,
1880
1975
  container,
1881
1976
  createRouteCallback(path, method) {
1882
- const handler = registry.getHandler(path, method);
1883
- if (!handler) return null;
1884
- return async (_err, request) => {
1885
- const httpRequest = mapRuntimeRequest(request);
1886
- const httpResponse = await executeHandlerPipeline(handler, httpRequest, {
1887
- container,
1888
- systemLayers: layers
1889
- });
1890
- return mapToRuntimeResponse(httpResponse);
1891
- };
1977
+ return buildCallback(registry.getHandler(path, method));
1978
+ },
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;
1892
1985
  }
1893
1986
  };
1894
1987
  }
@@ -1903,7 +1996,7 @@ async function startRuntime(options) {
1903
1996
  const appConfig = app.setup();
1904
1997
  const result = await bootstrapForRuntime();
1905
1998
  for (const def of appConfig.api?.http?.handlers ?? []) {
1906
- const callback = result.createRouteCallback(def.path, def.method);
1999
+ const callback = result.createRouteCallback(def.path, def.method) ?? await result.createRouteCallbackById(def.handler, def.location);
1907
2000
  if (callback) {
1908
2001
  app.registerHttpHandler(def.path, def.method, def.timeout, callback);
1909
2002
  }
@@ -1994,6 +2087,7 @@ __name(startRuntime, "startRuntime");
1994
2087
  mapToRuntimeResponse,
1995
2088
  mockRequest,
1996
2089
  registerModuleGraph,
2090
+ resolveHandlerByModuleRef,
1997
2091
  runLayerPipeline,
1998
2092
  startRuntime,
1999
2093
  tokenToString,