@owlmeans/server-api 0.1.18-rc.5 → 0.1.18-rc.51

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 (129) hide show
  1. package/README.md +203 -46
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-api/SKILL.md +128 -22
  4. package/build/consts.local.d.ts +2 -0
  5. package/build/consts.local.d.ts.map +1 -0
  6. package/build/consts.local.js +2 -0
  7. package/build/consts.local.js.map +1 -0
  8. package/build/errors.d.ts +12 -0
  9. package/build/errors.d.ts.map +1 -1
  10. package/build/errors.js +16 -0
  11. package/build/errors.js.map +1 -1
  12. package/build/helper.d.ts +3 -6
  13. package/build/helper.d.ts.map +1 -1
  14. package/build/helper.js +10 -1
  15. package/build/helper.js.map +1 -1
  16. package/build/hold.d.ts +35 -0
  17. package/build/hold.d.ts.map +1 -0
  18. package/build/hold.js +57 -0
  19. package/build/hold.js.map +1 -0
  20. package/build/index.d.ts +2 -0
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +2 -0
  23. package/build/index.js.map +1 -1
  24. package/build/protocol.d.ts +18 -0
  25. package/build/protocol.d.ts.map +1 -0
  26. package/build/protocol.js +87 -0
  27. package/build/protocol.js.map +1 -0
  28. package/build/server.d.ts +2 -3
  29. package/build/server.d.ts.map +1 -1
  30. package/build/server.js +39 -34
  31. package/build/server.js.map +1 -1
  32. package/build/types.d.ts +31 -2
  33. package/build/types.d.ts.map +1 -1
  34. package/build/types.local.d.ts +38 -0
  35. package/build/types.local.d.ts.map +1 -0
  36. package/build/types.local.js +2 -0
  37. package/build/types.local.js.map +1 -0
  38. package/build/utils/consts.d.ts +2 -0
  39. package/build/utils/consts.d.ts.map +1 -0
  40. package/build/utils/consts.js +2 -0
  41. package/build/utils/consts.js.map +1 -0
  42. package/build/utils/consts.local.d.ts +5 -0
  43. package/build/utils/consts.local.d.ts.map +1 -0
  44. package/build/utils/consts.local.js +5 -0
  45. package/build/utils/consts.local.js.map +1 -0
  46. package/build/utils/context/types.d.ts +13 -0
  47. package/build/utils/context/types.d.ts.map +1 -0
  48. package/build/utils/context/types.js +2 -0
  49. package/build/utils/context/types.js.map +1 -0
  50. package/build/utils/context.d.ts +3 -3
  51. package/build/utils/context.d.ts.map +1 -1
  52. package/build/utils/context.js +5 -2
  53. package/build/utils/context.js.map +1 -1
  54. package/build/utils/error/types.d.ts +52 -0
  55. package/build/utils/error/types.d.ts.map +1 -0
  56. package/build/utils/error/types.js +2 -0
  57. package/build/utils/error/types.js.map +1 -0
  58. package/build/utils/error.d.ts +5 -2
  59. package/build/utils/error.d.ts.map +1 -1
  60. package/build/utils/error.js +163 -13
  61. package/build/utils/error.js.map +1 -1
  62. package/build/utils/guards.d.ts +1 -5
  63. package/build/utils/guards.d.ts.map +1 -1
  64. package/build/utils/guards.js +15 -19
  65. package/build/utils/guards.js.map +1 -1
  66. package/build/utils/index.d.ts +6 -1
  67. package/build/utils/index.d.ts.map +1 -1
  68. package/build/utils/index.js +2 -1
  69. package/build/utils/index.js.map +1 -1
  70. package/build/utils/log.d.ts +11 -0
  71. package/build/utils/log.d.ts.map +1 -0
  72. package/build/utils/log.js +57 -0
  73. package/build/utils/log.js.map +1 -0
  74. package/build/utils/payload/types.d.ts +18 -0
  75. package/build/utils/payload/types.d.ts.map +1 -0
  76. package/build/utils/payload/types.js +2 -0
  77. package/build/utils/payload/types.js.map +1 -0
  78. package/build/utils/payload.d.ts +3 -15
  79. package/build/utils/payload.d.ts.map +1 -1
  80. package/build/utils/payload.js +41 -57
  81. package/build/utils/payload.js.map +1 -1
  82. package/build/utils/schema.d.ts +7 -0
  83. package/build/utils/schema.d.ts.map +1 -0
  84. package/build/utils/schema.js +20 -0
  85. package/build/utils/schema.js.map +1 -0
  86. package/build/utils/server.d.ts +3 -6
  87. package/build/utils/server.d.ts.map +1 -1
  88. package/build/utils/server.js +12 -13
  89. package/build/utils/server.js.map +1 -1
  90. package/build/utils/types.d.ts +18 -0
  91. package/build/utils/types.d.ts.map +1 -0
  92. package/build/utils/types.js +2 -0
  93. package/build/utils/types.js.map +1 -0
  94. package/build/utils/types.local.d.ts +9 -0
  95. package/build/utils/types.local.d.ts.map +1 -0
  96. package/build/utils/types.local.js +2 -0
  97. package/build/utils/types.local.js.map +1 -0
  98. package/package.json +22 -18
  99. package/src/consts.local.ts +1 -0
  100. package/src/errors.ts +18 -0
  101. package/src/helper.ts +13 -8
  102. package/src/hold.ts +66 -0
  103. package/src/index.ts +2 -0
  104. package/src/protocol.ts +149 -0
  105. package/src/server.ts +69 -69
  106. package/src/types.local.ts +44 -0
  107. package/src/types.ts +38 -4
  108. package/src/utils/consts.local.ts +7 -0
  109. package/src/utils/consts.ts +1 -0
  110. package/src/utils/context/types.ts +13 -0
  111. package/src/utils/context.ts +9 -4
  112. package/src/utils/error/types.ts +52 -0
  113. package/src/utils/error.ts +184 -12
  114. package/src/utils/guards.ts +16 -26
  115. package/src/utils/index.ts +6 -1
  116. package/src/utils/log.ts +62 -0
  117. package/src/utils/payload/types.ts +18 -0
  118. package/src/utils/payload.ts +42 -60
  119. package/src/utils/schema.ts +23 -0
  120. package/src/utils/server.ts +16 -21
  121. package/src/utils/types.local.ts +12 -0
  122. package/src/utils/types.ts +18 -0
  123. package/tests/error-status.spec.ts +349 -0
  124. package/tests/handlers.spec.ts +83 -0
  125. package/tests/json-body.spec.ts +72 -0
  126. package/tests/log-adapter.spec.ts +52 -0
  127. package/tests/tsconfig.json +12 -0
  128. package/tsconfig.json +1 -0
  129. package/build/.gitkeep +0 -0
