@adhd/apigen-engine-naming 0.2.2 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.2.4 (2026-09-26)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **vite-plugins:** absorb perf/test-resolve-fix — test-time @adhd/* source resolution ([3e344506](https://github.com/PseudoSky/adhd/commit/3e344506))
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **apigen-engine-naming:** close review doc/lint residuals ([c7c9fd45](https://github.com/PseudoSky/adhd/commit/c7c9fd45))
10
+ - **apigen:** close codegen-injection review residuals (cd9a5d6c) ([9acbe746](https://github.com/PseudoSky/adhd/commit/9acbe746))
11
+ - **apigen:** harden fastify/express codegen against injection (ff1b22ad, 61ecfab8) ([de2a5948](https://github.com/PseudoSky/adhd/commit/de2a5948))
12
+ - **vite:** restore import.meta.url in CJS output under vite 8 ([7916e639](https://github.com/PseudoSky/adhd/commit/7916e639))
13
+ - **nx:** finish the ESLint v9 flat-config migration and unblock the gate ([53f4ff3e](https://github.com/PseudoSky/adhd/commit/53f4ff3e))
14
+
15
+ ### ❤️ Thank You
16
+
17
+ - pseudosky
18
+
19
+ ## 0.2.3 (2026-09-24)
20
+
21
+ This was a version bump only for apigen-engine-naming to align it with other projects, there were no code changes.
22
+
1
23
  ## 0.2.2 (2026-08-07)
2
24
 
3
25
 
package/README.md CHANGED
@@ -9,10 +9,30 @@ Part of [apigen](../README.md).
9
9
  ## Public API
10
10
 
11
11
  ```ts
12
- import { toKebab, toCamel, toPascal, toSnake, normalizeFileName, project, checkCollisions, CollisionDetectedError, envelopeKey, envelopeCliFlag, envelopeEnvVar, envelopeMetaKey } from '@adhd/apigen-engine-naming';
12
+ import {
13
+ toKebab, toCamel, toPascal, toSnake, normalizeFileName,
14
+ project, httpVerb, checkCollisions, CollisionDetectedError,
15
+ envelopeKey, envelopeCliFlag, envelopeEnvVar, envelopeMetaKey,
16
+ sanitizeIdentifier, uniqueSanitizedIdentifiers,
17
+ escapeStringLiteral, escapeLineTerminators, toPosixPath, coercePort,
18
+ } from '@adhd/apigen-engine-naming';
13
19
  ```
14
20
 
15
21
  - **case helpers** — `toKebab` / `toCamel` / `toPascal` / `toSnake`, `normalizeFileName`.
16
22
  - **`project(...)`** — derive the namespace/id for a source.
23
+ - **`httpVerb(...)`** — resolve the effective HTTP verb for a composed schema.
17
24
  - **`checkCollisions(...)`** — detect duplicate export ids (throws `CollisionDetectedError`).
18
25
  - **envelope keys** — map an envelope field to its CLI flag / env var / MCP `_meta` key.
26
+ - **codegen emit primitives** (the single, context-correct surface every generator splices a
27
+ dynamic value through — see `src/lib/emit.ts`):
28
+ - `sanitizeIdentifier(id)` — a discovered id → a valid identifier.
29
+ - `uniqueSanitizedIdentifiers(ids)` — a collision-free **set** of identifiers (a lossy
30
+ sanitisation can map distinct ids to the same name; this disambiguates with a stable
31
+ `_2`, `_3`, … suffix so generated `import * as …_ns` declarations never collide).
32
+ - `escapeStringLiteral(value)` — a complete, quoted JS/TS string literal (escapes quotes,
33
+ backslashes, control chars, and `U+2028`/`U+2029`).
34
+ - `escapeLineTerminators(text)` — the `U+2028`/`U+2029` escape applied to an already-
35
+ serialized blob (e.g. a `JSON.stringify`'d schema map) before it is spliced.
36
+ - `toPosixPath(p)` — normalise a path to `/` separators.
37
+ - `coercePort(value)` — a caller-supplied `port` option → a validated integer literal
38
+ (rejects blank/non-numeric/out-of-range values instead of splicing them).
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { toKebab, toCamel, toPascal, toSnake, normalizeFileName, project, httpVerb, checkCollisions, CollisionDetectedError, envelopeKey, envelopeCliFlag, envelopeEnvVar, envelopeMetaKey, sanitizeIdentifier, } from './lib/naming';
1
+ export { toKebab, toCamel, toPascal, toSnake, normalizeFileName, project, httpVerb, checkCollisions, CollisionDetectedError, envelopeKey, envelopeCliFlag, envelopeEnvVar, envelopeMetaKey, sanitizeIdentifier, uniqueSanitizedIdentifiers, } from './lib/naming';
2
+ export { escapeLineTerminators, escapeStringLiteral, toPosixPath, coercePort, } from './lib/emit';
2
3
  export type { TransportProjection, HttpVerb, ProjectionConfig, CollisionError, } from './lib/naming';
package/index.js CHANGED
@@ -1,3 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require("ts-morph");require("node:fs");require("node:path");require("ts-json-schema-generator");const y=new Set(["string","number","boolean","integer"]);function P(e){if(typeof e!="object"||e===null)return!1;const t=e;if(t.$ref!==void 0||t.oneOf||t.anyOf||t.allOf)return!1;const{type:n}=t;return typeof n=="string"?y.has(n):Array.isArray(n)?n.every(r=>y.has(r)||r==="null"):Array.isArray(t.enum)&&t.enum.length>0?t.enum.every(r=>["string","number","boolean"].includes(typeof r)):!1}function C(e){if(!e||typeof e!="object")return!1;const t=e.properties;return t===void 0||typeof t!="object"||t===null?!1:Object.values(t).every(P)}const x={"decimal.js":"Decimal"};function _(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}Object.entries(x).map(([e,t])=>({pattern:new RegExp(`import\\(["'][^"']*${_(e)}[^"']*["']\\)\\.\\w+`,"g"),key:t}));function h(e){return e.words.join("-")}function k(e){return e.words.map((t,n)=>n===0?t:E(t)).join("")}function v(e){return e.words.map(E).join("")}function $(e){return e.words.join("_")}function w(e){return e.replace(/\.[^.]+$/,"").replace(/[._]+/g,"-")}function b(e,t={}){var l,u;const n=[e.namespace,...e.path],r=e.safe||C(e.input)?"GET":"POST",a=((u=(l=t.http)==null?void 0:l.verb)==null?void 0:u[e.id])??r,p="/"+n.map(s=>h(s)).join("/"),c=n.map(s=>$(s)).join("_"),i=n.map(s=>$(s)).slice(0,-1).join("."),g=v(n[n.length-2]??n[0]),f=v(n[n.length-1]),d=n.map(s=>h(s));return{http:{verb:a,route:p},mcp:{name:c},grpc:{package:i,service:g,method:f},cli:{path:d}}}function K(e,t,n){var a,p;const r=(p=(a=n.http)==null?void 0:a.verb)==null?void 0:p[e];return r==="GET"||r==="POST"?r:t["x-apigen-safe"]?"GET":"POST"}class j extends Error{constructor(t){const n=t.map(r=>` [${r.transport}] "${r.target}" ← ${r.ids[0]} vs ${r.ids[1]}`);super(`apigen-naming: ${t.length} projection collision(s) detected:
2
- ${n.join(`
3
- `)}`),this.name="CollisionDetectedError",this.collisions=t}}function M(e,t={}){const n=new Map,r=new Map,a=new Map,p=new Map,c=[];for(const o of e){const i=b(o,t),g=`${i.http.verb} ${i.http.route}`,f=n.get(g);f!==void 0&&f!==o.id?c.push({transport:"http",target:g,ids:[f,o.id]}):n.set(g,o.id);const d=r.get(i.mcp.name);d!==void 0&&d!==o.id?c.push({transport:"mcp",target:i.mcp.name,ids:[d,o.id]}):r.set(i.mcp.name,o.id);const l=`${i.grpc.package}.${i.grpc.service}/${i.grpc.method}`,u=a.get(l);u!==void 0&&u!==o.id?c.push({transport:"grpc",target:l,ids:[u,o.id]}):a.set(l,o.id);const s=i.cli.path.join(" "),m=p.get(s);m!==void 0&&m!==o.id?c.push({transport:"cli",target:s,ids:[m,o.id]}):p.set(s,o.id)}if(c.length>0)throw new j(c)}function S(e,t){return`x-${e}-${t}`}function O(e,t){return`--${e}-${t}`}function z(e,t){const n=r=>r.toUpperCase().replace(/-/g,"_");return e==="adhd"?`APIGEN_${n(t)}`:`APIGEN_${n(e)}_${n(t)}`}const A=S;function T(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,"_");return(t===""||/^[0-9]/.test(t))&&(t=`_${t}`),t}function E(e){return e.length===0?e:e[0].toUpperCase()+e.slice(1)}exports.CollisionDetectedError=j;exports.checkCollisions=M;exports.envelopeCliFlag=O;exports.envelopeEnvVar=z;exports.envelopeKey=S;exports.envelopeMetaKey=A;exports.httpVerb=K;exports.normalizeFileName=w;exports.project=b;exports.sanitizeIdentifier=T;exports.toCamel=k;exports.toKebab=h;exports.toPascal=v;exports.toSnake=$;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("node:fs");let e=require("node:module");require("node:path"),(0,e.createRequire)(require("node:url").pathToFileURL(__filename).href);var t={id:`date-time`,kind:`scalar`,schema:{type:`string`,format:`date-time`},matches(e){return e.type===`string`&&e.format===`date-time`},ownsValue(e){return e instanceof Date},encode(e,t,n){return e.toISOString()},decode(e,t,n){if(typeof e!=`string`){if(n.mode===`strict`)throw TypeError(`[date-time] expected a string on the wire at "${n.path}", got ${typeof e}`);return new Date(String(e))}if(n.mode===`strict`&&Number.isNaN(new Date(e).getTime()))throw TypeError(`[date-time] invalid date-time string at "${n.path}": ${JSON.stringify(e)}`);return new Date(e)}},n={id:`int64`,kind:`scalar`,schema:{type:`string`,format:`int64`},matches(e){return e.type===`string`&&e.format===`int64`},ownsValue(e){return typeof e==`bigint`},encode(e,t,n){return String(e)},decode(e,t,n){if(typeof e!=`string`){if(n.mode===`strict`)throw TypeError(`[int64] expected a string on the wire at "${n.path}", got ${typeof e}`);try{return BigInt(Math.trunc(Number(e)))}catch{return BigInt(0)}}if(n.mode===`strict`)return BigInt(e);if(/^-?\d+$/.test(e))try{return BigInt(e)}catch{return BigInt(0)}return BigInt(0)}};function r(e){return e}var i={id:`decimal`,kind:`scalar`,schema:{type:`string`,format:`decimal`},matches(e){return e.type===`string`&&e.format===`decimal`},encode(e,t,n){return e},decode(e,t,n){if(typeof e!=`string`){if(n.mode===`strict`)throw TypeError(`[decimal] expected a string on the wire at "${n.path}", got ${typeof e}`);return r(String(e))}if(n.mode===`strict`&&!/^-?\d+(\.\d+)?$/.test(e))throw TypeError(`[decimal] wire value "${e}" at "${n.path}" is not a valid decimal string`);return r(e)}},a={id:`byte`,kind:`scalar`,schema:{type:`string`,format:`byte`},matches(e){return e.type===`string`&&e.format===`byte`},ownsValue(e){return e instanceof Uint8Array},encode(e,t,n){return Buffer.from(e).toString(`base64`)},decode(e,t,n){if(typeof e!=`string`){if(n.mode===`strict`)throw TypeError(`[byte] expected a base64 string on the wire at "${n.path}", got ${typeof e}`);return new Uint8Array}if(n.mode===`strict`&&!/^[A-Za-z0-9+/]*={0,2}$/.test(e))throw TypeError(`[byte] wire value at "${n.path}" is not standard base64 (format:byte): "${e}"`);return new Uint8Array(Buffer.from(e,`base64`))}},o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,s={id:`uuid`,kind:`scalar`,schema:{type:`string`,format:`uuid`},matches(e){return e.type===`string`&&e.format===`uuid`},encode(e,t,n){return e.toLowerCase()},decode(e,t,n){if(typeof e!=`string`){if(n.mode===`strict`)throw TypeError(`[uuid] expected a string on the wire at "${n.path}", got ${typeof e}`);return String(e).toLowerCase()}if(n.mode===`strict`&&!o.test(e))throw TypeError(`[uuid] wire value "${e}" at "${n.path}" is not a lowercase-hyphenated RFC 4122 UUID`);return e}},c=`NaN`,l=`Infinity`,u=`-Infinity`,d=new Set([c,l,u]);t.id,n.id,i.id,a.id,s.id,{id:`number-special`,kind:`scalar`,schema:{type:`number`},matches(e){return e.type===`number`&&e.format===void 0},ownsValue(e){return typeof e==`number`&&!Number.isFinite(e)},encode(e,t,n){return Number.isNaN(e)?c:e===1/0?l:e===-1/0?u:e},decode(e,t,n){if(typeof e==`number`)return e;if(typeof e==`string`){if(e===c)return NaN;if(e===l)return 1/0;if(e===u)return-1/0;let t=Number(e);if(!Number.isNaN(t))return t}if(n.mode===`strict`)throw TypeError(`[number-special] unrecognized wire value at "${n.path}": ${JSON.stringify(e)}. Expected a number or one of ${[...d].join(`, `)}.`);return NaN}}.id,Object.freeze({typescript:{"date-time":{encode:`$.toISOString()`,decode:`new Date($)`,mode:`native`},int64:{encode:`String($)`,decode:`BigInt($)`,mode:`native`},decimal:{encode:`$.toString()`,decode:`new Decimal($)`,imports:[],dep:{name:`decimal.js`,version:`^10`},mode:`branded`},byte:{encode:`Buffer.from($).toString('base64')`,decode:`new Uint8Array(Buffer.from($, 'base64'))`,mode:`native`},uuid:{encode:`$.toLowerCase()`,decode:`$`,mode:`native`},"number-special":{encode:`numToWire($)`,decode:`wireToNum($)`,mode:`native`}},python:{"date-time":{encode:`$.isoformat()`,decode:`datetime.fromisoformat($)`,imports:[`from datetime import datetime`],mode:`native`},int64:{encode:`str($)`,decode:`int($)`,mode:`native`},decimal:{encode:`str($)`,decode:`Decimal($)`,imports:[`from decimal import Decimal`],mode:`native`},byte:{encode:`b64encode($).decode()`,decode:`b64decode($)`,imports:[`from base64 import b64encode, b64decode`],mode:`native`},uuid:{encode:`str($)`,decode:`UUID($)`,imports:[`from uuid import UUID`],mode:`native`},"number-special":{encode:`num_to_wire($)`,decode:`wire_to_num($)`,mode:`native`}},rust:{"date-time":{encode:`__SCAFFOLD_RUST_DATETIME_ENCODE__`,decode:`__SCAFFOLD_RUST_DATETIME_DECODE__`,imports:[`use chrono::{DateTime, Utc};`],dep:{name:`chrono`,version:`^0.4`},mode:`lib`},int64:{encode:`__SCAFFOLD_RUST_INT64_ENCODE__`,decode:`__SCAFFOLD_RUST_INT64_DECODE__`,imports:[`use serde_with::DisplayFromStr;`],dep:{name:`serde_with`,version:`^3`},mode:`lib`},decimal:{encode:`__SCAFFOLD_RUST_DECIMAL_ENCODE__`,decode:`__SCAFFOLD_RUST_DECIMAL_DECODE__`,imports:[`use rust_decimal::Decimal;`],dep:{name:`rust_decimal`,version:`^1`},mode:`lib`},byte:{encode:`__SCAFFOLD_RUST_BYTE_ENCODE__`,decode:`__SCAFFOLD_RUST_BYTE_DECODE__`,imports:[`use serde_with::base64::Base64;`],dep:{name:`serde_with`,version:`^3`},mode:`lib`},uuid:{encode:`__SCAFFOLD_RUST_UUID_ENCODE__`,decode:`__SCAFFOLD_RUST_UUID_DECODE__`,imports:[`use uuid::Uuid;`],dep:{name:`uuid`,version:`^1`},mode:`lib`},"number-special":{encode:`__SCAFFOLD_RUST_NUMSPECIAL_ENCODE__`,decode:`__SCAFFOLD_RUST_NUMSPECIAL_DECODE__`,mode:`native`}},go:{"date-time":{encode:`__SCAFFOLD_GO_DATETIME_ENCODE__`,decode:`__SCAFFOLD_GO_DATETIME_DECODE__`,imports:[`"time"`],mode:`native`},int64:{encode:`__SCAFFOLD_GO_INT64_ENCODE__`,decode:`__SCAFFOLD_GO_INT64_DECODE__`,imports:[`"strconv"`],mode:`native`},decimal:{encode:`__SCAFFOLD_GO_DECIMAL_ENCODE__`,decode:`__SCAFFOLD_GO_DECIMAL_DECODE__`,imports:[`"github.com/shopspring/decimal"`],dep:{name:`github.com/shopspring/decimal`,version:`v1`},mode:`lib`},byte:{encode:`__SCAFFOLD_GO_BYTE_ENCODE__`,decode:`__SCAFFOLD_GO_BYTE_DECODE__`,imports:[`"encoding/base64"`],mode:`native`},uuid:{encode:`__SCAFFOLD_GO_UUID_ENCODE__`,decode:`__SCAFFOLD_GO_UUID_DECODE__`,imports:[`"github.com/google/uuid"`],dep:{name:`github.com/google/uuid`,version:`v1`},mode:`lib`},"number-special":{encode:`__SCAFFOLD_GO_NUMSPECIAL_ENCODE__`,decode:`__SCAFFOLD_GO_NUMSPECIAL_DECODE__`,mode:`native`}},java:{"date-time":{encode:`$.toString()`,decode:`Instant.parse($)`,imports:[`import java.time.Instant;`],dep:{name:`jackson-datatype-jsr310`,version:`2.x`},mode:`lib`},int64:{encode:`@JsonFormat(shape = JsonFormat.Shape.STRING)`,decode:`@JsonFormat(shape = JsonFormat.Shape.STRING)`,imports:[`import com.fasterxml.jackson.annotation.JsonFormat;`],mode:`native`},decimal:{encode:`@JsonSerialize(using = ToStringSerializer.class)`,decode:`new BigDecimal($)`,imports:[`import java.math.BigDecimal;`,`import com.fasterxml.jackson.databind.annotation.JsonSerialize;`,`import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;`],mode:`native`},byte:{encode:`$`,decode:`$`,mode:`native`},uuid:{encode:`$.toString()`,decode:`UUID.fromString($)`,imports:[`import java.util.UUID;`],mode:`native`},"number-special":{encode:`@JsonSerialize(using = NumberSpecialSerializer.class)`,decode:`@JsonDeserialize(using = NumberSpecialDeserializer.class)`,imports:[`import com.fasterxml.jackson.databind.annotation.JsonSerialize;`,`import com.fasterxml.jackson.databind.annotation.JsonDeserialize;`],mode:`native`}}});var f=new Set([`string`,`number`,`boolean`,`integer`]);function p(e){if(typeof e!=`object`||!e)return!1;let t=e;if(t.$ref!==void 0||t.oneOf||t.anyOf||t.allOf)return!1;let{type:n}=t;return typeof n==`string`?f.has(n):Array.isArray(n)?n.every(e=>f.has(e)||e===`null`):Array.isArray(t.enum)&&t.enum.length>0?t.enum.every(e=>[`string`,`number`,`boolean`].includes(typeof e)):!1}function m(e){if(!e||typeof e!=`object`)return!1;let t=e.properties;return t===void 0||typeof t!=`object`||!t?!1:Object.values(t).every(p)}var h={"decimal.js":`Decimal`};function g(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}Object.entries(h).map(([e,t])=>({pattern:RegExp(`import\\(["'][^"']*${g(e)}[^"']*["']\\)\\.\\w+`,`g`),key:t}));function _(e){return e.words.join(`-`)}function v(e){return e.words.map((e,t)=>t===0?e:M(e)).join(``)}function y(e){return e.words.map(M).join(``)}function b(e){return e.words.join(`_`)}function x(e){return e.replace(/\.[^.]+$/,``).replace(/[._]+/g,`-`)}function S(e,t={}){let n=[e.namespace,...e.path],r=e.safe||m(e.input)?`GET`:`POST`,i=t.http?.verb?.[e.id]??r,a=`/`+n.map(e=>_(e)).join(`/`),o=n.map(e=>b(e)).join(`_`),s=n.map(e=>b(e)).slice(0,-1).join(`.`),c=y(n[n.length-2]??n[0]),l=y(n[n.length-1]),u=n.map(e=>_(e));return{http:{verb:i,route:a},mcp:{name:o},grpc:{package:s,service:c,method:l},cli:{path:u}}}function C(e,t,n){let r=n.http?.verb?.[e];return r===`GET`||r===`POST`?r:t[`x-apigen-safe`]?`GET`:`POST`}var w=class extends Error{constructor(e){let t=e.map(e=>` [${e.transport}] "${e.target}" ← ${e.ids[0]} vs ${e.ids[1]}`);super(`apigen-naming: ${e.length} projection collision(s) detected:\n${t.join(`
2
+ `)}`),this.name=`CollisionDetectedError`,this.collisions=e}};function T(e,t={}){let n=new Map,r=new Map,i=new Map,a=new Map,o=[];for(let s of e){let e=S(s,t),c=`${e.http.verb} ${e.http.route}`,l=n.get(c);l!==void 0&&l!==s.id?o.push({transport:`http`,target:c,ids:[l,s.id]}):n.set(c,s.id);let u=r.get(e.mcp.name);u!==void 0&&u!==s.id?o.push({transport:`mcp`,target:e.mcp.name,ids:[u,s.id]}):r.set(e.mcp.name,s.id);let d=`${e.grpc.package}.${e.grpc.service}/${e.grpc.method}`,f=i.get(d);f!==void 0&&f!==s.id?o.push({transport:`grpc`,target:d,ids:[f,s.id]}):i.set(d,s.id);let p=e.cli.path.join(` `),m=a.get(p);m!==void 0&&m!==s.id?o.push({transport:`cli`,target:p,ids:[m,s.id]}):a.set(p,s.id)}if(o.length>0)throw new w(o)}function E(e,t){return`x-${e}-${t}`}function D(e,t){return`--${e}-${t}`}function O(e,t){let n=e=>e.toUpperCase().replace(/-/g,`_`);return e===`adhd`?`APIGEN_${n(t)}`:`APIGEN_${n(e)}_${n(t)}`}var k=E;function A(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,`_`);return(t===``||/^[0-9]/.test(t))&&(t=`_${t}`),t}function j(e){let t=new Set,n=[];for(let r of e){let e=A(r),i=e,a=2;for(;t.has(i);)i=`${e}_${a}`,a+=1;t.add(i),n.push(i)}return n}function M(e){return e.length===0?e:e[0].toUpperCase()+e.slice(1)}function N(e){return e.replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`)}function P(e){return N(JSON.stringify(e))}function F(e,t=3e3){if(e==null)return t;if(typeof e!=`string`&&typeof e!=`number`||typeof e==`string`&&e.trim()===``)throw TypeError(`apigen: invalid port option ${JSON.stringify(e)} — expected an integer in [0, 65535]`);let n=Number(e);if(!Number.isInteger(n)||n<0||n>65535)throw TypeError(`apigen: invalid port option ${JSON.stringify(e)} — expected an integer in [0, 65535]`);return n}function I(e){return e.replace(/\\/g,`/`)}exports.CollisionDetectedError=w,exports.checkCollisions=T,exports.coercePort=F,exports.envelopeCliFlag=D,exports.envelopeEnvVar=O,exports.envelopeKey=E,exports.envelopeMetaKey=k,exports.escapeLineTerminators=N,exports.escapeStringLiteral=P,exports.httpVerb=C,exports.normalizeFileName=x,exports.project=S,exports.sanitizeIdentifier=A,exports.toCamel=v,exports.toKebab=_,exports.toPascal=y,exports.toPosixPath=I,exports.toSnake=b,exports.uniqueSanitizedIdentifiers=j;
package/index.mjs CHANGED
@@ -1,148 +1,541 @@
1
- import "ts-morph";
2
1
  import "node:fs";
2
+ import { createRequire as e } from "node:module";
3
3
  import "node:path";
4
- import "ts-json-schema-generator";
5
- const h = /* @__PURE__ */ new Set(["string", "number", "boolean", "integer"]);
6
- function j(e) {
7
- if (typeof e != "object" || e === null)
8
- return !1;
9
- const t = e;
10
- if (t.$ref !== void 0 || t.oneOf || t.anyOf || t.allOf)
11
- return !1;
12
- const { type: n } = t;
13
- return typeof n == "string" ? h.has(n) : Array.isArray(n) ? n.every((r) => h.has(r) || r === "null") : Array.isArray(t.enum) && t.enum.length > 0 ? t.enum.every((r) => ["string", "number", "boolean"].includes(typeof r)) : !1;
14
- }
15
- function E(e) {
16
- if (!e || typeof e != "object")
17
- return !1;
18
- const t = e.properties;
19
- return t === void 0 || typeof t != "object" || t === null ? !1 : Object.values(t).every(
20
- j
21
- );
22
- }
23
- const S = {
24
- "decimal.js": "Decimal"
4
+ e(import.meta.url);
5
+ //#endregion
6
+ //#region ../apigen-base-logical/src/lib/codecs/date-time.ts
7
+ var t = {
8
+ id: "date-time",
9
+ kind: "scalar",
10
+ schema: {
11
+ type: "string",
12
+ format: "date-time"
13
+ },
14
+ matches(e) {
15
+ return e.type === "string" && e.format === "date-time";
16
+ },
17
+ ownsValue(e) {
18
+ return e instanceof Date;
19
+ },
20
+ encode(e, t, n) {
21
+ return e.toISOString();
22
+ },
23
+ decode(e, t, n) {
24
+ if (typeof e != "string") {
25
+ if (n.mode === "strict") throw TypeError(`[date-time] expected a string on the wire at "${n.path}", got ${typeof e}`);
26
+ return new Date(String(e));
27
+ }
28
+ if (n.mode === "strict" && Number.isNaN(new Date(e).getTime())) throw TypeError(`[date-time] invalid date-time string at "${n.path}": ${JSON.stringify(e)}`);
29
+ return new Date(e);
30
+ }
31
+ }, n = {
32
+ id: "int64",
33
+ kind: "scalar",
34
+ schema: {
35
+ type: "string",
36
+ format: "int64"
37
+ },
38
+ matches(e) {
39
+ return e.type === "string" && e.format === "int64";
40
+ },
41
+ ownsValue(e) {
42
+ return typeof e == "bigint";
43
+ },
44
+ encode(e, t, n) {
45
+ return String(e);
46
+ },
47
+ decode(e, t, n) {
48
+ if (typeof e != "string") {
49
+ if (n.mode === "strict") throw TypeError(`[int64] expected a string on the wire at "${n.path}", got ${typeof e}`);
50
+ try {
51
+ return BigInt(Math.trunc(Number(e)));
52
+ } catch {
53
+ return BigInt(0);
54
+ }
55
+ }
56
+ if (n.mode === "strict") return BigInt(e);
57
+ if (/^-?\d+$/.test(e)) try {
58
+ return BigInt(e);
59
+ } catch {
60
+ return BigInt(0);
61
+ }
62
+ return BigInt(0);
63
+ }
25
64
  };
26
- function x(e) {
27
- return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28
- }
29
- Object.entries(S).map(([e, t]) => ({
30
- // Matches: import("...{modSpec}...").<anyMemberName> (single or double quotes)
31
- // The module specifier appears inside an absolute path, so we allow any
32
- // characters between the quote and the specifier, and between the specifier
33
- // and the closing quote. The trailing member accessor (`.default`, `.Decimal`,
34
- // …) is matched generically (`\w+`) rather than anchored to one spelling —
35
- // see the doc comment above for why.
36
- pattern: new RegExp(
37
- `import\\(["'][^"']*${x(e)}[^"']*["']\\)\\.\\w+`,
38
- "g"
39
- ),
40
- key: t
41
- }));
42
- function $(e) {
43
- return e.words.join("-");
65
+ //#endregion
66
+ //#region ../apigen-base-logical/src/lib/codecs/decimal.ts
67
+ function r(e) {
68
+ return e;
44
69
  }
45
- function M(e) {
46
- return e.words.map((t, n) => n === 0 ? t : b(t)).join("");
70
+ var i = {
71
+ id: "decimal",
72
+ kind: "scalar",
73
+ schema: {
74
+ type: "string",
75
+ format: "decimal"
76
+ },
77
+ matches(e) {
78
+ return e.type === "string" && e.format === "decimal";
79
+ },
80
+ encode(e, t, n) {
81
+ return e;
82
+ },
83
+ decode(e, t, n) {
84
+ if (typeof e != "string") {
85
+ if (n.mode === "strict") throw TypeError(`[decimal] expected a string on the wire at "${n.path}", got ${typeof e}`);
86
+ return r(String(e));
87
+ }
88
+ if (n.mode === "strict" && !/^-?\d+(\.\d+)?$/.test(e)) throw TypeError(`[decimal] wire value "${e}" at "${n.path}" is not a valid decimal string`);
89
+ return r(e);
90
+ }
91
+ }, a = {
92
+ id: "byte",
93
+ kind: "scalar",
94
+ schema: {
95
+ type: "string",
96
+ format: "byte"
97
+ },
98
+ matches(e) {
99
+ return e.type === "string" && e.format === "byte";
100
+ },
101
+ ownsValue(e) {
102
+ return e instanceof Uint8Array;
103
+ },
104
+ encode(e, t, n) {
105
+ return Buffer.from(e).toString("base64");
106
+ },
107
+ decode(e, t, n) {
108
+ if (typeof e != "string") {
109
+ if (n.mode === "strict") throw TypeError(`[byte] expected a base64 string on the wire at "${n.path}", got ${typeof e}`);
110
+ return /* @__PURE__ */ new Uint8Array();
111
+ }
112
+ if (n.mode === "strict" && !/^[A-Za-z0-9+/]*={0,2}$/.test(e)) throw TypeError(`[byte] wire value at "${n.path}" is not standard base64 (format:byte): "${e}"`);
113
+ return new Uint8Array(Buffer.from(e, "base64"));
114
+ }
115
+ }, o = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, s = {
116
+ id: "uuid",
117
+ kind: "scalar",
118
+ schema: {
119
+ type: "string",
120
+ format: "uuid"
121
+ },
122
+ matches(e) {
123
+ return e.type === "string" && e.format === "uuid";
124
+ },
125
+ encode(e, t, n) {
126
+ return e.toLowerCase();
127
+ },
128
+ decode(e, t, n) {
129
+ if (typeof e != "string") {
130
+ if (n.mode === "strict") throw TypeError(`[uuid] expected a string on the wire at "${n.path}", got ${typeof e}`);
131
+ return String(e).toLowerCase();
132
+ }
133
+ if (n.mode === "strict" && !o.test(e)) throw TypeError(`[uuid] wire value "${e}" at "${n.path}" is not a lowercase-hyphenated RFC 4122 UUID`);
134
+ return e;
135
+ }
136
+ }, c = "NaN", l = "Infinity", u = "-Infinity", d = /* @__PURE__ */ new Set([
137
+ c,
138
+ l,
139
+ u
140
+ ]);
141
+ t.id, n.id, i.id, a.id, s.id, {
142
+ id: "number-special",
143
+ kind: "scalar",
144
+ schema: { type: "number" },
145
+ matches(e) {
146
+ return e.type === "number" && e.format === void 0;
147
+ },
148
+ ownsValue(e) {
149
+ return typeof e == "number" && !Number.isFinite(e);
150
+ },
151
+ encode(e, t, n) {
152
+ return Number.isNaN(e) ? c : e === Infinity ? l : e === -Infinity ? u : e;
153
+ },
154
+ decode(e, t, n) {
155
+ if (typeof e == "number") return e;
156
+ if (typeof e == "string") {
157
+ if (e === c) return NaN;
158
+ if (e === l) return Infinity;
159
+ if (e === u) return -Infinity;
160
+ let t = Number(e);
161
+ if (!Number.isNaN(t)) return t;
162
+ }
163
+ if (n.mode === "strict") throw TypeError(`[number-special] unrecognized wire value at "${n.path}": ${JSON.stringify(e)}. Expected a number or one of ${[...d].join(", ")}.`);
164
+ return NaN;
165
+ }
166
+ }.id, Object.freeze({
167
+ typescript: {
168
+ "date-time": {
169
+ encode: "$.toISOString()",
170
+ decode: "new Date($)",
171
+ mode: "native"
172
+ },
173
+ int64: {
174
+ encode: "String($)",
175
+ decode: "BigInt($)",
176
+ mode: "native"
177
+ },
178
+ decimal: {
179
+ encode: "$.toString()",
180
+ decode: "new Decimal($)",
181
+ imports: [],
182
+ dep: {
183
+ name: "decimal.js",
184
+ version: "^10"
185
+ },
186
+ mode: "branded"
187
+ },
188
+ byte: {
189
+ encode: "Buffer.from($).toString('base64')",
190
+ decode: "new Uint8Array(Buffer.from($, 'base64'))",
191
+ mode: "native"
192
+ },
193
+ uuid: {
194
+ encode: "$.toLowerCase()",
195
+ decode: "$",
196
+ mode: "native"
197
+ },
198
+ "number-special": {
199
+ encode: "numToWire($)",
200
+ decode: "wireToNum($)",
201
+ mode: "native"
202
+ }
203
+ },
204
+ python: {
205
+ "date-time": {
206
+ encode: "$.isoformat()",
207
+ decode: "datetime.fromisoformat($)",
208
+ imports: ["from datetime import datetime"],
209
+ mode: "native"
210
+ },
211
+ int64: {
212
+ encode: "str($)",
213
+ decode: "int($)",
214
+ mode: "native"
215
+ },
216
+ decimal: {
217
+ encode: "str($)",
218
+ decode: "Decimal($)",
219
+ imports: ["from decimal import Decimal"],
220
+ mode: "native"
221
+ },
222
+ byte: {
223
+ encode: "b64encode($).decode()",
224
+ decode: "b64decode($)",
225
+ imports: ["from base64 import b64encode, b64decode"],
226
+ mode: "native"
227
+ },
228
+ uuid: {
229
+ encode: "str($)",
230
+ decode: "UUID($)",
231
+ imports: ["from uuid import UUID"],
232
+ mode: "native"
233
+ },
234
+ "number-special": {
235
+ encode: "num_to_wire($)",
236
+ decode: "wire_to_num($)",
237
+ mode: "native"
238
+ }
239
+ },
240
+ rust: {
241
+ "date-time": {
242
+ encode: "__SCAFFOLD_RUST_DATETIME_ENCODE__",
243
+ decode: "__SCAFFOLD_RUST_DATETIME_DECODE__",
244
+ imports: ["use chrono::{DateTime, Utc};"],
245
+ dep: {
246
+ name: "chrono",
247
+ version: "^0.4"
248
+ },
249
+ mode: "lib"
250
+ },
251
+ int64: {
252
+ encode: "__SCAFFOLD_RUST_INT64_ENCODE__",
253
+ decode: "__SCAFFOLD_RUST_INT64_DECODE__",
254
+ imports: ["use serde_with::DisplayFromStr;"],
255
+ dep: {
256
+ name: "serde_with",
257
+ version: "^3"
258
+ },
259
+ mode: "lib"
260
+ },
261
+ decimal: {
262
+ encode: "__SCAFFOLD_RUST_DECIMAL_ENCODE__",
263
+ decode: "__SCAFFOLD_RUST_DECIMAL_DECODE__",
264
+ imports: ["use rust_decimal::Decimal;"],
265
+ dep: {
266
+ name: "rust_decimal",
267
+ version: "^1"
268
+ },
269
+ mode: "lib"
270
+ },
271
+ byte: {
272
+ encode: "__SCAFFOLD_RUST_BYTE_ENCODE__",
273
+ decode: "__SCAFFOLD_RUST_BYTE_DECODE__",
274
+ imports: ["use serde_with::base64::Base64;"],
275
+ dep: {
276
+ name: "serde_with",
277
+ version: "^3"
278
+ },
279
+ mode: "lib"
280
+ },
281
+ uuid: {
282
+ encode: "__SCAFFOLD_RUST_UUID_ENCODE__",
283
+ decode: "__SCAFFOLD_RUST_UUID_DECODE__",
284
+ imports: ["use uuid::Uuid;"],
285
+ dep: {
286
+ name: "uuid",
287
+ version: "^1"
288
+ },
289
+ mode: "lib"
290
+ },
291
+ "number-special": {
292
+ encode: "__SCAFFOLD_RUST_NUMSPECIAL_ENCODE__",
293
+ decode: "__SCAFFOLD_RUST_NUMSPECIAL_DECODE__",
294
+ mode: "native"
295
+ }
296
+ },
297
+ go: {
298
+ "date-time": {
299
+ encode: "__SCAFFOLD_GO_DATETIME_ENCODE__",
300
+ decode: "__SCAFFOLD_GO_DATETIME_DECODE__",
301
+ imports: ["\"time\""],
302
+ mode: "native"
303
+ },
304
+ int64: {
305
+ encode: "__SCAFFOLD_GO_INT64_ENCODE__",
306
+ decode: "__SCAFFOLD_GO_INT64_DECODE__",
307
+ imports: ["\"strconv\""],
308
+ mode: "native"
309
+ },
310
+ decimal: {
311
+ encode: "__SCAFFOLD_GO_DECIMAL_ENCODE__",
312
+ decode: "__SCAFFOLD_GO_DECIMAL_DECODE__",
313
+ imports: ["\"github.com/shopspring/decimal\""],
314
+ dep: {
315
+ name: "github.com/shopspring/decimal",
316
+ version: "v1"
317
+ },
318
+ mode: "lib"
319
+ },
320
+ byte: {
321
+ encode: "__SCAFFOLD_GO_BYTE_ENCODE__",
322
+ decode: "__SCAFFOLD_GO_BYTE_DECODE__",
323
+ imports: ["\"encoding/base64\""],
324
+ mode: "native"
325
+ },
326
+ uuid: {
327
+ encode: "__SCAFFOLD_GO_UUID_ENCODE__",
328
+ decode: "__SCAFFOLD_GO_UUID_DECODE__",
329
+ imports: ["\"github.com/google/uuid\""],
330
+ dep: {
331
+ name: "github.com/google/uuid",
332
+ version: "v1"
333
+ },
334
+ mode: "lib"
335
+ },
336
+ "number-special": {
337
+ encode: "__SCAFFOLD_GO_NUMSPECIAL_ENCODE__",
338
+ decode: "__SCAFFOLD_GO_NUMSPECIAL_DECODE__",
339
+ mode: "native"
340
+ }
341
+ },
342
+ java: {
343
+ "date-time": {
344
+ encode: "$.toString()",
345
+ decode: "Instant.parse($)",
346
+ imports: ["import java.time.Instant;"],
347
+ dep: {
348
+ name: "jackson-datatype-jsr310",
349
+ version: "2.x"
350
+ },
351
+ mode: "lib"
352
+ },
353
+ int64: {
354
+ encode: "@JsonFormat(shape = JsonFormat.Shape.STRING)",
355
+ decode: "@JsonFormat(shape = JsonFormat.Shape.STRING)",
356
+ imports: ["import com.fasterxml.jackson.annotation.JsonFormat;"],
357
+ mode: "native"
358
+ },
359
+ decimal: {
360
+ encode: "@JsonSerialize(using = ToStringSerializer.class)",
361
+ decode: "new BigDecimal($)",
362
+ imports: [
363
+ "import java.math.BigDecimal;",
364
+ "import com.fasterxml.jackson.databind.annotation.JsonSerialize;",
365
+ "import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;"
366
+ ],
367
+ mode: "native"
368
+ },
369
+ byte: {
370
+ encode: "$",
371
+ decode: "$",
372
+ mode: "native"
373
+ },
374
+ uuid: {
375
+ encode: "$.toString()",
376
+ decode: "UUID.fromString($)",
377
+ imports: ["import java.util.UUID;"],
378
+ mode: "native"
379
+ },
380
+ "number-special": {
381
+ encode: "@JsonSerialize(using = NumberSpecialSerializer.class)",
382
+ decode: "@JsonDeserialize(using = NumberSpecialDeserializer.class)",
383
+ imports: ["import com.fasterxml.jackson.databind.annotation.JsonSerialize;", "import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"],
384
+ mode: "native"
385
+ }
386
+ }
387
+ });
388
+ //#endregion
389
+ //#region ../apigen-core-client/src/lib/get-safety.ts
390
+ var f = /* @__PURE__ */ new Set([
391
+ "string",
392
+ "number",
393
+ "boolean",
394
+ "integer"
395
+ ]);
396
+ function p(e) {
397
+ if (typeof e != "object" || !e) return !1;
398
+ let t = e;
399
+ if (t.$ref !== void 0 || t.oneOf || t.anyOf || t.allOf) return !1;
400
+ let { type: n } = t;
401
+ return typeof n == "string" ? f.has(n) : Array.isArray(n) ? n.every((e) => f.has(e) || e === "null") : Array.isArray(t.enum) && t.enum.length > 0 ? t.enum.every((e) => [
402
+ "string",
403
+ "number",
404
+ "boolean"
405
+ ].includes(typeof e)) : !1;
406
+ }
407
+ function m(e) {
408
+ if (!e || typeof e != "object") return !1;
409
+ let t = e.properties;
410
+ return t === void 0 || typeof t != "object" || !t ? !1 : Object.values(t).every(p);
411
+ }
412
+ //#endregion
413
+ //#region ../apigen-core-client/src/lib/schema-builders/ts-json-schema.ts
414
+ var h = { "decimal.js": "Decimal" };
415
+ function g(e) {
416
+ return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
417
+ }
418
+ Object.entries(h).map(([e, t]) => ({
419
+ pattern: RegExp(`import\\(["'][^"']*${g(e)}[^"']*["']\\)\\.\\w+`, "g"),
420
+ key: t
421
+ }));
422
+ //#endregion
423
+ //#region src/lib/naming.ts
424
+ function _(e) {
425
+ return e.words.join("-");
47
426
  }
48
427
  function v(e) {
49
- return e.words.map(b).join("");
428
+ return e.words.map((e, t) => t === 0 ? e : M(e)).join("");
50
429
  }
51
430
  function y(e) {
52
- return e.words.join("_");
53
- }
54
- function G(e) {
55
- return e.replace(/\.[^.]+$/, "").replace(/[._]+/g, "-");
56
- }
57
- function P(e, t = {}) {
58
- var l, u;
59
- const n = [e.namespace, ...e.path], r = e.safe || E(e.input) ? "GET" : "POST", p = ((u = (l = t.http) == null ? void 0 : l.verb) == null ? void 0 : u[e.id]) ?? r, a = "/" + n.map((s) => $(s)).join("/"), c = n.map((s) => y(s)).join("_"), i = n.map((s) => y(s)).slice(0, -1).join("."), g = v(n[n.length - 2] ?? n[0]), f = v(n[n.length - 1]), m = n.map((s) => $(s));
60
- return {
61
- http: { verb: p, route: a },
62
- mcp: { name: c },
63
- grpc: { package: i, service: g, method: f },
64
- cli: { path: m }
65
- };
66
- }
67
- function K(e, t, n) {
68
- var p, a;
69
- const r = (a = (p = n.http) == null ? void 0 : p.verb) == null ? void 0 : a[e];
70
- return r === "GET" || r === "POST" ? r : t["x-apigen-safe"] ? "GET" : "POST";
71
- }
72
- class _ extends Error {
73
- constructor(t) {
74
- const n = t.map(
75
- (r) => ` [${r.transport}] "${r.target}" ← ${r.ids[0]} vs ${r.ids[1]}`
76
- );
77
- super(
78
- `apigen-naming: ${t.length} projection collision(s) detected:
79
- ${n.join(`
80
- `)}`
81
- ), this.name = "CollisionDetectedError", this.collisions = t;
82
- }
83
- }
84
- function T(e, t = {}) {
85
- const n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), c = [];
86
- for (const o of e) {
87
- const i = P(o, t), g = `${i.http.verb} ${i.http.route}`, f = n.get(g);
88
- f !== void 0 && f !== o.id ? c.push({
89
- transport: "http",
90
- target: g,
91
- ids: [f, o.id]
92
- }) : n.set(g, o.id);
93
- const m = r.get(i.mcp.name);
94
- m !== void 0 && m !== o.id ? c.push({
95
- transport: "mcp",
96
- target: i.mcp.name,
97
- ids: [m, o.id]
98
- }) : r.set(i.mcp.name, o.id);
99
- const l = `${i.grpc.package}.${i.grpc.service}/${i.grpc.method}`, u = p.get(l);
100
- u !== void 0 && u !== o.id ? c.push({
101
- transport: "grpc",
102
- target: l,
103
- ids: [u, o.id]
104
- }) : p.set(l, o.id);
105
- const s = i.cli.path.join(" "), d = a.get(s);
106
- d !== void 0 && d !== o.id ? c.push({
107
- transport: "cli",
108
- target: s,
109
- ids: [d, o.id]
110
- }) : a.set(s, o.id);
111
- }
112
- if (c.length > 0)
113
- throw new _(c);
114
- }
115
- function w(e, t) {
116
- return `x-${e}-${t}`;
117
- }
118
- function z(e, t) {
119
- return `--${e}-${t}`;
120
- }
121
- function N(e, t) {
122
- const n = (r) => r.toUpperCase().replace(/-/g, "_");
123
- return e === "adhd" ? `APIGEN_${n(t)}` : `APIGEN_${n(e)}_${n(t)}`;
124
- }
125
- const D = w;
126
- function U(e) {
127
- let t = e.replace(/[^a-zA-Z0-9_$]/g, "_");
128
- return (t === "" || /^[0-9]/.test(t)) && (t = `_${t}`), t;
431
+ return e.words.map(M).join("");
129
432
  }
130
433
  function b(e) {
131
- return e.length === 0 ? e : e[0].toUpperCase() + e.slice(1);
132
- }
133
- export {
134
- _ as CollisionDetectedError,
135
- T as checkCollisions,
136
- z as envelopeCliFlag,
137
- N as envelopeEnvVar,
138
- w as envelopeKey,
139
- D as envelopeMetaKey,
140
- K as httpVerb,
141
- G as normalizeFileName,
142
- P as project,
143
- U as sanitizeIdentifier,
144
- M as toCamel,
145
- $ as toKebab,
146
- v as toPascal,
147
- y as toSnake
434
+ return e.words.join("_");
435
+ }
436
+ function x(e) {
437
+ return e.replace(/\.[^.]+$/, "").replace(/[._]+/g, "-");
438
+ }
439
+ function S(e, t = {}) {
440
+ let n = [e.namespace, ...e.path], r = e.safe || m(e.input) ? "GET" : "POST", i = t.http?.verb?.[e.id] ?? r, a = "/" + n.map((e) => _(e)).join("/"), o = n.map((e) => b(e)).join("_"), s = n.map((e) => b(e)).slice(0, -1).join("."), c = y(n[n.length - 2] ?? n[0]), l = y(n[n.length - 1]), u = n.map((e) => _(e));
441
+ return {
442
+ http: {
443
+ verb: i,
444
+ route: a
445
+ },
446
+ mcp: { name: o },
447
+ grpc: {
448
+ package: s,
449
+ service: c,
450
+ method: l
451
+ },
452
+ cli: { path: u }
453
+ };
454
+ }
455
+ function C(e, t, n) {
456
+ let r = n.http?.verb?.[e];
457
+ return r === "GET" || r === "POST" ? r : t["x-apigen-safe"] ? "GET" : "POST";
458
+ }
459
+ var w = class extends Error {
460
+ constructor(e) {
461
+ let t = e.map((e) => ` [${e.transport}] "${e.target}" ← ${e.ids[0]} vs ${e.ids[1]}`);
462
+ super(`apigen-naming: ${e.length} projection collision(s) detected:\n${t.join("\n")}`), this.name = "CollisionDetectedError", this.collisions = e;
463
+ }
148
464
  };
465
+ function T(e, t = {}) {
466
+ let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = [];
467
+ for (let s of e) {
468
+ let e = S(s, t), c = `${e.http.verb} ${e.http.route}`, l = n.get(c);
469
+ l !== void 0 && l !== s.id ? o.push({
470
+ transport: "http",
471
+ target: c,
472
+ ids: [l, s.id]
473
+ }) : n.set(c, s.id);
474
+ let u = r.get(e.mcp.name);
475
+ u !== void 0 && u !== s.id ? o.push({
476
+ transport: "mcp",
477
+ target: e.mcp.name,
478
+ ids: [u, s.id]
479
+ }) : r.set(e.mcp.name, s.id);
480
+ let d = `${e.grpc.package}.${e.grpc.service}/${e.grpc.method}`, f = i.get(d);
481
+ f !== void 0 && f !== s.id ? o.push({
482
+ transport: "grpc",
483
+ target: d,
484
+ ids: [f, s.id]
485
+ }) : i.set(d, s.id);
486
+ let p = e.cli.path.join(" "), m = a.get(p);
487
+ m !== void 0 && m !== s.id ? o.push({
488
+ transport: "cli",
489
+ target: p,
490
+ ids: [m, s.id]
491
+ }) : a.set(p, s.id);
492
+ }
493
+ if (o.length > 0) throw new w(o);
494
+ }
495
+ function E(e, t) {
496
+ return `x-${e}-${t}`;
497
+ }
498
+ function D(e, t) {
499
+ return `--${e}-${t}`;
500
+ }
501
+ function O(e, t) {
502
+ let n = (e) => e.toUpperCase().replace(/-/g, "_");
503
+ return e === "adhd" ? `APIGEN_${n(t)}` : `APIGEN_${n(e)}_${n(t)}`;
504
+ }
505
+ var k = E;
506
+ function A(e) {
507
+ let t = e.replace(/[^a-zA-Z0-9_$]/g, "_");
508
+ return (t === "" || /^[0-9]/.test(t)) && (t = `_${t}`), t;
509
+ }
510
+ function j(e) {
511
+ let t = /* @__PURE__ */ new Set(), n = [];
512
+ for (let r of e) {
513
+ let e = A(r), i = e, a = 2;
514
+ for (; t.has(i);) i = `${e}_${a}`, a += 1;
515
+ t.add(i), n.push(i);
516
+ }
517
+ return n;
518
+ }
519
+ function M(e) {
520
+ return e.length === 0 ? e : e[0].toUpperCase() + e.slice(1);
521
+ }
522
+ //#endregion
523
+ //#region src/lib/emit.ts
524
+ function N(e) {
525
+ return e.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
526
+ }
527
+ function P(e) {
528
+ return N(JSON.stringify(e));
529
+ }
530
+ function F(e, t = 3e3) {
531
+ if (e == null) return t;
532
+ if (typeof e != "string" && typeof e != "number" || typeof e == "string" && e.trim() === "") throw TypeError(`apigen: invalid port option ${JSON.stringify(e)} — expected an integer in [0, 65535]`);
533
+ let n = Number(e);
534
+ if (!Number.isInteger(n) || n < 0 || n > 65535) throw TypeError(`apigen: invalid port option ${JSON.stringify(e)} — expected an integer in [0, 65535]`);
535
+ return n;
536
+ }
537
+ function I(e) {
538
+ return e.replace(/\\/g, "/");
539
+ }
540
+ //#endregion
541
+ export { w as CollisionDetectedError, T as checkCollisions, F as coercePort, D as envelopeCliFlag, O as envelopeEnvVar, E as envelopeKey, k as envelopeMetaKey, N as escapeLineTerminators, P as escapeStringLiteral, C as httpVerb, x as normalizeFileName, S as project, A as sanitizeIdentifier, v as toCamel, _ as toKebab, y as toPascal, I as toPosixPath, b as toSnake, j as uniqueSanitizedIdentifiers };
package/lib/emit.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Replaces raw `U+2028` LINE SEPARATOR / `U+2029` PARAGRAPH SEPARATOR
3
+ * characters with their `\u2028` / `\u2029` escape sequences.
4
+ *
5
+ * `JSON.stringify` emits these two code points **verbatim** (they are valid in
6
+ * JSON) even though ECMAScript treats them as line terminators. That is a
7
+ * parse error inside a string literal on pre-ES2019 targets — this repo
8
+ * targets `es2018` (`tsconfig.base.json`) — and a raw separator also breaks any
9
+ * single-line context (a comment, a log line) it lands in.
10
+ *
11
+ * `escapeStringLiteral` applies this to a single string literal.
12
+ * `escapeLineTerminators` is the same escape applied **directly to an
13
+ * already-serialized blob** — e.g. a `JSON.stringify`'d schema map whose
14
+ * nested `description` fields may carry these separators — so a multi-value
15
+ * JSON blob spliced into generated source cannot smuggle a raw line terminator
16
+ * past `JSON.stringify`. It is only safe on JSON/source text (it never touches
17
+ * any other character); it is not a substitute for `escapeStringLiteral` on a
18
+ * raw value, because it does not add quotes or escape backslashes.
19
+ *
20
+ * @example escapeLineTerminators('a\u2028b') // → 'a\\u2028b'
21
+ */
22
+ export declare function escapeLineTerminators(text: string): string;
23
+ /**
24
+ * Renders a value as a **complete, double-quoted JavaScript/TypeScript string
25
+ * literal** (including the surrounding quotes), safe to splice into generated
26
+ * source in any string-literal position.
27
+ *
28
+ * Uses `JSON.stringify`, whose output is a valid ECMAScript string literal on
29
+ * ES2019+ targets (the JSON-superset proposal, V8 "JSON ⊂ ECMAScript") and
30
+ * correctly escapes the double-quote, the backslash, and all C0 control
31
+ * characters (newline, carriage return, tab, …). The two characters
32
+ * `JSON.stringify` emits **verbatim** even though they are line terminators —
33
+ * `U+2028` / `U+2029` — are then escaped via {@link escapeLineTerminators}
34
+ * (see that helper for why this repo needs it at `es2018`).
35
+ *
36
+ * @example escapeStringLiteral("it's") // → `"it's"`
37
+ * @example escapeStringLiteral('a\nb') // → `"a\\nb"` (no raw newline)
38
+ * @example escapeStringLiteral('\u2028') // → `"\\u2028"`
39
+ */
40
+ export declare function escapeStringLiteral(value: string): string;
41
+ /**
42
+ * Coerces a caller-supplied `port` option to a validated integer, safe to
43
+ * splice into generated source as a numeric literal.
44
+ *
45
+ * Generate/run options reach a plugin as **untyped** option values — a CLI
46
+ * `--opt port=8080` arrives as the string `'8080'`, and nothing prevents a
47
+ * hostile or fat-fingered `--opt port=8080); process.exit(1); //`. Splicing
48
+ * that raw (`await app.listen({ port: ${port} })`) is a code-injection vector.
49
+ * Collapsing the value to an integer in the valid TCP range `[0, 65535]`
50
+ * (0 = request an ephemeral port) before splicing closes it: the emitted
51
+ * literal is always a bare number.
52
+ *
53
+ * `undefined`/`null` fall back to `fallback` (3000, matching the generators'
54
+ * historical default). Only a string or a number is ever coerced — a blank
55
+ * string, a non-numeric string, a non-string/non-number (`true`, `[]`, an
56
+ * object), a non-integer, or an out-of-range value throws rather than silently
57
+ * emitting something wrong. The type gate matters: `Number(true)` is `1` and
58
+ * `Number([])` is `0`, so without it those values would sneak through as if
59
+ * they were ports.
60
+ *
61
+ * @example coercePort('8080') // → 8080
62
+ * @example coercePort(undefined) // → 3000
63
+ * @throws {TypeError} on a blank/non-numeric string, a non-string/non-number,
64
+ * a non-integer, or an out-of-range value.
65
+ */
66
+ export declare function coercePort(value: unknown, fallback?: number): number;
67
+ /**
68
+ * Normalises a filesystem path to POSIX separators (`/`).
69
+ *
70
+ * Generated specifiers and paths must be platform-stable: a Windows-joined
71
+ * `pkg\\src\\index.ts` spliced verbatim into an import would not match the
72
+ * value an OpenAPI/schema emitter produces on POSIX. Always emit `/`.
73
+ *
74
+ * @example toPosixPath('a\\b') // → 'a/b'
75
+ */
76
+ export declare function toPosixPath(p: string): string;
77
+ export { sanitizeIdentifier, uniqueSanitizedIdentifiers } from './naming';
package/lib/naming.d.ts CHANGED
@@ -237,3 +237,29 @@ export declare const envelopeMetaKey: typeof envelopeKey;
237
237
  * @example sanitizeIdentifier('123abc') // → '_123abc'
238
238
  */
239
239
  export declare function sanitizeIdentifier(id: string): string;
240
+ /**
241
+ * Assigns each raw id a **unique** codegen-safe identifier, disambiguating
242
+ * sanitisation collisions deterministically.
243
+ *
244
+ * {@link sanitizeIdentifier} is lossy: several distinct raw ids can collapse to
245
+ * the same identifier (`a-b`, `a_b`, `a.b` → `a_b`). A generator that emits one
246
+ * `import * as <id>_ns` / `const <id>_fns` pair **per package** would then emit
247
+ * duplicate declarations for a project holding two such packages — a hard
248
+ * `routes.ts` parse error (`Identifier 'a_b_ns' has already been declared`),
249
+ * or, worse, a silent last-writer-wins alias.
250
+ *
251
+ * This resolves the collision by appending a stable `_2`, `_3`, … suffix in
252
+ * input order: the FIRST id to claim a name keeps it (so a collision-free
253
+ * input is byte-identical to before), and each subsequent collision gets the
254
+ * next free suffix. The result is deterministic for a given input order, so
255
+ * generated output stays reproducible.
256
+ *
257
+ * @example uniqueSanitizedIdentifiers(['a-b', 'a_b', 'a.b'])
258
+ * // → ['a_b', 'a_b_2', 'a_b_3']
259
+ * @example uniqueSanitizedIdentifiers(['dispatch-cli'])
260
+ * // → ['dispatch_cli']
261
+ * @example uniqueSanitizedIdentifiers(['a-b', 'a_b', 'a_b_2'])
262
+ * // → ['a_b', 'a_b_2', 'a_b_2_2'] (the suffix is appended to the
263
+ * // colliding sanitized base, so an id already ending in `_2` collides anew)
264
+ */
265
+ export declare function uniqueSanitizedIdentifiers(ids: string[]): string[];
package/package.json CHANGED
@@ -1,13 +1,30 @@
1
1
  {
2
2
  "name": "@adhd/apigen-engine-naming",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "dependencies": {
5
- "@adhd/apigen-core-client": "^0.3.0"
5
+ "@adhd/apigen-core-client": "^0.3.2"
6
6
  },
7
7
  "main": "./index.js",
8
8
  "module": "./index.mjs",
9
9
  "typings": "./index.d.ts",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
+ },
13
+ "description": "Naming conventions for apigen code generation",
14
+ "keywords": [
15
+ "api",
16
+ "naming",
17
+ "codegen",
18
+ "typescript"
19
+ ],
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/PseudoSky/adhd.git",
24
+ "directory": "packages/apigen/apigen-engine-naming"
25
+ },
26
+ "homepage": "https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-engine-naming#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/PseudoSky/adhd/issues"
12
29
  }
13
30
  }