create-faas-app 8.0.0-beta.4 → 8.0.0-beta.41

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.
Files changed (44) hide show
  1. package/README.md +8 -5
  2. package/dist/index.d.ts +16 -15
  3. package/dist/index.mjs +155 -304
  4. package/index.mjs +1 -1
  5. package/package.json +20 -23
  6. package/template/admin/.env.example +1 -0
  7. package/template/admin/gitignore +4 -0
  8. package/template/admin/index.html +12 -0
  9. package/template/admin/package.json +35 -0
  10. package/template/admin/server.ts +33 -0
  11. package/template/admin/src/.faasjs/types.d.ts +16 -0
  12. package/template/admin/src/db/migrations/20250101000000_create_users.ts +12 -0
  13. package/template/admin/src/db/tables/users.ts +8 -0
  14. package/template/admin/src/faas.yaml +11 -0
  15. package/template/admin/src/features/auth/api/__tests__/me.test.ts +39 -0
  16. package/template/admin/src/features/auth/api/me.api.ts +23 -0
  17. package/template/admin/src/features/users/api/__tests__/create.test.ts +47 -0
  18. package/template/admin/src/features/users/api/__tests__/detail.test.ts +39 -0
  19. package/template/admin/src/features/users/api/__tests__/list.test.ts +31 -0
  20. package/template/admin/src/features/users/api/__tests__/update.test.ts +51 -0
  21. package/template/admin/src/features/users/api/create.api.ts +26 -0
  22. package/template/admin/src/features/users/api/detail.api.ts +23 -0
  23. package/template/admin/src/features/users/api/list.api.ts +22 -0
  24. package/template/admin/src/features/users/api/update.api.ts +35 -0
  25. package/template/admin/src/features/users/index.tsx +138 -0
  26. package/template/admin/src/main.tsx +24 -0
  27. package/template/admin/src/plugins/auth.ts +25 -0
  28. package/template/admin/src/types/faasjs-auth.d.ts +8 -0
  29. package/template/admin/tsconfig.json +4 -0
  30. package/template/admin/vite.config.ts +12 -0
  31. package/template/minimal/gitignore +4 -0
  32. package/template/minimal/index.html +12 -0
  33. package/template/minimal/package.json +27 -0
  34. package/template/minimal/server.ts +33 -0
  35. package/template/minimal/src/.faasjs/types.d.ts +12 -0
  36. package/template/minimal/src/faas.yaml +11 -0
  37. package/template/minimal/src/features/home/api/__tests__/hello.test.ts +17 -0
  38. package/template/minimal/src/features/home/api/hello.api.ts +13 -0
  39. package/template/minimal/src/features/home/index.tsx +37 -0
  40. package/template/minimal/src/main.tsx +5 -0
  41. package/template/minimal/src/react-client.ts +8 -0
  42. package/template/minimal/tsconfig.json +4 -0
  43. package/template/minimal/vite.config.ts +6 -0
  44. package/dist/index.cjs +0 -318
