@gaganref/confect-core 10.3.1-fork.46 → 10.3.1-fork.47

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"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"}
package/dist/Http.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import { mounts } from "./App.js";
3
+ import { scalarDocs } from "./internal/scalarDocs.js";
3
4
  import * as Predicate from "effect/Predicate";
4
5
  import * as Data from "effect/Data";
5
6
  import * as Result from "effect/Result";
6
7
  import * as SchemaAST from "effect/SchemaAST";
7
8
  import * as Layer from "effect/Layer";
8
9
  import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
9
- import * as HttpApiScalar from "effect/http-api/HttpApiScalar";
10
10
  //#region src/Http.ts
11
11
  var Http_exports = /* @__PURE__ */ __exportAll({
12
12
  DuplicateRoute: () => DuplicateRoute,
@@ -31,7 +31,7 @@ const TypeId = "~@confect/core/Http";
31
31
  */
32
32
  const make = (options) => {
33
33
  const api = HttpApiBuilder.layer(options.api).pipe(Layer.provide(options.handlers));
34
- const routes = options.docs === void 0 ? api : Layer.merge(api, HttpApiScalar.layerCdn(options.api, { path: options.docs }));
34
+ const routes = options.docs === void 0 ? api : Layer.merge(api, scalarDocs(options.api, options.docs));
35
35
  return {
36
36
  [TypeId]: TypeId,
37
37
  api: options.api,
package/dist/Http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Http.js","names":["mounts","App.mounts"],"sources":["../src/Http.ts"],"sourcesContent":["import * as Data from \"effect/Data\";\nimport * as Layer from \"effect/Layer\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Result from \"effect/Result\";\nimport type * as Schema from \"effect/Schema\";\nimport * as SchemaAST from \"effect/SchemaAST\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport * as HttpApiScalar from \"effect/http-api/HttpApiScalar\";\nimport * as App from \"./App.js\";\n\nconst TypeId = \"~@confect/core/Http\";\n\n/**\n * A backend's HTTP declaration: the app's own `HttpApi`, its handlers, and an\n * optional Scalar docs path. `routes` is the layer the generated\n * `convex/http.ts` serves. Mounted components serve their own routes.\n *\n * It does not reference `confect/app.ts`: `http.ts` runs in every HTTP action,\n * and `app.ts` imports component definitions that only load in Convex's config\n * analysis. Codegen checks these routes against the app's mounts.\n */\nexport interface Http<Api extends App.Api, Routes extends Layer.Any> {\n readonly [TypeId]: typeof TypeId;\n readonly api: Api;\n readonly docs: `/${string}` | undefined;\n readonly routes: Routes;\n}\n\nexport type Any = Http<App.Api, Layer.Any>;\n\n/**\n * Declare the backend's HTTP API, in `confect/http.ts`. Every route is an\n * `HttpApi` endpoint. `docs` serves a Scalar page for the api at that path.\n *\n * ```ts\n * export default Http.make({\n * api: TenantApi,\n * handlers: TenantHandlers,\n * docs: \"/docs\",\n * });\n * ```\n */\nexport const make = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n R,\n>(options: {\n readonly api: HttpApi.HttpApi<Id, Groups>;\n readonly handlers: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, never, R>;\n readonly docs?: `/${string}`;\n}) => {\n const api = HttpApiBuilder.layer(options.api).pipe(\n Layer.provide(options.handlers),\n );\n const routes =\n options.docs === undefined\n ? api\n : Layer.merge(\n api,\n HttpApiScalar.layerCdn(options.api, { path: options.docs }),\n );\n const http: Http<HttpApi.HttpApi<Id, Groups>, typeof routes> = {\n [TypeId]: TypeId,\n api: options.api,\n docs: options.docs,\n routes,\n };\n return http;\n};\n\nexport const isHttp = (u: unknown): u is Any =>\n Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId;\n\nexport type Owner =\n | { readonly kind: \"app\" }\n | { readonly kind: \"docs\" }\n | { readonly kind: \"mount\"; readonly mount: string };\n\nexport interface Route {\n readonly method: string;\n readonly path: string;\n readonly owner: Owner;\n}\n\nexport class RouteUnderMountPrefix extends Data.TaggedError(\n \"RouteUnderMountPrefix\",\n)<{\n readonly method: string;\n readonly path: string;\n readonly mount: string;\n readonly httpPrefix: string;\n}> {\n override get message(): string {\n return `The app route ${this.method} ${this.path} is under the HTTP prefix ${this.httpPrefix} of the mount \"${this.mount}\". Convex would let it shadow the component's routes; move it or change the prefix.`;\n }\n}\n\nexport class DuplicateRoute extends Data.TaggedError(\"DuplicateRoute\")<{\n readonly method: string;\n readonly path: string;\n readonly first: Owner;\n readonly second: Owner;\n}> {\n override get message(): string {\n return `${this.method} ${this.path} is declared twice (${ownerLabel(this.first)} and ${ownerLabel(this.second)}).`;\n }\n}\n\nexport class OverlappingMountPrefixes extends Data.TaggedError(\n \"OverlappingMountPrefixes\",\n)<{\n readonly first: { readonly mount: string; readonly httpPrefix: string };\n readonly second: { readonly mount: string; readonly httpPrefix: string };\n}> {\n override get message(): string {\n return `The mounts \"${this.first.mount}\" (${this.first.httpPrefix}) and \"${this.second.mount}\" (${this.second.httpPrefix}) have overlapping HTTP prefixes.`;\n }\n}\n\nexport type RouteError =\n | RouteUnderMountPrefix\n | DuplicateRoute\n | OverlappingMountPrefixes;\n\nconst ownerLabel = (owner: Owner): string =>\n owner.kind === \"mount\" ? `mount \"${owner.mount}\"` : owner.kind;\n\nconst trimSlash = (prefix: string): string =>\n prefix.length > 1 && prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n\nconst isUnder = (path: string, prefix: string): boolean =>\n prefix === \"/\" ||\n path === prefix ||\n path === `${prefix}/` ||\n path.startsWith(`${prefix}/`);\n\n/**\n * The parameter names an endpoint declares. As in Effect's router, a colon\n * segment is a parameter only when its name is declared; otherwise it is\n * literal text.\n */\nconst paramNames = (params: Schema.Top | undefined): ReadonlySet<string> => {\n if (params === undefined) return new Set();\n let ast = params.ast;\n while (ast.encoding !== undefined) {\n ast = ast.encoding[ast.encoding.length - 1].to;\n }\n return SchemaAST.isObjects(ast)\n ? new Set(ast.propertySignatures.map(({ name }) => String(name)))\n : new Set();\n};\n\ninterface KeyedRoute {\n readonly route: Route;\n /**\n * Method and path with declared parameter names erased.\n */\n readonly key: string;\n}\n\nconst apiRoutes = (\n api: App.Api,\n prefix: string,\n owner: Owner,\n): ReadonlyArray<KeyedRoute> =>\n Object.values<HttpApiGroup.Top>(api.groups).flatMap((group) =>\n Object.values(group.endpoints).map((endpoint) => {\n const path = prefix + endpoint.path;\n const declared = paramNames(endpoint.params);\n return {\n route: { method: endpoint.method, path, owner },\n key: `${endpoint.method} ${path.replaceAll(/:(\\w+)/g, (param, name: string) => (declared.has(name) ? \":\" : param))}`,\n };\n }),\n );\n\n/**\n * Every HTTP route the backend serves: the app's api, its docs page and each\n * mount's api under its prefix. Fails when an app route or the docs page is at\n * or under a mount's prefix (Convex accepts that and lets the app route\n * silently shadow the component's), when two routes share a method and path, or\n * when two mount prefixes overlap.\n */\nexport const routes = (\n app: App.Any,\n http?: Any,\n): Result.Result<ReadonlyArray<Route>, RouteError> => {\n const mounts = App.mounts(app).flatMap(({ name, httpPrefix, api }) =>\n httpPrefix === undefined\n ? []\n : [{ name, httpPrefix: trimSlash(httpPrefix), api }],\n );\n\n for (const [index, first] of mounts.entries()) {\n for (const second of mounts.slice(index + 1)) {\n if (\n isUnder(second.httpPrefix, first.httpPrefix) ||\n isUnder(first.httpPrefix, second.httpPrefix)\n ) {\n return Result.fail(\n new OverlappingMountPrefixes({\n first: { mount: first.name, httpPrefix: first.httpPrefix },\n second: { mount: second.name, httpPrefix: second.httpPrefix },\n }),\n );\n }\n }\n }\n\n const own: ReadonlyArray<KeyedRoute> =\n http === undefined\n ? []\n : [\n ...apiRoutes(http.api, \"\", { kind: \"app\" }),\n ...(http.docs === undefined\n ? []\n : [\n {\n route: {\n method: \"GET\",\n path: http.docs,\n owner: { kind: \"docs\" } as const,\n },\n key: `GET ${http.docs}`,\n },\n ]),\n ];\n\n for (const { route } of own) {\n const mount = mounts.find(({ httpPrefix }) =>\n isUnder(route.path, httpPrefix),\n );\n if (mount !== undefined) {\n return Result.fail(\n new RouteUnderMountPrefix({\n method: route.method,\n path: route.path,\n mount: mount.name,\n httpPrefix: mount.httpPrefix,\n }),\n );\n }\n }\n\n const all = [\n ...own,\n ...mounts.flatMap(({ name, httpPrefix, api }) =>\n api === undefined\n ? []\n : apiRoutes(api, httpPrefix, { kind: \"mount\", mount: name }),\n ),\n ];\n const seen = new Map<string, Route>();\n for (const { route, key } of all) {\n const previous = seen.get(key);\n if (previous !== undefined) {\n return Result.fail(\n new DuplicateRoute({\n method: route.method,\n path: route.path,\n first: previous.owner,\n second: route.owner,\n }),\n );\n }\n seen.set(key, route);\n }\n return Result.succeed(all.map(({ route }) => route));\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAYA,MAAM,SAAS;;;;;;;;;;;;;AAgCf,MAAa,QAIX,YAII;CACJ,MAAM,MAAM,eAAe,MAAM,QAAQ,GAAG,CAAC,CAAC,KAC5C,MAAM,QAAQ,QAAQ,QAAQ,CAChC;CACA,MAAM,SACJ,QAAQ,SAAS,KAAA,IACb,MACA,MAAM,MACJ,KACA,cAAc,SAAS,QAAQ,KAAK,EAAE,MAAM,QAAQ,KAAK,CAAC,CAC5D;CAON,OAAO;GALJ,SAAS;EACV,KAAK,QAAQ;EACb,MAAM,QAAQ;EACd;CAEQ;AACZ;AAEA,MAAa,UAAU,MACrB,UAAU,YAAY,GAAG,MAAM,KAAK,EAAE,YAAY;AAapD,IAAa,wBAAb,cAA2C,KAAK,YAC9C,uBACF,CAAC,CAKE;CACD,IAAa,UAAkB;EAC7B,OAAO,iBAAiB,KAAK,OAAO,GAAG,KAAK,KAAK,4BAA4B,KAAK,WAAW,iBAAiB,KAAK,MAAM;CAC3H;AACF;AAEA,IAAa,iBAAb,cAAoC,KAAK,YAAY,gBAAgB,CAAC,CAKnE;CACD,IAAa,UAAkB;EAC7B,OAAO,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK,sBAAsB,WAAW,KAAK,KAAK,EAAE,OAAO,WAAW,KAAK,MAAM,EAAE;CACjH;AACF;AAEA,IAAa,2BAAb,cAA8C,KAAK,YACjD,0BACF,CAAC,CAGE;CACD,IAAa,UAAkB;EAC7B,OAAO,eAAe,KAAK,MAAM,MAAM,KAAK,KAAK,MAAM,WAAW,SAAS,KAAK,OAAO,MAAM,KAAK,KAAK,OAAO,WAAW;CAC3H;AACF;AAOA,MAAM,cAAc,UAClB,MAAM,SAAS,UAAU,UAAU,MAAM,MAAM,KAAK,MAAM;AAE5D,MAAM,aAAa,WACjB,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI;AAEpE,MAAM,WAAW,MAAc,WAC7B,WAAW,OACX,SAAS,UACT,SAAS,GAAG,OAAO,MACnB,KAAK,WAAW,GAAG,OAAO,EAAE;;;;;;AAO9B,MAAM,cAAc,WAAwD;CAC1E,IAAI,WAAW,KAAA,GAAW,uBAAO,IAAI,IAAI;CACzC,IAAI,MAAM,OAAO;CACjB,OAAO,IAAI,aAAa,KAAA,GACtB,MAAM,IAAI,SAAS,IAAI,SAAS,SAAS,EAAE,CAAC;CAE9C,OAAO,UAAU,UAAU,GAAG,IAC1B,IAAI,IAAI,IAAI,mBAAmB,KAAK,EAAE,WAAW,OAAO,IAAI,CAAC,CAAC,oBAC9D,IAAI,IAAI;AACd;AAUA,MAAM,aACJ,KACA,QACA,UAEA,OAAO,OAAyB,IAAI,MAAM,CAAC,CAAC,SAAS,UACnD,OAAO,OAAO,MAAM,SAAS,CAAC,CAAC,KAAK,aAAa;CAC/C,MAAM,OAAO,SAAS,SAAS;CAC/B,MAAM,WAAW,WAAW,SAAS,MAAM;CAC3C,OAAO;EACL,OAAO;GAAE,QAAQ,SAAS;GAAQ;GAAM;EAAM;EAC9C,KAAK,GAAG,SAAS,OAAO,GAAG,KAAK,WAAW,YAAY,OAAO,SAAkB,SAAS,IAAI,IAAI,IAAI,MAAM,KAAM;CACnH;AACF,CAAC,CACH;;;;;;;;AASF,MAAa,UACX,KACA,SACoD;CACpD,MAAMA,WAASC,OAAW,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,YAAY,UAC1D,eAAe,KAAA,IACX,CAAC,IACD,CAAC;EAAE;EAAM,YAAY,UAAU,UAAU;EAAG;CAAI,CAAC,CACvD;CAEA,KAAK,MAAM,CAAC,OAAO,UAAUD,SAAO,QAAQ,GAC1C,KAAK,MAAM,UAAUA,SAAO,MAAM,QAAQ,CAAC,GACzC,IACE,QAAQ,OAAO,YAAY,MAAM,UAAU,KAC3C,QAAQ,MAAM,YAAY,OAAO,UAAU,GAE3C,OAAO,OAAO,KACZ,IAAI,yBAAyB;EAC3B,OAAO;GAAE,OAAO,MAAM;GAAM,YAAY,MAAM;EAAW;EACzD,QAAQ;GAAE,OAAO,OAAO;GAAM,YAAY,OAAO;EAAW;CAC9D,CAAC,CACH;CAKN,MAAM,MACJ,SAAS,KAAA,IACL,CAAC,IACD,CACE,GAAG,UAAU,KAAK,KAAK,IAAI,EAAE,MAAM,MAAM,CAAC,GAC1C,GAAI,KAAK,SAAS,KAAA,IACd,CAAC,IACD,CACE;EACE,OAAO;GACL,QAAQ;GACR,MAAM,KAAK;GACX,OAAO,EAAE,MAAM,OAAO;EACxB;EACA,KAAK,OAAO,KAAK;CACnB,CACF,CACN;CAEN,KAAK,MAAM,EAAE,WAAW,KAAK;EAC3B,MAAM,QAAQA,SAAO,MAAM,EAAE,iBAC3B,QAAQ,MAAM,MAAM,UAAU,CAChC;EACA,IAAI,UAAU,KAAA,GACZ,OAAO,OAAO,KACZ,IAAI,sBAAsB;GACxB,QAAQ,MAAM;GACd,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,YAAY,MAAM;EACpB,CAAC,CACH;CAEJ;CAEA,MAAM,MAAM,CACV,GAAG,KACH,GAAGA,SAAO,SAAS,EAAE,MAAM,YAAY,UACrC,QAAQ,KAAA,IACJ,CAAC,IACD,UAAU,KAAK,YAAY;EAAE,MAAM;EAAS,OAAO;CAAK,CAAC,CAC/D,CACF;CACA,MAAM,uBAAO,IAAI,IAAmB;CACpC,KAAK,MAAM,EAAE,OAAO,SAAS,KAAK;EAChC,MAAM,WAAW,KAAK,IAAI,GAAG;EAC7B,IAAI,aAAa,KAAA,GACf,OAAO,OAAO,KACZ,IAAI,eAAe;GACjB,QAAQ,MAAM;GACd,MAAM,MAAM;GACZ,OAAO,SAAS;GAChB,QAAQ,MAAM;EAChB,CAAC,CACH;EAEF,KAAK,IAAI,KAAK,KAAK;CACrB;CACA,OAAO,OAAO,QAAQ,IAAI,KAAK,EAAE,YAAY,KAAK,CAAC;AACrD"}
1
+ {"version":3,"file":"Http.js","names":["mounts","App.mounts"],"sources":["../src/Http.ts"],"sourcesContent":["import * as Data from \"effect/Data\";\nimport * as Layer from \"effect/Layer\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Result from \"effect/Result\";\nimport type * as Schema from \"effect/Schema\";\nimport * as SchemaAST from \"effect/SchemaAST\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport { scalarDocs } from \"./internal/scalarDocs.js\";\nimport * as App from \"./App.js\";\n\nconst TypeId = \"~@confect/core/Http\";\n\n/**\n * A backend's HTTP declaration: the app's own `HttpApi`, its handlers, and an\n * optional Scalar docs path. `routes` is the layer the generated\n * `convex/http.ts` serves. Mounted components serve their own routes.\n *\n * It does not reference `confect/app.ts`: `http.ts` runs in every HTTP action,\n * and `app.ts` imports component definitions that only load in Convex's config\n * analysis. Codegen checks these routes against the app's mounts.\n */\nexport interface Http<Api extends App.Api, Routes extends Layer.Any> {\n readonly [TypeId]: typeof TypeId;\n readonly api: Api;\n readonly docs: `/${string}` | undefined;\n readonly routes: Routes;\n}\n\nexport type Any = Http<App.Api, Layer.Any>;\n\n/**\n * Declare the backend's HTTP API, in `confect/http.ts`. Every route is an\n * `HttpApi` endpoint. `docs` serves a Scalar page for the api at that path.\n *\n * ```ts\n * export default Http.make({\n * api: TenantApi,\n * handlers: TenantHandlers,\n * docs: \"/docs\",\n * });\n * ```\n */\nexport const make = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n R,\n>(options: {\n readonly api: HttpApi.HttpApi<Id, Groups>;\n readonly handlers: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, never, R>;\n readonly docs?: `/${string}`;\n}) => {\n const api = HttpApiBuilder.layer(options.api).pipe(\n Layer.provide(options.handlers),\n );\n const routes =\n options.docs === undefined\n ? api\n : Layer.merge(api, scalarDocs(options.api, options.docs));\n const http: Http<HttpApi.HttpApi<Id, Groups>, typeof routes> = {\n [TypeId]: TypeId,\n api: options.api,\n docs: options.docs,\n routes,\n };\n return http;\n};\n\nexport const isHttp = (u: unknown): u is Any =>\n Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId;\n\nexport type Owner =\n | { readonly kind: \"app\" }\n | { readonly kind: \"docs\" }\n | { readonly kind: \"mount\"; readonly mount: string };\n\nexport interface Route {\n readonly method: string;\n readonly path: string;\n readonly owner: Owner;\n}\n\nexport class RouteUnderMountPrefix extends Data.TaggedError(\n \"RouteUnderMountPrefix\",\n)<{\n readonly method: string;\n readonly path: string;\n readonly mount: string;\n readonly httpPrefix: string;\n}> {\n override get message(): string {\n return `The app route ${this.method} ${this.path} is under the HTTP prefix ${this.httpPrefix} of the mount \"${this.mount}\". Convex would let it shadow the component's routes; move it or change the prefix.`;\n }\n}\n\nexport class DuplicateRoute extends Data.TaggedError(\"DuplicateRoute\")<{\n readonly method: string;\n readonly path: string;\n readonly first: Owner;\n readonly second: Owner;\n}> {\n override get message(): string {\n return `${this.method} ${this.path} is declared twice (${ownerLabel(this.first)} and ${ownerLabel(this.second)}).`;\n }\n}\n\nexport class OverlappingMountPrefixes extends Data.TaggedError(\n \"OverlappingMountPrefixes\",\n)<{\n readonly first: { readonly mount: string; readonly httpPrefix: string };\n readonly second: { readonly mount: string; readonly httpPrefix: string };\n}> {\n override get message(): string {\n return `The mounts \"${this.first.mount}\" (${this.first.httpPrefix}) and \"${this.second.mount}\" (${this.second.httpPrefix}) have overlapping HTTP prefixes.`;\n }\n}\n\nexport type RouteError =\n | RouteUnderMountPrefix\n | DuplicateRoute\n | OverlappingMountPrefixes;\n\nconst ownerLabel = (owner: Owner): string =>\n owner.kind === \"mount\" ? `mount \"${owner.mount}\"` : owner.kind;\n\nconst trimSlash = (prefix: string): string =>\n prefix.length > 1 && prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n\nconst isUnder = (path: string, prefix: string): boolean =>\n prefix === \"/\" ||\n path === prefix ||\n path === `${prefix}/` ||\n path.startsWith(`${prefix}/`);\n\n/**\n * The parameter names an endpoint declares. As in Effect's router, a colon\n * segment is a parameter only when its name is declared; otherwise it is\n * literal text.\n */\nconst paramNames = (params: Schema.Top | undefined): ReadonlySet<string> => {\n if (params === undefined) return new Set();\n let ast = params.ast;\n while (ast.encoding !== undefined) {\n ast = ast.encoding[ast.encoding.length - 1].to;\n }\n return SchemaAST.isObjects(ast)\n ? new Set(ast.propertySignatures.map(({ name }) => String(name)))\n : new Set();\n};\n\ninterface KeyedRoute {\n readonly route: Route;\n /**\n * Method and path with declared parameter names erased.\n */\n readonly key: string;\n}\n\nconst apiRoutes = (\n api: App.Api,\n prefix: string,\n owner: Owner,\n): ReadonlyArray<KeyedRoute> =>\n Object.values<HttpApiGroup.Top>(api.groups).flatMap((group) =>\n Object.values(group.endpoints).map((endpoint) => {\n const path = prefix + endpoint.path;\n const declared = paramNames(endpoint.params);\n return {\n route: { method: endpoint.method, path, owner },\n key: `${endpoint.method} ${path.replaceAll(/:(\\w+)/g, (param, name: string) => (declared.has(name) ? \":\" : param))}`,\n };\n }),\n );\n\n/**\n * Every HTTP route the backend serves: the app's api, its docs page and each\n * mount's api under its prefix. Fails when an app route or the docs page is at\n * or under a mount's prefix (Convex accepts that and lets the app route\n * silently shadow the component's), when two routes share a method and path, or\n * when two mount prefixes overlap.\n */\nexport const routes = (\n app: App.Any,\n http?: Any,\n): Result.Result<ReadonlyArray<Route>, RouteError> => {\n const mounts = App.mounts(app).flatMap(({ name, httpPrefix, api }) =>\n httpPrefix === undefined\n ? []\n : [{ name, httpPrefix: trimSlash(httpPrefix), api }],\n );\n\n for (const [index, first] of mounts.entries()) {\n for (const second of mounts.slice(index + 1)) {\n if (\n isUnder(second.httpPrefix, first.httpPrefix) ||\n isUnder(first.httpPrefix, second.httpPrefix)\n ) {\n return Result.fail(\n new OverlappingMountPrefixes({\n first: { mount: first.name, httpPrefix: first.httpPrefix },\n second: { mount: second.name, httpPrefix: second.httpPrefix },\n }),\n );\n }\n }\n }\n\n const own: ReadonlyArray<KeyedRoute> =\n http === undefined\n ? []\n : [\n ...apiRoutes(http.api, \"\", { kind: \"app\" }),\n ...(http.docs === undefined\n ? []\n : [\n {\n route: {\n method: \"GET\",\n path: http.docs,\n owner: { kind: \"docs\" } as const,\n },\n key: `GET ${http.docs}`,\n },\n ]),\n ];\n\n for (const { route } of own) {\n const mount = mounts.find(({ httpPrefix }) =>\n isUnder(route.path, httpPrefix),\n );\n if (mount !== undefined) {\n return Result.fail(\n new RouteUnderMountPrefix({\n method: route.method,\n path: route.path,\n mount: mount.name,\n httpPrefix: mount.httpPrefix,\n }),\n );\n }\n }\n\n const all = [\n ...own,\n ...mounts.flatMap(({ name, httpPrefix, api }) =>\n api === undefined\n ? []\n : apiRoutes(api, httpPrefix, { kind: \"mount\", mount: name }),\n ),\n ];\n const seen = new Map<string, Route>();\n for (const { route, key } of all) {\n const previous = seen.get(key);\n if (previous !== undefined) {\n return Result.fail(\n new DuplicateRoute({\n method: route.method,\n path: route.path,\n first: previous.owner,\n second: route.owner,\n }),\n );\n }\n seen.set(key, route);\n }\n return Result.succeed(all.map(({ route }) => route));\n};\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import * as HttpRouter from "effect/http/HttpRouter";
2
+ import type * as HttpApi from "effect/http-api/HttpApi";
3
+ import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
4
+ export declare const scalarDocs: <Id extends string, Groups extends HttpApiGroup.Constraint>(api: HttpApi.HttpApi<Id, Groups>, path: `/${string}`) => import("effect/Layer").Layer<never, never, HttpRouter.HttpRouter>;
5
+ //# sourceMappingURL=scalarDocs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalarDocs.d.ts","sourceRoot":"","sources":["../../src/internal/scalarDocs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AAErD,OAAO,KAAK,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAuBlE,eAAO,MAAM,UAAU,GACrB,EAAE,SAAS,MAAM,EACjB,MAAM,SAAS,YAAY,CAAC,UAAU,OAEjC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,QAC1B,IAAI,MAAM,EAAE,sEAuChB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as HttpRouter from "effect/http/HttpRouter";
3
+ import * as HttpServerResponse from "effect/http/HttpServerResponse";
4
+ import * as OpenApi from "effect/http-api/OpenApi";
5
+ //#region src/internal/scalarDocs.ts
6
+ const escape = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
7
+ const escapeAttribute = (value) => escape(value).replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
8
+ const escapeJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
9
+ const scalarDocs = (api, path) => HttpRouter.use((router) => {
10
+ let response;
11
+ return router.add("GET", path, Effect.sync(() => {
12
+ if (response !== void 0) return response;
13
+ const spec = OpenApi.fromApi(api);
14
+ const description = (tag) => !spec.info.description ? "" : `<meta name="${tag}" content="${escapeAttribute(spec.info.description)}"/>`;
15
+ response = HttpServerResponse.html(`<!doctype html>
16
+ <html>
17
+ <head>
18
+ <meta charset="utf-8" />
19
+ <title>${escape(spec.info.title)}</title>
20
+ ${description("description")}
21
+ ${description("og:description")}
22
+ <meta
23
+ name="viewport"
24
+ content="width=device-width, initial-scale=1" />
25
+ </head>
26
+ <body>
27
+ <div id="api-reference-container"></div>
28
+ <script src="${escapeAttribute("https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest/dist/browser/standalone.min.js")}" crossorigin><\/script>
29
+ <script>
30
+ window.Scalar.createApiReference(document.getElementById('api-reference-container'), {
31
+ ...${escapeJson({ _integration: "html" })},
32
+ content: ${escapeJson(spec)}
33
+ })
34
+ <\/script>
35
+ </body>
36
+ </html>`);
37
+ return response;
38
+ }));
39
+ });
40
+ //#endregion
41
+ export { scalarDocs };
42
+
43
+ //# sourceMappingURL=scalarDocs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalarDocs.js","names":[],"sources":["../../src/internal/scalarDocs.ts"],"sourcesContent":["import * as Effect from \"effect/Effect\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport * as OpenApi from \"effect/http-api/OpenApi\";\n\nconst escape = (value: string) =>\n value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n\nconst escapeAttribute = (value: string) =>\n escape(value).replaceAll('\"', \"&quot;\").replaceAll(\"'\", \"&#39;\");\n\nconst escapeJson = (value: unknown) =>\n JSON.stringify(value)\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n\n// WORKAROUND: Effect 4.0.6's HttpApiScalar module imports its inline Scalar\n// script dynamically, and Convex's bundler then splits a schema that reaches it\n// into an imported chunk, which Convex refuses. This copies `layerCdn`'s page\n// byte for byte. Remove when `checkSchemaBundle.mjs` passes with\n// `HttpApiScalar.layerCdn` restored.\nexport const scalarDocs = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n>(\n api: HttpApi.HttpApi<Id, Groups>,\n path: `/${string}`,\n) =>\n HttpRouter.use((router) => {\n let response: HttpServerResponse.HttpServerResponse | undefined;\n return router.add(\n \"GET\",\n path,\n Effect.sync(() => {\n if (response !== undefined) return response;\n const spec = OpenApi.fromApi(api);\n const description = (tag: string) =>\n !spec.info.description\n ? \"\"\n : `<meta name=\"${tag}\" content=\"${escapeAttribute(spec.info.description)}\"/>`;\n response = HttpServerResponse.html(`<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <title>${escape(spec.info.title)}</title>\n ${description(\"description\")}\n ${description(\"og:description\")}\n <meta\n name=\"viewport\"\n content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <div id=\"api-reference-container\"></div>\n <script src=\"${escapeAttribute(\"https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest/dist/browser/standalone.min.js\")}\" crossorigin></script>\n <script>\n window.Scalar.createApiReference(document.getElementById('api-reference-container'), {\n ...${escapeJson({ _integration: \"html\" })},\n content: ${escapeJson(spec)}\n })\n </script>\n </body>\n</html>`);\n return response;\n }),\n );\n });\n"],"mappings":";;;;;AAOA,MAAM,UAAU,UACd,MACG,WAAW,KAAK,OAAO,CAAC,CACxB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,KAAK,MAAM;AAE3B,MAAM,mBAAmB,UACvB,OAAO,KAAK,CAAC,CAAC,WAAW,MAAK,QAAQ,CAAC,CAAC,WAAW,KAAK,OAAO;AAEjE,MAAM,cAAc,UAClB,KAAK,UAAU,KAAK,CAAC,CAClB,WAAW,KAAK,SAAS,CAAC,CAC1B,WAAW,UAAU,SAAS,CAAC,CAC/B,WAAW,UAAU,SAAS;AAOnC,MAAa,cAIX,KACA,SAEA,WAAW,KAAK,WAAW;CACzB,IAAI;CACJ,OAAO,OAAO,IACZ,OACA,MACA,OAAO,WAAW;EAChB,IAAI,aAAa,KAAA,GAAW,OAAO;EACnC,MAAM,OAAO,QAAQ,QAAQ,GAAG;EAChC,MAAM,eAAe,QACnB,CAAC,KAAK,KAAK,cACP,KACA,eAAe,IAAI,aAAa,gBAAgB,KAAK,KAAK,WAAW,EAAE;EAC7E,WAAW,mBAAmB,KAAK;;;;aAI9B,OAAO,KAAK,KAAK,KAAK,EAAE;MAC/B,YAAY,aAAa,EAAE;MAC3B,YAAY,gBAAgB,EAAE;;;;;;;mBAOjB,gBAAgB,0FAA0F,EAAE;;;aAGlH,WAAW,EAAE,cAAc,OAAO,CAAC,EAAE;mBAC/B,WAAW,IAAI,EAAE;;;;QAI5B;EACA,OAAO;CACT,CAAC,CACH;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaganref/confect-core",
3
- "version": "10.3.1-fork.46",
3
+ "version": "10.3.1-fork.47",
4
4
  "peerDependencies": {
5
5
  "convex": "^1.48.0",
6
6
  "effect": ">=4.0.0 <4.1.0"
package/src/Http.ts CHANGED
@@ -7,7 +7,7 @@ import * as SchemaAST from "effect/SchemaAST";
7
7
  import type * as HttpApi from "effect/http-api/HttpApi";
8
8
  import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
9
9
  import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
10
- import * as HttpApiScalar from "effect/http-api/HttpApiScalar";
10
+ import { scalarDocs } from "./internal/scalarDocs.js";
11
11
  import * as App from "./App.js";
12
12
 
13
13
  const TypeId = "~@confect/core/Http";
@@ -57,10 +57,7 @@ export const make = <
57
57
  const routes =
58
58
  options.docs === undefined
59
59
  ? api
60
- : Layer.merge(
61
- api,
62
- HttpApiScalar.layerCdn(options.api, { path: options.docs }),
63
- );
60
+ : Layer.merge(api, scalarDocs(options.api, options.docs));
64
61
  const http: Http<HttpApi.HttpApi<Id, Groups>, typeof routes> = {
65
62
  [TypeId]: TypeId,
66
63
  api: options.api,
@@ -0,0 +1,72 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as HttpRouter from "effect/http/HttpRouter";
3
+ import * as HttpServerResponse from "effect/http/HttpServerResponse";
4
+ import type * as HttpApi from "effect/http-api/HttpApi";
5
+ import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
6
+ import * as OpenApi from "effect/http-api/OpenApi";
7
+
8
+ const escape = (value: string) =>
9
+ value
10
+ .replaceAll("&", "&amp;")
11
+ .replaceAll("<", "&lt;")
12
+ .replaceAll(">", "&gt;");
13
+
14
+ const escapeAttribute = (value: string) =>
15
+ escape(value).replaceAll('"', "&quot;").replaceAll("'", "&#39;");
16
+
17
+ const escapeJson = (value: unknown) =>
18
+ JSON.stringify(value)
19
+ .replaceAll("<", "\\u003c")
20
+ .replaceAll("\u2028", "\\u2028")
21
+ .replaceAll("\u2029", "\\u2029");
22
+
23
+ // WORKAROUND: Effect 4.0.6's HttpApiScalar module imports its inline Scalar
24
+ // script dynamically, and Convex's bundler then splits a schema that reaches it
25
+ // into an imported chunk, which Convex refuses. This copies `layerCdn`'s page
26
+ // byte for byte. Remove when `checkSchemaBundle.mjs` passes with
27
+ // `HttpApiScalar.layerCdn` restored.
28
+ export const scalarDocs = <
29
+ Id extends string,
30
+ Groups extends HttpApiGroup.Constraint,
31
+ >(
32
+ api: HttpApi.HttpApi<Id, Groups>,
33
+ path: `/${string}`,
34
+ ) =>
35
+ HttpRouter.use((router) => {
36
+ let response: HttpServerResponse.HttpServerResponse | undefined;
37
+ return router.add(
38
+ "GET",
39
+ path,
40
+ Effect.sync(() => {
41
+ if (response !== undefined) return response;
42
+ const spec = OpenApi.fromApi(api);
43
+ const description = (tag: string) =>
44
+ !spec.info.description
45
+ ? ""
46
+ : `<meta name="${tag}" content="${escapeAttribute(spec.info.description)}"/>`;
47
+ response = HttpServerResponse.html(`<!doctype html>
48
+ <html>
49
+ <head>
50
+ <meta charset="utf-8" />
51
+ <title>${escape(spec.info.title)}</title>
52
+ ${description("description")}
53
+ ${description("og:description")}
54
+ <meta
55
+ name="viewport"
56
+ content="width=device-width, initial-scale=1" />
57
+ </head>
58
+ <body>
59
+ <div id="api-reference-container"></div>
60
+ <script src="${escapeAttribute("https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest/dist/browser/standalone.min.js")}" crossorigin></script>
61
+ <script>
62
+ window.Scalar.createApiReference(document.getElementById('api-reference-container'), {
63
+ ...${escapeJson({ _integration: "html" })},
64
+ content: ${escapeJson(spec)}
65
+ })
66
+ </script>
67
+ </body>
68
+ </html>`);
69
+ return response;
70
+ }),
71
+ );
72
+ });