@entifix/service-shell 0.4.2 → 0.6.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.
Files changed (55) hide show
  1. package/README.md +15 -3
  2. package/dist/auth/index.d.ts +1 -1
  3. package/dist/auth/index.d.ts.map +1 -1
  4. package/dist/auth/index.js +1 -1
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/auth/require-organization.d.ts +2 -4
  7. package/dist/auth/require-organization.d.ts.map +1 -1
  8. package/dist/auth/require-organization.js +6 -8
  9. package/dist/auth/require-organization.js.map +1 -1
  10. package/dist/auth/require-principal.d.ts +1 -1
  11. package/dist/auth/require-principal.js.map +1 -1
  12. package/dist/auth/require-service-crossing.d.ts +7 -9
  13. package/dist/auth/require-service-crossing.d.ts.map +1 -1
  14. package/dist/auth/require-service-crossing.js +7 -9
  15. package/dist/auth/require-service-crossing.js.map +1 -1
  16. package/dist/auth/shared-token.d.ts +34 -0
  17. package/dist/auth/shared-token.d.ts.map +1 -0
  18. package/dist/auth/shared-token.js +39 -0
  19. package/dist/auth/shared-token.js.map +1 -0
  20. package/dist/entity-metadata-route.d.ts +1 -2
  21. package/dist/entity-metadata-route.d.ts.map +1 -1
  22. package/dist/entity-metadata-route.js +1 -2
  23. package/dist/entity-metadata-route.js.map +1 -1
  24. package/dist/index.d.ts +0 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +0 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/load-remote-configuration.d.ts +22 -10
  29. package/dist/load-remote-configuration.d.ts.map +1 -1
  30. package/dist/load-remote-configuration.js +15 -20
  31. package/dist/load-remote-configuration.js.map +1 -1
  32. package/dist/make-service.d.ts +13 -4
  33. package/dist/make-service.d.ts.map +1 -1
  34. package/dist/make-service.js +3 -3
  35. package/dist/make-service.js.map +1 -1
  36. package/dist/runtime.d.ts +12 -0
  37. package/dist/runtime.d.ts.map +1 -0
  38. package/dist/runtime.js +12 -0
  39. package/dist/runtime.js.map +1 -0
  40. package/dist/service-description-route.d.ts +7 -6
  41. package/dist/service-description-route.d.ts.map +1 -1
  42. package/dist/service-description-route.js +5 -5
  43. package/dist/service-description-route.js.map +1 -1
  44. package/dist/sse.d.ts +2 -2
  45. package/dist/sse.js +1 -1
  46. package/dist/sse.js.map +1 -1
  47. package/dist/testing.d.ts +7 -0
  48. package/dist/testing.d.ts.map +1 -0
  49. package/dist/testing.js +7 -0
  50. package/dist/testing.js.map +1 -0
  51. package/package.json +58 -10
  52. package/dist/auth/service-token.d.ts +0 -37
  53. package/dist/auth/service-token.d.ts.map +0 -1
  54. package/dist/auth/service-token.js +0 -62
  55. package/dist/auth/service-token.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/service-shell",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Effect HTTP service skeleton: server layer, health and metadata routes, permission guards, remote configuration and OpenTelemetry.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,6 +26,21 @@
26
26
  "types": "./dist/index.d.ts",
27
27
  "import": "./dist/index.js",
28
28
  "default": "./dist/index.js"
29
+ },
30
+ "./runtime": {
31
+ "types": "./dist/runtime.d.ts",
32
+ "import": "./dist/runtime.js",
33
+ "default": "./dist/runtime.js"
34
+ },
35
+ "./observability": {
36
+ "types": "./dist/observability.d.ts",
37
+ "import": "./dist/observability.js",
38
+ "default": "./dist/observability.js"
39
+ },
40
+ "./testing": {
41
+ "types": "./dist/testing.d.ts",
42
+ "import": "./dist/testing.js",
43
+ "default": "./dist/testing.js"
29
44
  }
30
45
  },
31
46
  "nx": {
@@ -50,26 +65,59 @@
50
65
  }
51
66
  },
