@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 +109 -4
- package/dist/index.d.mts +26 -0
- package/dist/index.mjs +1 -0
- package/package.json +33 -3
package/README.md
CHANGED
|
@@ -1,7 +1,112 @@
|
|
|
1
1
|
# @aburi/framework-trpc
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
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
|
+
}
|