create-faas-app 8.0.0-beta.3 → 8.0.0-beta.30

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 (42) hide show
  1. package/README.md +66 -9
  2. package/dist/index.d.ts +11 -16
  3. package/dist/index.mjs +147 -153
  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/migrations/20250101000000_create_users.ts +12 -0
  10. package/template/admin/package.json +34 -0
  11. package/template/admin/server.ts +33 -0
  12. package/template/admin/src/faas.yaml +11 -0
  13. package/template/admin/src/main.tsx +25 -0
  14. package/template/admin/src/pages/home/api/auth/__tests__/me.test.ts +39 -0
  15. package/template/admin/src/pages/home/api/auth/me.api.ts +23 -0
  16. package/template/admin/src/pages/home/api/users/__tests__/create.test.ts +47 -0
  17. package/template/admin/src/pages/home/api/users/__tests__/detail.test.ts +39 -0
  18. package/template/admin/src/pages/home/api/users/__tests__/list.test.ts +31 -0
  19. package/template/admin/src/pages/home/api/users/__tests__/update.test.ts +51 -0
  20. package/template/admin/src/pages/home/api/users/create.api.ts +26 -0
  21. package/template/admin/src/pages/home/api/users/detail.api.ts +23 -0
  22. package/template/admin/src/pages/home/api/users/list.api.ts +22 -0
  23. package/template/admin/src/pages/home/api/users/update.api.ts +35 -0
  24. package/template/admin/src/pages/home/index.tsx +163 -0
  25. package/template/admin/src/plugins/auth.ts +25 -0
  26. package/template/admin/src/types/faasjs-auth.d.ts +8 -0
  27. package/template/admin/src/types/faasjs-pg.d.ts +10 -0
  28. package/template/admin/tsconfig.json +4 -0
  29. package/template/admin/vite.config.ts +12 -0
  30. package/template/minimal/gitignore +4 -0
  31. package/template/minimal/index.html +12 -0
  32. package/template/minimal/package.json +26 -0
  33. package/template/minimal/server.ts +33 -0
  34. package/template/minimal/src/faas.yaml +11 -0
  35. package/template/minimal/src/main.tsx +5 -0
  36. package/template/minimal/src/pages/home/api/__tests__/hello.test.ts +17 -0
  37. package/template/minimal/src/pages/home/api/hello.api.ts +13 -0
  38. package/template/minimal/src/pages/home/index.tsx +46 -0
  39. package/template/minimal/src/react-client.ts +8 -0
  40. package/template/minimal/tsconfig.json +4 -0
  41. package/template/minimal/vite.config.ts +6 -0
  42. package/dist/index.cjs +0 -164
