@jlnstack/procedure 0.0.1
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 +7 -0
- package/README.md +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.cjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.mjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.cjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.mjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.cjs +18 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.mjs +12 -0
- package/dist/core-internal.cjs +71 -0
- package/dist/core-internal.d.cts +33 -0
- package/dist/core-internal.d.cts.map +1 -0
- package/dist/core-internal.d.mts +33 -0
- package/dist/core-internal.d.mts.map +1 -0
- package/dist/core-internal.mjs +71 -0
- package/dist/core-internal.mjs.map +1 -0
- package/dist/core.cjs +12 -0
- package/dist/core.d.cts +15 -0
- package/dist/core.d.cts.map +1 -0
- package/dist/core.d.mts +15 -0
- package/dist/core.d.mts.map +1 -0
- package/dist/core.mjs +13 -0
- package/dist/core.mjs.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/dist/next.cjs +45 -0
- package/dist/next.d.cts +47 -0
- package/dist/next.d.cts.map +1 -0
- package/dist/next.d.mts +47 -0
- package/dist/next.d.mts.map +1 -0
- package/dist/next.mjs +46 -0
- package/dist/next.mjs.map +1 -0
- package/dist/react.cjs +25 -0
- package/dist/react.d.cts +20 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.mts +20 -0
- package/dist/react.d.mts.map +1 -0
- package/dist/react.mjs +26 -0
- package/dist/react.mjs.map +1 -0
- package/dist/types.d.cts +6 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +6 -0
- package/dist/types.d.mts.map +1 -0
- package/package.json +64 -0
- package/src/core-internal.ts +206 -0
- package/src/core.ts +29 -0
- package/src/index.ts +1 -0
- package/src/next.ts +161 -0
- package/src/react.ts +66 -0
- package/src/types.ts +4 -0
- package/test/core.test-d.ts +89 -0
- package/test/core.test.ts +57 -0
- package/test/next.test-d.ts +177 -0
- package/test/next.test.ts +230 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +5 -0
- package/tsdown.config.ts +23 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { init } from "../src/next";
|
|
4
|
+
|
|
5
|
+
const createSchema = <T>(): StandardSchemaV1<T> =>
|
|
6
|
+
({
|
|
7
|
+
"~standard": {
|
|
8
|
+
version: 1,
|
|
9
|
+
vendor: "test",
|
|
10
|
+
validate: (value: unknown) => ({ value: value as T }),
|
|
11
|
+
},
|
|
12
|
+
}) as StandardSchemaV1<T>;
|
|
13
|
+
|
|
14
|
+
const createFailingSchema = <T>(): StandardSchemaV1<T> =>
|
|
15
|
+
({
|
|
16
|
+
"~standard": {
|
|
17
|
+
version: 1,
|
|
18
|
+
vendor: "test",
|
|
19
|
+
validate: () => ({ issues: [{ message: "Invalid" }] }),
|
|
20
|
+
},
|
|
21
|
+
}) as StandardSchemaV1<T>;
|
|
22
|
+
|
|
23
|
+
const factory = init({ ctx: () => ({ userId: "123" }) });
|
|
24
|
+
|
|
25
|
+
describe("init", () => {
|
|
26
|
+
it("provides initial context", async () => {
|
|
27
|
+
const fn = factory.procedure.run(({ ctx }) => ctx.userId);
|
|
28
|
+
expect(await (fn as () => Promise<string>)()).toBe("123");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns middleware factory", () => {
|
|
32
|
+
const mw = factory.middleware(({ next }) =>
|
|
33
|
+
next({ ctx: { role: "admin" } }),
|
|
34
|
+
);
|
|
35
|
+
expect(typeof mw).toBe("function");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("params", () => {
|
|
40
|
+
it("unwraps params promise and adds to context", async () => {
|
|
41
|
+
const fn = factory.procedure
|
|
42
|
+
.params<{ slug: string }>()
|
|
43
|
+
.run(({ ctx }) => ctx.params.slug);
|
|
44
|
+
|
|
45
|
+
const result = await fn({ params: Promise.resolve({ slug: "hello" }) });
|
|
46
|
+
expect(result).toBe("hello");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("works with schema validation", async () => {
|
|
50
|
+
const schema = createSchema<{ id: string }>();
|
|
51
|
+
const fn = factory.procedure
|
|
52
|
+
.params<{ id: string }, typeof schema>(schema)
|
|
53
|
+
.run(({ ctx }) => ctx.params.id);
|
|
54
|
+
|
|
55
|
+
const result = await fn({ params: Promise.resolve({ id: "42" }) });
|
|
56
|
+
expect(result).toBe("42");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("searchParams", () => {
|
|
61
|
+
it("validates and adds searchParams to context", async () => {
|
|
62
|
+
const schema = createSchema<{ page: string }>();
|
|
63
|
+
const fn = factory.procedure
|
|
64
|
+
.searchParams(schema)
|
|
65
|
+
.run(({ ctx }) => ctx.searchParams.page);
|
|
66
|
+
|
|
67
|
+
const result = await fn({ searchParams: Promise.resolve({ page: "1" }) });
|
|
68
|
+
expect(result).toBe("1");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("throws on invalid searchParams", async () => {
|
|
72
|
+
const schema = createFailingSchema<{ page: string }>();
|
|
73
|
+
const fn = factory.procedure
|
|
74
|
+
.searchParams(schema)
|
|
75
|
+
.run(({ ctx }) => ctx.searchParams.page);
|
|
76
|
+
|
|
77
|
+
await expect(
|
|
78
|
+
fn({ searchParams: Promise.resolve({ page: "1" }) }),
|
|
79
|
+
).rejects.toThrow("Invalid search params");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("page", () => {
|
|
84
|
+
it("is an alias for run", async () => {
|
|
85
|
+
const fn = factory.procedure.page(({ ctx }) => `user-${ctx.userId}`);
|
|
86
|
+
const result = await fn({
|
|
87
|
+
searchParams: Promise.resolve({}),
|
|
88
|
+
params: Promise.resolve({}),
|
|
89
|
+
});
|
|
90
|
+
expect(result).toBe("user-123");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("works with params and searchParams", async () => {
|
|
94
|
+
const paramsSchema = createSchema<{ slug: string }>();
|
|
95
|
+
const searchSchema = createSchema<{ q: string }>();
|
|
96
|
+
|
|
97
|
+
const fn = factory.procedure
|
|
98
|
+
.params<{ slug: string }, typeof paramsSchema>(paramsSchema)
|
|
99
|
+
.searchParams(searchSchema)
|
|
100
|
+
.page(({ ctx }) => `${ctx.params.slug}-${ctx.searchParams.q}`);
|
|
101
|
+
|
|
102
|
+
const result = await fn({
|
|
103
|
+
params: Promise.resolve({ slug: "post" }),
|
|
104
|
+
searchParams: Promise.resolve({ q: "test" }),
|
|
105
|
+
});
|
|
106
|
+
expect(result).toBe("post-test");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("rsc", () => {
|
|
111
|
+
it("executes and returns value", async () => {
|
|
112
|
+
const fn = factory.procedure.rsc(() => "RSC Content");
|
|
113
|
+
expect(await (fn as () => Promise<string>)()).toBe("RSC Content");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("metadata", () => {
|
|
118
|
+
it("returns Metadata object", async () => {
|
|
119
|
+
const fn = factory.procedure.metadata(() => ({ title: "My Page" }));
|
|
120
|
+
expect(await (fn as () => Promise<{ title: string }>)()).toEqual({
|
|
121
|
+
title: "My Page",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("can access context for dynamic metadata", async () => {
|
|
126
|
+
const fn = factory.procedure.metadata(({ ctx }) => ({
|
|
127
|
+
title: `User ${ctx.userId}`,
|
|
128
|
+
}));
|
|
129
|
+
expect(await (fn as () => Promise<{ title: string }>)()).toEqual({
|
|
130
|
+
title: "User 123",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("layout", () => {
|
|
136
|
+
it("receives children in input", async () => {
|
|
137
|
+
const fn = factory.procedure.layout(
|
|
138
|
+
({ input }) => `wrapped: ${input.children}`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const result = await fn({ children: "Content" });
|
|
142
|
+
expect(result).toBe("wrapped: Content");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("layoutMetadata", () => {
|
|
147
|
+
it("returns Metadata for layouts", async () => {
|
|
148
|
+
const fn = factory.procedure.layoutMetadata(() => ({
|
|
149
|
+
description: "Layout desc",
|
|
150
|
+
}));
|
|
151
|
+
expect(await (fn as () => Promise<{ description: string }>)()).toEqual({
|
|
152
|
+
description: "Layout desc",
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("staticParams", () => {
|
|
158
|
+
it("returns array of params for static generation", async () => {
|
|
159
|
+
const fn = factory.procedure
|
|
160
|
+
.params<{ slug: string }>()
|
|
161
|
+
.staticParams(() => [{ slug: "post-1" }, { slug: "post-2" }]);
|
|
162
|
+
|
|
163
|
+
const result = await fn({ params: Promise.resolve({ slug: "" }) });
|
|
164
|
+
expect(result).toEqual([{ slug: "post-1" }, { slug: "post-2" }]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("chaining", () => {
|
|
169
|
+
it("chains params with middleware", async () => {
|
|
170
|
+
const authMiddleware = factory.middleware(({ ctx, next }) =>
|
|
171
|
+
next({ ctx: { ...ctx, isAuth: true } }),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const fn = factory.procedure
|
|
175
|
+
.use(authMiddleware)
|
|
176
|
+
.params<{ id: string }>()
|
|
177
|
+
.page(({ ctx }) => `${ctx.isAuth}-${ctx.params.id}`);
|
|
178
|
+
|
|
179
|
+
const result = await fn({
|
|
180
|
+
params: Promise.resolve({ id: "42" }),
|
|
181
|
+
searchParams: Promise.resolve({}),
|
|
182
|
+
});
|
|
183
|
+
expect(result).toBe("true-42");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("chains multiple middlewares with next-specific methods", async () => {
|
|
187
|
+
const mw1 = factory.middleware(({ next }) => next({ ctx: { a: 1 } }));
|
|
188
|
+
const mw2 = factory.middleware(({ next }) => next({ ctx: { b: 2 } }));
|
|
189
|
+
|
|
190
|
+
const fn = factory.procedure
|
|
191
|
+
.use([mw1, mw2])
|
|
192
|
+
.params<{ slug: string }>()
|
|
193
|
+
.searchParams(createSchema<{ q: string }>())
|
|
194
|
+
.page(({ ctx }) => ctx.a + ctx.b);
|
|
195
|
+
|
|
196
|
+
const result = await (fn as any)({
|
|
197
|
+
params: Promise.resolve({ slug: "test" }),
|
|
198
|
+
searchParams: Promise.resolve({ q: "query" }),
|
|
199
|
+
});
|
|
200
|
+
expect(result).toBe(3);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("preserves context through entire chain", async () => {
|
|
204
|
+
const fn = factory.procedure
|
|
205
|
+
.use(({ next }) => next({ ctx: { step1: true } }))
|
|
206
|
+
.use(({ ctx, next }) => next({ ctx: { ...ctx, step2: true } }))
|
|
207
|
+
.params<{ id: string }>()
|
|
208
|
+
.page(({ ctx }) => `${ctx.step1}-${ctx.step2}-${ctx.params.id}`);
|
|
209
|
+
|
|
210
|
+
const result = await fn({
|
|
211
|
+
params: Promise.resolve({ id: "test" }),
|
|
212
|
+
searchParams: Promise.resolve({}),
|
|
213
|
+
});
|
|
214
|
+
expect(result).toBe("true-true-test");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("async context", () => {
|
|
219
|
+
it("handles async initial context", async () => {
|
|
220
|
+
const asyncFactory = init({
|
|
221
|
+
ctx: async () => {
|
|
222
|
+
await Promise.resolve();
|
|
223
|
+
return { asyncValue: "loaded" };
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const fn = asyncFactory.procedure.run(({ ctx }) => ctx.asyncValue);
|
|
228
|
+
expect(await (fn as () => Promise<string>)()).toBe("loaded");
|
|
229
|
+
});
|
|
230
|
+
});
|
package/tsconfig.json
ADDED
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from "tsdown";
|
|
2
|
+
|
|
3
|
+
export const input = [
|
|
4
|
+
"src/index.ts",
|
|
5
|
+
"src/core.ts",
|
|
6
|
+
"src/react.ts",
|
|
7
|
+
"src/next.ts",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
target: ["node18", "es2017"],
|
|
12
|
+
entry: input,
|
|
13
|
+
dts: {
|
|
14
|
+
sourcemap: true,
|
|
15
|
+
tsconfig: "./tsconfig.build.json",
|
|
16
|
+
},
|
|
17
|
+
unbundle: true,
|
|
18
|
+
format: ["cjs", "esm"],
|
|
19
|
+
outExtensions: (ctx) => ({
|
|
20
|
+
dts: ctx.format === "cjs" ? ".d.cts" : ".d.mts",
|
|
21
|
+
js: ctx.format === "cjs" ? ".cjs" : ".mjs",
|
|
22
|
+
}),
|
|
23
|
+
});
|