@cfast/core 0.0.1 → 0.1.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/llms.txt ADDED
@@ -0,0 +1,172 @@
1
+ # @cfast/core
2
+
3
+ > The app object that wires @cfast/* packages together with a typed plugin system.
4
+
5
+ ## When to use
6
+
7
+ Use `@cfast/core` when you want multiple cfast packages (auth, db, storage, etc.) to share a single per-request context without repeating initialization boilerplate in every route. It is optional -- individual packages remain fully usable standalone.
8
+
9
+ ## Key concepts
10
+
11
+ - **Plugin chain**: Plugins run in registration order via `.use()`. Each plugin's `setup()` receives everything prior plugins provided, plus `request` and `env`.
12
+ - **Namespaced context**: Each plugin's return value is nested under its `name` key (e.g., `ctx.auth.user`, `ctx.db.client`). No flat merging, no collisions.
13
+ - **Compile-time dependencies**: Dependent plugins declare requirements via `definePlugin<TRequires>()` (curried form). TypeScript catches missing dependencies at the `.use()` call site.
14
+ - **Client provider composition**: `<app.Provider>` nests all plugin React providers in registration order. `useApp()` accesses client-side plugin exports.
15
+
16
+ ## API Reference
17
+
18
+ ### Server (`@cfast/core`)
19
+
20
+ ```typescript
21
+ import { createApp, definePlugin, CfastPluginError, CfastConfigError } from "@cfast/core";
22
+
23
+ // Create the app
24
+ createApp(config: { env: TSchema; permissions: TPermissions }): App
25
+
26
+ // App methods
27
+ app.init(rawEnv: Record<string, unknown>): void // validate env (call once in Workers entry)
28
+ app.env(): ParsedEnv<TSchema> // typed validated environment
29
+ app.context(request, context?): Promise<AppContext> // build per-request context
30
+ app.loader(fn: (ctx, args) => T): (args) => Promise<T> // convenience route wrapper
31
+ app.action(fn: (ctx, args) => T): (args) => Promise<T> // convenience route wrapper
32
+ app.use(plugin): App // register a plugin (chainable)
33
+ app.Provider: ComponentType<{ children: ReactNode }> // composed React provider
34
+ app.permissions: TPermissions // permissions config
35
+ ```
36
+
37
+ ### Plugin definition
38
+
39
+ ```typescript
40
+ // Leaf plugin (no dependencies) -- direct form, full inference
41
+ definePlugin({
42
+ name: "analytics",
43
+ setup(ctx) { return { track: (event: string) => {} }; },
44
+ Provider?: ComponentType<{ children: ReactNode }>,
45
+ client?: { /* client-side values for useApp() */ },
46
+ }): CfastPlugin
47
+
48
+ // Dependent plugin -- curried form, specify TRequires explicitly
49
+ definePlugin<AuthPluginProvides>()({
50
+ name: "db",
51
+ setup(ctx) {
52
+ ctx.auth.user; // typed from AuthPluginProvides
53
+ return { client: createDb({}) };
54
+ },
55
+ }): CfastPlugin
56
+ ```
57
+
58
+ ### Plugin type token
59
+
60
+ ```typescript
61
+ // Each package exports a type token for dependents:
62
+ export type AuthPluginProvides = PluginProvides<typeof authPlugin>;
63
+ // Resolves to: { auth: { user: AuthUser | null; grants: Grant[]; instance: AuthInstance } }
64
+
65
+ // Multiple dependencies -- intersect type tokens:
66
+ definePlugin<AuthPluginProvides & DbPluginProvides>()({ ... })
67
+ ```
68
+
69
+ ### Client (`@cfast/core/client`)
70
+
71
+ ```typescript
72
+ import { useApp } from "@cfast/core/client";
73
+ import { CoreContext, createCoreProvider } from "@cfast/core/client";
74
+
75
+ useApp<T>(): T // typed access to all plugins' client exports (throws outside <app.Provider>)
76
+ ```
77
+
78
+ ### Error classes
79
+
80
+ ```typescript
81
+ CfastPluginError // thrown when plugin setup() fails; has .pluginName and .cause
82
+ CfastConfigError // thrown at startup for config issues (e.g., duplicate plugin names)
83
+ ```
84
+
85
+ ## Usage Examples
86
+
87
+ ### App setup
88
+
89
+ ```typescript
90
+ // app/cfast.ts
91
+ import { createApp } from "@cfast/core";
92
+ import { authPlugin } from "@cfast/auth";
93
+ import { dbPlugin } from "@cfast/db";
94
+ import { storagePlugin } from "@cfast/storage";
95
+
96
+ export const app = createApp({ env: envSchema, permissions })
97
+ .use(authPlugin({ magicLink: { sendMagicLink: async ({ email, url }) => {} } }))
98
+ .use(dbPlugin({ schema }))
99
+ .use(storagePlugin(storageSchema));
100
+ ```
101
+
102
+ ### Workers entry
103
+
104
+ ```typescript
105
+ // workers/app.ts
106
+ import { app } from "~/cfast";
107
+ export default {
108
+ async fetch(request, rawEnv, ctx) {
109
+ app.init(rawEnv);
110
+ return requestHandler(request, { cloudflare: { env: app.env(), ctx } });
111
+ },
112
+ };
113
+ ```
114
+
115
+ ### Route loader
116
+
117
+ ```typescript
118
+ // Using app.context() directly:
119
+ export async function loader({ request, context }: Route.LoaderArgs) {
120
+ const ctx = await app.context(request, context);
121
+ return ctx.db.client.query(posts).findMany().run({});
122
+ }
123
+
124
+ // Using convenience wrapper:
125
+ export const loader = app.loader(async (ctx, { params }) => {
126
+ return ctx.db.client.query(posts).findMany().run({});
127
+ });
128
+ ```
129
+
130
+ ### Client provider
131
+
132
+ ```typescript
133
+ // app/root.tsx
134
+ export function Layout({ children }: { children: React.ReactNode }) {
135
+ return (
136
+ <html><body>
137
+ <app.Provider>{children}</app.Provider>
138
+ </body></html>
139
+ );
140
+ }
141
+ ```
142
+
143
+ ### Wiring with @cfast/actions
144
+
145
+ ```typescript
146
+ export const { createAction, composeActions } = createActions({
147
+ getContext: async ({ request, context }) => {
148
+ const ctx = await app.context(request, context);
149
+ return { db: ctx.db.client, user: ctx.auth.user, grants: ctx.auth.grants };
150
+ },
151
+ });
152
+ ```
153
+
154
+ ## Integration
155
+
156
+ - **@cfast/env** -- `createApp({ env: schema })` delegates to `defineEnv()`. `app.init()` calls `env.init()`, `app.env()` calls `env.get()`.
157
+ - **@cfast/permissions** -- Accepted directly in `createApp()`. Available to all plugins.
158
+ - **@cfast/auth, @cfast/db, @cfast/storage** -- Each ships an optional plugin for core. The plugin wraps their standalone factory and registers with `.use()`.
159
+ - **@cfast/actions** -- Not wrapped by core. Actions have their own context mechanism. Use `app.context()` inside `createActions({ getContext })`.
160
+
161
+ ## Common Mistakes
162
+
163
+ - Registering plugins in the wrong order -- `dbPlugin` depends on `authPlugin`, so auth must be `.use()`'d first. TypeScript catches this at compile time.
164
+ - Calling `app.context()` before `app.init()` -- env is not validated yet, will throw.
165
+ - Calling `useApp()` outside `<app.Provider>` -- throws a context error.
166
+ - Registering two plugins with the same `name` -- throws `CfastConfigError` at import time.
167
+ - Doing expensive work in `setup()` -- it runs on every request. Move one-time init to the plugin factory (outer function).
168
+
169
+ ## See Also
170
+
171
+ - `@cfast/env` -- Type-safe Cloudflare bindings validated by `app.init()`.
172
+ - `@cfast/permissions` -- Grants resolved per-request and exposed via `app.context()`.
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@cfast/core",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "App composition layer with plugin system for @cfast/* packages",
5
+ "keywords": [
6
+ "cfast",
7
+ "cloudflare-workers",
8
+ "plugin",
9
+ "composition",
10
+ "framework"
11
+ ],
5
12
  "license": "MIT",
6
13
  "repository": {
7
14
  "type": "git",
@@ -22,15 +29,16 @@
22
29
  }
23
30
  },
24
31
  "files": [
25
- "dist"
32
+ "dist",
33
+ "llms.txt"
26
34
  ],
27
35
  "sideEffects": false,
28
36
  "publishConfig": {
29
37
  "access": "public"
30
38
  },
31
39
  "dependencies": {
32
- "@cfast/permissions": "0.0.1",
33
- "@cfast/env": "0.0.1"
40
+ "@cfast/env": "0.1.0",
41
+ "@cfast/permissions": "0.1.0"
34
42
  },
35
43
  "peerDependencies": {
36
44
  "react": ">=18"
@@ -1,49 +0,0 @@
1
- import { Schema, ParsedEnv } from '@cfast/env';
2
- import { Permissions } from '@cfast/permissions';
3
- import { ComponentType, ReactNode } from 'react';
4
-
5
- type CreateAppConfig<TSchema extends Schema, TPermissions extends Permissions> = {
6
- env: TSchema;
7
- permissions: TPermissions;
8
- };
9
- type PluginSetupContext<TRequires> = {
10
- request: Request;
11
- env: Record<string, unknown>;
12
- } & TRequires;
13
- type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires = {}, TClient = {}> = {
14
- name: TName;
15
- setup: (ctx: PluginSetupContext<TRequires>) => TProvides | Promise<TProvides>;
16
- Provider?: ComponentType<{
17
- children: ReactNode;
18
- }>;
19
- client?: TClient;
20
- };
21
- type PluginProvides<T> = T extends CfastPlugin<infer N, infer P, unknown, unknown> ? {
22
- [K in N]: P;
23
- } : never;
24
- type AppContext<TSchema extends Schema, TPluginContext> = {
25
- env: ParsedEnv<TSchema>;
26
- } & TPluginContext;
27
- type RouteArgs = {
28
- request: Request;
29
- params: Record<string, string | undefined>;
30
- context: unknown;
31
- };
32
- type App<TSchema extends Schema, TPermissions extends Permissions, TPluginContext, TClientContext> = {
33
- init(rawEnv: Record<string, unknown>): void;
34
- env(): ParsedEnv<TSchema>;
35
- context(request: Request, context?: unknown): Promise<AppContext<TSchema, TPluginContext>>;
36
- loader<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
37
- action<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
38
- use<TName extends string, TProvides, TClient>(plugin: CfastPlugin<TName, TProvides, TPluginContext, TClient>): App<TSchema, TPermissions, TPluginContext & {
39
- [K in TName]: TProvides;
40
- }, TClientContext & (TClient extends {} ? {
41
- [K in TName]: TClient;
42
- } : {})>;
43
- Provider: ComponentType<{
44
- children: ReactNode;
45
- }>;
46
- permissions: TPermissions;
47
- };
48
-
49
- export type { App as A, CreateAppConfig as C, PluginSetupContext as P, RouteArgs as R, CfastPlugin as a, AppContext as b, PluginProvides as c };
@@ -1,49 +0,0 @@
1
- import { Schema, ParsedEnv } from '@cfast/env';
2
- import { Permissions } from '@cfast/permissions';
3
- import { ComponentType, ReactNode } from 'react';
4
-
5
- type CreateAppConfig<TSchema extends Schema, TPermissions extends Permissions> = {
6
- env: TSchema;
7
- permissions: TPermissions;
8
- };
9
- type PluginSetupContext<TRequires> = {
10
- request: Request;
11
- env: Record<string, unknown>;
12
- } & TRequires;
13
- type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires = unknown, TClient = unknown> = {
14
- name: TName;
15
- setup: (ctx: PluginSetupContext<TRequires>) => TProvides | Promise<TProvides>;
16
- Provider?: ComponentType<{
17
- children: ReactNode;
18
- }>;
19
- client?: TClient;
20
- };
21
- type PluginProvides<T> = T extends CfastPlugin<infer N, infer P, unknown, unknown> ? {
22
- [K in N]: P;
23
- } : never;
24
- type AppContext<TSchema extends Schema, TPluginContext> = {
25
- env: ParsedEnv<TSchema>;
26
- } & TPluginContext;
27
- type RouteArgs = {
28
- request: Request;
29
- params: Record<string, string | undefined>;
30
- context: unknown;
31
- };
32
- type App<TSchema extends Schema, TPermissions extends Permissions, TPluginContext, TClientContext> = {
33
- init(rawEnv: Record<string, unknown>): void;
34
- env(): ParsedEnv<TSchema>;
35
- context(request: Request, context?: unknown): Promise<AppContext<TSchema, TPluginContext>>;
36
- loader<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
37
- action<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
38
- use<TName extends string, TProvides, TClient>(plugin: CfastPlugin<TName, TProvides, TPluginContext, TClient>): App<TSchema, TPermissions, TPluginContext & {
39
- [K in TName]: TProvides;
40
- }, TClientContext & (TClient extends object ? {
41
- [K in TName]: TClient;
42
- } : unknown)>;
43
- Provider: ComponentType<{
44
- children: ReactNode;
45
- }>;
46
- permissions: TPermissions;
47
- };
48
-
49
- export type { App as A, CreateAppConfig as C, PluginSetupContext as P, RouteArgs as R, CfastPlugin as a, AppContext as b, PluginProvides as c };
@@ -1,123 +0,0 @@
1
- import { Schema, ParsedEnv } from '@cfast/env';
2
- import { Permissions } from '@cfast/permissions';
3
- import { ComponentType, ReactNode } from 'react';
4
-
5
- /**
6
- * Configuration object for {@link createApp}.
7
- *
8
- * @typeParam TSchema - The env schema type from `@cfast/env`.
9
- * @typeParam TPermissions - The permissions definition from `@cfast/permissions`.
10
- */
11
- type CreateAppConfig<TSchema extends Schema, TPermissions extends Permissions> = {
12
- /** The environment variable schema. Validated at `app.init()` time via `@cfast/env`. */
13
- env: TSchema;
14
- /** The permissions config from `definePermissions()`. Made available to all plugins. */
15
- permissions: TPermissions;
16
- };
17
- /**
18
- * The context object passed to a plugin's `setup()` function.
19
- *
20
- * Contains the current request, validated env, and all values provided by prior plugins
21
- * (typed via `TRequires`).
22
- *
23
- * @typeParam TRequires - Intersection of prior plugin provides (e.g., `AuthPluginProvides`).
24
- */
25
- type PluginSetupContext<TRequires> = {
26
- /** The incoming HTTP request for the current invocation. */
27
- request: Request;
28
- /** The validated environment bindings. */
29
- env: Record<string, unknown>;
30
- } & TRequires;
31
- /**
32
- * A cfast plugin definition created by {@link definePlugin}.
33
- *
34
- * Plugins provide server-side context values via `setup()`, optional client-side React providers,
35
- * and optional client-side values accessible via `useApp()`.
36
- *
37
- * @typeParam TName - The unique plugin name, used as the namespace key in `AppContext`.
38
- * @typeParam TProvides - The type returned by `setup()`, accessible as `ctx[name]`.
39
- * @typeParam TRequires - The context shape this plugin depends on from prior plugins.
40
- * @typeParam TClient - Client-side values exposed via `useApp()`.
41
- */
42
- type CfastPlugin<TName extends string = string, TProvides = unknown, TRequires = unknown, TClient = unknown> = {
43
- /** Unique identifier used as the namespace key in the app context. */
44
- name: TName;
45
- /** Called per-request to produce the values this plugin provides. */
46
- setup: (ctx: PluginSetupContext<TRequires>) => TProvides | Promise<TProvides>;
47
- /** Optional client-side React provider, composed into `app.Provider`. */
48
- Provider?: ComponentType<{
49
- children: ReactNode;
50
- }>;
51
- /** Optional client-side values exposed via `useApp()`. */
52
- client?: TClient;
53
- };
54
- /**
55
- * Utility type that extracts `{ [name]: ReturnType<setup> }` from a plugin definition.
56
- *
57
- * Use this to create a type token that dependent plugins can reference via `definePlugin<TRequires>()`.
58
- *
59
- * @typeParam T - A `CfastPlugin` type to extract provides from.
60
- */
61
- type PluginProvides<T> = T extends CfastPlugin<infer N, infer P, unknown, unknown> ? {
62
- [K in N]: P;
63
- } : never;
64
- /**
65
- * The accumulated per-request context after all plugins have run.
66
- *
67
- * Contains the validated env plus each plugin's namespaced values.
68
- *
69
- * @typeParam TSchema - The env schema type.
70
- * @typeParam TPluginContext - The intersection of all registered plugins' provides.
71
- */
72
- type AppContext<TSchema extends Schema, TPluginContext> = {
73
- /** The validated environment bindings. */
74
- env: ParsedEnv<TSchema>;
75
- } & TPluginContext;
76
- /**
77
- * Route handler arguments passed through from React Router loaders and actions.
78
- */
79
- type RouteArgs = {
80
- /** The incoming HTTP request. */
81
- request: Request;
82
- /** URL route parameters (e.g., `{ postId: "abc" }`). */
83
- params: Record<string, string | undefined>;
84
- /** The React Router context object (contains `cloudflare.env`, etc.). */
85
- context: unknown;
86
- };
87
- /**
88
- * The app object returned by `createApp()` and extended by `.use()` calls.
89
- *
90
- * Provides methods for environment initialization, per-request context creation,
91
- * route handler wrappers, plugin registration, and a composed React provider.
92
- *
93
- * @typeParam TSchema - The env schema type.
94
- * @typeParam TPermissions - The permissions definition type.
95
- * @typeParam TPluginContext - The accumulated plugin context type.
96
- * @typeParam TClientContext - The accumulated client-side context type.
97
- */
98
- type App<TSchema extends Schema, TPermissions extends Permissions, TPluginContext, TClientContext> = {
99
- /** Validates and initializes environment bindings. Call once in the Workers entry point. */
100
- init(rawEnv: Record<string, unknown>): void;
101
- /** Returns the typed, validated environment. */
102
- env(): ParsedEnv<TSchema>;
103
- /** Builds the per-request context by running each plugin's `setup()` in order. */
104
- context(request: Request, context?: unknown): Promise<AppContext<TSchema, TPluginContext>>;
105
- /** Convenience wrapper for React Router loaders that auto-creates the app context. */
106
- loader<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
107
- /** Convenience wrapper for React Router actions that auto-creates the app context. */
108
- action<T>(fn: (ctx: AppContext<TSchema, TPluginContext>, args: RouteArgs) => T | Promise<T>): (args: RouteArgs) => Promise<T>;
109
- /** Registers a plugin, extending the app's context type. Throws on duplicate names. */
110
- use<TName extends string, TProvides, TClient>(plugin: CfastPlugin<TName, TProvides, TPluginContext, TClient>): App<TSchema, TPermissions, TPluginContext & {
111
- [K in TName]: TProvides;
112
- }, TClientContext & (TClient extends object ? {
113
- [K in TName]: TClient;
114
- } : unknown)>;
115
- /** Composed React provider tree from all registered plugins. */
116
- Provider: ComponentType<{
117
- children: ReactNode;
118
- }>;
119
- /** The permissions config passed to `createApp()`. */
120
- permissions: TPermissions;
121
- };
122
-
123
- export type { App as A, CreateAppConfig as C, PluginSetupContext as P, RouteArgs as R, CfastPlugin as a, AppContext as b, PluginProvides as c };