create-fullstack-scaffold 0.4.25 → 0.5.0

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 (135) hide show
  1. package/dist/cli/index.js +432 -15
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +10 -13
  4. package/template/eslint-rules/__tests__/no-merged-api-type-export.test.ts +96 -0
  5. package/template/eslint-rules/no-cross-module-service-import.js +4 -0
  6. package/template/eslint-rules/no-merged-api-type-export.js +190 -0
  7. package/template/eslint.config.js +3 -0
  8. package/template/package.json +9 -6
  9. package/template/scripts/sync-agent-hooks.mjs +189 -0
  10. package/template/src/admin/components/__tests__/StatsCard.test.tsx +2 -2
  11. package/template/src/admin/pages/ContentPage.tsx +1 -1
  12. package/template/src/admin/pages/DisputesPage.tsx +1 -1
  13. package/template/src/admin/pages/OrdersPage.tsx +1 -1
  14. package/template/src/admin/pages/TicketsPage.tsx +1 -1
  15. package/template/src/admin/pages/__tests__/ContentPage.test.tsx +5 -1
  16. package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +62 -8
  17. package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +8 -1
  18. package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +4 -2
  19. package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +40 -43
  20. package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +83 -21
  21. package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +3 -1
  22. package/template/src/admin/services/apiClient.ts +2 -3
  23. package/template/src/cli/modules/content/index.ts +3 -3
  24. package/template/src/cli/modules/dispute/index.ts +3 -3
  25. package/template/src/cli/modules/ticket/index.ts +3 -3
  26. package/template/src/cli/modules/todo/index.ts +1 -1
  27. package/template/src/cli/rpc/client.ts +3 -4
  28. package/template/src/cli/rpc/index.ts +1 -1
  29. package/template/src/client/App.tsx +3 -39
  30. package/template/src/client/AppRoutes.tsx +53 -0
  31. package/template/src/client/entry-server.tsx +75 -0
  32. package/template/src/client/main.tsx +3 -1
  33. package/template/src/client/pages/SearchPage.tsx +1 -1
  34. package/template/src/client/services/apiClient.ts +12 -4
  35. package/template/src/client/stores/__tests__/todoStore.test.ts +12 -3
  36. package/template/src/client/stores/entry-stores.ts +36 -0
  37. package/template/src/client/stores/notificationStore.ts +4 -4
  38. package/template/src/client/stores/todoStore.ts +2 -2
  39. package/template/src/merchant/pages/DisputesPage.tsx +3 -3
  40. package/template/src/merchant/pages/OrdersPage.tsx +1 -1
  41. package/template/src/merchant/pages/ProductsPage.tsx +1 -1
  42. package/template/src/merchant/pages/SettingsPage.tsx +1 -1
  43. package/template/src/server/__tests__/integration/isr-full-flow.test.ts +251 -0
  44. package/template/src/server/__tests__/integration/todos-api.test.ts +7 -4
  45. package/template/src/server/app.ts +6 -4
  46. package/template/src/server/core/__tests__/isr-cache.test.ts +96 -92
  47. package/template/src/server/core/__tests__/isr-invalidation.test.ts +29 -4
  48. package/template/src/server/core/__tests__/isr-registry.test.ts +215 -0
  49. package/template/src/server/core/__tests__/isr-renderer.test.ts +121 -0
  50. package/template/src/server/core/isr-cache.ts +6 -12
  51. package/template/src/server/core/isr-invalidation.ts +6 -12
  52. package/template/src/server/core/isr-registry.ts +104 -0
  53. package/template/src/server/core/isr-renderer.ts +75 -0
  54. package/template/src/server/db/schema/contents.ts +28 -20
  55. package/template/src/server/db/schema/disputes.ts +30 -22
  56. package/template/src/server/db/schema/notifications.ts +20 -14
  57. package/template/src/server/db/schema/orders.ts +23 -16
  58. package/template/src/server/db/schema/plugins.ts +56 -38
  59. package/template/src/server/db/schema/products.ts +24 -17
  60. package/template/src/server/db/schema/tickets.ts +44 -31
  61. package/template/src/server/db/schema/todos.ts +26 -18
  62. package/template/src/server/entries/cloudflare.ts +82 -19
  63. package/template/src/server/entries/node.ts +0 -2
  64. package/template/src/server/index.ts +0 -1
  65. package/template/src/server/isr-modules.ts +10 -0
  66. package/template/src/server/module-admin/__tests__/admin-service.test.ts +36 -12
  67. package/template/src/server/module-admin/routes/admin-notification-routes.ts +2 -1
  68. package/template/src/server/module-admin/routes/admin-routes.ts +3 -0
  69. package/template/src/server/module-admin/routes/dashboard-routes.ts +3 -0
  70. package/template/src/server/module-admin/services/admin-service.ts +57 -13
  71. package/template/src/server/module-auth/routes/auth-routes.ts +3 -0
  72. package/template/src/server/module-auth/routes/profile-routes.ts +3 -0
  73. package/template/src/server/module-captcha/routes/captcha-routes.ts +3 -0
  74. package/template/src/server/module-chat/routes/chat-routes.ts +4 -1
  75. package/template/src/server/module-content/__tests__/content-route.test.ts +2 -1
  76. package/template/src/server/module-content/__tests__/content-service.test.ts +2 -1
  77. package/template/src/server/module-content/__tests__/isr.test.ts +138 -0
  78. package/template/src/server/module-content/isr.ts +63 -0
  79. package/template/src/server/module-content/routes/content-routes.ts +10 -10
  80. package/template/src/server/module-content/routes/public-content-routes.ts +8 -4
  81. package/template/src/server/module-content/routes/topics-routes.ts +3 -0
  82. package/template/src/server/module-content/services/content-service.ts +23 -10
  83. package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +2 -1
  84. package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +2 -1
  85. package/template/src/server/module-dispute/routes/dispute-routes.ts +11 -10
  86. package/template/src/server/module-dispute/services/dispute-service.ts +15 -3
  87. package/template/src/server/module-file/routes/file-routes.ts +3 -0
  88. package/template/src/server/module-merchant/routes/merchant-routes.ts +3 -0
  89. package/template/src/server/module-merchant/services/merchant-service.ts +8 -8
  90. package/template/src/server/module-notifications/routes/notification-routes.ts +5 -1
  91. package/template/src/server/module-order/__tests__/order-route.test.ts +8 -8
  92. package/template/src/server/module-order/__tests__/order-service.test.ts +4 -3
  93. package/template/src/server/module-order/routes/cart-routes.ts +3 -0
  94. package/template/src/server/module-order/routes/order-routes.ts +9 -4
  95. package/template/src/server/module-order/routes/orders-mock-routes.ts +3 -0
  96. package/template/src/server/module-order/services/order-service.ts +25 -13
  97. package/template/src/server/module-permission/__tests__/audit-log-service.test.ts +464 -0
  98. package/template/src/server/module-permission/__tests__/role-service.test.ts +348 -0
  99. package/template/src/server/module-permission/routes/audit-log-routes.ts +3 -0
  100. package/template/src/server/module-permission/routes/permission-routes.ts +3 -0
  101. package/template/src/server/module-permission/routes/role-routes.ts +3 -0
  102. package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +3 -0
  103. package/template/src/server/module-plugin/routes/plugin-routes.ts +3 -0
  104. package/template/src/server/module-plugin/services/admin-plugin-service.ts +10 -18
  105. package/template/src/server/module-plugin/services/admin-stats-service.ts +28 -9
  106. package/template/src/server/module-plugin/services/plugin-query-service.ts +42 -66
  107. package/template/src/server/module-tenant/routes/tenant-routes.ts +5 -1
  108. package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +2 -1
  109. package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +4 -3
  110. package/template/src/server/module-ticket/routes/ticket-routes.ts +11 -10
  111. package/template/src/server/module-ticket/services/ticket-service.ts +16 -5
  112. package/template/src/server/module-todos/__tests__/isr.test.ts +105 -0
  113. package/template/src/server/module-todos/__tests__/todo-service.test.ts +12 -9
  114. package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +6 -6
  115. package/template/src/server/module-todos/isr.ts +41 -0
  116. package/template/src/server/module-todos/routes/todos-routes.ts +12 -5
  117. package/template/src/server/module-todos/services/todo-service.ts +31 -10
  118. package/template/src/server/route-registry.ts +0 -4
  119. package/template/src/server/rpc-merge.ts +27 -0
  120. package/template/src/server/rpc-surface.ts +117 -0
  121. package/template/src/server/rpc-type-canary.ts +80 -0
  122. package/template/src/server/test-utils/test-client.ts +7 -9
  123. package/template/src/server/test-utils/test-isr-helper.ts +140 -0
  124. package/template/src/shared/modules/content/schemas.ts +14 -0
  125. package/template/src/shared/modules/dispute/schemas.ts +14 -0
  126. package/template/src/shared/modules/order/schemas.ts +9 -1
  127. package/template/src/shared/modules/ticket/schemas.ts +14 -0
  128. package/template/src/shared/modules/todos/index.ts +4 -0
  129. package/template/src/shared/modules/todos/schemas.ts +14 -0
  130. package/template/src/shared/schemas/index.ts +18 -0
  131. package/template/tsup.config.ts +48 -1
  132. package/template/vitest.config.ts +19 -3
  133. package/template/vitest.setup.ts +68 -5
  134. package/template/patches/typescript+5.9.3.patch +0 -24
  135. /package/template/patches/{hono+4.12.16.patch → hono+4.12.34.patch} +0 -0
