@aburi/framework-trpc 0.0.0 → 0.1.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/README.md CHANGED
@@ -1,7 +1,112 @@
1
1
  # @aburi/framework-trpc
2
2
 
3
- This version holds no code. It reserves the package name so that releases can be published from
4
- GitHub Actions through npm Trusted Publishing.
3
+ tRPC framework plugin for `@aburi/core`. Classifies the **server** side of
4
+ tRPC — the instance, routers, procedures, base procedures and middleware —
5
+ into `framework:trpc:*` extKinds, so they are Boundaries in the IR. The client
6
+ side, the calls that reach a procedure over the network, is
7
+ [`@aburi/effects-trpc`](../effects-trpc/README.md)'s.
5
8
 
6
- Install `@aburi/framework-trpc@^0.1.0` or later. See
7
- <https://github.com/kage1020/Aburi/tree/main/packages/framework-trpc> for the plugin itself.
9
+ Every shape is a `const` whose own declarator holds it:
10
+
11
+ | Source shape | `extKind` | `derivedBy` |
12
+ |---|---|---|
13
+ | `const t = initTRPC.create()`, `initTRPC.context<Context>().meta<Meta>().create({ … })` | `framework:trpc:instance` | `framework:trpc:instance:initTRPC.create` |
14
+ | `const appRouter = t.router({ … })`, `router({ … })`, `createTRPCRouter({ … })` | `framework:trpc:router` | `framework:trpc:router:t.router` |
15
+ | `const merged = t.mergeRouters(a, b)`, `mergeRouters(a, b)` | `framework:trpc:router` | `framework:trpc:router:t.mergeRouters` |
16
+ | `const byId = publicProcedure.input(schema).query(({ input }) => …)` | `framework:trpc:procedure` | `framework:trpc:procedure:publicProcedure.query` |
17
+ | `const create = protectedProcedure.mutation(…)`, `t.procedure.subscription(…)` | `framework:trpc:procedure` | `framework:trpc:procedure:protectedProcedure.mutation` |
18
+ | `const publicProcedure = t.procedure` | `framework:trpc:base-procedure` | `framework:trpc:base-procedure:t.procedure` |
19
+ | `const protectedProcedure = t.procedure.use(isAuthed)`, `protectedProcedure.use(isAdmin)` | `framework:trpc:base-procedure` | `framework:trpc:base-procedure:t.use` |
20
+ | `const isAuthed = t.middleware(({ ctx, next }) => …)`, `middleware(fn)` | `framework:trpc:middleware` | `framework:trpc:middleware:t.middleware` |
21
+
22
+ A procedure is a `query`, `mutation` or `subscription` handed its resolver — a
23
+ function, an async generator (tRPC v11's subscriptions), a name for one, or a
24
+ call that returns one (`withCache(listUsers)`) — on a chain built from a
25
+ procedure: one that reads `.procedure` straight off a plain name
26
+ (`t.procedure.query(…)`) or starts from a name that is `procedure` or ends in
27
+ `Procedure` (`publicProcedure`, `protectedProcedure`), which is how tRPC's
28
+ documentation and create-t3-app name their base procedures. The chain is what
29
+ tells `publicProcedure.query(resolve)` from `db.query(fn)` and from the client
30
+ call `client.user.byId.query({ id })`. A base procedure is the same chain ending
31
+ in `use`, `input`, `output`, `meta` or `concat` instead, or the bare
32
+ `t.procedure`. That bare read is taken only off a plain name and only in a file
33
+ that imports `@trpc/server`, the file that creates the instance: `procedure` is
34
+ a domain word as well, and `form.procedure` reads the same. The three procedure
35
+ kinds share one extKind, as an Express route's methods do, and `derivedBy` names
36
+ which one it is.
37
+
38
+ A router is `router` or `createTRPCRouter` handed one object literal, or
39
+ `mergeRouters` handed the routers it merges; another library's
40
+ `createRouter({ routes })` is not one. An alias of a factory
41
+ (`export const createTRPCRouter = t.router`) is not classified: it builds
42
+ nothing until it is called.
43
+
44
+ `derivedBy` names the call `<receiver>.<method>`, read off the syntax tree as
45
+ every framework plugin reads it: the method called and the name the receiver
46
+ chain starts from, through `as`, `satisfies`, `!`, parentheses and every call
47
+ on the way. So a chain wrapped over several lines, or with a comment in it,
48
+ gives the same entry, and `t.procedure.use(timing).query(fn)` is
49
+ `t.query`. The bare `t.procedure` is named the same way, `t.procedure`.
50
+
51
+ Confidence is `high` when the file imports `@trpc/server` or one of its
52
+ subpaths, type-only imports included, and `medium` otherwise. Most of the
53
+ names this plugin reads are tRPC's conventions rather than its exports
54
+ (`initTRPC` is the exception), and the usual layout keeps the import in one
55
+ file: create-t3-app's routers import
56
+ `createTRPCRouter` and `publicProcedure` from `~/server/api/trpc`, so their
57
+ routers and procedures are `medium`.
58
+
59
+ A procedure's resolver is its Symbol's body, so the calls, rules and effects
60
+ written in it are the procedure's: with
61
+ [`@aburi/effects-prisma`](../effects-prisma/README.md), a mutation whose
62
+ resolver calls `prisma.user.create(…)` carries `db.write`.
63
+
64
+ Not classified today:
65
+
66
+ - A procedure written inside a router's object literal
67
+ (`router({ byId: publicProcedure.query(…) })`). `@aburi/lang-typescript`
68
+ gives an object handed to a call no member Symbols, so there is no Symbol to
69
+ classify, and the resolver is in no Symbol either. The router itself is
70
+ classified. Declare the procedure as a `const` and name it in the router to
71
+ give it a Symbol.
72
+ - What is built on a base procedure whose name is neither `procedure` nor ends
73
+ in `Procedure` (`const base = t.procedure`, then `base.query(fn)` or
74
+ `base.use(x)`). `base` itself is classified, but the plugin reads one
75
+ declaration at a time, so it cannot see where `base` came from.
76
+ - The bare `t.procedure` in a file that does not import `@trpc/server`.
77
+ - A router or middleware factory under a name other than tRPC's
78
+ (`const makeRouter = t.router`, then `makeRouter({ … })`), for the same reason.
79
+ - `experimental_standaloneMiddleware`, `createCallerFactory(appRouter)` and
80
+ `appRouter.createCaller(ctx)`.
81
+ - A file that reaches `@trpc/server` only through CommonJS
82
+ (`const { initTRPC } = require("@trpc/server")`): the import list carries no
83
+ edge for a `require` call, so its shapes are `medium`.
84
+
85
+ Classified though not tRPC's: the receiver's name is all a declaration shows,
86
+ so another library's shapes under tRPC's names are read as tRPC's, at `medium`
87
+ in a file that does not import `@trpc/server`. A ts-rest contract
88
+ (`c.router({ getPost: … })`) and an oRPC router (`os.router({ ping })`) are
89
+ routers, `app.middleware(handler)` is a middleware, and a `query` on a name
90
+ ending in `Procedure` (`storedProcedure.query(sql)`) is a procedure.
91
+
92
+ ## Install
93
+
94
+ ```bash
95
+ pnpm add @aburi/framework-trpc
96
+ ```
97
+
98
+ ## Usage
99
+
100
+ ```ts
101
+ import { trpcFrameworkPlugin } from "@aburi/framework-trpc"
102
+ ```
103
+
104
+ The plugin's `provides.frameworks: ["trpc"]` matches `@aburi/core`'s Component
105
+ autodetect against a workspace `@trpc/server` npm dependency, and `aburi init`
106
+ writes `"framework-trpc"` into `aburi.json`'s `frameworks` array for one.
107
+
108
+ ## See also
109
+
110
+ - [`docs/design/lang-plugin.md`](../../docs/design/lang-plugin.md) — the framework `classifySymbol` contract this plugin implements.
111
+ - [`docs/design/extension-vocab.md`](../../docs/design/extension-vocab.md) — how framework `extKind` namespaces (`framework:trpc:*`) plug into the shared vocab.
112
+ - [`@aburi/effects-trpc`](../effects-trpc/README.md) — the client side of tRPC.
@@ -0,0 +1,26 @@
1
+ import { FrameworkClassifyContext, FrameworkManifest, FrameworkPlugin, ImportEdge, OpaqueAstNode, PluginContext, SymbolCandidate, SymbolClassification } from "@aburi/types";
2
+
3
+ //#region src/classify.d.ts
4
+ declare function classifyTrpcSymbol(symbol: SymbolCandidate<OpaqueAstNode>, ctx: FrameworkClassifyContext): SymbolClassification | null;
5
+ //#endregion
6
+ //#region src/ext-kinds.d.ts
7
+ declare const TRPC_EXT_KINDS: readonly ["framework:trpc:instance", "framework:trpc:router", "framework:trpc:procedure", "framework:trpc:base-procedure", "framework:trpc:middleware"];
8
+ type TrpcExtKind = (typeof TRPC_EXT_KINDS)[number];
9
+ declare const TRPC_DERIVED_BY_PREFIX = "framework:trpc";
10
+ //#endregion
11
+ //#region src/imports.d.ts
12
+ declare function hasTrpcServerImport(imports: readonly ImportEdge[]): boolean;
13
+ //#endregion
14
+ //#region src/manifest.d.ts
15
+ declare const frameworkTrpcManifest: FrameworkManifest;
16
+ //#endregion
17
+ //#region src/plugin.d.ts
18
+ declare class TrpcFrameworkPlugin implements FrameworkPlugin<OpaqueAstNode> {
19
+ readonly manifest: import("@aburi/types").FrameworkManifest;
20
+ init(_ctx: PluginContext): Promise<void>;
21
+ classifySymbol(symbol: SymbolCandidate<OpaqueAstNode>, ctx: FrameworkClassifyContext): SymbolClassification | null;
22
+ }
23
+ /** Singleton so `manifest` identity is stable for consumers comparing against the constant. */
24
+ declare const trpcFrameworkPlugin: TrpcFrameworkPlugin;
25
+ //#endregion
26
+ export { TRPC_DERIVED_BY_PREFIX, TRPC_EXT_KINDS, type TrpcExtKind, TrpcFrameworkPlugin, classifyTrpcSymbol, frameworkTrpcManifest, hasTrpcServerImport, trpcFrameworkPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{asSyntaxNode as e,calleeName as t,declaratorValue as n,lastQnameSegment as r,receiverChain as i,unwrapValue as a,writtenChildren as o}from"@aburi/core";import{COMPUTED_TARGET_SEGMENT as s}from"@aburi/types";function c(e){if(e.type===`call_expression`){let n=t(e),r=e.childForFieldName(`function`);if(n===null||r===null)return null;let a=r.type===`member_expression`?r.childForFieldName(`object`):null,o=a===null?{path:[],root:n.name}:i(a);return{call:e,name:n.name,leaf:n.leaf,...o}}if(e.type===`member_expression`){let t=e.childForFieldName(`object`),n=e.childForFieldName(`property`)?.text??``;if(t===null||n.length===0)return null;let r=i(t);return{call:null,name:`${r.root}.${n}`,leaf:n,...r}}return null}const l=[`framework:trpc:instance`,`framework:trpc:router`,`framework:trpc:procedure`,`framework:trpc:base-procedure`,`framework:trpc:middleware`],u=`framework:trpc`,d=`@trpc/server`;function f(e){return e.some(({source:e})=>e===d||e.startsWith(`${d}/`))}const p=new Set([`router`,`createTRPCRouter`]),m=new Set([`query`,`mutation`,`subscription`]),h=new Set([`use`,`input`,`output`,`meta`,`concat`]),g=new Set([`arrow_function`,`function_expression`,`generator_function`,`identifier`,`member_expression`,`call_expression`]),_=/(^p|P)rocedure$/;function v(t,i){if(t.kind!==`const`)return null;let a=e(t.fullNode);if(a===null)return null;let o=n(a,r(t.name)),s=o===null?null:c(o),l=f(i.imports),d=s===null?null:y(s,l);return s===null||d===null?null:{extKind:`${u}:${d}`,derivedBy:`${u}:${d}:${s.name}`,confidence:l?`high`:`medium`}}function y(e,t){let{call:n,leaf:r}=e;if(n===null)return r===`procedure`&&e.path.length===0&&x(e.root)&&t?`base-procedure`:null;let i=S(n);if(r===`create`)return e.root===`initTRPC`?`instance`:null;if(p.has(r)){let[e,...t]=i;return e!==void 0&&t.length===0&&a(e).type===`object`?`router`:null}return r===`mergeRouters`?i.length>0?`router`:null:r===`middleware`?C(i)?`middleware`:null:b(e)?m.has(r)?C(i)?`procedure`:null:h.has(r)?`base-procedure`:null:null}function b(e){return e.path.at(-1)===`procedure`&&x(e.root)||_.test(e.root)}function x(e){return e!==`this`&&e!==s}function S(e){let t=e.childForFieldName(`arguments`);return t?.type===`arguments`?o(t):[]}function C(e){let[t,...n]=e;return t!==void 0&&n.length===0&&g.has(a(t).type)}const w={$schema:`https://aburi.kage1020.com/schema/aburi.plugin.v1.json`,name:`framework-trpc`,version:`0.0.0`,type:`framework`,engines:{aburi:`*`},provides:{effects:[],effectPrefixes:[],extKinds:[{id:`framework:trpc:instance`,baseKind:`const`,description:"The tRPC instance — a const initialised by `initTRPC.create(...)`, configured on the way or not (`initTRPC.context<C>().create()`)."},{id:`framework:trpc:router`,baseKind:`const`,description:"A tRPC router — a const initialised by `router({...})` or `createTRPCRouter({...})`, on the instance or not, or by `mergeRouters(...)`."},{id:`framework:trpc:procedure`,baseKind:`const`,description:"A tRPC procedure — a const initialised by a `query`, `mutation` or `subscription` handed its resolver, on a chain built from a procedure (`t.procedure`, or a name that is `procedure` or ends in `Procedure`)."},{id:`framework:trpc:base-procedure`,baseKind:`const`,description:"A reusable base procedure — a const initialised by `t.procedure`, or by `use`, `input`, `output`, `meta` or `concat` on a chain built from a procedure."},{id:`framework:trpc:middleware`,baseKind:`const`,description:"A tRPC middleware — a const initialised by `middleware(fn)`, on the instance or not."}],extKindPrefixes:[u],derivedByPrefixes:[u],frameworks:[`trpc`]}};var T=class{manifest=w;async init(e){}classifySymbol(e,t){return v(e,t)}};const E=new T;export{u as TRPC_DERIVED_BY_PREFIX,l as TRPC_EXT_KINDS,T as TrpcFrameworkPlugin,v as classifyTrpcSymbol,w as frameworkTrpcManifest,f as hasTrpcServerImport,E as trpcFrameworkPlugin};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aburi/framework-trpc",
3
- "version": "0.0.0",
4
- "description": "Name reservation for the Aburi tRPC framework plugin. Install 0.1.0 or later.",
3
+ "version": "0.1.0",
4
+ "description": "Aburi tRPC framework plugin: recognizes the server side of tRPC — the initTRPC instance, routers and merged routers, query / mutation / subscription procedures, base procedures and middleware — emitting framework:trpc:* extKinds",
5
5
  "license": "Apache-2.0",
6
6
  "author": "kage1020",
7
7
  "homepage": "https://github.com/kage1020/Aburi/tree/main/packages/framework-trpc#readme",
@@ -13,10 +13,40 @@
13
13
  "url": "git+https://github.com/kage1020/Aburi.git",
14
14
  "directory": "packages/framework-trpc"
15
15
  },
16
+ "type": "module",
17
+ "main": "./dist/index.mjs",
18
+ "types": "./dist/index.d.mts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
16
26
  "files": [
27
+ "dist",
17
28
  "README.md"
18
29
  ],
30
+ "sideEffects": false,
19
31
  "publishConfig": {
20
32
  "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "@aburi/core": "0.6.0",
36
+ "@aburi/types": "0.6.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^26.0.1",
40
+ "tsdown": "^0.22.3",
41
+ "typescript": "^6.0.3",
42
+ "vitest": "^4.1.11",
43
+ "@aburi/lang-typescript": "0.6.0",
44
+ "@aburi/test-harness": "0.0.1",
45
+ "@aburi/test-support": "0.0.2"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "typecheck": "tsc -p tsconfig.json --noEmit",
50
+ "test": "vitest run"
21
51
  }
22
- }
52
+ }