@owlmeans/server-api 0.1.18-rc.4 → 0.1.18-rc.41

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.
Files changed (58) hide show
  1. package/README.md +202 -46
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-api/SKILL.md +104 -22
  4. package/build/errors.d.ts +12 -0
  5. package/build/errors.d.ts.map +1 -1
  6. package/build/errors.js +16 -0
  7. package/build/errors.js.map +1 -1
  8. package/build/helper.d.ts.map +1 -1
  9. package/build/helper.js +10 -1
  10. package/build/helper.js.map +1 -1
  11. package/build/hold.d.ts +52 -0
  12. package/build/hold.d.ts.map +1 -0
  13. package/build/hold.js +56 -0
  14. package/build/hold.js.map +1 -0
  15. package/build/index.d.ts +2 -0
  16. package/build/index.d.ts.map +1 -1
  17. package/build/index.js +2 -0
  18. package/build/index.js.map +1 -1
  19. package/build/protocol.d.ts +45 -0
  20. package/build/protocol.d.ts.map +1 -0
  21. package/build/protocol.js +86 -0
  22. package/build/protocol.js.map +1 -0
  23. package/build/server.d.ts.map +1 -1
  24. package/build/server.js +9 -13
  25. package/build/server.js.map +1 -1
  26. package/build/types.d.ts +10 -2
  27. package/build/types.d.ts.map +1 -1
  28. package/build/utils/error.d.ts +64 -1
  29. package/build/utils/error.d.ts.map +1 -1
  30. package/build/utils/error.js +160 -8
  31. package/build/utils/error.js.map +1 -1
  32. package/build/utils/guards.d.ts.map +1 -1
  33. package/build/utils/guards.js +11 -15
  34. package/build/utils/guards.js.map +1 -1
  35. package/build/utils/payload.d.ts.map +1 -1
  36. package/build/utils/payload.js +6 -2
  37. package/build/utils/payload.js.map +1 -1
  38. package/build/utils/server.d.ts +3 -2
  39. package/build/utils/server.d.ts.map +1 -1
  40. package/build/utils/server.js +5 -6
  41. package/build/utils/server.js.map +1 -1
  42. package/package.json +13 -11
  43. package/src/errors.ts +18 -0
  44. package/src/helper.ts +10 -1
  45. package/src/hold.ts +84 -0
  46. package/src/index.ts +2 -0
  47. package/src/protocol.ts +185 -0
  48. package/src/server.ts +36 -45
  49. package/src/types.ts +15 -4
  50. package/src/utils/error.ts +198 -10
  51. package/src/utils/guards.ts +10 -17
  52. package/src/utils/payload.ts +6 -2
  53. package/src/utils/server.ts +8 -8
  54. package/tests/error-status.spec.ts +290 -0
  55. package/tests/handlers.spec.ts +83 -0
  56. package/tests/tsconfig.json +12 -0
  57. package/tsconfig.json +1 -0
  58. package/build/.gitkeep +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEjF,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAY,EAAE,KAAmB,EAAE,EAAE;IAC/D,IAAI,IAAI,GAAW,YAAY,CAAA;IAC/B,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,IAAI,GAAG,kBAAkB,CAAA;IAC3B,CAAC;SAAM,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;QACxC,IAAI,GAAG,eAAe,CAAA;IACxB,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CACnB,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,KAAc,CAAC,CAAC,CAAC,OAAO,CACtE,CAAA;IACH,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../src/utils/guards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAGnE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAK3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,SAAS,GAAU,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAC3D,CAAC,UAAU,gBAAgB,CAAC,cAAc,CAAC,OAC/C,cAAc,SAAS,YAAY,KACvC,OAAO,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC,CA0D/C,CAAA"}
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../src/utils/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAGnE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAM3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,SAAS,GAAU,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAC3D,CAAC,UAAU,gBAAgB,CAAC,cAAc,CAAC,OAC/C,cAAc,SAAS,YAAY,KACvC,OAAO,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAmD/C,CAAA"}
@@ -1,7 +1,7 @@
1
- import { isContextWithoutIds, Layer } from '@owlmeans/context';
2
1
  import { provideResponse } from '@owlmeans/entrypoint';