@@ -2,16 +2,12 @@
2
2
  * @framework-baseline a124c5bce28416ca
3
3
  *
4
4
  * @framework-modify
5
- * @reason 自动生成的基准更新
6
- * @impact 无功能影响
7
- */
8
-
9
- /**
10
- * ISR cache invalidation utilities.
11
- * Call these from service layer when content changes.
5
+ * @reason 模块化改造:purgeAllPages 改为遍历注册表
6
+ * @impact 不再硬编码路由列表
12
7
  */
13
8
 
14
9
  import type { ISRCache } from './isr-cache'
10
+ import { isrRegistry } from './isr-registry'
15
11
 
16
12
  let _cache: ISRCache | null = null
17
13
 
@@ -36,10 +32,8 @@ export async function purgeContentPages(): Promise<void> {
36
32
 
37
33
  export async function purgeAllPages(): Promise<void> {
38
34
  if (!_cache) return
39
- await _cache.purge('/')
40
- await _cache.purge('/todos')
41
- await _cache.purge('/content')
42
- await _cache.purge('/notifications')
43
- await _cache.purge('/websocket')
35
+ for (const path of isrRegistry.getExactPaths()) {
36
+ await _cache.purge(path)
37
+ }
44
38
  await _cache.purgePattern('isr:/content/*')
45
39
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @framework-baseline faa47d8c6e71847d
3
+ *
4
+ *
5
+ * @framework-modify
6
+ * @reason prettier 格式化与注释结构整理(无逻辑改动)
7
+ * @impact 框架文件维护性修改,行为见测试
8
+ */
9
+
10
+ /**
11
+ * ISR Route Registry — modules register their ISR routes here.
12
+ * Full SSG mode: fetches data for meta tags AND pre-populates stores for React SSR.
13
+ * The CF entry and ISR cache use this to discover which routes need ISR.
14
+ */
15
+
16
+ export type ISRMatcher = string | ((pathname: string) => boolean)
17
+
18
+ export interface ISRRouterContext {
19
+ /** D1 database binding (Cloudflare) or null (Node.js / test) */
20
+ db?: unknown
21
+ /** Raw env object (for module-specific needs) */
22
+ env?: unknown
23
+ }
24
+
25
+ export interface ISRRouteEntry {
26
+ /** Module name that owns this route */
27
+ module: string
28
+ /**
29
+ * Path matcher: exact string or function.
30
+ * String starting with '/' and ending with '/' = prefix match.
31
+ * String starting with '/' = exact match.
32
+ * Function = custom matcher.
33
+ */
34
+ match: ISRMatcher
35
+ /** Fetch data needed for SSR rendering and meta tags */
36
+ fetch: (pathname: string, ctx: ISRRouterContext) => Promise<unknown>
37
+ /** Generate meta tags from fetched data */
38
+ meta: (data: unknown, pathname: string) => { title: string; description: string }
39
+ /** Max age in seconds (optional, per-route override) */
40
+ maxAge?: number
41
+ }
42
+
43
+ class ISRRegistry {
44
+ private entries: ISRRouteEntry[] = []
45
+
46
+ register(entry: ISRRouteEntry): void {
47
+ const idx = this.entries.findIndex(e => e.module === entry.module && e.match === entry.match)
48
+ if (idx >= 0) {
49
+ this.entries[idx] = entry
50
+ } else {
51
+ this.entries.push(entry)
52
+ }
53
+ }
54
+
55
+ registerMany(entries: ISRRouteEntry[]): void {
56
+ for (const entry of entries) {
57
+ this.register(entry)
58
+ }
59
+ }
60
+
61
+ match(pathname: string): ISRRouteEntry | null {
62
+ for (const entry of this.entries) {
63
+ if (typeof entry.match === 'string' && !entry.match.endsWith('/')) {
64
+ if (pathname === entry.match) return entry
65
+ }
66
+ }
67
+ for (const entry of this.entries) {
68
+ if (typeof entry.match === 'string' && entry.match.endsWith('/') && entry.match !== '/') {
69
+ if (pathname.startsWith(entry.match)) return entry
70
+ }
71
+ }
72
+ for (const entry of this.entries) {
73
+ if (typeof entry.match === 'function') {
74
+ if (entry.match(pathname)) return entry
75
+ }
76
+ }
77
+ for (const entry of this.entries) {
78
+ if (entry.match === '/' && pathname === '/') return entry
79
+ }
80
+ return null
81
+ }
82
+
83
+ isISRRoute(pathname: string): boolean {
84
+ return this.match(pathname) !== null
85
+ }
86
+
87
+ getExactPaths(): string[] {
88
+ return this.entries.filter(e => typeof e.match === 'string').map(e => e.match as string)
89
+ }
90
+
91
+ getAll(): ISRRouteEntry[] {
92
+ return [...this.entries]
93
+ }
94
+
95
+ clear(): void {
96
+ this.entries = []
97
+ }
98
+ }
99
+
100
+ export const isrRegistry = new ISRRegistry()
101
+
102
+ export function createISRRegistry(): ISRRegistry {
103
+ return new ISRRegistry()
104
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @framework-baseline cec621fae360ba2b
3
+ *
4
+ *
5
+ * @framework-modify
6
+ * @reason prettier 格式化与注释结构整理;body 改为可选字段
7
+ * @impact 框架文件维护性修改,行为见测试
8
+ */
9
+
10
+ /**
11
+ * ISR HTML renderer — injects SSR body content AND meta tags into HTML template.
12
+ * Full SSG mode: combines React SSR output with SEO meta tags.
13
+ */
14
+
15
+ export function escapeHtml(str: string): string {
16
+ return str
17
+ .replace(/&/g, '&amp;')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;')
20
+ .replace(/"/g, '&quot;')
21
+ }
22
+
23
+ export interface ISRPageMeta {
24
+ title: string
25
+ description: string
26
+ }
27
+
28
+ export interface ISRRenderOptions {
29
+ /** Pre-built HTML template (from ASSETS or readFileSync) */
30
+ template: string | null
31
+ /** SSR body content (React renderToString output); omitted in meta-only mode */
32
+ body?: string
33
+ /** Meta tags */
34
+ meta: ISRPageMeta
35
+ }
36
+
37
+ /**
38
+ * Inject ISR content (SSR body + meta tags) into an HTML template.
39
+ * If template is null, generates a minimal standalone HTML.
40
+ */
41
+ export function renderISRPage(opts: ISRRenderOptions): string {
42
+ const { template, body = '', meta } = opts
43
+ const safeTitle = escapeHtml(meta.title)
44
+ const safeDesc = escapeHtml(meta.description)
45
+
46
+ if (template) {
47
+ let html = template
48
+ html = html.replace(/<title>[^<]*<\/title>/, `<title>${safeTitle}</title>`)
49
+ html = html.replace(/<meta\s+name="description"[^>]*>/, '')
50
+ html = html.replace(/<meta\s+property="og:title"[^>]*>/, '')
51
+ html = html.replace(/<meta\s+property="og:description"[^>]*>/, '')
52
+ html = html.replace(
53
+ '</head>',
54
+ ` <meta name="description" content="${safeDesc}" />\n <meta property="og:title" content="${safeTitle}" />\n <meta property="og:description" content="${safeDesc}" />\n <meta name="generator" content="ISR-SSG" />\n </head>`
55
+ )
56
+ html = html.replace('<div id="root"></div>', `<div id="root">${body}</div>`)
57
+ return html
58
+ }
59
+
60
+ return `<!DOCTYPE html>
61
+ <html lang="en">
62
+ <head>
63
+ <meta charset="UTF-8" />
64
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
65
+ <title>${safeTitle}</title>
66
+ <meta name="description" content="${safeDesc}" />
67
+ <meta property="og:title" content="${safeTitle}" />
68
+ <meta property="og:description" content="${safeDesc}" />
69
+ <meta name="generator" content="ISR-SSG" />
70
+ </head>
71
+ <body>
72
+ <div id="root">${body}</div>
73
+ </body>
74
+ </html>`
75
+ }
@@ -1,4 +1,4 @@
1
- import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
1
+ import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
2
2
  import { sql } from 'drizzle-orm'
3
3
 
4
4
  export const contentStatuses = ['draft', 'published', 'archived'] as const
@@ -7,25 +7,33 @@ export type ContentStatus = (typeof contentStatuses)[number]
7
7
  export const contentCategories = ['article', 'announcement', 'tutorial', 'news', 'policy'] as const
8
8
  export type ContentCategory = (typeof contentCategories)[number]
9
9
 
10
- export const contents = sqliteTable('contents', {
11
- id: integer('id').primaryKey({ autoIncrement: true }),
12
- title: text('title').notNull(),
13
- body: text('body').notNull(),
14
- excerpt: text('excerpt'),
15
- category: text('category', { enum: contentCategories }).notNull(),
16
- tags: text('tags'),
17
- status: text('status', { enum: contentStatuses }).notNull().default('draft'),
18
- author: text('author').notNull(),
19
- viewCount: integer('view_count').notNull().default(0),
20
- likeCount: integer('like_count').notNull().default(0),
21
- publishedAt: integer('published_at', { mode: 'timestamp' }),
22
- createdAt: integer('created_at', { mode: 'timestamp' })
23
- .notNull()
24
- .default(sql`(unixepoch() * 1000)`),
25
- updatedAt: integer('updated_at', { mode: 'timestamp' })
26
- .notNull()
27
- .default(sql`(unixepoch() * 1000)`),
28
- })
10
+ export const contents = sqliteTable(
11
+ 'contents',
12
+ {
13
+ id: integer('id').primaryKey({ autoIncrement: true }),
14
+ title: text('title').notNull(),
15
+ body: text('body').notNull(),
16
+ excerpt: text('excerpt'),
17
+ category: text('category', { enum: contentCategories }).notNull(),
18
+ tags: text('tags'),
19
+ status: text('status', { enum: contentStatuses }).notNull().default('draft'),
20
+ author: text('author').notNull(),
21
+ viewCount: integer('view_count').notNull().default(0),
22
+ likeCount: integer('like_count').notNull().default(0),
23
+ publishedAt: integer('published_at', { mode: 'timestamp' }),
24
+ createdAt: integer('created_at', { mode: 'timestamp' })
25
+ .notNull()
26
+ .default(sql`(unixepoch() * 1000)`),
27
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
28
+ .notNull()
29
+ .default(sql`(unixepoch() * 1000)`),
30
+ },
31
+ table => ({
32
+ statusIdx: index('contents_status_idx').on(table.status),
33
+ categoryIdx: index('contents_category_idx').on(table.category),
34
+ createdAtIdx: index('contents_created_at_idx').on(table.createdAt),
35
+ })
36
+ )
29
37
 
30
38
  export type ContentTable = typeof contents.$inferSelect
31
39
  export type NewContent = typeof contents.$inferInsert
@@ -1,4 +1,4 @@
1
- import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
1
+ import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
2
2
  import { sql } from 'drizzle-orm'
3
3
 
4
4
  export const disputeTypes = [
@@ -13,27 +13,35 @@ export type DisputeType = (typeof disputeTypes)[number]
13
13
  export const disputeStatuses = ['pending', 'investigating', 'resolved', 'rejected'] as const
14
14
  export type DisputeStatus = (typeof disputeStatuses)[number]
15
15
 
16
- export const disputes = sqliteTable('disputes', {
17
- id: integer('id').primaryKey({ autoIncrement: true }),
18
- disputeNo: text('dispute_no').notNull(),
19
- orderId: text('order_id').notNull(),
20
- orderNo: text('order_no').notNull(),
21
- customerName: text('customer_name').notNull(),
22
- customerEmail: text('customer_email').notNull(),
23
- type: text('type', { enum: disputeTypes }).notNull(),
24
- status: text('status', { enum: disputeStatuses }).notNull().default('pending'),
25
- description: text('description').notNull(),
26
- resolution: text('resolution'),
27
- amount: integer('amount').notNull(),
28
- resolvedAt: integer('resolved_at', { mode: 'timestamp' }),
29
- resolvedBy: text('resolved_by'),
30
- createdAt: integer('created_at', { mode: 'timestamp' })
31
- .notNull()
32
- .default(sql`(unixepoch() * 1000)`),
33
- updatedAt: integer('updated_at', { mode: 'timestamp' })
34
- .notNull()
35
- .default(sql`(unixepoch() * 1000)`),
36
- })
16
+ export const disputes = sqliteTable(
17
+ 'disputes',
18
+ {
19
+ id: integer('id').primaryKey({ autoIncrement: true }),
20
+ disputeNo: text('dispute_no').notNull(),
21
+ orderId: text('order_id').notNull(),
22
+ orderNo: text('order_no').notNull(),
23
+ customerName: text('customer_name').notNull(),
24
+ customerEmail: text('customer_email').notNull(),
25
+ type: text('type', { enum: disputeTypes }).notNull(),
26
+ status: text('status', { enum: disputeStatuses }).notNull().default('pending'),
27
+ description: text('description').notNull(),
28
+ resolution: text('resolution'),
29
+ amount: integer('amount').notNull(),
30
+ resolvedAt: integer('resolved_at', { mode: 'timestamp' }),
31
+ resolvedBy: text('resolved_by'),
32
+ createdAt: integer('created_at', { mode: 'timestamp' })
33
+ .notNull()
34
+ .default(sql`(unixepoch() * 1000)`),
35
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
36
+ .notNull()
37
+ .default(sql`(unixepoch() * 1000)`),
38
+ },
39
+ table => ({
40
+ statusIdx: index('disputes_status_idx').on(table.status),
41
+ orderIdIdx: index('disputes_order_id_idx').on(table.orderId),
42
+ createdAtIdx: index('disputes_created_at_idx').on(table.createdAt),
43
+ })
44
+ )
37
45
 
38
46
  export type DisputeTable = typeof disputes.$inferSelect
39
47
  export type NewDispute = typeof disputes.$inferInsert
@@ -1,16 +1,22 @@
1
- import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
2
- import { sql } from 'drizzle-orm';
1
+ import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
2
+ import { sql } from 'drizzle-orm'
3
3
 
4
- export const notifications = sqliteTable('notifications', {
5
- id: text('id').primaryKey(),
6
- type: text('type').notNull(),
7
- title: text('title').notNull(),
8
- message: text('message').notNull(),
9
- read: integer('read', { mode: 'boolean' }).notNull().default(false),
10
- createdAt: integer('created_at', { mode: 'timestamp' })
11
- .notNull()
12
- .default(sql`(unixepoch() * 1000)`),
13
- });
4
+ export const notifications = sqliteTable(
5
+ 'notifications',
6
+ {
7
+ id: text('id').primaryKey(),
8
+ type: text('type').notNull(),
9
+ title: text('title').notNull(),
10
+ message: text('message').notNull(),
11
+ read: integer('read', { mode: 'boolean' }).notNull().default(false),
12
+ createdAt: integer('created_at', { mode: 'timestamp' })
13
+ .notNull()
14
+ .default(sql`(unixepoch() * 1000)`),
15
+ },
16
+ table => ({
17
+ createdAtIdx: index('notifications_created_at_idx').on(table.createdAt),
18
+ })
19
+ )
14
20
 
15
- export type NotificationTable = typeof notifications.$inferSelect;
16
- export type NewNotification = typeof notifications.$inferInsert;
21
+ export type NotificationTable = typeof notifications.$inferSelect
22
+ export type NewNotification = typeof notifications.$inferInsert
@@ -1,4 +1,4 @@
1
- import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
1
+ import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
2
2
  import { sql } from 'drizzle-orm'
3
3
 
4
4
  export const orderStatuses = [
@@ -10,21 +10,28 @@ export const orderStatuses = [
10
10
  ] as const
11
11
  export type OrderStatus = (typeof orderStatuses)[number]
12
12
 
13
- export const orders = sqliteTable('orders', {
14
- id: integer('id').primaryKey({ autoIncrement: true }),
15
- orderNo: text('order_no').notNull(),
16
- customerName: text('customer_name').notNull(),
17
- customerEmail: text('customer_email').notNull(),
18
- productName: text('product_name').notNull(),
19
- amount: integer('amount').notNull(),
20
- status: text('status', { enum: orderStatuses }).notNull().default('pending'),
21
- createdAt: integer('created_at', { mode: 'timestamp' })
22
- .notNull()
23
- .default(sql`(unixepoch() * 1000)`),
24
- updatedAt: integer('updated_at', { mode: 'timestamp' })
25
- .notNull()
26
- .default(sql`(unixepoch() * 1000)`),
27
- })
13
+ export const orders = sqliteTable(
14
+ 'orders',
15
+ {
16
+ id: integer('id').primaryKey({ autoIncrement: true }),
17
+ orderNo: text('order_no').notNull(),
18
+ customerName: text('customer_name').notNull(),
19
+ customerEmail: text('customer_email').notNull(),
20
+ productName: text('product_name').notNull(),
21
+ amount: integer('amount').notNull(),
22
+ status: text('status', { enum: orderStatuses }).notNull().default('pending'),
23
+ createdAt: integer('created_at', { mode: 'timestamp' })
24
+ .notNull()
25
+ .default(sql`(unixepoch() * 1000)`),
26
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
27
+ .notNull()
28
+ .default(sql`(unixepoch() * 1000)`),
29
+ },
30
+ table => ({
31
+ statusIdx: index('orders_status_idx').on(table.status),
32
+ createdAtIdx: index('orders_created_at_idx').on(table.createdAt),
33
+ })
34
+ )
28
35
 
29
36
  export type OrderTable = typeof orders.$inferSelect
30
37
  export type NewOrder = typeof orders.$inferInsert
@@ -1,38 +1,47 @@
1
- import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core'
1
+ import { sqliteTable, text, integer, uniqueIndex, index } from 'drizzle-orm/sqlite-core'
2
2
  import { sql } from 'drizzle-orm'
3
3
 
4
4
  export const pluginStatus = ['pending', 'approved', 'rejected'] as const
5
5
  export type PluginStatus = (typeof pluginStatus)[number]
6
6
 
7
- export const plugins = sqliteTable('plugins', {
8
- id: text('id').primaryKey(),
9
- name: text('name').notNull(),
10
- slug: text('slug').notNull().unique(),
11
- description: text('description').notNull().default(''),
12
- readme: text('readme'),
13
- authorId: text('author_id').notNull(),
14
- authorName: text('author_name').notNull(),
15
- repositoryUrl: text('repository_url'),
16
- homepageUrl: text('homepage_url'),
17
- npmPackage: text('npm_package'),
18
- license: text('license'),
19
- version: text('version').notNull().default('0.0.1'),
20
- status: text('status', { enum: pluginStatus }).notNull().default('pending'),
21
- downloadCount: integer('download_count').notNull().default(0),
22
- viewCount: integer('view_count').notNull().default(0),
23
- featured: integer('featured', { mode: 'boolean' }).notNull().default(false),
24
- screenshotUrl: text('screenshot_url'),
25
- siteUrls: text('site_urls'),
26
- tags: text('tags'),
27
- commands: text('commands'),
28
- rejectReason: text('reject_reason'),
29
- createdAt: integer('created_at', { mode: 'timestamp' })
30
- .notNull()
31
- .default(sql`(unixepoch() * 1000)`),
32
- updatedAt: integer('updated_at', { mode: 'timestamp' })
33
- .notNull()
34
- .default(sql`(unixepoch() * 1000)`),
35
- })
7
+ export const plugins = sqliteTable(
8
+ 'plugins',
9
+ {
10
+ id: text('id').primaryKey(),
11
+ name: text('name').notNull(),
12
+ slug: text('slug').notNull().unique(),
13
+ description: text('description').notNull().default(''),
14
+ readme: text('readme'),
15
+ authorId: text('author_id').notNull(),
16
+ authorName: text('author_name').notNull(),
17
+ repositoryUrl: text('repository_url'),
18
+ homepageUrl: text('homepage_url'),
19
+ npmPackage: text('npm_package'),
20
+ license: text('license'),
21
+ version: text('version').notNull().default('0.0.1'),
22
+ status: text('status', { enum: pluginStatus }).notNull().default('pending'),
23
+ downloadCount: integer('download_count').notNull().default(0),
24
+ viewCount: integer('view_count').notNull().default(0),
25
+ featured: integer('featured', { mode: 'boolean' }).notNull().default(false),
26
+ screenshotUrl: text('screenshot_url'),
27
+ siteUrls: text('site_urls'),
28
+ tags: text('tags'),
29
+ commands: text('commands'),
30
+ rejectReason: text('reject_reason'),
31
+ createdAt: integer('created_at', { mode: 'timestamp' })
32
+ .notNull()
33
+ .default(sql`(unixepoch() * 1000)`),
34
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
35
+ .notNull()
36
+ .default(sql`(unixepoch() * 1000)`),
37
+ },
38
+ table => ({
39
+ statusIdx: index('plugins_status_idx').on(table.status),
40
+ slugIdx: index('plugins_slug_idx').on(table.slug),
41
+ authorIdIdx: index('plugins_author_id_idx').on(table.authorId),
42
+ createdAtIdx: index('plugins_created_at_idx').on(table.createdAt),
43
+ })
44
+ )
36
45
 
37
46
  export const pluginVersions = sqliteTable(
38
47
  'plugin_versions',
@@ -53,6 +62,7 @@ export const pluginVersions = sqliteTable(
53
62
  },
54
63
  table => ({
55
64
  pluginVersionUnique: uniqueIndex('plugin_version_unique').on(table.pluginId, table.version),
65
+ pluginIdIdx: index('plugin_versions_plugin_id_idx').on(table.pluginId),
56
66
  })
57
67
  )
58
68
 
@@ -77,14 +87,20 @@ export const pluginReviews = sqliteTable(
77
87
  })
78
88
  )
79
89
 
80
- export const pluginCategories = sqliteTable('plugin_categories', {
81
- id: text('id').primaryKey(),
82
- name: text('name').notNull().unique(),
83
- slug: text('slug').notNull().unique(),
84
- description: text('description'),
85
- icon: text('icon'),
86
- sortOrder: integer('sort_order').notNull().default(0),
87
- })
90
+ export const pluginCategories = sqliteTable(
91
+ 'plugin_categories',
92
+ {
93
+ id: text('id').primaryKey(),
94
+ name: text('name').notNull().unique(),
95
+ slug: text('slug').notNull().unique(),
96
+ description: text('description'),
97
+ icon: text('icon'),
98
+ sortOrder: integer('sort_order').notNull().default(0),
99
+ },
100
+ table => ({
101
+ slugIdx: index('plugin_categories_slug_idx').on(table.slug),
102
+ })
103
+ )
88
104
 
89
105
  export const pluginCategoryMappings = sqliteTable(
90
106
  'plugin_category_mappings',
@@ -101,6 +117,8 @@ export const pluginCategoryMappings = sqliteTable(
101
117
  table.pluginId,
102
118
  table.categoryId
103
119
  ),
120
+ categoryIdIdx: index('plugin_category_mappings_category_id_idx').on(table.categoryId),
121
+ pluginIdIdx: index('plugin_category_mappings_plugin_id_idx').on(table.pluginId),
104
122
  })
105
123
  )
106
124
 
@@ -1,25 +1,32 @@
1
- import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
1
+ import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
2
2
  import { sql } from 'drizzle-orm'
3
3
 
4
4
  export const productStatuses = ['active', 'inactive', 'out_of_stock'] as const
5
5
  export type ProductStatus = (typeof productStatuses)[number]
6
6
 
7
- export const products = sqliteTable('products', {
8
- id: text('id').primaryKey(),
9
- name: text('name').notNull(),
10
- description: text('description'),
11
- price: integer('price').notNull(), // 单位:分
12
- status: text('status', { enum: productStatuses }).notNull().default('active'),
13
- stock: integer('stock').notNull().default(0),
14
- imageUrl: text('image_url'),
15
- merchantId: integer('merchant_id').notNull(), // Changed to integer to match merchants.id
16
- createdAt: integer('created_at', { mode: 'timestamp' })
17
- .notNull()
18
- .default(sql`(unixepoch() * 1000)`),
19
- updatedAt: integer('updated_at', { mode: 'timestamp' })
20
- .notNull()
21
- .default(sql`(unixepoch() * 1000)`),
22
- })
7
+ export const products = sqliteTable(
8
+ 'products',
9
+ {
10
+ id: text('id').primaryKey(),
11
+ name: text('name').notNull(),
12
+ description: text('description'),
13
+ price: integer('price').notNull(), // 单位:分
14
+ status: text('status', { enum: productStatuses }).notNull().default('active'),
15
+ stock: integer('stock').notNull().default(0),
16
+ imageUrl: text('image_url'),
17
+ merchantId: integer('merchant_id').notNull(), // Changed to integer to match merchants.id
18
+ createdAt: integer('created_at', { mode: 'timestamp' })
19
+ .notNull()
20
+ .default(sql`(unixepoch() * 1000)`),
21
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
22
+ .notNull()
23
+ .default(sql`(unixepoch() * 1000)`),
24
+ },
25
+ table => ({
26
+ statusIdx: index('products_status_idx').on(table.status),
27
+ merchantIdIdx: index('products_merchant_id_idx').on(table.merchantId),
28
+ })
29
+ )
23
30
 
24
31
  export type Product = typeof products.$inferSelect
25
32
  export type NewProduct = typeof products.$inferInsert