Gathering detailed insights and metrics for bistrio
Gathering detailed insights and metrics for bistrio
Gathering detailed insights and metrics for bistrio
Gathering detailed insights and metrics for bistrio
npm install bistrio
Typescript
Module System
Node Version
NPM Version
51.4
Supply Chain
38.4
Quality
76.9
Maintenance
50
Vulnerability
99.3
License
Verify real, reachable, and deliverable emails with instant MX records, SMTP checks, and disposable email detection.
Total Downloads
11,424
Last Day
5
Last Week
10
Last Month
46
Last Year
8,304
Latest Version
0.8.9
Package Id
bistrio@0.8.9
Unpacked Size
944.48 kB
Size
130.89 kB
File Count
426
NPM Version
10.2.3
Node Version
20.10.0
Published on
Aug 25, 2024
Cumulative downloads
Total Downloads
Last Day
0%
5
Compared to previous day
Last Week
400%
10
Compared to previous week
Last Month
-41.8%
46
Compared to previous month
Last Year
166.2%
8,304
Compared to previous year
13
27
This is a web application framework made with TypeScript, React, and Zod. It focuses on the presentation layer as described in the three-tier architecture, covering both the frontend written in JSX and the backend layer (the surface of the server) that communicates with the frontend.
Many frameworks like Next.js primarily focus on the frontend. In such cases, I believe there are several challenges:
To address these challenges, our framework takes responsibility "from the frontend to the entrance of the backend." This approach results in:
The backend is built following the philosophy of REST, defining Resources and their methods as the framework's responsibility. Since it's not a full-stack solution, you can choose any database or other components for implementing Resources.
Since routing holds the most information in web systems, we recommend starting development from routing to maintain system consistency.
Multiple central Routing information is generated from a single Routes information:
Our framework does not follow the MVC design pattern. Although MVC is a familiar and straightforward concept for many, our framework intentionally avoids using Controllers.
This is because Controllers, despite being files where one might want to write logic, are concepts that deal with web information. This conflict can lead to issues like the so-called Fat Controllers. To counter this, many people tend to delegate processing from Controllers to other classes.
In our system, Resources are somewhat similar to Controllers but do not carry web-related information, positioning them broadly as Models. These files can contain a lot of logic according to use cases. If necessary, developers can consider other concepts (similar to Models in MVC) for commonalities.
As a result, many functions traditionally handled by Controllers have been moved to Routes:
Routes are defined using a custom DSL, structuring the basic content around corresponding Resources.
While we introduce this below, some documentation may not be fully prepared, and specifications might be adjusted. For the latest usage examples, see example/tasks.
The resources
method defined in Router sets up endpoints to convey request information to Resources.
At this point, Zod's type information is assigned to each action, ensuring that only schema-validated data is handled by Resources. This means input validation is automated, ensuring only specified data is received by the server, enhancing robustness.
1// Defines CRUD for a single Resource corresponding to the `/tasks` path. 2r.resources('tasks', { 3 name: 'tasks', // The name of the resource (TasksResource interface is automatically generated) 4 actions: crud(), // Defines typical actions 5 construct: { 6 // Specifies Zod schemas to define accepted data 7 create: { schema: taskCreateWithTagsSchema }, 8 update: { schema: taskUpdateWithTagsSchema }, 9 }, 10})
Taking the tasks
resource as an example, the default routing would look like this, defined all at once by the crud()
function:
action | method | path | type | page | Main Purpose |
---|---|---|---|---|---|
index | GET | /tasks | true | List view | |
show | GET | /tasks/$id | true | Detail view | |
build | GET | /tasks/build | true | New creation view | |
edit | GET | /tasks/$id/edit | true | Edit view | |
list | GET | /tasks.json | json | Fetch list as JSON | |
load | GET | /tasks/$id.json | json | Fetch details as JSON | |
create | POST | /tasks/ | Create action | ||
update | PUT,PATCh | /tasks/$id | Update action | ||
delete | DELETE | /tasks/$id | Delete action |
For example, the edit action for /tasks
would be /tasks/$id/edit
(where $id
is a placeholder).
Besides crud()
, there's also an api()
function, which only defines list
, load
, create
, update
, and delete
.
Both crud()
and api()
can be filtered with common arguments:
1crud({ only: ['index', 'load'] }) // Only defines index and load 2api({ except: ['list', 'load'] }) // Defines create, update, delete, excluding list and load 3 4crud('index', 'load') // Only defines index and load (syntax sugar for 'only')
Furthermore, actions can not only be the return values of utilities like crud()
but also custom-defined. For example, you can add a custom action done
like this:
1r.resources({ 2 ... 3 actions: [...crud(), { action: 'done', path: '$id/done', method: 'post', type: 'json' }], 4 ... 5})
This method is for creating pages unrelated to Resources.
1r.pages('/', ['/', '/about']) // Defines routing for `/` and `/about`
Resources are based on the REST concept, allowing developers to freely create necessary methods. These methods can be called as actions from Routes.
logic directly in them.
After defining Routes, running npm run bistrio:gen
will automatically generate corresponding interfaces in .bistrio/resources
. Using these types to implement actual Resources ensures smooth operation.
Create a directory matching the URL path hierarchy in server/resources
and create a resource.ts
file.
For example, the Resource for /tasks
corresponds to the file server/resources/tasks/resource.ts
. The content looks like this, with the utility function defineResource
provided to assist in creation.
1import { CustomActionOptions } from '@/server/customizers' 2import { TasksResource } from '@bistrio/resources' 3 4//... 5 6export default defineResource( 7 () => 8 ({ 9 // Create methods corresponding to each action name 10 list: async (params): Promise<Paginated<Task>> => { 11 return { 12 //... 13 } 14 }, 15 16 load: async ({ id }): Promise<Task> => { 17 // This is an example using prisma 18 const task = await prisma.task.findUniqueOrThrow({ id }) 19 return task 20 }, 21 22 // ... 23 done: async ({ id }) => await prisma.task.update({ where: { id }, data: { done: true } }), 24 }) as const satisfies TasksResource<CustomActionOptions>, // This specification makes the specific type available externally 25)
For a more practical example, see example/tasks/server/resources/tasks/resource.ts.
When creating a Resource, keep the following points in mind:
TaskResource
type is a generic type that can specify custom argument types. Specify types defined by the system, like CustomActionOptions
.as const satisfies TasksResource<CustomActionOptions>
to ensure the return of a specific type.CustomActionOptions
Processes like extracting user information from sessions are not performed within Resources. Since such processes are common across most parts of the system, they are handled before calling an action in server/customizers/index.ts
's createActionOptions
. Customize this content according to your application.
You can implement using the req
object from the ctx
variable, which is derived from Express. This return value is set as an optional argument for each action in the resource, making it available within the action.
1export type CustomActionOptions = { 2 user?: User 3 admin?: { 4 id: number 5 accessedAt: Date 6 } 7} & ActionOptions // It is also required to be of type ActionOptions 8 9export const createActionOptions: CreateActionOptionFunction = (ctx) => { 10 const customActionOptions: CustomActionOptions = buildActionOptions({ user: ctx.req.user as User }) 11 12 if (ctx.params.adminId) { 13 // For example, if it's an admin, additional specific information is included 14 customActionOptions.admin = { 15 id: Number(ctx.params.adminId), 16 accessedAt: new Date(), 17 } 18 } 19 20 return customActionOptions 21}
For example, if you add a load
action to a Resource, it will be set as the second argument and available for use. If there are no arguments, only CustomActionOptions
is set as the argument.
1 load: async ({ id }, options: CustomActionOptions): Promise<Task> => { 2 // You can write processing using options 3 },
Views are written in JSX. In this framework, server Resources can be manipulated through RenderSupport. With the introduction of Suspense in React 18, there's no need to write code heavily reliant on useEffect.
Views are conventionally called Pages in frontend JS.
Create a directory in universal/pages
matching the URL path hierarchy and create files with matching names.
For example:
/about
: universal/pages/about.tsx
/
: universal/pages/index.tsx
(index is a special name indicating /
)/test/mypage
: universal/pages/test/mypage.tsx
When implementing a Page, you need to use data from the server. At this time, information is obtained through RenderSupport
.
For instance, to call the load
action of the tasks resource, you would write:
1import { useRenderSupport } from '@bistrio/routes/main' 2 3// ... 4 5function Task({ id }: { id: number }) { 6 const rs = useRenderSupport() 7 const task = rs.suspendedResources().tasks.load({ id }) // Communicates to call the load action of tasks resource 8 // rs.suspendedResources() retrieves stubs of resources adapted for Suspense. 9 10 return <>{/* ... */}</> 11}
useRenderSupport
placed in the automatically generated '@bistrio/routes/main' (the framework does not provide a fixed type).rs.resources()
returns an implementation that gives a Promise.Running npm run console
starts the REPL. You can call each resource via the global variable resources
.
For example, you can test the load
action of the tasks resource like this:
1$ npm run console 2 3> tasks@0.0.0 console 4> DEBUG=-bistrio:console NODE_ENV=development dotenv -e .env.development -- node --import ./dist/server/console.js 5 6Welcome to Node.js v20.10.0. 7Type ".help" for more information. 8> await resources.tasks.load({id: 1}) 9{ 10 id: 1, 11 title: 'Test1', 12 description: 'Test1 Description', 13 done: false, 14 createdAt: 2023-12-23T05:45:07.584Z, 15 updatedAt: 2024-01-28T07:57:17.471Z, 16 tags: [ 'tag1', 'tag2' ] 17} 18>
Since everything is automatically generated from common Routes, there's no need to worry about aligning multiple routings between the client and server.
Automatically created from Routes and Resource information, there's no need to be concerned about generating stubs that match server-side code.
Endpoint information used in hyperlinks, etc., is automatically generated from Routes, so following the types ensures no broken links.
The directory structure during development is as follows. For more details, check the example implementation provided in example/tasks.
No vulnerabilities found.
No security vulnerabilities found.