@@ -1,64 +1,48 @@
1
1
  import { EntrypointOutcome } from '@owlmeans/entrypoint';
2
- import { ACCEPTED, CREATED, OK, SERVER_ERROR } from '@owlmeans/api';
3
- export const provideRequest = (alias, req, provision) => {
4
- provision = provision ?? false;
5
- return {
6
- alias,
7
- auth: req._auth ?? undefined,
8
- params: req.params,
9
- body: req.body,
10
- headers: req.headers,
11
- query: req.query,
12
- path: req.url,
13
- original: provision ? req : undefined
2
+ import { ACCEPTED, CREATED, OK } from '@owlmeans/api';
3
+ import { httpErrorHelper } from './error.js';
4
+ export const createPayloadHelper = () => {
5
+ const provideRequest = (alias, req, provision) => {
6
+ provision = provision ?? false;
7
+ return {
8
+ alias,
9
+ auth: req._auth ?? undefined,
10
+ // Resolved once by `authorize` and carried by reference, exactly like `_auth`, so a handler
11
+ // reached through any path sees the same entity the guard admitted.
12
+ entity: req._entity ?? undefined,
13
+ params: req.params,
14
+ body: req.body,
15
+ headers: req.headers,
16
+ query: req.query,
17
+ path: req.url,
18
+ original: provision ? req : undefined
19
+ };
14
20
  };
15
- };
16
- /**
17
- * Emits the response onto the fastify reply if the handler produced an
18
- * error or an outcome. Returns `true` when something was actually sent so
19
- * callers don't rely on `reply.sent`, which in fastify v5 only flips once
20
- * the raw socket finishes writing (asynchronously) and therefore reads
21
- * `false` synchronously right after `reply.send()`.
22
- *
23
- * @throws {Error}
24
- */
25
- export const executeResponse = (response, reply, throwOnError) => {
26
- if (response.error != null) {
27
- if (throwOnError ?? false) {
28
- throw response.error;
29
- }
30
- reply.code(SERVER_ERROR).send(response.error.message);
31
- return true;
32
- }
33
- else if (response.outcome != null) {
34
- switch (response.outcome) {
35
- case EntrypointOutcome.Accepted:
36
- reply.code(ACCEPTED).send(response.value);
37
- break;
38
- case EntrypointOutcome.Created:
39
- reply.code(CREATED).send(response.value);
40
- break;
41
- case EntrypointOutcome.Ok:
42
- default:
43
- reply.code(OK).send(response.value);
44
- }
45
- return true;
46
- }
47
- return false;
48
- };
49
- export const fixFormatDates = (schema) => {
50
- if (schema.type === 'object') {
51
- if (schema.format === 'date-time') {
52
- schema.type = 'string';
53
- schema.format = 'date-time';
54
- if (schema.required != null) {
55
- delete schema.required;
21
+ const executeResponse = (response, reply, throwOnError) => {
22
+ if (response.error != null) {
23
+ if (throwOnError ?? false) {
24
+ throw response.error;
56
25
  }
26
+ httpErrorHelper.handleError(response.error, reply);
27
+ return true;
57
28
  }
58
- else if (schema.properties != null) {
59
- schema.properties = Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, fixFormatDates(value)]));
29
+ else if (response.outcome != null) {
30
+ switch (response.outcome) {
31
+ case EntrypointOutcome.Accepted:
32
+ reply.code(ACCEPTED).send(response.value);
33
+ break;
34
+ case EntrypointOutcome.Created:
35
+ reply.code(CREATED).send(response.value);
36
+ break;
37
+ case EntrypointOutcome.Ok:
38
+ default:
39
+ reply.code(OK).send(response.value);
40
+ }
41
+ return true;
60
42
  }
61
- }
62
- return schema;
43
+ return false;
44
+ };
45
+ return { provideRequest, executeResponse };
63
46
  };
47
+ export const payloadHelper = createPayloadHelper();
64
48
  //# sourceMappingURL=payload.js.map
@@ -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":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAG5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAkB,EAAE;IACrD,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,SAAmB,EAAmB,EAAE;QAC3F,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;QAC9B,OAAO;YACL,KAAK;YACL,IAAI,EAAG,GAAW,CAAC,KAAK,IAAI,SAAS;YACrC,4FAA4F;YAC5F,oEAAoE;YACpE,MAAM,EAAG,GAAW,CAAC,OAAO,IAAI,SAAS;YACzC,MAAM,EAAE,GAAG,CAAC,MAA4D;YACxE,IAAI,EAAE,GAAG,CAAC,IAA2B;YACrC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAA2D;YACtE,IAAI,EAAE,GAAG,CAAC,GAAG;YACb,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;SACtC,CAAA;IACH,CAAC,CAAA;IAED,MAAM,eAAe,GAAG,CAAI,QAA6B,EAAE,KAAe,EAAE,YAAsB,EAAW,EAAE;QAC7G,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;YAC3B,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,QAAQ,CAAC,KAAK,CAAA;YACtB,CAAC;YACD,eAAe,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;YAClD,OAAO,IAAI,CAAA;QACb,CAAC;aAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;YACpC,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACzB,KAAK,iBAAiB,CAAC,QAAQ;oBAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;oBACzC,MAAK;gBACP,KAAK,iBAAiB,CAAC,OAAO;oBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;oBACxC,MAAK;gBACP,KAAK,iBAAiB,CAAC,EAAE,CAAC;gBAC1B;oBACE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;IAED,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAA;AAC5C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAA"}
@@ -0,0 +1,7 @@
1
+ import type { AnySchemaObject } from 'ajv';
2
+ /**
3
+ * A request schema Fastify can serve: a `date-time` object becomes a `date-time` string (and loses
4
+ * its `required` list), recursively through object properties. Mutates and returns `schema`.
5
+ */
6
+ export declare const fixFormatDates: (schema: AnySchemaObject) => AnySchemaObject;
7
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAE1C;;;GAGG;AACH,eAAO,MAAM,cAAc,WAAY,eAAe,KAAG,eAgBxD,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A request schema Fastify can serve: a `date-time` object becomes a `date-time` string (and loses
3
+ * its `required` list), recursively through object properties. Mutates and returns `schema`.
4
+ */
5
+ export const fixFormatDates = (schema) => {
6
+ if (schema.type === 'object') {
7
+ if (schema.format === 'date-time') {
8
+ schema.type = 'string';
9
+ schema.format = 'date-time';
10
+ if (schema.required != null) {
11
+ delete schema.required;
12
+ }
13
+ }
14
+ else if (schema.properties != null) {
15
+ schema.properties = Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, fixFormatDates(value)]));
16
+ }
17
+ }
18
+ return schema;
19
+ };
20
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAuB,EAAmB,EAAE;IACzE,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,10 +1,7 @@
1
1
  import type { FastifyReply, FastifyRequest } from 'fastify';