52
67
  "dependencies": {
68
+ "@swc/helpers": "0.5.23",
69
+ "@entifix/business": "0.6.0",
70
+ "@entifix/core": "0.6.0",
71
+ "@entifix/tooling": "0.6.0",
72
+ "@entifix/authz": "0.6.0"
73
+ },
74
+ "devDependencies": {
53
75
  "@effect/opentelemetry": "4.0.5",
54
76
  "@effect/platform-node": "4.0.5",
77
+ "@opentelemetry/api": "1.9.1",
55
78
  "@opentelemetry/context-async-hooks": "2.12.0",
56
79
  "@opentelemetry/exporter-metrics-otlp-http": "0.223.0",
57
80
  "@opentelemetry/exporter-trace-otlp-http": "0.223.0",
58
81
  "@opentelemetry/sdk-metrics": "2.12.0",
59
82
  "@opentelemetry/sdk-trace-base": "2.12.0",
60
- "@swc/helpers": "0.5.23",
61
- "@entifix/authz": "0.4.2",
62
- "@entifix/business": "0.4.2",
63
- "@entifix/core": "0.4.2",
64
- "@entifix/tooling": "0.4.2"
65
- },
66
- "devDependencies": {
67
- "@opentelemetry/api": "1.9.1",
68
83
  "effect": "4.0.5",
69
- "@entifix/testing-unit": "0.4.2"
84
+ "@entifix/testing-unit": "0.6.0"
70
85
  },
71
86
  "peerDependencies": {
87
+ "@effect/opentelemetry": "^4.0.5",
88
+ "@effect/platform-node": "^4.0.5",
72
89
  "@opentelemetry/api": ">=1.9 <2",
90
+ "@opentelemetry/context-async-hooks": ">=2.12 <3",
91
+ "@opentelemetry/exporter-metrics-otlp-http": ">=0.223 <0.224",
92
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.223 <0.224",
93
+ "@opentelemetry/sdk-metrics": ">=2.12 <3",
94
+ "@opentelemetry/sdk-trace-base": ">=2.12 <3",
73
95
  "effect": "^4.0.5"
96
+ },
97
+ "peerDependenciesMeta": {
98
+ "@effect/opentelemetry": {
99
+ "optional": true
100
+ },
101
+ "@effect/platform-node": {
102
+ "optional": true
103
+ },
104
+ "@opentelemetry/api": {
105
+ "optional": true
106
+ },
107
+ "@opentelemetry/context-async-hooks": {
108
+ "optional": true
109
+ },
110
+ "@opentelemetry/exporter-metrics-otlp-http": {
111
+ "optional": true
112
+ },
113
+ "@opentelemetry/exporter-trace-otlp-http": {
114
+ "optional": true
115
+ },
116
+ "@opentelemetry/sdk-metrics": {
117
+ "optional": true
118
+ },
119
+ "@opentelemetry/sdk-trace-base": {
120
+ "optional": true
121
+ }
74
122
  }
75
123
  }
