@entifix/service-shell 0.7.2 → 0.8.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/dist/entity-routes.d.ts +11 -15
- package/dist/entity-routes.d.ts.map +1 -1
- package/dist/entity-routes.js +10 -1
- package/dist/entity-routes.js.map +1 -1
- package/package.json +6 -6
package/dist/entity-routes.d.ts
CHANGED
|
@@ -92,21 +92,17 @@ export interface EntityRoutesOptions<T extends Entity, R, G extends EntityRouteG
|
|
|
92
92
|
/** The `500` for anything a route did not anticipate. */
|
|
93
93
|
export declare const routeFailure: (error: unknown) => Effect.Effect<HttpServerResponse.HttpServerResponse, import("effect/http/HttpBody").HttpBodyError, never>;
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* reach them, which records a reader sees, and what a save must pass. What it
|
|
101
|
-
* never varies on is the wire: the load request is parsed against the entity's
|
|
102
|
-
* own metadata (so a query can only name a member the entity declared
|
|
103
|
-
* filterable or sortable), every answer is an envelope, and the links name
|
|
104
|
-
* exactly the verbs mounted — never one that would `404`.
|
|
105
|
-
*
|
|
106
|
-
* A path that is not one of these verbs is written by hand beside it, and a
|
|
107
|
-
* verb whose generic form does not fit is replaced with `override`.
|
|
95
|
+
* {@link deriveEntityRoutes}, with a curried form for a caller that has to
|
|
96
|
+
* name the services its `override`, `scope` or `prepare` handlers need:
|
|
97
|
+
* `makeEntityRoutes.for<R>()(Entity, options)` names `R` and still infers the
|
|
98
|
+
* entity and the guard from the value, which a single call with explicit type
|
|
99
|
+
* arguments cannot do.
|
|
108
100
|
*/
|
|
109
|
-
export declare const makeEntityRoutes: <T extends Entity, R = never, const G extends EntityRouteGuard =
|
|
101
|
+
export declare const makeEntityRoutes: (<T extends Entity, R = never, const G extends EntityRouteGuard = never>(entityConstructor: EntityConstructor<T>, options: EntityRoutesOptions<T, R, G>) => import("effect/Layer").Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request<"Requires", PolicyDecisionTag> | HttpRouter.Request<"Requires", EntityRepositoryFactoryTag> | HttpRouter.Request<"Requires", TokenServiceTag> | HttpRouter.Request.From<"Requires", Exclude<Exclude<R, HttpRouter.Provided>, HttpRouter.Provided>> | HttpRouter.Request.From<"Requires", Exclude<Exclude<G extends {
|
|
110
102
|
readonly kind: "crossing";
|
|
111
|
-
} ? ServiceCrossingPolicyTag | ServiceCrossingTokenTag : never, HttpRouter.Provided>, HttpRouter.Provided>> | HttpRouter.Request<"Error", unknown
|
|
103
|
+
} ? ServiceCrossingPolicyTag | ServiceCrossingTokenTag : never, HttpRouter.Provided>, HttpRouter.Provided>> | HttpRouter.Request<"Error", unknown>>) & {
|
|
104
|
+
for: <R>() => <T extends Entity, const G extends EntityRouteGuard>(entityConstructor: EntityConstructor<T>, options: EntityRoutesOptions<T, R, G>) => import("effect/Layer").Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request<"Requires", PolicyDecisionTag> | HttpRouter.Request<"Requires", EntityRepositoryFactoryTag> | HttpRouter.Request<"Requires", TokenServiceTag> | HttpRouter.Request<"Error", unknown> | HttpRouter.Request.From<"Requires", Exclude<Exclude<R, HttpRouter.Provided>, HttpRouter.Provided>> | HttpRouter.Request.From<"Requires", Exclude<Exclude<G extends {
|
|
105
|
+
readonly kind: "crossing";
|
|
106
|
+
} ? ServiceCrossingPolicyTag | ServiceCrossingTokenTag : never, HttpRouter.Provided>, HttpRouter.Provided>>>;
|
|
107
|
+
};
|
|
112
108
|
//# sourceMappingURL=entity-routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-routes.d.ts","sourceRoot":"","sources":["../src/entity-routes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"entity-routes.d.ts","sourceRoot":"","sources":["../src/entity-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/entity-routes.js
CHANGED
|
@@ -71,7 +71,7 @@ const invalid = (error, label, code)=>{
|
|
|
71
71
|
*
|
|
72
72
|
* A path that is not one of these verbs is written by hand beside it, and a
|
|
73
73
|
* verb whose generic form does not fit is replaced with `override`.
|
|
74
|
-
*/
|
|
74
|
+
*/ const deriveEntityRoutes = (entityConstructor, options)=>{
|
|
75
75
|
var _options_basePath;
|
|
76
76
|
const key = envelopeEntityName(entityConstructor);
|
|
77
77
|
const base = (_options_basePath = options.basePath) != null ? _options_basePath : `/api/${key}`;
|
|
@@ -298,5 +298,14 @@ const invalid = (error, label, code)=>{
|
|
|
298
298
|
...options.verbs.map((verb)=>HttpRouter.route(ROUTES[verb].method, ROUTES[verb].path, handlerFor(verb)))
|
|
299
299
|
]);
|
|
300
300
|
};
|
|
301
|
+
/**
|
|
302
|
+
* {@link deriveEntityRoutes}, with a curried form for a caller that has to
|
|
303
|
+
* name the services its `override`, `scope` or `prepare` handlers need:
|
|
304
|
+
* `makeEntityRoutes.for<R>()(Entity, options)` names `R` and still infers the
|
|
305
|
+
* entity and the guard from the value, which a single call with explicit type
|
|
306
|
+
* arguments cannot do.
|
|
307
|
+
*/ export const makeEntityRoutes = Object.assign(deriveEntityRoutes, {
|
|
308
|
+
for: ()=>(entityConstructor, options)=>deriveEntityRoutes(entityConstructor, options)
|
|
309
|
+
});
|
|
301
310
|
|
|
302
311
|
//# sourceMappingURL=entity-routes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/entity-routes.ts"],"sourcesContent":["import {\n type Action,\n permissionForEntity,\n type PolicyDecisionTag,\n type ServiceCrossingPolicyTag,\n} from '@entifix/authz';\nimport {\n deleteUCFactory,\n EntityIdTag,\n EntityLoadRequestTag,\n EntityRepositoryFactoryTag,\n EntityRepositoryTag,\n EntityTag,\n getUCFactory,\n loadUCFactory,\n type RepositoryScope,\n saveUCFactory,\n type TokenServiceTag,\n} from '@entifix/business';\nimport {\n EntifixBuildError,\n type EntifixEnvelopeLink,\n type Entity,\n type EntityConstructor,\n type EntityId,\n type EntityLoadRequest,\n envelopeEntityName,\n extractMetaEntity,\n type FilterGroup,\n makeEntityEnvelope,\n makeEntityPageEnvelope,\n parseLoadRequestParams,\n readEntityEnvelope,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/http';\n\nimport {\n type RequestPrincipal,\n requireCrossing,\n requireOrganization,\n requirePermission,\n requireServiceCrossing,\n type ServiceCrossingTokenTag,\n} from './auth/index.js';\nimport { entityMetadataRoute } from './entity-metadata-route.js';\n\n/** The five generic verbs a route set can mount. */\nexport type EntityVerb = 'list' | 'get' | 'create' | 'update' | 'delete';\n\n/**\n * Who may reach a route, and where the organization a per-organization store\n * needs comes from.\n *\n * - `public` — anybody; nothing is checked and no organization is known.\n * - `permission` — a verified session holding the permission the entity\n * derives for the verb.\n * - `organization` — the same, plus the session's active organization, which\n * is what a per-organization store opens.\n * - `crossing` — the fleet's crossing token and no session at all. With\n * `organization: true` the caller names the organization in a header, which\n * only a caller that has proved it is the fleet may do.\n */\nexport type EntityRouteGuard =\n | { readonly kind: 'public' }\n | { readonly kind: 'permission' }\n | { readonly kind: 'organization' }\n | { readonly kind: 'crossing'; readonly organization: boolean };\n\n/** What a route learned about its caller from its guard. */\nexport interface EntityRouteContext extends RepositoryScope {\n readonly principal?: RequestPrincipal;\n}\n\n/**\n * Which records of an entity a caller may see.\n *\n * `filter` narrows a list and `includes` decides a by-id read — both are\n * needed, because `id` is the one member no query can name. `'none'` is a\n * caller who holds the read grant and owns nothing: an empty page and a `404`,\n * never a `403`, because owning no records is not an error.\n */\nexport type EntityReadScope<T extends Entity> =\n | 'all'\n | 'none'\n | {\n readonly filter: FilterGroup<T>;\n readonly includes: (entity: T) => boolean;\n };\n\n/**\n * A save refused by a rule the entity's own deserializer does not make — a\n * closed set, a rule over the other records — answered as `status` with\n * `code`, which is what a client translates.\n */\nexport class EntityRouteRefusal {\n readonly _tag = 'EntityRouteRefusal';\n\n constructor(\n readonly status: 400 | 409 | 422,\n readonly code: string,\n readonly detail?: string,\n ) {}\n}\n\n/** A hand-written handler for one verb, mounted under the derived guard. */\nexport type EntityRouteOverride<R> = (\n context: EntityRouteContext,\n links: EntityRouteLinks,\n) => Effect.Effect<HttpServerResponse.HttpServerResponse, unknown, R>;\n\n/** The affordances a route set derives from the verbs it mounts. */\nexport interface EntityRouteLinks {\n readonly record: (id: EntityId) => EntifixEnvelopeLink[];\n readonly collection: () => EntifixEnvelopeLink[];\n}\n\nexport interface EntityRoutesOptions<\n T extends Entity,\n R,\n G extends EntityRouteGuard = EntityRouteGuard,\n> {\n readonly verbs: readonly EntityVerb[];\n /** One guard for every verb, or one for the reads and one for the writes. */\n readonly guard: G | { readonly read: G; readonly write: G };\n /** Defaults to `/api/<entity key>`. */\n readonly basePath?: `/${string}`;\n /** Narrows the reads to what this caller may see. */\n readonly scope?: (\n context: EntityRouteContext,\n ) => Effect.Effect<EntityReadScope<T>, never, R>;\n /**\n * Runs on every create and update, after the body is read and before the\n * save — and may change what is saved, or refuse it.\n */\n readonly prepare?: (\n entity: T,\n context: EntityRouteContext & { readonly verb: 'create' | 'update' },\n ) => Effect.Effect<T, EntityRouteRefusal | EntifixBuildError, R>;\n /**\n * A hand-written handler in place of the generic one for a verb. It keeps\n * the derived guard and receives the derived links, so the escape hatch\n * cannot drift from the rest of the set.\n */\n readonly override?: Partial<Record<EntityVerb, EntityRouteOverride<R>>>;\n}\n\nconst ACTION: Record<EntityVerb, Action> = {\n list: 'read',\n get: 'read',\n create: 'write',\n update: 'write',\n delete: 'delete',\n};\n\nconst READS: ReadonlySet<EntityVerb> = new Set(['list', 'get']);\n\n/**\n * What a route set needs from the host beyond the repository factory: a\n * session's services always (its `$metadata` reads the caller), the fleet's\n * crossing services only when a guard is a crossing.\n */\ntype GuardServices<G extends EntityRouteGuard> =\n | TokenServiceTag\n | PolicyDecisionTag\n | (G extends { readonly kind: 'crossing' }\n ? ServiceCrossingTokenTag | ServiceCrossingPolicyTag\n : never);\n\n/** The `500` for anything a route did not anticipate. */\nexport const routeFailure = (error: unknown) =>\n HttpServerResponse.json(\n { error: 'request failed', code: 'unexpected', detail: String(error) },\n { status: 500 },\n );\n\nconst invalid = (error: unknown, label: string, code: string) =>\n error instanceof EntifixBuildError\n ? HttpServerResponse.json(\n {\n error: label,\n code: refusalCode(error) ?? code,\n detail: error.message,\n },\n { status: 400 },\n )\n : error instanceof EntityRouteRefusal\n ? HttpServerResponse.json(\n {\n error: 'the record cannot be saved',\n code: error.code,\n ...(error.detail === undefined ? {} : { detail: error.detail }),\n },\n { status: error.status },\n )\n : routeFailure(error);\n\n/**\n * A domain error may carry its own code (`mediaNotHttps`), so the client can\n * say which rule failed rather than \"invalid body\".\n */\nconst refusalCode = (error: EntifixBuildError): string | undefined =>\n 'code' in error && typeof error.code === 'string' ? error.code : undefined;\n\n/**\n * The generic routes for one entity — list, read, create, update, delete and\n * its `$metadata` — derived from the entity and bound to storage through the\n * host's {@link EntityRepositoryFactoryTag}.\n *\n * What a route set varies on is stated, not written: which verbs exist, who may\n * reach them, which records a reader sees, and what a save must pass. What it\n * never varies on is the wire: the load request is parsed against the entity's\n * own metadata (so a query can only name a member the entity declared\n * filterable or sortable), every answer is an envelope, and the links name\n * exactly the verbs mounted — never one that would `404`.\n *\n * A path that is not one of these verbs is written by hand beside it, and a\n * verb whose generic form does not fit is replaced with `override`.\n */\nexport const makeEntityRoutes = <\n T extends Entity,\n R = never,\n const G extends EntityRouteGuard = EntityRouteGuard,\n>(\n entityConstructor: EntityConstructor<T>,\n options: EntityRoutesOptions<T, R, G>,\n) => {\n const key = envelopeEntityName(entityConstructor);\n const base: `/${string}` = options.basePath ?? `/api/${key}`;\n const mounted = new Set(options.verbs);\n\n const links: EntityRouteLinks = {\n record: id => {\n const href = `${base}/${String(id)}`;\n return [\n ...(mounted.has('get')\n ? [{ rel: 'self', href, method: 'GET' } as const]\n : []),\n ...(mounted.has('list')\n ? [{ rel: 'list', href: base, method: 'GET' } as const]\n : []),\n ...(mounted.has('update')\n ? [{ rel: 'update', href, method: 'PUT' } as const]\n : []),\n ...(mounted.has('delete')\n ? [{ rel: 'delete', href, method: 'DELETE' } as const]\n : []),\n ];\n },\n collection: () => [\n ...(mounted.has('list')\n ? [{ rel: 'self', href: base, method: 'GET' } as const]\n : []),\n ...(mounted.has('create')\n ? [{ rel: 'create', href: base, method: 'POST' } as const]\n : []),\n ],\n };\n\n const guardFor = (verb: EntityVerb): EntityRouteGuard => {\n const guard = options.guard as\n | EntityRouteGuard\n | { readonly read: EntityRouteGuard; readonly write: EntityRouteGuard };\n return 'kind' in guard ? guard : READS.has(verb) ? guard.read : guard.write;\n };\n\n const guarded = <A, E, RR>(\n verb: EntityVerb,\n use: (context: EntityRouteContext) => Effect.Effect<A, E, RR>,\n ) => {\n const guard = guardFor(verb);\n const permission = permissionForEntity(entityConstructor, ACTION[verb]);\n switch (guard.kind) {\n case 'public':\n return use({});\n case 'permission':\n return requirePermission(permission)(principal => use({ principal }));\n case 'organization':\n return requireOrganization(permission)((organizationId, principal) =>\n use({ organizationId, principal }),\n );\n case 'crossing':\n return guard.organization\n ? requireServiceCrossing(permission)(organizationId =>\n use({ organizationId }),\n )\n : requireCrossing(permission)(use({}));\n }\n };\n\n const repository = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const factory = yield* EntityRepositoryFactoryTag;\n return yield* factory.for(entityConstructor, {\n ...(context.organizationId === undefined\n ? {}\n : { organizationId: context.organizationId }),\n });\n });\n\n const scopeOf = (context: EntityRouteContext) =>\n options.scope ? options.scope(context) : Effect.succeed('all' as const);\n\n const readLoadRequest = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const search = new URL(request.url, 'http://localhost').searchParams;\n return yield* Effect.try({\n try: () =>\n parseLoadRequestParams(\n entityConstructor,\n search,\n ) as unknown as EntityLoadRequest<T>,\n catch: error => error as EntifixBuildError,\n });\n });\n\n const notFound = HttpServerResponse.json(\n {\n message: 'not found',\n code: 'notFound',\n entity: extractMetaEntity(entityConstructor).key,\n },\n { status: 404 },\n );\n\n const list = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const parsed = yield* readLoadRequest;\n const scope = yield* scopeOf(context);\n if (scope === 'none') {\n // Still parsed: whether a query is well-formed must not depend on who\n // asked.\n return yield* HttpServerResponse.json(\n makeEntityPageEnvelope(\n entityConstructor,\n { items: [], total: 0, request: parsed },\n links.collection(),\n ),\n );\n }\n // Conjoined, never substituted: the translator `$and`s every top-level\n // element, so an added element can only narrow, and no `or` in a query\n // string can escape it.\n const request: EntityLoadRequest<T> =\n scope === 'all'\n ? parsed\n : {\n ...parsed,\n filtering: [...(parsed.filtering ?? []), scope.filter],\n };\n const page = yield* loadUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(\n EntityLoadRequestTag,\n request as unknown as EntityLoadRequest,\n ),\n );\n return yield* HttpServerResponse.json(\n makeEntityPageEnvelope(entityConstructor, page, links.collection()),\n );\n }).pipe(\n Effect.catch(error => invalid(error, 'invalid query', 'invalidQuery')),\n );\n\n const get = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const scope = yield* scopeOf(context);\n if (scope === 'none') return yield* notFound;\n const found = yield* getUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityIdTag, params.id),\n Effect.option,\n );\n // Out of scope answers exactly as absent does: a `403` would confirm the\n // record exists to somebody who may not read it.\n if (\n found._tag === 'None' ||\n (scope !== 'all' && !scope.includes(found.value))\n ) {\n return yield* notFound;\n }\n return yield* HttpServerResponse.json(\n makeEntityEnvelope(\n entityConstructor,\n found.value,\n links.record(params.id),\n ),\n );\n }).pipe(Effect.catch(routeFailure));\n\n const save = (verb: 'create' | 'update') => (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* request.json;\n let entity = yield* readEntityEnvelope(entityConstructor, body);\n\n // On update the URL is authoritative, so a record cannot be renamed\n // onto another one by editing its payload.\n if (verb === 'update') {\n const params = yield* HttpRouter.params;\n entity.id = params.id;\n }\n if (options.prepare) {\n entity = yield* options.prepare(entity, { ...context, verb });\n }\n\n const saved = yield* saveUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityTag, entity),\n );\n return yield* HttpServerResponse.json(\n makeEntityEnvelope(entityConstructor, saved, links.record(saved.id)),\n );\n }).pipe(\n Effect.catch(error =>\n invalid(error, 'invalid request body', 'invalidBody'),\n ),\n );\n\n /**\n * Answers with an envelope rather than a bare `204`: every message between\n * entifix artifacts is one, and the fetch client parses every response as\n * JSON.\n */\n const remove = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n yield* deleteUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityIdTag, params.id),\n );\n return yield* HttpServerResponse.json({\n meta: { type: 'entity', entity: key, links: links.collection() },\n data: { id: params.id },\n });\n }).pipe(Effect.catch(routeFailure));\n\n const generic = (verb: EntityVerb, context: EntityRouteContext) => {\n switch (verb) {\n case 'list':\n return list(context);\n case 'get':\n return get(context);\n case 'create':\n return save('create')(context);\n case 'update':\n return save('update')(context);\n case 'delete':\n return remove(context);\n }\n };\n\n /**\n * Each guard asks for different services, so the branches do not unify on\n * their own; the cast states the union every guard this set can reach\n * actually needs.\n */\n const handlerFor = (verb: EntityVerb) =>\n guarded(verb, context => {\n const override = options.override?.[verb];\n return override ? override(context, links) : generic(verb, context);\n }) as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n unknown,\n | R\n | EntityRepositoryFactoryTag\n | GuardServices<G>\n | HttpServerRequest.HttpServerRequest\n | HttpRouter.RouteContext\n >;\n\n const ROUTES: Record<\n EntityVerb,\n { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; path: `/${string}` }\n > = {\n list: { method: 'GET', path: base },\n get: { method: 'GET', path: `${base}/:id` },\n create: { method: 'POST', path: base },\n update: { method: 'PUT', path: `${base}/:id` },\n delete: { method: 'DELETE', path: `${base}/:id` },\n };\n\n return HttpRouter.addAll([\n // A literal, so it wins over `/:id` regardless of order.\n HttpRouter.route(\n 'GET',\n `${base}/$metadata`,\n entityMetadataRoute(entityConstructor),\n ),\n ...options.verbs.map(verb =>\n HttpRouter.route(\n ROUTES[verb].method,\n ROUTES[verb].path,\n handlerFor(verb),\n ),\n ),\n ]);\n};\n"],"names":["permissionForEntity","deleteUCFactory","EntityIdTag","EntityLoadRequestTag","EntityRepositoryFactoryTag","EntityRepositoryTag","EntityTag","getUCFactory","loadUCFactory","saveUCFactory","EntifixBuildError","envelopeEntityName","extractMetaEntity","makeEntityEnvelope","makeEntityPageEnvelope","parseLoadRequestParams","readEntityEnvelope","Effect","HttpRouter","HttpServerRequest","HttpServerResponse","requireCrossing","requireOrganization","requirePermission","requireServiceCrossing","entityMetadataRoute","EntityRouteRefusal","status","code","detail","_tag","ACTION","list","get","create","update","delete","READS","Set","routeFailure","error","json","String","invalid","label","refusalCode","message","undefined","makeEntityRoutes","entityConstructor","options","key","base","basePath","mounted","verbs","links","record","id","href","has","rel","method","collection","guardFor","verb","guard","read","write","guarded","use","permission","kind","principal","organizationId","organization","repository","context","gen","factory","for","scopeOf","scope","succeed","readLoadRequest","request","search","URL","url","searchParams","try","catch","notFound","entity","parsed","items","total","filtering","filter","page","pipe","provideService","params","found","option","includes","value","save","body","prepare","saved","remove","meta","type","data","generic","handlerFor","override","ROUTES","path","addAll","route","map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/entity-routes.ts"],"sourcesContent":["import {\n type Action,\n permissionForEntity,\n type PolicyDecisionTag,\n type ServiceCrossingPolicyTag,\n} from '@entifix/authz';\nimport {\n deleteUCFactory,\n EntityIdTag,\n EntityLoadRequestTag,\n EntityRepositoryFactoryTag,\n EntityRepositoryTag,\n EntityTag,\n getUCFactory,\n loadUCFactory,\n type RepositoryScope,\n saveUCFactory,\n type TokenServiceTag,\n} from '@entifix/business';\nimport {\n EntifixBuildError,\n type EntifixEnvelopeLink,\n type Entity,\n type EntityConstructor,\n type EntityId,\n type EntityLoadRequest,\n envelopeEntityName,\n extractMetaEntity,\n type FilterGroup,\n makeEntityEnvelope,\n makeEntityPageEnvelope,\n parseLoadRequestParams,\n readEntityEnvelope,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/http';\n\nimport {\n type RequestPrincipal,\n requireCrossing,\n requireOrganization,\n requirePermission,\n requireServiceCrossing,\n type ServiceCrossingTokenTag,\n} from './auth/index.js';\nimport { entityMetadataRoute } from './entity-metadata-route.js';\n\n/** The five generic verbs a route set can mount. */\nexport type EntityVerb = 'list' | 'get' | 'create' | 'update' | 'delete';\n\n/**\n * Who may reach a route, and where the organization a per-organization store\n * needs comes from.\n *\n * - `public` — anybody; nothing is checked and no organization is known.\n * - `permission` — a verified session holding the permission the entity\n * derives for the verb.\n * - `organization` — the same, plus the session's active organization, which\n * is what a per-organization store opens.\n * - `crossing` — the fleet's crossing token and no session at all. With\n * `organization: true` the caller names the organization in a header, which\n * only a caller that has proved it is the fleet may do.\n */\nexport type EntityRouteGuard =\n | { readonly kind: 'public' }\n | { readonly kind: 'permission' }\n | { readonly kind: 'organization' }\n | { readonly kind: 'crossing'; readonly organization: boolean };\n\n/** What a route learned about its caller from its guard. */\nexport interface EntityRouteContext extends RepositoryScope {\n readonly principal?: RequestPrincipal;\n}\n\n/**\n * Which records of an entity a caller may see.\n *\n * `filter` narrows a list and `includes` decides a by-id read — both are\n * needed, because `id` is the one member no query can name. `'none'` is a\n * caller who holds the read grant and owns nothing: an empty page and a `404`,\n * never a `403`, because owning no records is not an error.\n */\nexport type EntityReadScope<T extends Entity> =\n | 'all'\n | 'none'\n | {\n readonly filter: FilterGroup<T>;\n readonly includes: (entity: T) => boolean;\n };\n\n/**\n * A save refused by a rule the entity's own deserializer does not make — a\n * closed set, a rule over the other records — answered as `status` with\n * `code`, which is what a client translates.\n */\nexport class EntityRouteRefusal {\n readonly _tag = 'EntityRouteRefusal';\n\n constructor(\n readonly status: 400 | 409 | 422,\n readonly code: string,\n readonly detail?: string,\n ) {}\n}\n\n/** A hand-written handler for one verb, mounted under the derived guard. */\nexport type EntityRouteOverride<R> = (\n context: EntityRouteContext,\n links: EntityRouteLinks,\n) => Effect.Effect<HttpServerResponse.HttpServerResponse, unknown, R>;\n\n/** The affordances a route set derives from the verbs it mounts. */\nexport interface EntityRouteLinks {\n readonly record: (id: EntityId) => EntifixEnvelopeLink[];\n readonly collection: () => EntifixEnvelopeLink[];\n}\n\nexport interface EntityRoutesOptions<\n T extends Entity,\n R,\n G extends EntityRouteGuard = EntityRouteGuard,\n> {\n readonly verbs: readonly EntityVerb[];\n /** One guard for every verb, or one for the reads and one for the writes. */\n readonly guard: G | { readonly read: G; readonly write: G };\n /** Defaults to `/api/<entity key>`. */\n readonly basePath?: `/${string}`;\n /** Narrows the reads to what this caller may see. */\n readonly scope?: (\n context: EntityRouteContext,\n ) => Effect.Effect<EntityReadScope<T>, never, R>;\n /**\n * Runs on every create and update, after the body is read and before the\n * save — and may change what is saved, or refuse it.\n */\n readonly prepare?: (\n entity: T,\n context: EntityRouteContext & { readonly verb: 'create' | 'update' },\n ) => Effect.Effect<T, EntityRouteRefusal | EntifixBuildError, R>;\n /**\n * A hand-written handler in place of the generic one for a verb. It keeps\n * the derived guard and receives the derived links, so the escape hatch\n * cannot drift from the rest of the set.\n */\n readonly override?: Partial<Record<EntityVerb, EntityRouteOverride<R>>>;\n}\n\nconst ACTION: Record<EntityVerb, Action> = {\n list: 'read',\n get: 'read',\n create: 'write',\n update: 'write',\n delete: 'delete',\n};\n\nconst READS: ReadonlySet<EntityVerb> = new Set(['list', 'get']);\n\n/**\n * What a route set needs from the host beyond the repository factory: a\n * session's services always (its `$metadata` reads the caller), the fleet's\n * crossing services only when a guard is a crossing.\n */\ntype GuardServices<G extends EntityRouteGuard> =\n | TokenServiceTag\n | PolicyDecisionTag\n | (G extends { readonly kind: 'crossing' }\n ? ServiceCrossingTokenTag | ServiceCrossingPolicyTag\n : never);\n\n/** The `500` for anything a route did not anticipate. */\nexport const routeFailure = (error: unknown) =>\n HttpServerResponse.json(\n { error: 'request failed', code: 'unexpected', detail: String(error) },\n { status: 500 },\n );\n\nconst invalid = (error: unknown, label: string, code: string) =>\n error instanceof EntifixBuildError\n ? HttpServerResponse.json(\n {\n error: label,\n code: refusalCode(error) ?? code,\n detail: error.message,\n },\n { status: 400 },\n )\n : error instanceof EntityRouteRefusal\n ? HttpServerResponse.json(\n {\n error: 'the record cannot be saved',\n code: error.code,\n ...(error.detail === undefined ? {} : { detail: error.detail }),\n },\n { status: error.status },\n )\n : routeFailure(error);\n\n/**\n * A domain error may carry its own code (`mediaNotHttps`), so the client can\n * say which rule failed rather than \"invalid body\".\n */\nconst refusalCode = (error: EntifixBuildError): string | undefined =>\n 'code' in error && typeof error.code === 'string' ? error.code : undefined;\n\n/**\n * The generic routes for one entity — list, read, create, update, delete and\n * its `$metadata` — derived from the entity and bound to storage through the\n * host's {@link EntityRepositoryFactoryTag}.\n *\n * What a route set varies on is stated, not written: which verbs exist, who may\n * reach them, which records a reader sees, and what a save must pass. What it\n * never varies on is the wire: the load request is parsed against the entity's\n * own metadata (so a query can only name a member the entity declared\n * filterable or sortable), every answer is an envelope, and the links name\n * exactly the verbs mounted — never one that would `404`.\n *\n * A path that is not one of these verbs is written by hand beside it, and a\n * verb whose generic form does not fit is replaced with `override`.\n */\nconst deriveEntityRoutes = <\n T extends Entity,\n R = never,\n // `never`, not the whole union: TypeScript infers no type argument once a\n // caller names one, so `makeEntityRoutes<T, R>(…)` used to default `G` to\n // every guard. Then the crossing tags became requirements of a route that\n // guards on a session, and the error surfaced at the host's `Layer.provide`\n // instead of here. Defaulting to `never` refuses that call at the guard\n // itself; `makeEntityRoutes.for<R>()` is the way to name `R` alone.\n const G extends EntityRouteGuard = never,\n>(\n entityConstructor: EntityConstructor<T>,\n options: EntityRoutesOptions<T, R, G>,\n) => {\n const key = envelopeEntityName(entityConstructor);\n const base: `/${string}` = options.basePath ?? `/api/${key}`;\n const mounted = new Set(options.verbs);\n\n const links: EntityRouteLinks = {\n record: id => {\n const href = `${base}/${String(id)}`;\n return [\n ...(mounted.has('get')\n ? [{ rel: 'self', href, method: 'GET' } as const]\n : []),\n ...(mounted.has('list')\n ? [{ rel: 'list', href: base, method: 'GET' } as const]\n : []),\n ...(mounted.has('update')\n ? [{ rel: 'update', href, method: 'PUT' } as const]\n : []),\n ...(mounted.has('delete')\n ? [{ rel: 'delete', href, method: 'DELETE' } as const]\n : []),\n ];\n },\n collection: () => [\n ...(mounted.has('list')\n ? [{ rel: 'self', href: base, method: 'GET' } as const]\n : []),\n ...(mounted.has('create')\n ? [{ rel: 'create', href: base, method: 'POST' } as const]\n : []),\n ],\n };\n\n const guardFor = (verb: EntityVerb): EntityRouteGuard => {\n const guard = options.guard as\n | EntityRouteGuard\n | { readonly read: EntityRouteGuard; readonly write: EntityRouteGuard };\n return 'kind' in guard ? guard : READS.has(verb) ? guard.read : guard.write;\n };\n\n const guarded = <A, E, RR>(\n verb: EntityVerb,\n use: (context: EntityRouteContext) => Effect.Effect<A, E, RR>,\n ) => {\n const guard = guardFor(verb);\n const permission = permissionForEntity(entityConstructor, ACTION[verb]);\n switch (guard.kind) {\n case 'public':\n return use({});\n case 'permission':\n return requirePermission(permission)(principal => use({ principal }));\n case 'organization':\n return requireOrganization(permission)((organizationId, principal) =>\n use({ organizationId, principal }),\n );\n case 'crossing':\n return guard.organization\n ? requireServiceCrossing(permission)(organizationId =>\n use({ organizationId }),\n )\n : requireCrossing(permission)(use({}));\n }\n };\n\n const repository = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const factory = yield* EntityRepositoryFactoryTag;\n return yield* factory.for(entityConstructor, {\n ...(context.organizationId === undefined\n ? {}\n : { organizationId: context.organizationId }),\n });\n });\n\n const scopeOf = (context: EntityRouteContext) =>\n options.scope ? options.scope(context) : Effect.succeed('all' as const);\n\n const readLoadRequest = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const search = new URL(request.url, 'http://localhost').searchParams;\n return yield* Effect.try({\n try: () =>\n parseLoadRequestParams(\n entityConstructor,\n search,\n ) as unknown as EntityLoadRequest<T>,\n catch: error => error as EntifixBuildError,\n });\n });\n\n const notFound = HttpServerResponse.json(\n {\n message: 'not found',\n code: 'notFound',\n entity: extractMetaEntity(entityConstructor).key,\n },\n { status: 404 },\n );\n\n const list = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const parsed = yield* readLoadRequest;\n const scope = yield* scopeOf(context);\n if (scope === 'none') {\n // Still parsed: whether a query is well-formed must not depend on who\n // asked.\n return yield* HttpServerResponse.json(\n makeEntityPageEnvelope(\n entityConstructor,\n { items: [], total: 0, request: parsed },\n links.collection(),\n ),\n );\n }\n // Conjoined, never substituted: the translator `$and`s every top-level\n // element, so an added element can only narrow, and no `or` in a query\n // string can escape it.\n const request: EntityLoadRequest<T> =\n scope === 'all'\n ? parsed\n : {\n ...parsed,\n filtering: [...(parsed.filtering ?? []), scope.filter],\n };\n const page = yield* loadUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(\n EntityLoadRequestTag,\n request as unknown as EntityLoadRequest,\n ),\n );\n return yield* HttpServerResponse.json(\n makeEntityPageEnvelope(entityConstructor, page, links.collection()),\n );\n }).pipe(\n Effect.catch(error => invalid(error, 'invalid query', 'invalidQuery')),\n );\n\n const get = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const scope = yield* scopeOf(context);\n if (scope === 'none') return yield* notFound;\n const found = yield* getUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityIdTag, params.id),\n Effect.option,\n );\n // Out of scope answers exactly as absent does: a `403` would confirm the\n // record exists to somebody who may not read it.\n if (\n found._tag === 'None' ||\n (scope !== 'all' && !scope.includes(found.value))\n ) {\n return yield* notFound;\n }\n return yield* HttpServerResponse.json(\n makeEntityEnvelope(\n entityConstructor,\n found.value,\n links.record(params.id),\n ),\n );\n }).pipe(Effect.catch(routeFailure));\n\n const save = (verb: 'create' | 'update') => (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* request.json;\n let entity = yield* readEntityEnvelope(entityConstructor, body);\n\n // On update the URL is authoritative, so a record cannot be renamed\n // onto another one by editing its payload.\n if (verb === 'update') {\n const params = yield* HttpRouter.params;\n entity.id = params.id;\n }\n if (options.prepare) {\n entity = yield* options.prepare(entity, { ...context, verb });\n }\n\n const saved = yield* saveUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityTag, entity),\n );\n return yield* HttpServerResponse.json(\n makeEntityEnvelope(entityConstructor, saved, links.record(saved.id)),\n );\n }).pipe(\n Effect.catch(error =>\n invalid(error, 'invalid request body', 'invalidBody'),\n ),\n );\n\n /**\n * Answers with an envelope rather than a bare `204`: every message between\n * entifix artifacts is one, and the fetch client parses every response as\n * JSON.\n */\n const remove = (context: EntityRouteContext) =>\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n yield* deleteUCFactory<T>().pipe(\n Effect.provideService(EntityRepositoryTag, yield* repository(context)),\n Effect.provideService(EntityIdTag, params.id),\n );\n return yield* HttpServerResponse.json({\n meta: { type: 'entity', entity: key, links: links.collection() },\n data: { id: params.id },\n });\n }).pipe(Effect.catch(routeFailure));\n\n const generic = (verb: EntityVerb, context: EntityRouteContext) => {\n switch (verb) {\n case 'list':\n return list(context);\n case 'get':\n return get(context);\n case 'create':\n return save('create')(context);\n case 'update':\n return save('update')(context);\n case 'delete':\n return remove(context);\n }\n };\n\n /**\n * Each guard asks for different services, so the branches do not unify on\n * their own; the cast states the union every guard this set can reach\n * actually needs.\n */\n const handlerFor = (verb: EntityVerb) =>\n guarded(verb, context => {\n const override = options.override?.[verb];\n return override ? override(context, links) : generic(verb, context);\n }) as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n unknown,\n | R\n | EntityRepositoryFactoryTag\n | GuardServices<G>\n | HttpServerRequest.HttpServerRequest\n | HttpRouter.RouteContext\n >;\n\n const ROUTES: Record<\n EntityVerb,\n { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; path: `/${string}` }\n > = {\n list: { method: 'GET', path: base },\n get: { method: 'GET', path: `${base}/:id` },\n create: { method: 'POST', path: base },\n update: { method: 'PUT', path: `${base}/:id` },\n delete: { method: 'DELETE', path: `${base}/:id` },\n };\n\n return HttpRouter.addAll([\n // A literal, so it wins over `/:id` regardless of order.\n HttpRouter.route(\n 'GET',\n `${base}/$metadata`,\n entityMetadataRoute(entityConstructor),\n ),\n ...options.verbs.map(verb =>\n HttpRouter.route(\n ROUTES[verb].method,\n ROUTES[verb].path,\n handlerFor(verb),\n ),\n ),\n ]);\n};\n\n/**\n * {@link deriveEntityRoutes}, with a curried form for a caller that has to\n * name the services its `override`, `scope` or `prepare` handlers need:\n * `makeEntityRoutes.for<R>()(Entity, options)` names `R` and still infers the\n * entity and the guard from the value, which a single call with explicit type\n * arguments cannot do.\n */\nexport const makeEntityRoutes = Object.assign(deriveEntityRoutes, {\n for:\n <R>() =>\n <T extends Entity, const G extends EntityRouteGuard>(\n entityConstructor: EntityConstructor<T>,\n options: EntityRoutesOptions<T, R, G>,\n ) =>\n deriveEntityRoutes<T, R, G>(entityConstructor, options),\n});\n"],"names":["permissionForEntity","deleteUCFactory","EntityIdTag","EntityLoadRequestTag","EntityRepositoryFactoryTag","EntityRepositoryTag","EntityTag","getUCFactory","loadUCFactory","saveUCFactory","EntifixBuildError","envelopeEntityName","extractMetaEntity","makeEntityEnvelope","makeEntityPageEnvelope","parseLoadRequestParams","readEntityEnvelope","Effect","HttpRouter","HttpServerRequest","HttpServerResponse","requireCrossing","requireOrganization","requirePermission","requireServiceCrossing","entityMetadataRoute","EntityRouteRefusal","status","code","detail","_tag","ACTION","list","get","create","update","delete","READS","Set","routeFailure","error","json","String","invalid","label","refusalCode","message","undefined","deriveEntityRoutes","entityConstructor","options","key","base","basePath","mounted","verbs","links","record","id","href","has","rel","method","collection","guardFor","verb","guard","read","write","guarded","use","permission","kind","principal","organizationId","organization","repository","context","gen","factory","for","scopeOf","scope","succeed","readLoadRequest","request","search","URL","url","searchParams","try","catch","notFound","entity","parsed","items","total","filtering","filter","page","pipe","provideService","params","found","option","includes","value","save","body","prepare","saved","remove","meta","type","data","generic","handlerFor","override","ROUTES","path","addAll","route","map","makeEntityRoutes","Object","assign"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entifix/service-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@swc/helpers": "0.5.23",
|
|
69
|
-
"@entifix/
|
|
70
|
-
"@entifix/
|
|
71
|
-
"@entifix/
|
|
72
|
-
"@entifix/
|
|
69
|
+
"@entifix/business": "0.8.0",
|
|
70
|
+
"@entifix/tooling": "0.8.0",
|
|
71
|
+
"@entifix/core": "0.8.0",
|
|
72
|
+
"@entifix/authz": "0.8.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@effect/opentelemetry": "4.0.5",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@opentelemetry/sdk-metrics": "2.12.0",
|
|
82
82
|
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
83
83
|
"effect": "4.0.5",
|
|
84
|
-
"@entifix/testing-unit": "0.
|
|
84
|
+
"@entifix/testing-unit": "0.8.0"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"@effect/opentelemetry": "^4.0.5",
|