2
2
  import type { ServerEntrypoint } from '@owlmeans/server-entrypoint';
3
- import type { CommonEntrypoint } from '@owlmeans/entrypoint';
4
- import type { ServerContext, ServerConfig } from '@owlmeans/server-context';
5
- type Config = ServerConfig;
6
- type Context = ServerContext<Config>;
7
- export declare const canServeModule: (context: Context, module: CommonEntrypoint) => module is ServerEntrypoint<unknown>;
3
+ import { type CommonEntrypoint } from '@owlmeans/entrypoint';
4
+ import type { ServerContext2 } from './types.local.js';
5
+ export declare const canServeModule: (context: ServerContext2, module: CommonEntrypoint) => module is ServerEntrypoint<unknown>;
8
6
  export declare const createServerHandler: (module: ServerEntrypoint<FastifyRequest>, location: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
9
- export {};
10
7
  //# sourceMappingURL=server.d.ts.map
@@ -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;AAC3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,EAAE,KAAK,gBAAgB,EAAqC,MAAM,sBAAsB,CAAA;AAQ/F,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGrE,eAAO,MAAM,cAAc,YAAa,cAAc,UAAU,gBAAgB,KAAG,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAcpH,CAAA;AAED,eAAO,MAAM,mBAAmB,WAAY,gBAAgB,CAAC,cAAc,CAAC,YAAY,MAAM,WAChF,cAAc,SAAS,YAAY,kBAuC9C,CAAA"}
@@ -1,10 +1,10 @@
1
- import { AppType } from '@owlmeans/context';
2
- import { assertContext } from '@owlmeans/context';
1
+ import { AppType, assertContext } from '@owlmeans/context';
3
2
  import { provideResponse } from '@owlmeans/entrypoint';
4
3
  import { ResilientError } from '@owlmeans/error';
5
4
  import { OK } from '@owlmeans/api';
6
- import { handleError } from './error.js';
7
- import { executeResponse, provideRequest } from './payload.js';
5
+ import { logger } from '@owlmeans/log';
6
+ import { httpErrorHelper } from './error.js';
7
+ import { payloadHelper } from './payload.js';
8
8
  import { authorize } from './guards.js';
9
9
  import { RouteProtocols } from '@owlmeans/route';
10
10
  export const canServeModule = (context, module) => {
@@ -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;
@@ -27,12 +29,12 @@ export const createServerHandler = (module, location) => async (req, reply) => {
27
29
  context = authorized[0];
28
30
  module = authorized[1];
29
31
  const response = provideResponse(reply);
30
- const request = provideRequest(module.alias, req, true);
32
+ const request = payloadHelper.provideRequest(module.alias, req, true);
31
33
  const gates = module.getGates();
32
34
  for (const [srv, params] of gates) {
33
35
  const gate = context.service(srv);
34
36
  await gate.assert(request, response, params);
35
- executeResponse(response, reply, true);
37
+ payloadHelper.executeResponse(response, reply, true);
36
38
  }
37
39
  await module.handle(request, response);
38
40
  // Don't rely on `reply.sent` here: in fastify v5 it is backed by
@@ -41,22 +43,19 @@ export const createServerHandler = (module, location) => async (req, reply) => {
41
43
  // synchronously right after `reply.send()`. Track the emitted state
42
44
  // explicitly instead, and only fall back to a default response when
43
45
  // neither `executeResponse` nor the handler itself (hijack) replied.
44
- const responded = executeResponse(response, reply, true);
46
+ const responded = payloadHelper.executeResponse(response, reply, true);
45
47
  if (!responded && !reply.sent) {
46
- console.warn(`SENDS DEFAULT RESPONSE: ${module.alias}`);
48
+ logger('http').warn(`SENDS DEFAULT RESPONSE: ${module.alias}`);
47
49
  reply.code(OK).send(response.value);
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
+ httpErrorHelper.handleError(error, reply, httpErrorHelper.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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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * What an error class may declare about the HTTP status it is answered with.
3
+ *
4
+ * Structural on purpose: the classes that declare it live in packages that must not depend on an
5
+ * HTTP server (`@owlmeans/payment`, a product's contract package), so they write a plain
6
+ * `public static httpStatus = 409` and nothing imports this type.
7
+ */
8
+ export interface HttpStatusDeclaration {
9
+ httpStatus?: unknown;
10
+ /** Explicit opt-in for a declared 5xx response. Client-error declarations need no opt-in. */
11
+ allowServerErrorStatus?: unknown;
12
+ }
13
+ export interface SerializedHttpError {
14
+ status: number;
15
+ body: string;
16
+ incidentId: string;
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
2
+ import type { Config as ApiConfig } from '../types.js';
3
+ export type Config = ServerConfig;
4
+ export interface Context<C extends Config = Config> extends ServerContext<C> {
5
+ }
6
+ export type Severity = 'debug' | 'info' | 'warn' | 'error';
7
+ export type ServerConfig2 = ApiConfig;
8
+ export type ServerContext2 = ServerContext<ServerConfig2>;
9
+ //# sourceMappingURL=types.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.local.d.ts","sourceRoot":"","sources":["../../src/utils/types.local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,MAAM,MAAM,GAAG,YAAY,CAAA;AAEjC,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEhF,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,MAAM,MAAM,aAAa,GAAG,SAAS,CAAA;AAErC,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.local.js","sourceRoot":"","sources":["../../src/utils/types.local.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/server-api",
3
- "version": "0.1.18-rc.5",
3
+ "version": "0.1.18-rc.51",
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",
@@ -29,22 +30,23 @@
29
30
  },
30
31
  "dependencies": {
31
32
  "@fastify/cors": "^11.3.0",
32
- "@fastify/helmet": "^13.1.0",
33
- "@fastify/middie": "^9.0.2",
34
- "@fastify/multipart": "^9.0.1",
35
- "@owlmeans/api": "^0.1.18-rc.5",
36
- "@owlmeans/auth": "^0.1.18-rc.5",
37
- "@owlmeans/auth-common": "^0.1.18-rc.5",
38
- "@owlmeans/context": "^0.1.18-rc.5",
39
- "@owlmeans/error": "^0.1.18-rc.5",
40
- "@owlmeans/entrypoint": "^0.1.18-rc.5",
41
- "@owlmeans/route": "^0.1.18-rc.5",
42
- "@owlmeans/server-context": "^0.1.18-rc.5",
43
- "@owlmeans/server-entrypoint": "^0.1.18-rc.5",
33
+ "@fastify/helmet": "^13.1.1",
34
+ "@fastify/middie": "^9.3.4",
35
+ "@fastify/multipart": "^10.1.2",
36
+ "@owlmeans/api": "^0.1.18-rc.47",
37
+ "@owlmeans/auth": "^0.1.18-rc.40",
38
+ "@owlmeans/auth-common": "^0.1.18-rc.47",
39
+ "@owlmeans/context": "^0.1.18-rc.34",
40
+ "@owlmeans/entrypoint": "^0.1.18-rc.41",
41
+ "@owlmeans/error": "^0.1.18-rc.38",
42
+ "@owlmeans/log": "^0.1.18-rc.2",
43
+ "@owlmeans/route": "^0.1.18-rc.35",
44
+ "@owlmeans/server-context": "^0.1.18-rc.43",
45
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.40",
44
46
  "ajv-errors": "^3.0.0",
45
- "fastify": "^5.0.0",
46
- "fastify-raw-body": "^5.0.0",
47
- "pino-pretty": "^13.0.0"
47
+ "fastify": "^5.12.5",
48
+ "fastify-raw-body": "^6.0.1",
49
+ "pino-pretty": "^13.2.0"
48
50
  },
49
51
  "peerDependencies": {
50
52
  "ajv": "*",
@@ -52,7 +54,9 @@
52
54
  },
53
55
  "devDependencies": {
54
56
  "@owlmeans/dep-config": "workspace:*",
55
- "@types/node": "^26.1.0",
57
+ "@owlmeans/resource": "^0.1.18-rc.40",
58
+ "@types/bun": "^1.4.2",
59
+ "@types/node": "^26.6.4",
56
60
  "nodemon": "^3.1.14",
57
61
  "typescript": "^7.0.2"
58
62
  },
@@ -0,0 +1 @@
1
+ export const bodyLimit = 1024 * 1024 * 20
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
@@ -1,15 +1,21 @@
1
1
 
2
- import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
- import { assertContext } from '@owlmeans/context'
4
- import { EntrypointOutcome } from '@owlmeans/entrypoint'
5
- import type { AbstractRequest, AbstractResponse } from '@owlmeans/entrypoint'
2
+ import { type BasicConfig, type BasicContext, assertContext } from '@owlmeans/context'
3
+ import { EntrypointOutcome, type AbstractRequest, type AbstractResponse } from '@owlmeans/entrypoint'
6
4
  import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint'
7
- import type { Config, Context } from './types.js'
5
+ import type { Config, Context, UploadedFile } from './types.js'
8
6
  import type { FastifyRequest } from 'fastify'
9
- import type { MultipartFile } from '@fastify/multipart'
10
7
 
8
+ /**
9
+ * The context a handler runs against.
10
+ *
11
+ * The HTTP boundary hangs the request-scoped context on the raw Fastify request, so a handler
12
+ * reached that way gets it from there. A request that did not come from Fastify has no `original`
13
+ * at all — a queued call is rebuilt from an envelope, and a socket frame carries its own — so this
14
+ * has to fall back rather than dereference: these handlers are the same functions on every
15
+ * transport, and only the way they were reached differs.
16
+ */
11
17
  const _castContextFromOriginal = <C extends BasicConfig, T extends BasicContext<C> = BasicContext<C>>(req: AbstractRequest, def: T): T => {
12
- return req.original._ctx ?? def
18
+ return req.original?._ctx ?? def
13
19
  }
14
20
 
15
21
  export const handleBody: <T>(
@@ -86,4 +92,3 @@ export const extractUploadedFile = async <T extends {} = {}>(req: AbstractReques
86
92
  return request.file()
87
93
  }
88
94
 
89
- export interface UploadedFile extends MultipartFile { }
package/src/hold.ts ADDED
@@ -0,0 +1,66 @@
1
+ import Fastify from 'fastify'
2
+ import { logger } from '@owlmeans/log'
3
+ import type { ServerConfig } from '@owlmeans/server-context'
4
+ import { CLOSED_HOST, OPENED_HOST, PORT } from './consts.js'
5
+ import type { ApiPortHold, ApiPortHoldOptions } from './types.js'
6
+
7
+ /**
8
+ * Own the app's port while the context initializes, and answer for the app until `listen()`
9
+ * takes over.
10
+ *
11
+ * The API server itself cannot do this: it builds its route table from the entrypoints the
12
+ * context knows about, so it can only bind AFTER `init()` resolves — and Fastify refuses new
13
+ * routes once it is listening. Without a hold, the whole boot window, and every way a boot can
14
+ * fail, leaves nothing on the port; the edge then answers a bare upstream connect error that
15
+ * names neither the app nor the reason, which is indistinguishable from a crashed pod or an app
16
+ * that was never installed.
17
+ *
18
+ * So the boot sequence an app wants is:
19
+ *
20
+ * const hold = await holdApiPort(cfg, { okPath, payload }) // bind FIRST
21
+ * try { ...initialize... } catch { record the failure; return } // hold keeps answering
22
+ * await hold.release()
23
+ * await context.getApiServer().listen()
24
+ *
25
+ * Everything that is not the health path answers **503, not 404**: the routes the app really
26
+ * serves do not exist yet, and "no such route" would be a lie that outlives the boot.
27
+ *
28
+ * The port and host are resolved exactly as `listen()` resolves them — from the service's own
29
+ * declaration in `cfg.services[cfg.service]` — so the hold and the server can never disagree
30
+ * about which socket they are trading.
31
+ *
32
+ * A bind failure here (EADDRINUSE above all) throws to the caller and must be treated as fatal:
33
+ * a process that continues past it ends the boot with nothing listening and nothing holding the
34
+ * event loop, i.e. a clean exit 0 while a predecessor keeps serving stale code. Name the error
35
+ * and exit non-zero — that is what makes a supervisor reclaim the port instead of respawning
36
+ * into it.
37
+ */
38
+ export const holdApiPort = async (
39
+ cfg: ServerConfig, opts: ApiPortHoldOptions = {}
40
+ ): Promise<ApiPortHold> => {
41
+ const config = cfg.services[cfg.service]
42
+ const port = config?.internalPort ?? config?.port ?? PORT
43
+ const host = config?.opened === true ? OPENED_HOST : CLOSED_HOST
44
+
45
+ const payload = opts.payload ?? (() => ({ status: 'OK' }))
46
+
47
+ // Quiet and disposable: no logger, no plugins, and connections are force-closed on release so
48
+ // the handover to the real server is bounded rather than waiting out a keep-alive.
49
+ const server = Fastify({ logger: false, forceCloseConnections: true })
50
+
51
+ if (opts.okPath != null) {
52
+ server.all(opts.okPath, async (_request, reply) => {
53
+ return await reply.code(200).send(payload())
54
+ })
55
+ }
56
+ server.setNotFoundHandler(async (_request, reply) => {
57
+ return await reply.code(503).send(payload())
58
+ })
59
+
60
+ await server.listen({ port, host })
61
+ logger('server').info(`api-server: holding ${host}:${port} for the boot${opts.okPath != null ? ` (${opts.okPath})` : ''}`, undefined, { event: 'server.hold' })
62
+
63
+ return {
64
+ release: async () => { await server.close() },
65
+ }
66
+ }
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'