@@ -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('pages/home/api/auth/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('pages/home/api/users/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('pages/home/api/users/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('pages/home/api/users/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('pages/home/api/users/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,163 @@
1
+ declare module '@faasjs/types' {
2
+ interface FaasActions {
3
+ '/pages/home/api/users/list': {
4
+ Params: { limit: number }
5
+ Data: { total?: number; rows?: { id: number; name: string }[] }
6
+ }
7
+ '/pages/home/api/users/create': {
8
+ Params: { name?: string | undefined }
9
+ Data: { message?: string; total?: number; user?: { id: number; name: string } }
10
+ }
11
+ '/pages/home/api/users/update': {
12
+ Params: { id: number; name: string }
13
+ Data: { message?: string; user?: { id: number; name: string } }
14
+ }
15
+ '/pages/home/api/users/detail': {
16
+ Params: { id: number }
17
+ Data: { user?: { id: number; name: string } }
18
+ }
19
+ '/pages/home/api/auth/me': {
20
+ Params: Record<string, never>
21
+ Data: { current_user?: { id: number; name: string; role: string } }
22
+ }
23
+ }
24
+ }
25
+
26
+ import { faas, useApp } from '@faasjs/ant-design'
27
+ import { useFaas } from '@faasjs/react'
28
+ import { Button, Card, Input, Space, Table, Typography } from 'antd'
29
+ import { useState } from 'react'
30
+
31
+ export default function HomePage() {
32
+ const app = useApp()
33
+ const [name, setName] = useState('FaasJS')
34
+ const [messageText, setMessageText] = useState('Create your first user through the FaasJS API')
35
+
36
+ const {
37
+ data: listData,
38
+ loading: listLoading,
39
+ reload,
40
+ } = useFaas('/pages/home/api/users/list', { limit: 10 })
41
+
42
+ const rows = listData?.rows || []
43
+
44
+ const [creating, setCreating] = useState(false)
45
+ const callApi = async () => {
46
+ setCreating(true)
47
+ try {
48
+ const response = await faas('/pages/home/api/users/create', {
49
+ name: name.trim() || undefined,
50
+ })
51
+ const result = response.data
52
+ const nextMessage =
53
+ result?.user && typeof result.total === 'number'
54
+ ? `Created ${result.user.name} (#${result.user.id}). Total users: ${result.total}`
55
+ : result?.message || 'Empty response'
56
+
57
+ setMessageText(nextMessage)
58
+ app.message.success('User saved to PostgreSQL')
59
+ reload()
60
+ } catch (error: unknown) {
61
+ const errorMessage = error instanceof Error ? error.message : 'Request failed'
62
+ setMessageText(errorMessage)
63
+ app.notification.error({
64
+ message: 'API call failed',
65
+ description: errorMessage,
66
+ })
67
+ } finally {
68
+ setCreating(false)
69
+ }
70
+ }
71
+
72
+ const [authLoading, setAuthLoading] = useState(false)
73
+ const callAuthDemo = async () => {
74
+ setAuthLoading(true)
75
+ try {
76
+ const response = await faas(
77
+ '/pages/home/api/auth/me',
78
+ {},
79
+ {
80
+ headers: { authorization: 'Bearer demo-admin' },
81
+ },
82
+ )
83
+ const currentUser = response.data?.current_user
84
+ setMessageText(`Auth plugin injected current user: ${currentUser?.name || 'unknown'}`)
85
+ app.message.success('Auth plugin demo loaded current_user')
86
+ } catch (error: unknown) {
87
+ const errorMessage = error instanceof Error ? error.message : 'Auth demo failed'
88
+ setMessageText(errorMessage)
89
+ app.notification.error({
90
+ message: 'Auth demo failed',
91
+ description: errorMessage,
92
+ })
93
+ } finally {
94
+ setAuthLoading(false)
95
+ }
96
+ }
97
+
98
+ return (
99
+ <div
100
+ style={{
101
+ minHeight: '100vh',
102
+ display: 'grid',
103
+ placeItems: 'center',
104
+ padding: 24,
105
+ background: 'linear-gradient(135deg, #f5f7fa 0%, #e4ecfb 100%)',
106
+ }}
107
+ >
108
+ <Card
109
+ style={{
110
+ width: '100%',
111
+ maxWidth: 640,
112
+ boxShadow: '0 20px 45px rgba(15, 23, 42, 0.08)',
113
+ }}
114
+ >
115
+ <Space direction="vertical" size="large" style={{ width: '100%' }}>
116
+ <div>
117
+ <Typography.Title level={2}>FaasJS Admin App</Typography.Title>
118
+ <Typography.Paragraph type="secondary">
119
+ This starter follows the curated FaasJS path: React, Ant Design, PostgreSQL,
120
+ pg-dev-powered tests, and a simple auth plugin demo.
121
+ </Typography.Paragraph>
122
+ <Typography.Paragraph type="secondary">
123
+ Set <code>DATABASE_URL</code> from <code>.env.example</code> and run{' '}
124
+ <code>npm run db:migrate</code> before using the page in development.
125
+ </Typography.Paragraph>
126
+ </div>
127
+
128
+ <Input
129
+ value={name}
130
+ onChange={(event) => setName(event.target.value)}
131
+ placeholder="Who should the admin app create?"
132
+ />
133
+
134
+ <Space wrap>
135
+ <Button onClick={() => reload()} loading={listLoading}>
136
+ Load users slice
137
+ </Button>
138
+ <Button type="primary" loading={creating} onClick={callApi}>
139
+ Create /pages/home/api/users/create
140
+ </Button>
141
+ <Button loading={authLoading} onClick={callAuthDemo}>
142
+ Call auth plugin demo
143
+ </Button>
144
+ </Space>
145
+
146
+ <Table
147
+ rowKey="id"
148
+ size="small"
149
+ pagination={false}
150
+ loading={listLoading}
151
+ dataSource={rows}
152
+ columns={[
153
+ { title: 'ID', dataIndex: 'id' },
154
+ { title: 'Name', dataIndex: 'name' },
155
+ ]}
156
+ />
157
+
158
+ <Typography.Paragraph style={{ marginBottom: 0 }}>{messageText}</Typography.Paragraph>
159
+ </Space>
160
+ </Card>
161
+ </div>
162
+ )
163
+ }
@@ -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,10 @@
1
+ import '@faasjs/pg'
2
+
3
+ declare module '@faasjs/pg' {
4
+ interface Tables {
5
+ users: {
6
+ id: number
7
+ name: string
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "@faasjs/types/tsconfig/build.json",
3
+ "include": ["src", "migrations", "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>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vp dev",
8
+ "build": "vp build",
9
+ "start": "node --import @faasjs/node-utils/register-hooks server.ts",
10
+ "test": "vp test"
11
+ },
12
+ "devDependencies": {
13
+ "@faasjs/dev": "*"
14
+ },
15
+ "peerDependencies": {
16
+ "@faasjs/core": "*"
17
+ },
18
+ "overrides": {
19
+ "vite": "npm:@voidzero-dev/vite-plus-core",
20
+ "vitest": "npm:@voidzero-dev/vite-plus-test"
21
+ },
22
+ "engines": {
23
+ "node": ">=26.0.0",
24
+ "npm": ">=11.0.0"
25
+ }
26
+ }
@@ -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,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,5 @@
1
+ import { createRoot } from 'react-dom/client'
2
+
3
+ import HomePage from './pages/home'
4
+
5
+ createRoot(document.getElementById('root') as HTMLElement).render(<HomePage />)