@cfast/core 0.1.2 → 0.1.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/README.md +3 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.ts +51 -4
- package/dist/index.js +5 -1
- package/dist/{types-C2gJHKfQ.d.ts → types-CQyKq4D1.d.ts} +42 -11
- package/llms.txt +34 -2
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ import { app } from '~/cfast';
|
|
|
130
130
|
|
|
131
131
|
export async function loader({ request, context }: Route.LoaderArgs) {
|
|
132
132
|
const ctx = await app.context(request, context);
|
|
133
|
-
return ctx.db.client.query(posts).findMany().run(
|
|
133
|
+
return ctx.db.client.query(posts).findMany().run();
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
@@ -165,7 +165,7 @@ Optional convenience wrappers that call `app.context()` and pass the result as t
|
|
|
165
165
|
|
|
166
166
|
```typescript
|
|
167
167
|
export const loader = app.loader(async (ctx, { params }) => {
|
|
168
|
-
return ctx.db.client.query(posts).findMany().run(
|
|
168
|
+
return ctx.db.client.query(posts).findMany().run();
|
|
169
169
|
});
|
|
170
170
|
```
|
|
171
171
|
|
|
@@ -443,7 +443,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
|
|
|
443
443
|
}
|
|
444
444
|
await ctx['rate-limit'].consume();
|
|
445
445
|
|
|
446
|
-
return ctx.db.client.query(posts).findMany().run(
|
|
446
|
+
return ctx.db.client.query(posts).findMany().run();
|
|
447
447
|
}
|
|
448
448
|
```
|
|
449
449
|
|
package/dist/client/index.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Schema } from '@cfast/env';
|
|
2
2
|
import { Permissions } from '@cfast/permissions';
|
|
3
|
-
import { C as CreateAppConfig, A as App, a as CfastPlugin, P as PluginSetupContext, R as RequiresFromPlugins } from './types-
|
|
4
|
-
export { b as AppContext, c as
|
|
3
|
+
import { C as CreateAppConfig, A as App, a as CfastPlugin, P as PluginSetupContext, R as RequiresFromPlugins } from './types-CQyKq4D1.js';
|
|
4
|
+
export { b as AppContext, c as PluginContext, d as PluginProvides, e as RouteArgs } from './types-CQyKq4D1.js';
|
|
5
5
|
import { ComponentType, ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -90,7 +90,7 @@ declare function createApp<TSchema extends Schema, TPermissions extends Permissi
|
|
|
90
90
|
* });
|
|
91
91
|
* ```
|
|
92
92
|
*/
|
|
93
|
-
declare function definePlugin<TName extends string, TProvides, const TRequires extends readonly CfastPlugin<string, unknown, any, unknown>[] = [], TClient = unknown>(config: {
|
|
93
|
+
declare function definePlugin<TName extends string, TProvides, const TRequires extends readonly CfastPlugin<string, unknown, any, unknown, any>[] = [], TClient = unknown>(config: {
|
|
94
94
|
name: TName;
|
|
95
95
|
requires?: TRequires;
|
|
96
96
|
setup: (ctx: PluginSetupContext<RequiresFromPlugins<TRequires>>) => TProvides | Promise<TProvides>;
|
|
@@ -107,6 +107,53 @@ declare function definePlugin<TRequires>(): <TName extends string, TProvides, TC
|
|
|
107
107
|
}>;
|
|
108
108
|
client?: TClient;
|
|
109
109
|
}) => CfastPlugin<TName, Awaited<TProvides>, TRequires, TClient>;
|
|
110
|
+
/**
|
|
111
|
+
* Returns an env-typed `definePlugin` factory.
|
|
112
|
+
*
|
|
113
|
+
* The scaffolded `Cloudflare.Env` type from `worker-configuration.d.ts` is
|
|
114
|
+
* not known to `@cfast/core` at build time, so the generic `definePlugin`
|
|
115
|
+
* exposes `ctx.env` as the loose `Record<string, unknown>` shape and
|
|
116
|
+
* consumers have to cast bindings (e.g. `ctx.env.DB as D1Database`). Apps
|
|
117
|
+
* that want precise bindings call this factory once with their env type
|
|
118
|
+
* and re-export the typed `definePlugin` from a local module:
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* // app/plugins/define-plugin.ts
|
|
122
|
+
* import { definePluginFor } from "@cfast/core";
|
|
123
|
+
*
|
|
124
|
+
* export const definePlugin = definePluginFor<Cloudflare.Env>();
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* Plugin authors then import from their local module and get `ctx.env.DB`
|
|
128
|
+
* typed as `D1Database` without any casting:
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* import { definePlugin } from "~/plugins/define-plugin";
|
|
132
|
+
*
|
|
133
|
+
* export const dbPlugin = definePlugin({
|
|
134
|
+
* name: "db",
|
|
135
|
+
* setup(ctx) {
|
|
136
|
+
* const db = ctx.env.DB; // D1Database, no cast
|
|
137
|
+
* return { client: createDb({ d1: db }) };
|
|
138
|
+
* },
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* The returned factory has the same shape as {@link definePlugin} (with the
|
|
143
|
+
* curried legacy overload preserved) so existing code that switches from the
|
|
144
|
+
* generic form to the env-typed form only needs an import swap.
|
|
145
|
+
*/
|
|
146
|
+
declare function definePluginFor<TEnv>(): {
|
|
147
|
+
<TName extends string, TProvides, const TRequires extends readonly CfastPlugin<string, unknown, any, unknown, any>[] = [], TClient = unknown>(config: {
|
|
148
|
+
name: TName;
|
|
149
|
+
requires?: TRequires;
|
|
150
|
+
setup: (ctx: PluginSetupContext<RequiresFromPlugins<TRequires>, TEnv>) => TProvides | Promise<TProvides>;
|
|
151
|
+
Provider?: ComponentType<{
|
|
152
|
+
children: ReactNode;
|
|
153
|
+
}>;
|
|
154
|
+
client?: TClient;
|
|
155
|
+
}): CfastPlugin<TName, Awaited<TProvides>, RequiresFromPlugins<TRequires>, TClient, TEnv>;
|
|
156
|
+
};
|
|
110
157
|
|
|
111
158
|
/**
|
|
112
159
|
* Error thrown when a plugin's `setup()` function fails during `app.context()`.
|
|
@@ -134,4 +181,4 @@ declare class CfastConfigError extends Error {
|
|
|
134
181
|
constructor(message: string);
|
|
135
182
|
}
|
|
136
183
|
|
|
137
|
-
export { App, CfastConfigError, CfastPlugin, CfastPluginError, CreateAppConfig, PluginSetupContext, createApp, definePlugin };
|
|
184
|
+
export { App, CfastConfigError, CfastPlugin, CfastPluginError, CreateAppConfig, PluginSetupContext, createApp, definePlugin, definePluginFor };
|
package/dist/index.js
CHANGED
|
@@ -114,9 +114,13 @@ function definePlugin(config) {
|
|
|
114
114
|
}
|
|
115
115
|
return config;
|
|
116
116
|
}
|
|
117
|
+
function definePluginFor() {
|
|
118
|
+
return (config) => config;
|
|
119
|
+
}
|
|
117
120
|
export {
|
|
118
121
|
CfastConfigError,
|
|
119
122
|
CfastPluginError,
|
|
120
123
|
createApp,
|
|
121
|
-
definePlugin
|
|
124
|
+
definePlugin,
|
|
125
|
+
definePluginFor
|
|
122
126
|
};
|
|
@@ -20,14 +20,43 @@ type CreateAppConfig<TSchema extends Schema, TPermissions extends Permissions> =
|
|
|
20
20
|
* Contains the current request, validated env, and all values provided by prior plugins
|
|
21
21
|
* (typed via `TRequires`).
|
|
22
22
|
*
|
|
23
|
+
* By default `env` is typed as the loose `Record<string, unknown>` shape used
|
|
24
|
+
* by the framework's generic plugin layer. Consumer apps that want precise
|
|
25
|
+
* bindings (e.g. `Cloudflare.Env` from `worker-configuration.d.ts`) can
|
|
26
|
+
* specialise the type via {@link definePluginFor} or by importing the
|
|
27
|
+
* {@link PluginContext} helper directly.
|
|
28
|
+
*
|
|
23
29
|
* @typeParam TRequires - Intersection of prior plugin provides (e.g., `AuthPluginProvides`).
|
|
30
|
+
* @typeParam TEnv - Env shape (defaults to the loose record shape).
|
|
24
31
|
*/
|
|
25
|
-
type PluginSetupContext<TRequires
|
|
32
|
+
type PluginSetupContext<TRequires, TEnv = Record<string, unknown>> = {
|
|
26
33
|
/** The incoming HTTP request for the current invocation. */
|
|
27
34
|
request: Request;
|
|
28
35
|
/** The validated environment bindings. */
|
|
29
|
-
env:
|
|
36
|
+
env: TEnv;
|
|
30
37
|
} & TRequires;
|
|
38
|
+
/**
|
|
39
|
+
* Convenience alias for consumers who want to annotate a plugin's `setup(ctx)`
|
|
40
|
+
* parameter without going through {@link definePluginFor}. Re-exported from
|
|
41
|
+
* the package entry as `PluginContext`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import type { PluginContext } from "@cfast/core";
|
|
46
|
+
* import type { PluginProvides } from "@cfast/core";
|
|
47
|
+
* import type { authPlugin } from "./plugins/auth";
|
|
48
|
+
*
|
|
49
|
+
* export const dbPlugin = definePlugin({
|
|
50
|
+
* name: "db",
|
|
51
|
+
* requires: [authPlugin],
|
|
52
|
+
* setup(ctx: PluginContext<Cloudflare.Env, PluginProvides<typeof authPlugin>>) {
|
|
53
|
+
* const db = ctx.env.DB; // typed as D1Database, no cast
|
|
54
|
+
* return { client: createDb({ d1: db }) };
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
type PluginContext<TEnv = Record<string, unknown>, TRequires = unknown> = PluginSetupContext<TRequires, TEnv>;
|
|
31
60
|
/**
|
|
32
61
|
* A cfast plugin definition created by {@link definePlugin}.
|
|
33
62
|
*
|
|
@@ -38,8 +67,10 @@ type PluginSetupContext<TRequires> = {
|
|
|
38
67
|
* @typeParam TProvides - The type returned by `setup()`, accessible as `ctx[name]`.
|
|
39
68
|
* @typeParam TRequires - The context shape this plugin depends on from prior plugins.
|
|
40
69
|
* @typeParam TClient - Client-side values exposed via `useApp()`.
|
|
70
|
+
* @typeParam TEnv - Env shape seen by `setup()`. Defaults to the loose record
|
|
71
|
+
* shape so non-specialised plugins do not need to parameterise this slot.
|
|
41
72
|
*/
|
|
42
|
-
type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires = unknown, TClient = unknown
|
|
73
|
+
type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires = unknown, TClient = unknown, TEnv = Record<string, unknown>> = {
|
|
43
74
|
/** Unique identifier used as the namespace key in the app context. */
|
|
44
75
|
name: TName;
|
|
45
76
|
/**
|
|
@@ -49,9 +80,9 @@ type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires =
|
|
|
49
80
|
* required plugin's provides, and `app.use(this)` will throw at registration
|
|
50
81
|
* time if any of these plugins have not yet been registered.
|
|
51
82
|
*/
|
|
52
|
-
requires?: readonly CfastPlugin<string, unknown, any, unknown>[];
|
|
83
|
+
requires?: readonly CfastPlugin<string, unknown, any, unknown, any>[];
|
|
53
84
|
/** Called per-request to produce the values this plugin provides. */
|
|
54
|
-
setup: (ctx: PluginSetupContext<TRequires>) => TProvides | Promise<TProvides>;
|
|
85
|
+
setup: (ctx: PluginSetupContext<TRequires, TEnv>) => TProvides | Promise<TProvides>;
|
|
55
86
|
/** Optional client-side React provider, composed into `app.Provider`. */
|
|
56
87
|
Provider?: ComponentType<{
|
|
57
88
|
children: ReactNode;
|
|
@@ -80,8 +111,8 @@ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) exten
|
|
|
80
111
|
* required plugins each contribute their own `{ name: provides }` shape rather
|
|
81
112
|
* than collapsing into a union of widened `provides` values.
|
|
82
113
|
*/
|
|
83
|
-
type RequiresFromPlugins<R extends readonly CfastPlugin<string, unknown, any, unknown>[]> = R extends readonly [] ? unknown : UnionToIntersection<{
|
|
84
|
-
[K in keyof R]: R[K] extends CfastPlugin<infer N, infer P, unknown, unknown> ? {
|
|
114
|
+
type RequiresFromPlugins<R extends readonly CfastPlugin<string, unknown, any, unknown, any>[]> = R extends readonly [] ? unknown : UnionToIntersection<{
|
|
115
|
+
[K in keyof R]: R[K] extends CfastPlugin<infer N, infer P, unknown, unknown, any> ? {
|
|
85
116
|
[Key in N]: P;
|
|
86
117
|
} : never;
|
|
87
118
|
}[number]>;
|
|
@@ -108,7 +139,7 @@ type RuntimePlugin = {
|
|
|
108
139
|
* `app.use(plugin)` walks this list and verifies each entry is already
|
|
109
140
|
* registered, throwing `CfastConfigError` immediately if not.
|
|
110
141
|
*/
|
|
111
|
-
requires?: readonly CfastPlugin<string, unknown, any, unknown>[];
|
|
142
|
+
requires?: readonly CfastPlugin<string, unknown, any, unknown, any>[];
|
|
112
143
|
/**
|
|
113
144
|
* Setup function called per-request.
|
|
114
145
|
*
|
|
@@ -130,7 +161,7 @@ type RuntimePlugin = {
|
|
|
130
161
|
*
|
|
131
162
|
* @typeParam T - A `CfastPlugin` type to extract provides from.
|
|
132
163
|
*/
|
|
133
|
-
type PluginProvides<T> = T extends CfastPlugin<infer N, infer P, unknown, unknown> ? {
|
|
164
|
+
type PluginProvides<T> = T extends CfastPlugin<infer N, infer P, unknown, unknown, any> ? {
|
|
134
165
|
[K in N]: P;
|
|
135
166
|
} : never;
|
|
136
167
|
/**
|
|
@@ -179,7 +210,7 @@ type App<TSchema extends Schema, TPermissions extends Permissions, TPluginContex
|
|
|
179
210
|
/** Convenience wrapper for React Router actions that auto-creates the app context. */
|
|
180
211
|
action<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
|
|
181
212
|
/** Registers a plugin, extending the app's context type. Throws on duplicate names. */
|
|
182
|
-
use<TName extends string, TProvides, TClient>(plugin: CfastPlugin<TName, TProvides, TPluginContext, TClient>): App<TSchema, TPermissions, TPluginContext & {
|
|
213
|
+
use<TName extends string, TProvides, TClient, TEnv = any>(plugin: CfastPlugin<TName, TProvides, TPluginContext, TClient, TEnv>): App<TSchema, TPermissions, TPluginContext & {
|
|
183
214
|
[K in TName]: TProvides;
|
|
184
215
|
}, TClientContext & (TClient extends object ? {
|
|
185
216
|
[K in TName]: TClient;
|
|
@@ -192,4 +223,4 @@ type App<TSchema extends Schema, TPermissions extends Permissions, TPluginContex
|
|
|
192
223
|
permissions: TPermissions;
|
|
193
224
|
};
|
|
194
225
|
|
|
195
|
-
export type { App as A, CreateAppConfig as C, PluginSetupContext as P, RequiresFromPlugins as R, CfastPlugin as a, AppContext as b,
|
|
226
|
+
export type { App as A, CreateAppConfig as C, PluginSetupContext as P, RequiresFromPlugins as R, CfastPlugin as a, AppContext as b, PluginContext as c, PluginProvides as d, RouteArgs as e, RuntimePlugin as f };
|
package/llms.txt
CHANGED
|
@@ -106,6 +106,38 @@ definePlugin<AuthPluginProvides>()({
|
|
|
106
106
|
Prefer the inferred form (`requires: [authPlugin]`) when you can — it removes
|
|
107
107
|
the need to declare and import a `PluginProvides` type token.
|
|
108
108
|
|
|
109
|
+
### Typed env (`definePluginFor<Env>`)
|
|
110
|
+
|
|
111
|
+
The generic `definePlugin` exposes `ctx.env` as `Record<string, unknown>`
|
|
112
|
+
because `@cfast/core` cannot know your scaffolded `Cloudflare.Env` shape. To
|
|
113
|
+
avoid `ctx.env.DB as D1Database` casts in every plugin, call `definePluginFor`
|
|
114
|
+
once with your env type and re-export the typed factory from a local module:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// app/plugins/define-plugin.ts
|
|
118
|
+
import { definePluginFor } from "@cfast/core";
|
|
119
|
+
export const definePlugin = definePluginFor<Cloudflare.Env>();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// app/plugins/db.ts
|
|
124
|
+
import { definePlugin } from "~/plugins/define-plugin";
|
|
125
|
+
|
|
126
|
+
export const dbPlugin = definePlugin({
|
|
127
|
+
name: "db",
|
|
128
|
+
requires: [authPlugin],
|
|
129
|
+
setup(ctx) {
|
|
130
|
+
const db = ctx.env.DB; // typed as D1Database — no cast
|
|
131
|
+
return { client: createDb({ d1: db, /* ... */ }) };
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The returned factory has the same shape as `definePlugin`, so plugins built
|
|
137
|
+
with it slot into existing `app.use()` calls without further changes. If you
|
|
138
|
+
prefer to type a single plugin in-place without a factory, import the
|
|
139
|
+
`PluginContext<Env, TRequires>` helper and annotate the parameter directly.
|
|
140
|
+
|
|
109
141
|
### Client (`@cfast/core/client`)
|
|
110
142
|
|
|
111
143
|
```typescript
|
|
@@ -158,12 +190,12 @@ export default {
|
|
|
158
190
|
// Using app.context() directly:
|
|
159
191
|
export async function loader({ request, context }: Route.LoaderArgs) {
|
|
160
192
|
const ctx = await app.context(request, context);
|
|
161
|
-
return ctx.db.client.query(posts).findMany().run(
|
|
193
|
+
return ctx.db.client.query(posts).findMany().run();
|
|
162
194
|
}
|
|
163
195
|
|
|
164
196
|
// Using convenience wrapper:
|
|
165
197
|
export const loader = app.loader(async (ctx, { params }) => {
|
|
166
|
-
return ctx.db.client.query(posts).findMany().run(
|
|
198
|
+
return ctx.db.client.query(posts).findMany().run();
|
|
167
199
|
});
|
|
168
200
|
```
|
|
169
201
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfast/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "App composition layer with plugin system for @cfast/* packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cfast",
|
|
@@ -36,14 +36,18 @@
|
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"@cfast/permissions": "0.2.0",
|
|
41
|
-
"@cfast/env": "0.1.1"
|
|
42
|
-
},
|
|
43
39
|
"peerDependencies": {
|
|
40
|
+
"@cfast/env": ">=0.1.0 <0.3.0",
|
|
41
|
+
"@cfast/permissions": ">=0.3.0 <0.6.0",
|
|
44
42
|
"react": ">=18"
|
|
45
43
|
},
|
|
46
44
|
"peerDependenciesMeta": {
|
|
45
|
+
"@cfast/env": {
|
|
46
|
+
"optional": false
|
|
47
|
+
},
|
|
48
|
+
"@cfast/permissions": {
|
|
49
|
+
"optional": false
|
|
50
|
+
},
|
|
47
51
|
"react": {
|
|
48
52
|
"optional": true
|
|
49
53
|
}
|
|
@@ -55,7 +59,9 @@
|
|
|
55
59
|
"react-dom": "^19.2.4",
|
|
56
60
|
"tsup": "^8",
|
|
57
61
|
"typescript": "^5.7",
|
|
58
|
-
"vitest": "^4.1.0"
|
|
62
|
+
"vitest": "^4.1.0",
|
|
63
|
+
"@cfast/permissions": "0.5.1",
|
|
64
|
+
"@cfast/env": "0.2.1"
|
|
59
65
|
},
|
|
60
66
|
"scripts": {
|
|
61
67
|
"build": "tsup src/index.ts src/client/index.ts --format esm --dts",
|