@@ -0,0 +1,33 @@
1
+ import { dirname, join } from 'node:path'
2
+ import { loadEnvFile } from 'node:process'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import { Server, staticHandler } from '@faasjs/core'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+
10
+ try {
11
+ loadEnvFile()
12
+ } catch (error) {
13
+ console.warn('[faasjs] Failed to load env file', error)
14
+ }
15
+
16
+ const publicHandler = staticHandler({
17
+ root: join(__dirname, 'public'),
18
+ notFound: false,
19
+ })
20
+
21
+ const distHandler = staticHandler({
22
+ root: join(__dirname, 'dist'),
23
+ notFound: 'index.html',
24
+ })
25
+
26
+ new Server(join(__dirname, 'src'), {
27
+ beforeHandle: async (req, res, ctx) => {
28
+ if (!req.url || req.method !== 'GET') return
29
+
30
+ await publicHandler(req, res, ctx)
31
+ await distHandler(req, res, ctx)
32
+ },
33
+ }).listen()
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Generated by @faasjs/dev.
3
+ *
4
+ * Do not edit this file manually.
5
+ */
6
+ import type { InferFaasAction } from '@faasjs/types'
7
+
8
+ declare module '@faasjs/types' {
9
+ interface FaasActions {
10
+ "features/auth/api/me": InferFaasAction<typeof import("../features/auth/api/me.api")>
11
+ "features/users/api/create": InferFaasAction<typeof import("../features/users/api/create.api")>
12
+ "features/users/api/detail": InferFaasAction<typeof import("../features/users/api/detail.api")>
13
+ "features/users/api/list": InferFaasAction<typeof import("../features/users/api/list.api")>
14
+ "features/users/api/update": InferFaasAction<typeof import("../features/users/api/update.api")>
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ import type { SchemaBuilder } from '@faasjs/pg'
2
+
3
+ export function up(builder: SchemaBuilder) {
4
+ builder.createTable('users', (table) => {
5
+ table.specificType('id', 'serial').primary()
6
+ table.string('name')
7
+ })
8
+ }
9
+
10
+ export function down(builder: SchemaBuilder) {
11
+ builder.dropTable('users')
12
+ }
@@ -0,0 +1,8 @@
1
+ declare module '@faasjs/pg' {
2
+ interface Tables {
3
+ users: {
4
+ id: number
5
+ name: string
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ defaults:
2
+ server:
3
+ root: .
4
+ base: /
5
+ plugins:
6
+ http:
7
+ config:
8
+ cookie:
9
+ secure: false
10
+ session:
11
+ secret: '{{secret}}'
@@ -0,0 +1,39 @@
1
+ import { testApi } from '@faasjs/dev'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import api from '../me.api'
5
+
6
+ describe('features/auth/api/me', () => {
7
+ it('returns the current user from the auth plugin demo', async () => {
8
+ const handler = testApi(api)
9
+
10
+ const { statusCode, data } = await handler(
11
+ {},
12
+ {
13
+ headers: {
14
+ authorization: 'Bearer demo-admin',
15
+ },
16
+ },
17
+ )
18
+
19
+ expect(statusCode).toEqual(200)
20
+ expect(data).toEqual({
21
+ current_user: {
22
+ id: 1,
23
+ name: 'Demo Admin',
24
+ role: 'admin',
25
+ },
26
+ })
27
+ })
28
+
29
+ it('rejects requests without the demo token', async () => {
30
+ const handler = testApi(api)
31
+
32
+ const { statusCode, error } = await handler()
33
+
34
+ expect(statusCode).toEqual(401)
35
+ expect(error).toEqual({
36
+ message: 'Missing demo auth token',
37
+ })
38
+ })
39
+ })
@@ -0,0 +1,23 @@
1
+ import { defineApi, HttpError } from '@faasjs/core'
2
+ import { z } from '@faasjs/utils'
3
+
4
+ import { AuthPlugin } from '../../../plugins/auth'
5
+
6
+ const api = defineApi({
7
+ schema: z.object({}).strict(),
8
+ async handler({ current_user }) {
9
+ if (!current_user)
10
+ throw new HttpError({
11
+ statusCode: 401,
12
+ message: 'Missing demo auth token',
13
+ })
14
+
15
+ return {
16
+ current_user,
17
+ }
18
+ },
19
+ })
20
+
21
+ api.plugins.unshift(new AuthPlugin())
22
+
23
+ export default api
@@ -0,0 +1,47 @@
1
+ import { testApi } from '@faasjs/dev'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import api from '../create.api'
6
+
7
+ describe('features/users/api/create', () => {
8
+ it('creates a user with the shared pg bootstrap', async () => {
9
+ const handler = testApi(api)
10
+
11
+ const { statusCode, data } = await handler({ name: 'world' })
12
+
13
+ expect(statusCode).toEqual(200)
14
+ expect(data).toEqual({
15
+ message: 'Created user #1',
16
+ total: 1,
17
+ user: {
18
+ id: 1,
19
+ name: 'world',
20
+ },
21
+ })
22
+
23
+ const client = await getClient()
24
+
25
+ await expect(client.query('users').orderBy('id', 'ASC')).resolves.toEqual([
26
+ {
27
+ id: 1,
28
+ name: 'world',
29
+ },
30
+ ])
31
+ })
32
+
33
+ it('uses the default name when params.name is missing', async () => {
34
+ const handler = testApi(api)
35
+
36
+ const { data } = await handler({})
37
+
38
+ expect(data).toEqual({
39
+ message: 'Created user #1',
40
+ total: 1,
41
+ user: {
42
+ id: 1,
43
+ name: 'FaasJS',
44
+ },
45
+ })
46
+ })
47
+ })
@@ -0,0 +1,39 @@
1
+ import { testApi } from '@faasjs/dev'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import api from '../detail.api'
6
+
7
+ describe('features/users/api/detail', () => {
8
+ it('returns one user', async () => {
9
+ const client = await getClient()
10
+ const [created] = await client.query('users').insert(
11
+ {
12
+ name: 'Ada',
13
+ },
14
+ {
15
+ returning: ['id'],
16
+ },
17
+ )
18
+ const handler = testApi(api)
19
+ const { statusCode, data } = await handler({ id: created.id })
20
+
21
+ expect(statusCode).toEqual(200)
22
+ expect(data).toEqual({
23
+ user: {
24
+ id: created.id,
25
+ name: 'Ada',
26
+ },
27
+ })
28
+ })
29
+
30
+ it('returns 404 when the user is missing', async () => {
31
+ const handler = testApi(api)
32
+ const { statusCode, error } = await handler({ id: 404 })
33
+
34
+ expect(statusCode).toEqual(404)
35
+ expect(error).toEqual({
36
+ message: 'User not found',
37
+ })
38
+ })
39
+ })
@@ -0,0 +1,31 @@
1
+ import { testApi } from '@faasjs/dev'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import api from '../list.api'
6
+
7
+ describe('features/users/api/list', () => {
8
+ it('lists users with total count', async () => {
9
+ const client = await getClient()
10
+
11
+ await client.query('users').insert([{ name: 'Ada' }, { name: 'Grace' }])
12
+
13
+ const handler = testApi(api)
14
+ const { statusCode, data } = await handler({ limit: 10 })
15
+
16
+ expect(statusCode).toEqual(200)
17
+ expect(data).toEqual({
18
+ total: 2,
19
+ rows: [
20
+ {
21
+ id: 2,
22
+ name: 'Grace',
23
+ },
24
+ {
25
+ id: 1,
26
+ name: 'Ada',
27
+ },
28
+ ],
29
+ })
30
+ })
31
+ })
@@ -0,0 +1,51 @@
1
+ import { testApi } from '@faasjs/dev'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import api from '../update.api'
6
+
7
+ describe('features/users/api/update', () => {
8
+ it('updates one user', async () => {
9
+ const client = await getClient()
10
+ const [created] = await client.query('users').insert(
11
+ {
12
+ name: 'Ada',
13
+ },
14
+ {
15
+ returning: ['id'],
16
+ },
17
+ )
18
+ const handler = testApi(api)
19
+ const { statusCode, data } = await handler({
20
+ id: created.id,
21
+ name: 'Ada Lovelace',
22
+ })
23
+
24
+ expect(statusCode).toEqual(200)
25
+ expect(data).toEqual({
26
+ message: `Updated user #${created.id}`,
27
+ user: {
28
+ id: created.id,
29
+ name: 'Ada Lovelace',
30
+ },
31
+ })
32
+
33
+ await expect(client.query('users').where('id', created.id).first()).resolves.toEqual({
34
+ id: created.id,
35
+ name: 'Ada Lovelace',
36
+ })
37
+ })
38
+
39
+ it('returns 404 when the user is missing', async () => {
40
+ const handler = testApi(api)
41
+ const { statusCode, error } = await handler({
42
+ id: 404,
43
+ name: 'Missing',
44
+ })
45
+
46
+ expect(statusCode).toEqual(404)
47
+ expect(error).toEqual({
48
+ message: 'User not found',
49
+ })
50
+ })
51
+ })
@@ -0,0 +1,26 @@
1
+ import { defineApi } from '@faasjs/core'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { z } from '@faasjs/utils'
4
+
5
+ export default defineApi({
6
+ schema: z.object({
7
+ name: z.nonemptystring().optional(),
8
+ }),
9
+ async handler({ params }) {
10
+ const client = await getClient()
11
+ const [user] = await client.query('users').insert(
12
+ {
13
+ name: params.name || 'FaasJS',
14
+ },
15
+ {
16
+ returning: ['id', 'name'],
17
+ },
18
+ )
19
+
20
+ return {
21
+ message: `Created user #${user.id}`,
22
+ total: await client.query('users').count(),
23
+ user,
24
+ }
25
+ },
26
+ })
@@ -0,0 +1,23 @@
1
+ import { defineApi, HttpError } from '@faasjs/core'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { z } from '@faasjs/utils'
4
+
5
+ export default defineApi({
6
+ schema: z.object({
7
+ id: z.positiveint(),
8
+ }),
9
+ async handler({ params }) {
10
+ const client = await getClient()
11
+ const user = await client.query('users').select('id', 'name').where('id', params.id).first()
12
+
13
+ if (!user)
14
+ throw new HttpError({
15
+ statusCode: 404,
16
+ message: 'User not found',
17
+ })
18
+
19
+ return {
20
+ user,
21
+ }
22
+ },
23
+ })
@@ -0,0 +1,22 @@
1
+ import { defineApi } from '@faasjs/core'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { z } from '@faasjs/utils'
4
+
5
+ export default defineApi({
6
+ schema: z.object({
7
+ limit: z.positiveint().max(50).default(20),
8
+ }),
9
+ async handler({ params }) {
10
+ const client = await getClient()
11
+ const users = await client
12
+ .query('users')
13
+ .select('id', 'name')
14
+ .orderBy('id', 'DESC')
15
+ .limit(params.limit)
16
+
17
+ return {
18
+ total: await client.query('users').count(),
19
+ rows: users,
20
+ }
21
+ },
22
+ })
@@ -0,0 +1,35 @@
1
+ import { defineApi, HttpError } from '@faasjs/core'
2
+ import { getClient } from '@faasjs/pg'
3
+ import { z } from '@faasjs/utils'
4
+
5
+ export default defineApi({
6
+ schema: z.object({
7
+ id: z.positiveint(),
8
+ name: z.nonemptystring(),
9
+ }),
10
+ async handler({ params }) {
11
+ const client = await getClient()
12
+ const [user] = await client
13
+ .query('users')
14
+ .where('id', params.id)
15
+ .update(
16
+ {
17
+ name: params.name,
18
+ },
19
+ {
20
+ returning: ['id', 'name'],
21
+ },
22
+ )
23
+
24
+ if (!user)
25
+ throw new HttpError({
26
+ statusCode: 404,
27
+ message: 'User not found',
28
+ })
29
+
30
+ return {
31
+ message: `Updated user #${user.id}`,
32
+ user,
33
+ }
34
+ },
35
+ })
@@ -0,0 +1,138 @@
1
+ import { faas, useApp, useFaas } from '@faasjs/ant-design'
2
+ import { toErrorMessage } from '@faasjs/utils'
3
+ import { Button, Card, Input, Space, Table, Typography } from 'antd'
4
+ import { useState } from 'react'
5
+
6
+ export default function UsersPage() {
7
+ const app = useApp()
8
+ const [name, setName] = useState('FaasJS')
9
+ const [messageText, setMessageText] = useState('Create your first user through the FaasJS API')
10
+
11
+ const {
12
+ data: listData,
13
+ loading: listLoading,
14
+ reload,
15
+ } = useFaas('features/users/api/list', { limit: 10 })
16
+
17
+ const rows = listData?.rows || []
18
+
19
+ const [creating, setCreating] = useState(false)
20
+ const callApi = async () => {
21
+ setCreating(true)
22
+ try {
23
+ const response = await faas('features/users/api/create', {
24
+ name: name.trim() || undefined,
25
+ })
26
+ const result = response.data
27
+ const nextMessage =
28
+ result?.user && typeof result.total === 'number'
29
+ ? `Created ${result.user.name} (#${result.user.id}). Total users: ${result.total}`
30
+ : result?.message || 'Empty response'
31
+
32
+ setMessageText(nextMessage)
33
+ app.message.success('User saved to PostgreSQL')
34
+ await reload()
35
+ } catch (error: unknown) {
36
+ const errorMessage = toErrorMessage(error, 'Request failed')
37
+ setMessageText(errorMessage)
38
+ app.notification.error({
39
+ message: 'API call failed',
40
+ description: errorMessage,
41
+ })
42
+ } finally {
43
+ setCreating(false)
44
+ }
45
+ }
46
+
47
+ const [authLoading, setAuthLoading] = useState(false)
48
+ const callAuthDemo = async () => {
49
+ setAuthLoading(true)
50
+ try {
51
+ const response = await faas(
52
+ 'features/auth/api/me',
53
+ {},
54
+ {
55
+ headers: { authorization: 'Bearer demo-admin' },
56
+ },
57
+ )
58
+ const currentUser = response.data?.current_user
59
+ setMessageText(`Auth plugin injected current user: ${currentUser?.name || 'unknown'}`)
60
+ app.message.success('Auth plugin demo loaded current_user')
61
+ } catch (error: unknown) {
62
+ const errorMessage = toErrorMessage(error, 'Auth demo failed')
63
+ setMessageText(errorMessage)
64
+ app.notification.error({
65
+ message: 'Auth demo failed',
66
+ description: errorMessage,
67
+ })
68
+ } finally {
69
+ setAuthLoading(false)
70
+ }
71
+ }
72
+
73
+ return (
74
+ <div
75
+ style={{
76
+ minHeight: '100vh',
77
+ display: 'grid',
78
+ placeItems: 'center',
79
+ padding: 24,
80
+ background: 'linear-gradient(135deg, #f5f7fa 0%, #e4ecfb 100%)',
81
+ }}
82
+ >
83
+ <Card
84
+ style={{
85
+ width: '100%',
86
+ maxWidth: 640,
87
+ boxShadow: '0 20px 45px rgba(15, 23, 42, 0.08)',
88
+ }}
89
+ >
90
+ <Space direction="vertical" size="large" style={{ width: '100%' }}>
91
+ <div>
92
+ <Typography.Title level={2}>FaasJS Admin App</Typography.Title>
93
+ <Typography.Paragraph type="secondary">
94
+ This starter follows the curated FaasJS path: React, Ant Design, PostgreSQL,
95
+ pg-dev-powered tests, and a simple auth plugin demo.
96
+ </Typography.Paragraph>
97
+ <Typography.Paragraph type="secondary">
98
+ Set <code>DATABASE_URL</code> from <code>.env.example</code> and run{' '}
99
+ <code>npm run db:migrate</code> before using the page in development.
100
+ </Typography.Paragraph>
101
+ </div>
102
+
103
+ <Input
104
+ value={name}
105
+ onChange={(event) => setName(event.target.value)}
106
+ placeholder="Who should the admin app create?"
107
+ />
108
+
109
+ <Space wrap>
110
+ <Button onClick={() => reload()} loading={listLoading}>
111
+ Load users slice
112
+ </Button>
113
+ <Button type="primary" loading={creating} onClick={callApi}>
114
+ Create features/users/api/create
115
+ </Button>
116
+ <Button loading={authLoading} onClick={callAuthDemo}>
117
+ Call auth plugin demo
118
+ </Button>
119
+ </Space>
120
+
121
+ <Table
122
+ rowKey="id"
123
+ size="small"
124
+ pagination={false}
125
+ loading={listLoading}
126
+ dataSource={rows}
127
+ columns={[
128
+ { title: 'ID', dataIndex: 'id' },
129
+ { title: 'Name', dataIndex: 'name' },
130
+ ]}
131
+ />
132
+
133
+ <Typography.Paragraph style={{ marginBottom: 0 }}>{messageText}</Typography.Paragraph>
134
+ </Space>
135
+ </Card>
136
+ </div>
137
+ )
138
+ }
@@ -0,0 +1,24 @@
1
+ import { App } from '@faasjs/ant-design'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ import UsersPage from './features/users'
5
+
6
+ createRoot(document.getElementById('root') as HTMLElement).render(
7
+ <App
8
+ configProviderProps={{
9
+ theme: {
10
+ token: {
11
+ borderRadius: 16,
12
+ colorPrimary: '#1677ff',
13
+ },
14
+ },
15
+ }}
16
+ faasConfigProviderProps={{
17
+ faasClientOptions: {
18
+ baseUrl: '/',
19
+ },
20
+ }}
21
+ >
22
+ <UsersPage />
23
+ </App>,
24
+ )
@@ -0,0 +1,25 @@
1
+ import type { InvokeData, Next, Plugin } from '@faasjs/core'
2
+
3
+ export type CurrentUser = {
4
+ id: number
5
+ name: string
6
+ role: 'admin'
7
+ }
8
+
9
+ export class AuthPlugin implements Plugin {
10
+ public readonly name = 'auth'
11
+ public readonly type = 'auth'
12
+
13
+ public async onInvoke(data: InvokeData, next: Next): Promise<void> {
14
+ const token = data.event?.headers?.authorization || data.event?.headers?.Authorization
15
+
16
+ if (token === 'Bearer demo-admin')
17
+ data.current_user = {
18
+ id: 1,
19
+ name: 'Demo Admin',
20
+ role: 'admin',
21
+ } satisfies CurrentUser
22
+
23
+ await next()
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ import '@faasjs/core'
2
+ import type { CurrentUser } from '../plugins/auth'
3
+
4
+ declare module '@faasjs/core' {
5
+ interface DefineApiInject {
6
+ current_user?: CurrentUser
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "@faasjs/types/tsconfig/build.json",
3
+ "include": ["src", "src/.faasjs/types.d.ts", "vite.config.ts", "server.ts"]
4
+ }
@@ -0,0 +1,12 @@
1
+ import { ViteConfig } from '@faasjs/dev'
2
+ import { PgVitestPlugin } from '@faasjs/pg-dev'
3
+ import { defineConfig } from 'vite-plus'
4
+
5
+ export default defineConfig({
6
+ ...ViteConfig,
7
+ plugins: [...ViteConfig.plugins, PgVitestPlugin()],
8
+ test: {
9
+ fileParallelism: false,
10
+ testTimeout: 30_000,
11
+ },
12
+ })
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ .env
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FaasJS App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>