3
2
  import { executeResponse, provideRequest } from './payload.js';
4
3
  import { AuthFailedError } from '../errors.js';
4
+ import { attachEntity } from '@owlmeans/auth-common';
5
5
  export const authorize = async (context, module, req, reply) => {
6
6
  const guards = module.getGuards();
7
7
  if (guards.length > 0) {
@@ -34,20 +34,16 @@ export const authorize = async (context, module, req, reply) => {
34
34
  if (request.auth != null) {
35
35
  req._auth = request.auth;
36
36
  }
37
- if (request.auth?.entityId != null) {
38
- // @TODO Probably we need to downgrade context in this case
39
- if (!isContextWithoutIds(context)) {
40
- throw SyntaxError(`Context should be without ids during authorization ${context.cfg.layer}:${context.cfg.layerId}`);
41
- }
42
- if (isContextWithoutIds(context) && context.cfg.layer !== Layer.Service) {
43
- context = await context.updateContext(undefined, Layer.Service);
44
- await context.waitForInitialized();
45
- }
46
- context = await context.updateContext(request.auth.entityId, Layer.Entity);
47
- await context.waitForInitialized();
48
- // We elevate module to the context level if it was changed
49
- module = context.entrypoint(module.alias);
50
- await module.resolve();
37
+ // The token names the organization by slug; everything downstream — grants, records, minted
38
+ // infrastructure names — keys on the entity's stable id. Resolving here, once, is what keeps a
39
+ // rename from being a sweep of every handler: the id arrives on the request, and a slug that
40
+ // has since been retired still resolves to the entity that retired it.
41
+ const entity = await attachEntity(context, request);
42
+ if (entity != null) {
43
+ // Carried on the raw request the same way `_auth` is, so the request the handler is given
44
+ // later — a different object, built after this runs — sees the same resolution.
45
+ ;
46
+ req._entity = entity;
51
47
  }
52
48
  }
53
49
  // Update context in request object
@@ -1 +1 @@
1
- {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/utils/guards.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/utils/guards.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAKpD,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAU,EAAE,MAAwC,EACpD,GAAmB,EAAE,KAAmB,EACQ,EAAE;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAA;IACjC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;QACvC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAEjD,IAAI,KAAK,GAA6B,SAAS,CAAA;QAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAiB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YACnD,IAAI,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC1C,KAAK,GAAG,MAAM,CAAA;YAChB,CAAC;YACD,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;YACtC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,MAAK;YACP,CAAC;QACH,CAAC;QAED,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,eAAe,EAAE,CAAA;QAC7B,CAAC;QAED,MAAM,YAAY,GAAG,eAAe,CAAO,KAAK,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,CAAU,OAAO,EAAE,YAAY,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACxC,CAAC;QACD,eAAe,CAAC,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QAC1C,6EAA6E;QAC7E,oCAAoC;QACpC,6GAA6G;QAC7G,IAAI;QACJ,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC;QAClC,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,GAAW,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAA;QACnC,CAAC;QAED,4FAA4F;QAC5F,+FAA+F;QAC/F,6FAA6F;QAC7F,uEAAuE;QACvE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACnD,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,0FAA0F;YAC1F,gFAAgF;YAChF,CAAC;YAAC,GAAW,CAAC,OAAO,GAAG,MAAM,CAAA;QAChC,CAAC;IACH,CAAC;IACD,mCAAmC;IAClC,GAAW,CAAC,IAAI,GAAG,OAAO,CAAA;IAE3B,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAC1B,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAE1C,eAAO,MAAM,cAAc,UAAW,MAAM,OAAO,OAAO,cAAc,OAAO,KAAG,eAYjF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,SAAS,QAAQ,iBAAiB,OAAO,KAAG,OAuB3G,CAAA;AAGD,eAAO,MAAM,cAAc,WAAY,eAAe,oBAgBrD,CAAA"}
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAG1C,eAAO,MAAM,cAAc,UAAW,MAAM,OAAO,OAAO,cAAc,OAAO,KAAG,eAejF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,SAAS,QAAQ,iBAAiB,OAAO,KAAG,OAuB3G,CAAA;AAGD,eAAO,MAAM,cAAc,WAAY,eAAe,oBAgBrD,CAAA"}
@@ -1,10 +1,14 @@
1
1
  import { EntrypointOutcome } from '@owlmeans/entrypoint';
2
- import { ACCEPTED, CREATED, OK, SERVER_ERROR } from '@owlmeans/api';
2
+ import { ACCEPTED, CREATED, OK } from '@owlmeans/api';
3
+ import { handleError } from './error.js';
3
4
  export const provideRequest = (alias, req, provision) => {
4
5
  provision = provision ?? false;
5
6
  return {
6
7
  alias,
7
8
  auth: req._auth ?? undefined,
9
+ // Resolved once by `authorize` and carried by reference, exactly like `_auth`, so a handler
10
+ // reached through any path sees the same entity the guard admitted.
11
+ entity: req._entity ?? undefined,
8
12
  params: req.params,
9
13
  body: req.body,
10
14
  headers: req.headers,
@@ -27,7 +31,7 @@ export const executeResponse = (response, reply, throwOnError) => {
27
31
  if (throwOnError ?? false) {
28
32
  throw response.error;
29
33
  }
30
- reply.code(SERVER_ERROR).send(response.error.message);
34
+ handleError(response.error, reply);
31
35
  return true;
32
36
  }
33
37
  else if (response.outcome != null) {
@@ -1 +1 @@
1
- {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAGnE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,SAAmB,EAAmB,EAAE;IAClG,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;IAC9B,OAAO;QACL,KAAK;QACL,IAAI,EAAG,GAAW,CAAC,KAAK,IAAI,SAAS;QACrC,MAAM,EAAE,GAAG,CAAC,MAA4D;QACxE,IAAI,EAAE,GAAG,CAAC,IAA2B;QACrC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,KAA2D;QACtE,IAAI,EAAE,GAAG,CAAC,GAAG;QACb,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KACtC,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAI,QAA6B,EAAE,KAAe,EAAE,YAAsB,EAAW,EAAE;IACpH,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC,KAAK,CAAA;QACtB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrD,OAAO,IAAI,CAAA;IACb,CAAC;SAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;QACpC,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;YACzB,KAAK,iBAAiB,CAAC,QAAQ;gBAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACzC,MAAK;YACP,KAAK,iBAAiB,CAAC,OAAO;gBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACxC,MAAK;YACP,KAAK,iBAAiB,CAAC,EAAE,CAAC;YAC1B;gBACE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAGD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAuB,EAAE,EAAE;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAA;YACtB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;YAC3B,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,MAAM,CAAC,QAAQ,CAAA;YACxB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CACpC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,KAAwB,CAAC,CAAC,CAAC,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import type { FastifyReply, FastifyRequest } from 'fastify';
2
2
  import type { ServerEntrypoint } from '@owlmeans/server-entrypoint';
3
3
  import type { CommonEntrypoint } from '@owlmeans/entrypoint';
4
- import type { ServerContext, ServerConfig } from '@owlmeans/server-context';
5
- type Config = ServerConfig;
4
+ import type { ServerContext } from '@owlmeans/server-context';
5
+ import type { Config as ApiConfig } from '../types.js';
6
+ type Config = ApiConfig;
6
7
  type Context = ServerContext<Config>;
7
8
  export declare const canServeModule: (context: Context, module: CommonEntrypoint) => module is ServerEntrypoint<unknown>;
8
9
  export declare const createServerHandler: (module: ServerEntrypoint<FastifyRequest>, location: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAQ3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,KAAK,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;AAEpC,eAAO,MAAM,cAAc,YAAa,OAAO,UAAU,gBAAgB,KAAG,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAY7G,CAAA;AAED,eAAO,MAAM,mBAAmB,WAAY,gBAAgB,CAAC,cAAc,CAAC,YAAY,MAAM,WAChF,cAAc,SAAS,YAAY,kBA0C9C,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAO7D,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtD,KAAK,MAAM,GAAG,SAAS,CAAA;AACvB,KAAK,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;AAEpC,eAAO,MAAM,cAAc,YAAa,OAAO,UAAU,gBAAgB,KAAG,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAc7G,CAAA;AAED,eAAO,MAAM,mBAAmB,WAAY,gBAAgB,CAAC,cAAc,CAAC,YAAY,MAAM,WAChF,cAAc,SAAS,YAAY,kBAuC9C,CAAA"}
@@ -3,7 +3,7 @@ import { assertContext } from '@owlmeans/context';
3
3
  import { provideResponse } from '@owlmeans/entrypoint';
4
4
  import { ResilientError } from '@owlmeans/error';
5
5
  import { OK } from '@owlmeans/api';
6
- import { handleError } from './error.js';
6
+ import { errorExposure, handleError } from './error.js';
7
7
  import { executeResponse, provideRequest } from './payload.js';
8
8
  import { authorize } from './guards.js';
9
9
  import { RouteProtocols } from '@owlmeans/route';
@@ -14,7 +14,9 @@ export const canServeModule = (context, module) => {
14
14
  if (module.route.route.service != null && module.route.route.service !== context.cfg.service) {
15
15
  return false;
16
16
  }
17
- if (module.route.route.protocol === RouteProtocols.SOCKET) {
17
+ // HTTP serves only its own protocol. Custom transports are handled by their owning packages.
18
+ if (module.route.route.protocol != null
19
+ && module.route.route.protocol !== RouteProtocols.WEB) {
18
20
  return false;
19
21
  }
20
22
  return 'isIntermediate' in module.route;
@@ -48,15 +50,12 @@ export const createServerHandler = (module, location) => async (req, reply) => {
48
50
  }
49
51
  }
50
52
  catch (error) {
51
- console.error(`Error in ${module.alias} (${location})`);
52
- console.error(JSON.stringify(error, null, 2));
53
- console.error(error);
54
53
  if (module.fixer != null) {
55
54
  const fixer = context.service(module.fixer);
56
55
  fixer.handle(reply, ResilientError.ensure(error));
57
56
  return;
58
57
  }
59
- handleError(error, reply);
58
+ handleError(error, reply, errorExposure(context.cfg));
60
59
  }
61
60
  };
62
61
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/server-api",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 264 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -32,15 +33,15 @@
32
33
  "@fastify/helmet": "^13.1.0",
33
34
  "@fastify/middie": "^9.0.2",
34
35
  "@fastify/multipart": "^9.0.1",
35
- "@owlmeans/api": "^0.1.18-rc.4",
36
- "@owlmeans/auth": "^0.1.18-rc.4",
37
- "@owlmeans/auth-common": "^0.1.18-rc.4",
38
- "@owlmeans/context": "^0.1.18-rc.4",
39
- "@owlmeans/error": "^0.1.18-rc.4",
40
- "@owlmeans/entrypoint": "^0.1.18-rc.4",
41
- "@owlmeans/route": "^0.1.18-rc.4",
42
- "@owlmeans/server-context": "^0.1.18-rc.4",
43
- "@owlmeans/server-entrypoint": "^0.1.18-rc.4",
36
+ "@owlmeans/api": "^0.1.18-rc.37",
37
+ "@owlmeans/auth": "^0.1.18-rc.35",
38
+ "@owlmeans/auth-common": "^0.1.18-rc.37",
39
+ "@owlmeans/context": "^0.1.18-rc.32",
40
+ "@owlmeans/error": "^0.1.18-rc.35",
41
+ "@owlmeans/entrypoint": "^0.1.18-rc.36",
42
+ "@owlmeans/route": "^0.1.18-rc.33",
43
+ "@owlmeans/server-context": "^0.1.18-rc.36",
44
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.35",
44
45
  "ajv-errors": "^3.0.0",
45
46
  "fastify": "^5.0.0",
46
47
  "fastify-raw-body": "^5.0.0",
@@ -52,6 +53,7 @@
52
53
  },
53
54
  "devDependencies": {
54
55
  "@owlmeans/dep-config": "workspace:*",
56
+ "@types/bun": "^1.4.0",
55
57
  "@types/node": "^26.1.0",
56
58
  "nodemon": "^3.1.14",
57
59
  "typescript": "^7.0.2"
package/src/errors.ts CHANGED
@@ -29,7 +29,25 @@ export class NoFileError extends ApiError {
29
29
  }
30
30
  }
31
31
 
32
+ /**
33
+ * `handlers().body/params/request` was handed something it cannot serve — not a plain callback,
34
+ * and not a `BoundEntrypointHandler` bound to the SAME protocol it is being wrapped for. Thrown
35
+ * from inside the bound handler's own request path, so the module still loads and every OTHER
36
+ * route on it still answers; only the misconfigured one reports why, as a 500 rather than the
37
+ * opaque `TypeError: handler is not a function` a double wrap used to fail with. See
38
+ * `handlers()` in `protocol.ts` for what does and does not reach here.
39
+ */
40
+ export class HandlerMisconfiguredError extends ApiError {
41
+ public static override typeName = 'HandlerMisconfiguredError'
42
+
43
+ constructor(message: string = 'error') {
44
+ super(`handler:misconfigured:${message}`)
45
+ this.type = HandlerMisconfiguredError.typeName
46
+ }
47
+ }
48
+
32
49
  ResilientError.registerErrorClass(AuthFailedError)
33
50
  ResilientError.registerErrorClass(AccessError)
34
51
  ResilientError.registerErrorClass(NoFileError)
52
+ ResilientError.registerErrorClass(HandlerMisconfiguredError)
35
53
 
package/src/helper.ts CHANGED
@@ -8,8 +8,17 @@ import type { Config, Context } from './types.js'
8
8
  import type { FastifyRequest } from 'fastify'
9
9
  import type { MultipartFile } from '@fastify/multipart'
10
10
 
11
+ /**
12
+ * The context a handler runs against.
13
+ *
14
+ * The HTTP boundary hangs the request-scoped context on the raw Fastify request, so a handler
15
+ * reached that way gets it from there. A request that did not come from Fastify has no `original`
16
+ * at all — a queued call is rebuilt from an envelope, and a socket frame carries its own — so this
17
+ * has to fall back rather than dereference: these handlers are the same functions on every
18
+ * transport, and only the way they were reached differs.
19
+ */
11
20
  const _castContextFromOriginal = <C extends BasicConfig, T extends BasicContext<C> = BasicContext<C>>(req: AbstractRequest, def: T): T => {
12
- return req.original._ctx ?? def
21
+ return req.original?._ctx ?? def
13
22
  }
14
23
 
15
24
  export const handleBody: <T>(
package/src/hold.ts ADDED
@@ -0,0 +1,84 @@
1
+ import Fastify from 'fastify'
2
+ import type { ServerConfig } from '@owlmeans/server-context'
3
+ import { CLOSED_HOST, OPENED_HOST, PORT } from './consts.js'
4
+
5
+ export interface ApiPortHoldOptions {
6
+ /**
7
+ * The one path answered 200 while the hold is in place — the app's health endpoint, so
8
+ * whatever supervises the process keeps a conventional liveness answer through the boot.
9
+ * Everything else answers 503 with the same body. Absent, everything is 503.
10
+ */
11
+ okPath?: string
12
+ /** Body of every answer, evaluated per request so a changing boot phase is reported live. */
13
+ payload?: () => unknown
14
+ }
15
+
16
+ export interface ApiPortHold {
17
+ /**
18
+ * Free the port for the real server. Awaits the actual close — `listen()` follows immediately,
19
+ * and a predecessor still bound makes it throw — and force-closes keep-alive sockets, so a
20
+ * supervisor's own poller cannot hold the release open.
21
+ */
22
+ release: () => Promise<void>
23
+ }
24
+
25
+ /**
26
+ * Own the app's port while the context initializes, and answer for the app until `listen()`
27
+ * takes over.
28
+ *
29
+ * The API server itself cannot do this: it builds its route table from the entrypoints the
30
+ * context knows about, so it can only bind AFTER `init()` resolves — and Fastify refuses new
31
+ * routes once it is listening. Without a hold, the whole boot window, and every way a boot can
32
+ * fail, leaves nothing on the port; the edge then answers a bare upstream connect error that
33
+ * names neither the app nor the reason, which is indistinguishable from a crashed pod or an app
34
+ * that was never installed.
35
+ *
36
+ * So the boot sequence an app wants is:
37
+ *
38
+ * const hold = await holdApiPort(cfg, { okPath, payload }) // bind FIRST
39
+ * try { ...initialize... } catch { record the failure; return } // hold keeps answering
40
+ * await hold.release()
41
+ * await context.getApiServer().listen()
42
+ *
43
+ * Everything that is not the health path answers **503, not 404**: the routes the app really
44
+ * serves do not exist yet, and "no such route" would be a lie that outlives the boot.
45
+ *
46
+ * The port and host are resolved exactly as `listen()` resolves them — from the service's own
47
+ * declaration in `cfg.services[cfg.service]` — so the hold and the server can never disagree
48
+ * about which socket they are trading.
49
+ *
50
+ * A bind failure here (EADDRINUSE above all) throws to the caller and must be treated as fatal:
51
+ * a process that continues past it ends the boot with nothing listening and nothing holding the
52
+ * event loop, i.e. a clean exit 0 while a predecessor keeps serving stale code. Name the error
53
+ * and exit non-zero — that is what makes a supervisor reclaim the port instead of respawning
54
+ * into it.
55
+ */
56
+ export const holdApiPort = async (
57
+ cfg: ServerConfig, opts: ApiPortHoldOptions = {}
58
+ ): Promise<ApiPortHold> => {
59
+ const config = cfg.services[cfg.service]
60
+ const port = config?.internalPort ?? config?.port ?? PORT
61
+ const host = config?.opened === true ? OPENED_HOST : CLOSED_HOST
62
+
63
+ const payload = opts.payload ?? (() => ({ status: 'OK' }))
64
+
65
+ // Quiet and disposable: no logger, no plugins, and connections are force-closed on release so
66
+ // the handover to the real server is bounded rather than waiting out a keep-alive.
67
+ const server = Fastify({ logger: false, forceCloseConnections: true })
68
+
69
+ if (opts.okPath != null) {
70
+ server.all(opts.okPath, async (_request, reply) => {
71
+ return await reply.code(200).send(payload())
72
+ })
73
+ }
74
+ server.setNotFoundHandler(async (_request, reply) => {
75
+ return await reply.code(503).send(payload())
76
+ })
77
+
78
+ await server.listen({ port, host })
79
+ console.log(`api-server: holding ${host}:${port} for the boot${opts.okPath != null ? ` (${opts.okPath})` : ''}`)
80
+
81
+ return {
82
+ release: async () => { await server.close() },
83
+ }
84
+ }
package/src/index.ts CHANGED
@@ -4,3 +4,5 @@ export * from './server.js'
4
4
  export * from './consts.js'
5
5
  export * from './errors.js'
6
6
  export * from './helper.js'
7
+ export * from './hold.js'
8
+ export * from './protocol.js'
@@ -0,0 +1,185 @@
1
+ import { assertContext } from '@owlmeans/context'
2
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
+ import { EntrypointOutcome } from '@owlmeans/entrypoint'
4
+ import type {
5
+ BodyOf, EntrypointProtocolDeclaration, HandlerRequest, OpenRequest, OpenValue, ParamsOf, RequestOf, ResponseOf,
6
+ } from '@owlmeans/entrypoint'
7
+ import type { AbstractRequest } from '@owlmeans/entrypoint'
8
+ import type { BoundEntrypointHandler } from '@owlmeans/server-entrypoint'
9
+ import { HandlerMisconfiguredError } from './errors.js'
10
+
11
+ /**
12
+ * True for a response the declaration left with no compile-time contract: `typed<any>()` (an
13
+ * `[any] extends [X]` conditional does not produce `any`'s usual both-branches union once the
14
+ * checked type is wrapped in a tuple, so it resolves to a single branch like any other type) and
15
+ * a response inferred as bare `typed()` with nothing to infer from, which resolves to `undefined`
16
+ * with no shape to validate a handler's return against — the same signal a zero-argument
17
+ * `contract()` produces for a genuinely response-less route. Neither means "this handler must
18
+ * return void": it means the declaration did not constrain the response, so the handler is free to
19
+ * return whatever its route actually sends.
20
+ */
21
+ type IsUntypedResponse<T> = 0 extends (1 & T) ? true : [T] extends [undefined] ? true : false
22
+
23
+ /**
24
+ * The type a bound handler is allowed to return for a given protocol response.
25
+ *
26
+ * A concretely typed or schema-backed `Response` is returned unchanged — the handler must produce
27
+ * exactly that shape. An untyped one (see `IsUntypedResponse` above) relaxes to `any`: the
28
+ * declaration made no promise about the response, so nothing here should force a handler bound to
29
+ * it into returning `void`.
30
+ */
31
+ type HandlerResponse<Response> = IsUntypedResponse<Response> extends true ? any : Response
32
+ type MaybePromise<Value> = Value | Promise<Value>
33
+ type BodyProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { body: OpenValue }
34
+ ? Protocol : never
35
+ type ParamsProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { params: object }
36
+ ? Protocol : never
37
+
38
+ interface ContextCarrier<Context> {
39
+ _ctx?: Context
40
+ }
41
+
42
+ const contextFor = <Context extends BasicContext<BasicConfig>>(
43
+ request: AbstractRequest,
44
+ fallback: Context,
45
+ ): Context => {
46
+ const original = request.original
47
+ if (original != null && typeof original === 'object' && '_ctx' in original) {
48
+ return (original as ContextCarrier<Context>)._ctx ?? fallback
49
+ }
50
+
51
+ return fallback
52
+ }
53
+
54
+ const bind = <Protocol extends EntrypointProtocolDeclaration, Context extends BasicContext<BasicConfig>>(
55
+ protocol: Protocol,
56
+ execute: (request: HandlerRequest<RequestOf<Protocol>>, context: Context) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>,
57
+ ): BoundEntrypointHandler<Protocol> => ({
58
+ protocol,
59
+ bind: ref => async (request, response) => {
60
+ const fallback = assertContext<BasicConfig, Context>(ref.ref?.ctx, protocol.alias)
61
+
62
+ try {
63
+ const typedRequest = (request as HandlerRequest<OpenRequest>) as HandlerRequest<RequestOf<Protocol>>
64
+ const value = await execute(
65
+ typedRequest,
66
+ contextFor(request, fallback),
67
+ )
68
+ response.resolve(value as ResponseOf<Protocol>, EntrypointOutcome.Ok)
69
+ } catch (error) {
70
+ response.reject(error as Error)
71
+ }
72
+
73
+ return response.value
74
+ },
75
+ })
76
+
77
+ /** Structural, not `instanceof`: a `BoundEntrypointHandler` crosses no class boundary a runtime check could use. */
78
+ type AnyBoundHandler = BoundEntrypointHandler<EntrypointProtocolDeclaration>
79
+
80
+ /**
81
+ * `value` is shaped like a `BoundEntrypointHandler` — never a plain function, which also carries
82
+ * its own `.bind` and would otherwise pass a `typeof value.bind === 'function'` check alone.
83
+ */
84
+ const isBoundHandler = (value: unknown): value is AnyBoundHandler =>
85
+ typeof value === 'object' && value != null
86
+ && typeof (value as { bind?: unknown }).bind === 'function'
87
+ && (value as { protocol?: unknown }).protocol != null
88
+
89
+ const warnedAliases = new Set<string>()
90
+
91
+ /**
92
+ * What `body`/`params`/`request` do with something that is not the plain callback they declare —
93
+ * the shape a handler module wraps its own export in when it is bound TWICE. `null` means the
94
+ * second argument really is a callback and the caller should wrap it as usual.
95
+ *
96
+ * A handler bound to THIS protocol is returned unchanged, with a one-time warning: the module
97
+ * already produced a working `BoundEntrypointHandler`, and re-wrapping it here would only wrap it
98
+ * again — returning it as-is is what makes `bind(protocol, api.body(protocol, alreadyBound))`
99
+ * behave exactly like `bind(protocol, alreadyBound)`, which is what a wrap-once file looks like at
100
+ * this call site. Anything else — a handler bound to a DIFFERENT protocol, or a value that is
101
+ * neither a function nor a bound handler at all — answers every request on this route with
102
+ * {@link HandlerMisconfiguredError} instead of throwing `TypeError: handler is not a function`
103
+ * out of the request pipeline's own `try`, where the type name and the alias it happened on are
104
+ * both lost.
105
+ */
106
+ const toleratedHandler = <Protocol extends EntrypointProtocolDeclaration>(
107
+ protocol: Protocol, handler: unknown
108
+ ): BoundEntrypointHandler<Protocol> | null => {
109
+ if (typeof handler === 'function') return null
110
+
111
+ if (isBoundHandler(handler)) {
112
+ if (handler.protocol.alias === protocol.alias) {
113
+ if (!warnedAliases.has(protocol.alias)) {
114
+ warnedAliases.add(protocol.alias)
115
+ console.warn(
116
+ `[server-api] ${protocol.alias}: handler is already bound — pass it to bind() directly; `
117
+ + 'wrapping it again here is a type error (TS2345 "BoundEntrypointHandler<…> is not '
118
+ + 'assignable"), tolerated at runtime for a project generated before the wrap-once rule.'
119
+ )
120
+ }
121
+
122
+ return handler as unknown as BoundEntrypointHandler<Protocol>
123
+ }
124
+
125
+ return bind<Protocol, BasicContext<BasicConfig>>(protocol, () => {
126
+ throw new HandlerMisconfiguredError(
127
+ `${protocol.alias}: handler is bound to a different protocol ('${handler.protocol.alias}')`
128
+ )
129
+ })
130
+ }
131
+
132
+ return bind<Protocol, BasicContext<BasicConfig>>(protocol, () => {
133
+ throw new HandlerMisconfiguredError(`${protocol.alias}: handler is not a function`)
134
+ })
135
+ }
136
+
137
+ /**
138
+ * Make server handlers that infer request sections and response values from their protocol.
139
+ * The protocol is passed once; application callbacks never name request or reply generics.
140
+ */
141
+ export const handlers = <Context extends BasicContext<BasicConfig>>() => ({
142
+ body: <Protocol extends EntrypointProtocolDeclaration>(
143
+ protocol: Protocol,
144
+ handler: Protocol extends BodyProtocol<Protocol> ? (
145
+ payload: BodyOf<Protocol>,
146
+ context: Context,
147
+ request: HandlerRequest<RequestOf<Protocol>>,
148
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>
149
+ : never,
150
+ ): BoundEntrypointHandler<Protocol> => toleratedHandler(protocol, handler) ?? bind<Protocol, Context>(protocol, (request, context) =>
151
+ (handler as (
152
+ payload: BodyOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>,
153
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>)(request.body as BodyOf<Protocol>, context, request)),
154
+
155
+ params: <Protocol extends EntrypointProtocolDeclaration>(
156
+ protocol: Protocol,
157
+ handler: Protocol extends ParamsProtocol<Protocol> ? (
158
+ payload: ParamsOf<Protocol>,
159
+ context: Context,
160
+ request: HandlerRequest<RequestOf<Protocol>>,
161
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>
162
+ : never,
163
+ ): BoundEntrypointHandler<Protocol> => toleratedHandler(protocol, handler) ?? bind<Protocol, Context>(protocol, (request, context) =>
164
+ (handler as (
165
+ payload: ParamsOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>,
166
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>)(request.params as ParamsOf<Protocol>, context, request)),
167
+
168
+ request: <Protocol extends EntrypointProtocolDeclaration>(
169
+ protocol: Protocol,
170
+ handler: (
171
+ request: HandlerRequest<RequestOf<Protocol>>,
172
+ context: Context,
173
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>,
174
+ ): BoundEntrypointHandler<Protocol> => toleratedHandler(protocol, handler) ?? bind<Protocol, Context>(protocol, handler),
175
+ })
176
+
177
+ /** Access a Fastify-specific upload through an explicit boundary helper, never `request.original`. */
178
+ export const uploadedFile = async (
179
+ request: HandlerRequest<{ body: object }>,
180
+ ): Promise<import('@fastify/multipart').MultipartFile | undefined> => {
181
+ const original = (request as AbstractRequest).original
182
+ if (original == null || typeof original !== 'object' || !('file' in original)) return undefined
183
+
184
+ return (original as import('fastify').FastifyRequest).file()
185
+ }