@@ -1,37 +0,0 @@
1
- import { Effect } from 'effect';
2
- import { HttpServerRequest, HttpServerResponse } from 'effect/http';
3
- /** The header a caller of config-service's fleet lookup presents. */
4
- export declare const SERVICE_TOKEN_HEADER = "x-service-token";
5
- /**
6
- * Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same
7
- * convention as the seeded `jwt.secret`: obviously a placeholder, replaced per
8
- * environment through `CONFIG_SERVICE_TOKEN`.
9
- */
10
- export declare const DEV_SERVICE_TOKEN = "dev-config-service-token-change-me";
11
- /**
12
- * The expected token, read from the environment on each call rather than captured
13
- * at module load — so a test can set it without re-importing the module.
14
- */
15
- export declare const serviceToken: () => string;
16
- /**
17
- * Guards config-service's fleet configuration lookup.
18
- *
19
- * `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and
20
- * `rabbitmq.uri` in plaintext, because that is exactly what a booting service
21
- * needs — the values cannot be redacted here without breaking the boot the route
22
- * exists to serve. So the route is gated on a shared token instead, which closes
23
- * the drive-by read that being merely "cluster-internal" did not.
24
- *
25
- * This is deliberately **not** service identity: every caller presents the same
26
- * token, so it proves fleet membership and nothing more. Per-service credentials,
27
- * with the response scoped to the service that asked, are separate work; this is
28
- * the cheap step that stops the port from being self-serve.
29
- *
30
- * It lives here beside {@link loadRemoteConfiguration}, the client half of the
31
- * same protocol, so the header and the token cannot drift apart.
32
- *
33
- * Health endpoints are **not** guarded — a probe must answer without credentials,
34
- * or a readiness check becomes an authentication problem.
35
- */
36
- export declare const requireServiceToken: <A, E, R>(route: Effect.Effect<A, E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse | A, import("effect/http/HttpBody").HttpBodyError | E, HttpServerRequest.HttpServerRequest | R>;
37
- //# sourceMappingURL=service-token.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"service-token.d.ts","sourceRoot":"","sources":["../../src/auth/service-token.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,uCAAuC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAO,MACuB,CAAC;AAYxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,wJAiBtE,CAAC"}
@@ -1,62 +0,0 @@
1
- import { timingSafeEqual } from 'node:crypto';
2
- import { Effect } from 'effect';
3
- import { HttpServerRequest, HttpServerResponse } from 'effect/http';
4
- /** The header a caller of config-service's fleet lookup presents. */ export const SERVICE_TOKEN_HEADER = 'x-service-token';
5
- /**
6
- * Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same
7
- * convention as the seeded `jwt.secret`: obviously a placeholder, replaced per
8
- * environment through `CONFIG_SERVICE_TOKEN`.
9
- */ export const DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';
10
- /**
11
- * The expected token, read from the environment on each call rather than captured
12
- * at module load — so a test can set it without re-importing the module.
13
- */ export const serviceToken = ()=>{
14
- var _process_env_CONFIG_SERVICE_TOKEN;
15
- return (_process_env_CONFIG_SERVICE_TOKEN = process.env.CONFIG_SERVICE_TOKEN) != null ? _process_env_CONFIG_SERVICE_TOKEN : DEV_SERVICE_TOKEN;
16
- };
17
- /** Constant-time comparison, so the token cannot be recovered byte by byte. */ const matches = (provided)=>{
18
- if (provided === undefined) return false;
19
- const left = Buffer.from(provided);
20
- const right = Buffer.from(serviceToken());
21
- // `timingSafeEqual` throws on a length mismatch, so the lengths are compared
22
- // first — the length of a shared secret is not what protects it.
23
- return left.length === right.length && timingSafeEqual(left, right);
24
- };
25
- /**
26
- * Guards config-service's fleet configuration lookup.
27
- *
28
- * `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and
29
- * `rabbitmq.uri` in plaintext, because that is exactly what a booting service
30
- * needs — the values cannot be redacted here without breaking the boot the route
31
- * exists to serve. So the route is gated on a shared token instead, which closes
32
- * the drive-by read that being merely "cluster-internal" did not.
33
- *
34
- * This is deliberately **not** service identity: every caller presents the same
35
- * token, so it proves fleet membership and nothing more. Per-service credentials,
36
- * with the response scoped to the service that asked, are separate work; this is
37
- * the cheap step that stops the port from being self-serve.
38
- *
39
- * It lives here beside {@link loadRemoteConfiguration}, the client half of the
40
- * same protocol, so the header and the token cannot drift apart.
41
- *
42
- * Health endpoints are **not** guarded — a probe must answer without credentials,
43
- * or a readiness check becomes an authentication problem.
44
- */ export const requireServiceToken = (route)=>// The return type is inferred rather than written out: `HttpServerResponse.json`
45
- // contributes an `HttpBodyError` to the failure channel, and spelling the type
46
- // out while omitting it is what makes `tsc` refuse the declaration emit — which
47
- // `@nx/js:swc` hides, leaving a green build with no `.d.ts` and a TS6305
48
- // cascade in every consumer. `requirePrincipal` infers for the same reason.
49
- Effect.gen(function*() {
50
- const request = yield* HttpServerRequest.HttpServerRequest;
51
- if (!matches(request.headers[SERVICE_TOKEN_HEADER])) {
52
- return yield* HttpServerResponse.json({
53
- error: 'unauthenticated',
54
- code: 'unauthenticated'
55
- }, {
56
- status: 401
57
- });
58
- }
59
- return yield* route;
60
- });
61
-
62
- //# sourceMappingURL=service-token.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/auth/service-token.ts"],"sourcesContent":["import { timingSafeEqual } from 'node:crypto';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest, HttpServerResponse } from 'effect/http';\n\n/** The header a caller of config-service's fleet lookup presents. */\nexport const SERVICE_TOKEN_HEADER = 'x-service-token';\n\n/**\n * Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same\n * convention as the seeded `jwt.secret`: obviously a placeholder, replaced per\n * environment through `CONFIG_SERVICE_TOKEN`.\n */\nexport const DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';\n\n/**\n * The expected token, read from the environment on each call rather than captured\n * at module load — so a test can set it without re-importing the module.\n */\nexport const serviceToken = (): string =>\n process.env.CONFIG_SERVICE_TOKEN ?? DEV_SERVICE_TOKEN;\n\n/** Constant-time comparison, so the token cannot be recovered byte by byte. */\nconst matches = (provided: string | undefined): boolean => {\n if (provided === undefined) return false;\n const left = Buffer.from(provided);\n const right = Buffer.from(serviceToken());\n // `timingSafeEqual` throws on a length mismatch, so the lengths are compared\n // first — the length of a shared secret is not what protects it.\n return left.length === right.length && timingSafeEqual(left, right);\n};\n\n/**\n * Guards config-service's fleet configuration lookup.\n *\n * `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and\n * `rabbitmq.uri` in plaintext, because that is exactly what a booting service\n * needs — the values cannot be redacted here without breaking the boot the route\n * exists to serve. So the route is gated on a shared token instead, which closes\n * the drive-by read that being merely \"cluster-internal\" did not.\n *\n * This is deliberately **not** service identity: every caller presents the same\n * token, so it proves fleet membership and nothing more. Per-service credentials,\n * with the response scoped to the service that asked, are separate work; this is\n * the cheap step that stops the port from being self-serve.\n *\n * It lives here beside {@link loadRemoteConfiguration}, the client half of the\n * same protocol, so the header and the token cannot drift apart.\n *\n * Health endpoints are **not** guarded — a probe must answer without credentials,\n * or a readiness check becomes an authentication problem.\n */\nexport const requireServiceToken = <A, E, R>(route: Effect.Effect<A, E, R>) =>\n // The return type is inferred rather than written out: `HttpServerResponse.json`\n // contributes an `HttpBodyError` to the failure channel, and spelling the type\n // out while omitting it is what makes `tsc` refuse the declaration emit — which\n // `@nx/js:swc` hides, leaving a green build with no `.d.ts` and a TS6305\n // cascade in every consumer. `requirePrincipal` infers for the same reason.\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n\n if (!matches(request.headers[SERVICE_TOKEN_HEADER])) {\n return yield* HttpServerResponse.json(\n { error: 'unauthenticated', code: 'unauthenticated' },\n { status: 401 },\n );\n }\n\n return yield* route;\n });\n"],"names":["timingSafeEqual","Effect","HttpServerRequest","HttpServerResponse","SERVICE_TOKEN_HEADER","DEV_SERVICE_TOKEN","serviceToken","process","env","CONFIG_SERVICE_TOKEN","matches","provided","undefined","left","Buffer","from","right","length","requireServiceToken","route","gen","request","headers","json","error","code","status"],"mappings":"AAAA,SAASA,eAAe,QAAQ,cAAc;AAE9C,SAASC,MAAM,QAAQ,SAAS;AAChC,SAASC,iBAAiB,EAAEC,kBAAkB,QAAQ,cAAc;AAEpE,mEAAmE,GACnE,OAAO,MAAMC,uBAAuB,kBAAkB;AAEtD;;;;CAIC,GACD,OAAO,MAAMC,oBAAoB,qCAAqC;AAEtE;;;CAGC,GACD,OAAO,MAAMC,eAAe;QAC1BC;YAAAA,oCAAAA,QAAQC,GAAG,CAACC,oBAAoB,YAAhCF,oCAAoCF;EAAkB;AAExD,6EAA6E,GAC7E,MAAMK,UAAU,CAACC;IACf,IAAIA,aAAaC,WAAW,OAAO;IACnC,MAAMC,OAAOC,OAAOC,IAAI,CAACJ;IACzB,MAAMK,QAAQF,OAAOC,IAAI,CAACT;IAC1B,6EAA6E;IAC7E,iEAAiE;IACjE,OAAOO,KAAKI,MAAM,KAAKD,MAAMC,MAAM,IAAIjB,gBAAgBa,MAAMG;AAC/D;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAME,sBAAsB,CAAUC,QAC3C,iFAAiF;IACjF,+EAA+E;IAC/E,gFAAgF;IAChF,yEAAyE;IACzE,4EAA4E;IAC5ElB,OAAOmB,GAAG,CAAC;QACT,MAAMC,UAAU,OAAOnB,kBAAkBA,iBAAiB;QAE1D,IAAI,CAACQ,QAAQW,QAAQC,OAAO,CAAClB,qBAAqB,GAAG;YACnD,OAAO,OAAOD,mBAAmBoB,IAAI,CACnC;gBAAEC,OAAO;gBAAmBC,MAAM;YAAkB,GACpD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,OAAO,OAAOP;IAChB,GAAG"}