create-protobase 0.23.13 → 0.24.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 (240) hide show
  1. package/dist/ask-preset.js +25 -0
  2. package/dist/create-project.js +7 -6
  3. package/dist/index.js +19 -9
  4. package/dist/next-steps.js +8 -2
  5. package/dist/presets.js +17 -0
  6. package/dist/templates/erp/.build-test-abf2a11f/protobase.config.ts +6 -0
  7. package/dist/templates/erp/.build-test-abf2a11f/resources.ts +2 -0
  8. package/dist/templates/erp/.build-test-b878b26f/config/index.ts +2 -0
  9. package/dist/templates/erp/.build-test-b878b26f/config/things/data.ts +2 -0
  10. package/dist/templates/erp/.build-test-b878b26f/config/things/ui.ts +2 -0
  11. package/dist/templates/erp/.env.example +14 -0
  12. package/dist/templates/erp/AGENTS.md +24 -0
  13. package/dist/templates/erp/auth/auth.ts +68 -0
  14. package/dist/templates/erp/components/orders-per-day.tsx +21 -0
  15. package/dist/templates/erp/config/bottom-bar.ts +9 -0
  16. package/dist/templates/erp/config/categories/data.ts +20 -0
  17. package/dist/templates/erp/config/categories/ui.ts +11 -0
  18. package/dist/templates/erp/config/companies/data.ts +23 -0
  19. package/dist/templates/erp/config/companies/ui.ts +22 -0
  20. package/dist/templates/erp/config/company_tags/data.ts +16 -0
  21. package/dist/templates/erp/config/company_tags/ui.ts +7 -0
  22. package/dist/templates/erp/config/countries/data.ts +13 -0
  23. package/dist/templates/erp/config/countries/ui.ts +6 -0
  24. package/dist/templates/erp/config/currencies/data.ts +14 -0
  25. package/dist/templates/erp/config/currencies/ui.ts +6 -0
  26. package/dist/templates/erp/config/emp_master/data.ts +19 -0
  27. package/dist/templates/erp/config/emp_master/ui.ts +7 -0
  28. package/dist/templates/erp/config/index.ts +42 -0
  29. package/dist/templates/erp/config/invoice_lines/data.ts +23 -0
  30. package/dist/templates/erp/config/invoice_lines/ui.ts +7 -0
  31. package/dist/templates/erp/config/invoices/data.ts +26 -0
  32. package/dist/templates/erp/config/invoices/ui.ts +38 -0
  33. package/dist/templates/erp/config/jobs.test.ts +23 -0
  34. package/dist/templates/erp/config/jobs.ts +29 -0
  35. package/dist/templates/erp/config/locations/data.ts +18 -0
  36. package/dist/templates/erp/config/locations/ui.ts +6 -0
  37. package/dist/templates/erp/config/order_lines/data.ts +20 -0
  38. package/dist/templates/erp/config/order_lines/ui.ts +7 -0
  39. package/dist/templates/erp/config/orders/data.ts +27 -0
  40. package/dist/templates/erp/config/orders/ui.ts +41 -0
  41. package/dist/templates/erp/config/organizations/data.ts +17 -0
  42. package/dist/templates/erp/config/organizations/ui.ts +6 -0
  43. package/dist/templates/erp/config/overview/page.tsx +80 -0
  44. package/dist/templates/erp/config/pages.test.ts +14 -0
  45. package/dist/templates/erp/config/people/data.ts +21 -0
  46. package/dist/templates/erp/config/people/ui.ts +6 -0
  47. package/dist/templates/erp/config/products/data.ts +25 -0
  48. package/dist/templates/erp/config/products/ui.ts +6 -0
  49. package/dist/templates/erp/config/roles.test.ts +56 -0
  50. package/dist/templates/erp/config/roles.ts +54 -0
  51. package/dist/templates/erp/config/shell.test.ts +24 -0
  52. package/dist/templates/erp/config/stock_levels/data.ts +16 -0
  53. package/dist/templates/erp/config/stock_levels/ui.ts +6 -0
  54. package/dist/templates/erp/config/stock_moves/data.ts +22 -0
  55. package/dist/templates/erp/config/stock_moves/ui.ts +25 -0
  56. package/dist/templates/erp/config/tags/data.ts +17 -0
  57. package/dist/templates/erp/config/tags/ui.ts +6 -0
  58. package/dist/templates/erp/config/user-menu.ts +9 -0
  59. package/dist/templates/erp/config/users/data.ts +19 -0
  60. package/dist/templates/erp/config/users/ui.ts +9 -0
  61. package/dist/templates/erp/config/warehouses/data.ts +19 -0
  62. package/dist/templates/erp/config/warehouses/ui.ts +6 -0
  63. package/dist/templates/erp/data.ts +3 -0
  64. package/dist/templates/erp/db/connection.ts +7 -0
  65. package/dist/templates/erp/db/drop.ts +9 -0
  66. package/dist/templates/erp/db/kysely.ts +13 -0
  67. package/dist/templates/erp/db/migrations/001_core.sql +48 -0
  68. package/dist/templates/erp/db/migrations/002_crm.sql +59 -0
  69. package/dist/templates/erp/db/migrations/003_catalog.sql +38 -0
  70. package/dist/templates/erp/db/migrations/004_sales.sql +86 -0
  71. package/dist/templates/erp/db/migrations/005_inventory.sql +65 -0
  72. package/dist/templates/erp/db/migrations/006_hr_legacy.sql +16 -0
  73. package/dist/templates/erp/db/migrations/007_filter_indexes.sql +4 -0
  74. package/dist/templates/erp/db/migrations/008_calculated_totals.sql +98 -0
  75. package/dist/templates/erp/db/migrations/011_auth_organizations.sql +22 -0
  76. package/dist/templates/erp/db/migrations/012_jobs.sql +531 -0
  77. package/dist/templates/erp/db/migrations/013_jobs.sql +98 -0
  78. package/dist/templates/erp/db/migrations/016_jobs.sql +99 -0
  79. package/dist/templates/erp/db/migrations/017_filter_indexes.sql +5 -0
  80. package/dist/templates/erp/db/migrations/018_sort_indexes.sql +4 -0
  81. package/dist/templates/erp/db/migrations/019_auth_users.sql +18 -0
  82. package/dist/templates/erp/functions/unpaid-orders.ts +13 -0
  83. package/dist/templates/erp/package.json +38 -0
  84. package/dist/templates/erp/protobase.config.ts +22 -0
  85. package/dist/templates/erp/protobase.ui.tsx +14 -0
  86. package/dist/templates/erp/seed/auth-organizations.ts +11 -0
  87. package/dist/templates/erp/seed/base-organization.test.ts +17 -0
  88. package/dist/templates/erp/seed/base-organization.ts +10 -0
  89. package/dist/templates/erp/seed/base.ts +22 -0
  90. package/dist/templates/erp/seed/builders/categories.ts +18 -0
  91. package/dist/templates/erp/seed/builders/company.ts +61 -0
  92. package/dist/templates/erp/seed/builders/invoice.ts +31 -0
  93. package/dist/templates/erp/seed/builders/order.ts +66 -0
  94. package/dist/templates/erp/seed/builders/product.ts +46 -0
  95. package/dist/templates/erp/seed/calendar.ts +60 -0
  96. package/dist/templates/erp/seed/cli.ts +54 -0
  97. package/dist/templates/erp/seed/copy.ts +22 -0
  98. package/dist/templates/erp/seed/data/catalogTree.ts +72 -0
  99. package/dist/templates/erp/seed/data/layout.ts +19 -0
  100. package/dist/templates/erp/seed/data/names.ts +44 -0
  101. package/dist/templates/erp/seed/data/reference.ts +53 -0
  102. package/dist/templates/erp/seed/format.ts +24 -0
  103. package/dist/templates/erp/seed/maintenance.ts +63 -0
  104. package/dist/templates/erp/seed/rng.ts +36 -0
  105. package/dist/templates/erp/seed/scales.ts +9 -0
  106. package/dist/templates/erp/seed/steps/catalog.ts +34 -0
  107. package/dist/templates/erp/seed/steps/crm.ts +62 -0
  108. package/dist/templates/erp/seed/steps/hr.ts +26 -0
  109. package/dist/templates/erp/seed/steps/ledger.ts +88 -0
  110. package/dist/templates/erp/seed/steps/reference.ts +24 -0
  111. package/dist/templates/erp/seed/steps/sales.ts +58 -0
  112. package/dist/templates/erp/seed/steps/warehouses.ts +42 -0
  113. package/dist/templates/erp/seed/world.ts +86 -0
  114. package/dist/templates/erp/vitest.config.ts +4 -0
  115. package/dist/templates/real-estate/.env.example +9 -0
  116. package/dist/templates/real-estate/AGENTS.md +24 -0
  117. package/dist/templates/real-estate/_.gitignore +3 -0
  118. package/dist/templates/real-estate/_bunfig.toml +4 -0
  119. package/dist/templates/real-estate/auth/auth.ts +36 -0
  120. package/dist/templates/real-estate/config/amenities/data.ts +12 -0
  121. package/dist/templates/real-estate/config/amenities/ui.ts +7 -0
  122. package/dist/templates/real-estate/config/arrears/data.ts +19 -0
  123. package/dist/templates/real-estate/config/arrears/ui.ts +21 -0
  124. package/dist/templates/real-estate/config/deposits/data.ts +21 -0
  125. package/dist/templates/real-estate/config/deposits/ui.ts +17 -0
  126. package/dist/templates/real-estate/config/index.ts +41 -0
  127. package/dist/templates/real-estate/config/inspections/data.ts +22 -0
  128. package/dist/templates/real-estate/config/inspections/ui.ts +15 -0
  129. package/dist/templates/real-estate/config/lease_tenants/data.ts +16 -0
  130. package/dist/templates/real-estate/config/lease_tenants/ui.ts +8 -0
  131. package/dist/templates/real-estate/config/leases/data.ts +29 -0
  132. package/dist/templates/real-estate/config/leases/ui.ts +33 -0
  133. package/dist/templates/real-estate/config/organizations/data.ts +17 -0
  134. package/dist/templates/real-estate/config/organizations/ui.ts +7 -0
  135. package/dist/templates/real-estate/config/overview/page.tsx +57 -0
  136. package/dist/templates/real-estate/config/owners/data.ts +22 -0
  137. package/dist/templates/real-estate/config/owners/ui.ts +17 -0
  138. package/dist/templates/real-estate/config/pages.test.ts +49 -0
  139. package/dist/templates/real-estate/config/payments/data.ts +20 -0
  140. package/dist/templates/real-estate/config/payments/ui.ts +14 -0
  141. package/dist/templates/real-estate/config/properties/data.ts +26 -0
  142. package/dist/templates/real-estate/config/properties/ui.ts +21 -0
  143. package/dist/templates/real-estate/config/rent_charges/data.ts +22 -0
  144. package/dist/templates/real-estate/config/rent_charges/ui.ts +27 -0
  145. package/dist/templates/real-estate/config/roles.test.ts +49 -0
  146. package/dist/templates/real-estate/config/roles.ts +45 -0
  147. package/dist/templates/real-estate/config/shell.test.ts +18 -0
  148. package/dist/templates/real-estate/config/tenants/data.ts +23 -0
  149. package/dist/templates/real-estate/config/tenants/ui.ts +27 -0
  150. package/dist/templates/real-estate/config/tickets/data.ts +33 -0
  151. package/dist/templates/real-estate/config/tickets/ui.ts +58 -0
  152. package/dist/templates/real-estate/config/unit_amenities/data.ts +15 -0
  153. package/dist/templates/real-estate/config/unit_amenities/ui.ts +8 -0
  154. package/dist/templates/real-estate/config/units/data.ts +24 -0
  155. package/dist/templates/real-estate/config/units/ui.ts +41 -0
  156. package/dist/templates/real-estate/config/user-menu.ts +9 -0
  157. package/dist/templates/real-estate/config/users/data.ts +19 -0
  158. package/dist/templates/real-estate/config/users/ui.ts +11 -0
  159. package/dist/templates/real-estate/config/valuations/data.ts +19 -0
  160. package/dist/templates/real-estate/config/valuations/ui.ts +13 -0
  161. package/dist/templates/real-estate/config/vendors/data.ts +21 -0
  162. package/dist/templates/real-estate/config/vendors/ui.ts +18 -0
  163. package/dist/templates/real-estate/config/work_orders/data.ts +22 -0
  164. package/dist/templates/real-estate/config/work_orders/ui.ts +18 -0
  165. package/dist/templates/real-estate/db/connection.ts +7 -0
  166. package/dist/templates/real-estate/db/drop.ts +9 -0
  167. package/dist/templates/real-estate/db/kysely.ts +13 -0
  168. package/dist/templates/real-estate/db/migrate.ts +30 -0
  169. package/dist/templates/real-estate/db/migrations/001_core.sql +33 -0
  170. package/dist/templates/real-estate/db/migrations/002_portfolio.sql +97 -0
  171. package/dist/templates/real-estate/db/migrations/003_leasing.sql +78 -0
  172. package/dist/templates/real-estate/db/migrations/004_billing.sql +88 -0
  173. package/dist/templates/real-estate/db/migrations/005_maintenance.sql +105 -0
  174. package/dist/templates/real-estate/db/migrations/006_outstanding.sql +14 -0
  175. package/dist/templates/real-estate/db/migrations/007_auth.sql +36 -0
  176. package/dist/templates/real-estate/db/migrations/008_auth_snake_case.sql +109 -0
  177. package/dist/templates/real-estate/db/migrations/009_auth.sql +6 -0
  178. package/dist/templates/real-estate/db/migrations/010_auth.sql +4 -0
  179. package/dist/templates/real-estate/db/migrations/011_filter_indexes.sql +8 -0
  180. package/dist/templates/real-estate/db/migrations/012_sort_indexes.sql +2 -0
  181. package/dist/templates/real-estate/db/migrations/013_auth_users.sql +17 -0
  182. package/dist/templates/real-estate/node-types.d.ts +1 -0
  183. package/dist/templates/real-estate/package.json +38 -0
  184. package/dist/templates/real-estate/protobase.config.ts +5 -0
  185. package/dist/templates/real-estate/protobase.ui.tsx +16 -0
  186. package/dist/templates/real-estate/seed/base-organization.test.ts +17 -0
  187. package/dist/templates/real-estate/seed/base-organization.ts +10 -0
  188. package/dist/templates/real-estate/seed/base.ts +18 -0
  189. package/dist/templates/real-estate/seed/builders/allocation.ts +47 -0
  190. package/dist/templates/real-estate/seed/builders/ledger.ts +52 -0
  191. package/dist/templates/real-estate/seed/builders/property.ts +98 -0
  192. package/dist/templates/real-estate/seed/builders/tenancy.ts +99 -0
  193. package/dist/templates/real-estate/seed/builders/ticket.ts +115 -0
  194. package/dist/templates/real-estate/seed/calendar.ts +77 -0
  195. package/dist/templates/real-estate/seed/cli.ts +50 -0
  196. package/dist/templates/real-estate/seed/copy.ts +22 -0
  197. package/dist/templates/real-estate/seed/data/names.ts +31 -0
  198. package/dist/templates/real-estate/seed/data/reference.ts +63 -0
  199. package/dist/templates/real-estate/seed/format.ts +24 -0
  200. package/dist/templates/real-estate/seed/iban.ts +15 -0
  201. package/dist/templates/real-estate/seed/maintenance.ts +63 -0
  202. package/dist/templates/real-estate/seed/rng.ts +36 -0
  203. package/dist/templates/real-estate/seed/scales.ts +9 -0
  204. package/dist/templates/real-estate/seed/steps/billing.ts +44 -0
  205. package/dist/templates/real-estate/seed/steps/leasing.ts +60 -0
  206. package/dist/templates/real-estate/seed/steps/maintenance.ts +77 -0
  207. package/dist/templates/real-estate/seed/steps/portfolio.ts +97 -0
  208. package/dist/templates/real-estate/seed/steps/reference.ts +21 -0
  209. package/dist/templates/real-estate/seed/world.ts +92 -0
  210. package/dist/templates/real-estate/tsconfig.json +16 -0
  211. package/dist/templates/real-estate/vitest.config.ts +4 -0
  212. package/dist/templates/scratch/_.gitignore +3 -0
  213. package/dist/templates/scratch/_bunfig.toml +4 -0
  214. package/dist/templates/scratch/db/migrate.ts +30 -0
  215. package/dist/templates/scratch/db/migrations/001_auth.sql +36 -0
  216. package/dist/templates/scratch/db/migrations/002_auth_snake_case.sql +109 -0
  217. package/dist/templates/scratch/db/migrations/003_auth.sql +6 -0
  218. package/dist/templates/scratch/db/migrations/004_auth.sql +4 -0
  219. package/dist/templates/scratch/node-types.d.ts +1 -0
  220. package/dist/{template → templates/scratch}/package.json +5 -5
  221. package/dist/templates/scratch/tsconfig.json +16 -0
  222. package/package.json +2 -2
  223. /package/dist/{template → templates/erp}/_.gitignore +0 -0
  224. /package/dist/{template → templates/erp}/_bunfig.toml +0 -0
  225. /package/dist/{template → templates/erp}/db/migrate.ts +0 -0
  226. /package/dist/{template/db/migrations/001_auth.sql → templates/erp/db/migrations/009_auth.sql} +0 -0
  227. /package/dist/{template/db/migrations/002_auth_snake_case.sql → templates/erp/db/migrations/010_auth_snake_case.sql} +0 -0
  228. /package/dist/{template/db/migrations/003_auth.sql → templates/erp/db/migrations/014_auth.sql} +0 -0
  229. /package/dist/{template/db/migrations/004_auth.sql → templates/erp/db/migrations/015_auth.sql} +0 -0
  230. /package/dist/{template → templates/erp}/node-types.d.ts +0 -0
  231. /package/dist/{template → templates/erp}/tsconfig.json +0 -0
  232. /package/dist/{template → templates/scratch}/.env.example +0 -0
  233. /package/dist/{template → templates/scratch}/AGENTS.md +0 -0
  234. /package/dist/{template → templates/scratch}/auth/auth.ts +0 -0
  235. /package/dist/{template → templates/scratch}/config/index.ts +0 -0
  236. /package/dist/{template → templates/scratch}/config/user-menu.ts +0 -0
  237. /package/dist/{template → templates/scratch}/db/connection.ts +0 -0
  238. /package/dist/{template → templates/scratch}/db/migrations/.gitkeep +0 -0
  239. /package/dist/{template → templates/scratch}/protobase.config.ts +0 -0
  240. /package/dist/{template → templates/scratch}/vitest.config.ts +0 -0
@@ -0,0 +1,77 @@
1
+ import type { Db } from '../../db/connection'
2
+ import { ticketAt } from '../builders/ticket'
3
+ import { tenanciesOf } from '../builders/allocation'
4
+ import { DAY, windowEnd, windowStart } from '../calendar'
5
+ import { copyRows } from '../copy'
6
+ import { iso, isoDate, money, row } from '../format'
7
+ import { int, pick, rngAt, weighted } from '../rng'
8
+ import type { World } from '../world'
9
+ import { unitsOf } from './portfolio'
10
+
11
+ function* vendorRows(world: World) {
12
+ const createdAt = iso(windowStart - 2 * 365 * DAY)
13
+ for (const org of world.orgs) {
14
+ for (const vendor of org.vendors) {
15
+ const domain = `${vendor.name.toLowerCase().replace(/[^a-z]+/g, '')}.nl`
16
+ const rand = rngAt(org.seed + 16, vendor.id)
17
+ yield row(vendor.id, org.id, vendor.name, vendor.trade, `planning@${domain}`, `+31 ${int(rand, 10, 79)} ${int(rand, 1_000_000, 9_999_999)}`, money(vendor.rate * 100), true, createdAt, createdAt)
18
+ }
19
+ }
20
+ }
21
+
22
+ function* ticketRows(world: World) {
23
+ for (const org of world.orgs) {
24
+ for (let ordinal = 0; ordinal < org.ticketCount; ordinal++) {
25
+ const t = ticketAt(org, ordinal)
26
+ yield row(t.id, org.id, t.number, t.propertyId, t.unitId, t.reportedBy, t.assignedTo, t.category, t.priority, t.status, t.title, t.description, iso(t.reportedAt), t.closedAt === null ? null : iso(t.closedAt), iso(t.reportedAt), iso(t.updatedAt))
27
+ }
28
+ }
29
+ }
30
+
31
+ function* workOrderRows(world: World) {
32
+ let id = 1
33
+ for (const org of world.orgs) {
34
+ for (let ordinal = 0; ordinal < org.ticketCount; ordinal++) {
35
+ const ticket = ticketAt(org, ordinal)
36
+ for (const w of ticket.workOrders) {
37
+ const updatedAt = w.completedOn === null ? w.createdAt : Math.min(w.completedOn + 18 * 3_600_000, windowEnd)
38
+ yield row(id++, org.id, ticket.id, w.vendorId, isoDate(w.scheduledFor), w.completedOn === null ? null : isoDate(w.completedOn), w.hours?.toFixed(2) ?? null, w.costCents === null ? null : money(w.costCents), w.notes, iso(w.createdAt), iso(updatedAt))
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ const notes = { good: [null, 'Clean and complete.', 'Meter readings taken.'], fair: ['Wear on the floor in the living room.', 'Walls need painting.', 'Small damage to the kitchen worktop.'], poor: ['Holes in the walls, floor damaged.', 'Mould in the bathroom, needs treatment.', 'Garden neglected, rubbish left behind.'] } as const
45
+
46
+ // A move-in inspection for every lease that starts in the window, a move-out for every one that ends in it, and now
47
+ // and then a periodic one.
48
+ function* inspectionRows(world: World) {
49
+ let id = 1
50
+ for (const org of world.orgs) {
51
+ for (const { property, unit, ordinal } of unitsOf(org)) {
52
+ const rand = rngAt(org.seed + 17, ordinal)
53
+ const inspection = (leaseId: number | null, kind: 'move_in' | 'move_out' | 'periodic', on: number, condition: 'good' | 'fair' | 'poor') => {
54
+ const recordedAt = Math.min(on + int(rand, 10, 17) * 3_600_000, windowEnd)
55
+ return row(id++, org.id, unit.id, leaseId, pick(rand, org.inspectors).id, kind, isoDate(on), condition, pick(rand, notes[condition]), iso(recordedAt), iso(recordedAt))
56
+ }
57
+ const tenancies = tenanciesOf(org.seed, ordinal, property.createdAt)
58
+ for (const [index, tenancy] of tenancies.entries()) {
59
+ const leaseId = org.leaseFirstId + org.leaseStarts[ordinal]! + index
60
+ const moveIn = tenancy.start - int(rand, 0, 2) * DAY
61
+ if (moveIn >= windowStart && moveIn <= windowEnd) yield inspection(leaseId, 'move_in', moveIn, weighted(rand, [['good', 80], ['fair', 18], ['poor', 2]] as const))
62
+ if (tenancy.end !== null && tenancy.end >= windowStart && tenancy.end <= windowEnd) yield inspection(leaseId, 'move_out', tenancy.end, weighted(rand, [['good', 45], ['fair', 40], ['poor', 15]] as const))
63
+ }
64
+ if (rand() < 0.3) {
65
+ const on = Math.floor((Math.max(windowStart, property.createdAt) + rand() * (windowEnd - Math.max(windowStart, property.createdAt))) / DAY) * DAY
66
+ yield inspection(null, 'periodic', on, weighted(rand, [['good', 70], ['fair', 25], ['poor', 5]] as const))
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export const seedMaintenance = async (sql: Db, world: World) => {
73
+ await copyRows(sql, 'maintenance.vendors (id, organization_id, name, trade, email, phone, hourly_rate, active, created_at, updated_at)', vendorRows(world))
74
+ await copyRows(sql, 'maintenance.tickets (id, organization_id, number, property_id, unit_id, reported_by, assigned_to, category, priority, status, title, description, reported_at, closed_at, created_at, updated_at)', ticketRows(world))
75
+ await copyRows(sql, 'maintenance.work_orders (id, organization_id, ticket_id, vendor_id, scheduled_for, completed_on, hours, cost, notes, created_at, updated_at)', workOrderRows(world))
76
+ await copyRows(sql, 'maintenance.inspections (id, organization_id, unit_id, lease_id, inspector_id, kind, inspected_on, condition, notes, created_at, updated_at)', inspectionRows(world))
77
+ }
@@ -0,0 +1,97 @@
1
+ import type { Db } from '../../db/connection'
2
+ import { ownerAt, propertyAt, unitAt, type SeededProperty, type SeededUnit } from '../builders/property'
3
+ import { tenanciesOf } from '../builders/allocation'
4
+ import { unitStatus } from '../builders/tenancy'
5
+ import { DAY, windowEnd } from '../calendar'
6
+ import { copyRows } from '../copy'
7
+ import type { AmenityCode } from '../data/reference'
8
+ import { iso, isoDate, money, row } from '../format'
9
+ import { int, rngAt } from '../rng'
10
+ import type { Org, World } from '../world'
11
+
12
+ function* ownerRows(world: World) {
13
+ for (const org of world.orgs) {
14
+ for (let ordinal = 0; ordinal < org.ownerCount; ordinal++) {
15
+ const owner = ownerAt(org, ordinal)
16
+ yield row(owner.id, org.id, owner.name, owner.kind, owner.email, owner.phone, owner.iban, owner.feePercent.toFixed(2), iso(owner.createdAt), iso(owner.createdAt))
17
+ }
18
+ }
19
+ }
20
+
21
+ function* propertyRows(world: World) {
22
+ for (const org of world.orgs) {
23
+ for (let ordinal = 0; ordinal < org.propertyCount; ordinal++) {
24
+ const p = propertyAt(org, ordinal)
25
+ yield row(p.id, org.id, p.ownerId, p.name, p.kind, p.street, p.houseNumber, p.postalCode, p.city, p.latitude, p.longitude, p.builtYear, iso(p.createdAt), iso(p.createdAt))
26
+ }
27
+ }
28
+ }
29
+
30
+ // Every unit with its property, in unit order.
31
+ export function* unitsOf(org: Org): Generator<{ property: SeededProperty; unit: SeededUnit; ordinal: number }> {
32
+ for (let p = 0; p < org.propertyCount; p++) {
33
+ const property = propertyAt(org, p)
34
+ for (let ordinal = org.unitStarts[p]!; ordinal < org.unitStarts[p + 1]!; ordinal++) yield { property, unit: unitAt(org, property, ordinal), ordinal }
35
+ }
36
+ }
37
+
38
+ function* unitRows(world: World) {
39
+ for (const org of world.orgs) {
40
+ for (const { property, unit, ordinal } of unitsOf(org)) {
41
+ const status = unitStatus(tenanciesOf(org.seed, ordinal, property.createdAt))
42
+ yield row(unit.id, org.id, property.id, unit.label, unit.floor, unit.bedrooms, unit.area, unit.energyLabel, money(unit.baseRentCents), status, iso(unit.createdAt), iso(unit.createdAt))
43
+ }
44
+ }
45
+ }
46
+
47
+ // Amenities by kind of home, with how often a unit has each.
48
+ const houseAmenities: Array<[AmenityCode, number]> = [['garden', 0.85], ['parking', 0.4], ['storage_room', 0.3], ['solar_panels', 0.35], ['heat_pump', 0.15], ['furnished', 0.03]]
49
+ const flatAmenities: Array<[AmenityCode, number]> = [['balcony', 0.6], ['bike_storage', 0.5], ['storage_room', 0.35], ['parking', 0.2], ['solar_panels', 0.1], ['heat_pump', 0.08], ['furnished', 0.08]]
50
+
51
+ function* unitAmenityRows(world: World) {
52
+ for (const org of world.orgs) {
53
+ for (const { property, unit, ordinal } of unitsOf(org)) {
54
+ const rand = rngAt(org.seed + 14, ordinal)
55
+ const codes = (property.kind === 'single_family' ? houseAmenities : flatAmenities).filter(([, chance]) => rand() < chance).map(([code]) => code)
56
+ if (property.kind !== 'single_family' && property.units > 12) codes.push('lift')
57
+ if (unit.topFloor && rand() < 0.25) codes.push('roof_terrace')
58
+ for (const code of codes) yield row(unit.id, code, org.id, iso(unit.createdAt))
59
+ }
60
+ }
61
+ }
62
+
63
+ // Market value per m2 from the rent per m2, and how prices moved year on year.
64
+ const valueYears = [[2023, 0.93], [2024, 0.96], [2025, 1.0], [2026, 1.06]] as const
65
+
66
+ function* valuationRows(world: World) {
67
+ let id = 1
68
+ for (const org of world.orgs) {
69
+ for (let ordinal = 0; ordinal < org.propertyCount; ordinal++) {
70
+ const property = propertyAt(org, ordinal)
71
+ let area = 0
72
+ for (let unit = org.unitStarts[ordinal]!; unit < org.unitStarts[ordinal + 1]!; unit++) area += unitAt(org, property, unit).area
73
+ const rand = rngAt(org.seed + 15, ordinal)
74
+ const perM2 = property.rentPerM2 * 210 * (0.85 + rand() * 0.3)
75
+ for (const [year, factor] of valueYears) {
76
+ const valuedOn = Date.UTC(year, 0, 1)
77
+ if (valuedOn < property.createdAt) continue
78
+ const value = Math.round((area * perM2 * factor) / 1000) * 1000
79
+ yield row(id++, org.id, property.id, isoDate(valuedOn), money(value * 100), 'woz', iso(valuedOn + 40 * DAY), iso(valuedOn + 40 * DAY))
80
+ }
81
+ if (rand() < 0.12) {
82
+ const valuedOn = Math.floor((property.createdAt + rand() * (windowEnd - property.createdAt)) / DAY) * DAY
83
+ const value = Math.round((area * perM2 * (1.05 + rand() * 0.2)) / 1000) * 1000
84
+ const recordedAt = Math.min(valuedOn + int(rand, 3, 20) * DAY, windowEnd)
85
+ yield row(id++, org.id, property.id, isoDate(valuedOn), money(value * 100), 'appraisal', iso(recordedAt), iso(recordedAt))
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ export const seedPortfolio = async (sql: Db, world: World) => {
92
+ await copyRows(sql, 'portfolio.owners (id, organization_id, name, kind, email, phone, iban, management_fee_percent, created_at, updated_at)', ownerRows(world))
93
+ await copyRows(sql, 'portfolio.properties (id, organization_id, owner_id, name, kind, street, house_number, postal_code, city, latitude, longitude, built_year, created_at, updated_at)', propertyRows(world))
94
+ await copyRows(sql, 'portfolio.units (id, organization_id, property_id, label, floor, bedrooms, area_m2, energy_label, base_rent, status, created_at, updated_at)', unitRows(world))
95
+ await copyRows(sql, 'portfolio.unit_amenities (unit_id, amenity_code, organization_id, added_at)', unitAmenityRows(world))
96
+ await copyRows(sql, 'portfolio.valuations (id, organization_id, property_id, valued_on, value, source, created_at, updated_at)', valuationRows(world))
97
+ }
@@ -0,0 +1,21 @@
1
+ import type { Db } from '../../db/connection'
2
+ import { DAY, windowStart } from '../calendar'
3
+ import { copyRows } from '../copy'
4
+ import { amenities } from '../data/reference'
5
+ import { iso, row } from '../format'
6
+ import type { World } from '../world'
7
+
8
+ export const seedReference = async (sql: Db, world: World) => {
9
+ const createdAt = iso(windowStart - 10 * 365 * DAY)
10
+ await copyRows(sql, 'portfolio.amenities (code, name)', amenities.map((amenity) => row(...amenity)))
11
+ await copyRows(
12
+ sql,
13
+ 'core.organizations (id, name, slug, city, kvk_number, created_at, updated_at)',
14
+ world.orgs.map((org) => row(org.id, org.name, org.slug, org.city, org.kvk, createdAt, createdAt)),
15
+ )
16
+ await copyRows(
17
+ sql,
18
+ 'core.users (id, organization_id, email, name, role, active, created_at, updated_at)',
19
+ world.orgs.flatMap((org) => org.users.map((user) => row(user.id, org.id, user.email, user.name, user.role, true, createdAt, createdAt))),
20
+ )
21
+ }
@@ -0,0 +1,92 @@
1
+ import { coSigned, propertyAcquiredAt, propertyShapeAt, tenanciesOf } from './builders/allocation'
2
+ import { timestamps } from './calendar'
3
+ import { orgSpecs } from './data/reference'
4
+ import { scales, type ScaleName } from './scales'
5
+
6
+ // Splits a scale total over the organizations by share; the last one takes the remainder.
7
+ const splitTotal = (total: number) => {
8
+ const parts: number[] = []
9
+ for (const [index, spec] of orgSpecs.entries()) {
10
+ parts.push(index === orgSpecs.length - 1 ? total - parts.reduce((sum, part) => sum + part, 0) : Math.round(total * spec.share))
11
+ }
12
+ return parts
13
+ }
14
+
15
+ // Every table's ids are allocated here, so builders can compute foreign keys without lookups. Tables whose rows are
16
+ // only ever referenced from inside one step (charges, payments, work orders, inspections, valuations) number their
17
+ // rows as the step streams them.
18
+ export const buildWorld = (name: ScaleName) => {
19
+ const scale = scales[name]
20
+ const properties = splitTotal(scale.properties)
21
+ const next = { user: 1, owner: 1, property: 1, unit: 1, lease: 1, tenant: 1, vendor: 1, ticket: 1 }
22
+
23
+ const orgs = orgSpecs.map((spec, index) => {
24
+ const propertyCount = properties[index]!
25
+ const unitStarts = new Uint32Array(propertyCount + 1)
26
+ for (let ordinal = 0; ordinal < propertyCount; ordinal++) unitStarts[ordinal + 1] = unitStarts[ordinal]! + propertyShapeAt(spec.seed, ordinal).units
27
+ const unitCount = unitStarts[propertyCount]!
28
+
29
+ // Per unit: its property, and where its leases start in the organization's lease numbering.
30
+ const unitProperty = new Uint32Array(unitCount)
31
+ const leaseStarts = new Uint32Array(unitCount + 1)
32
+ for (let property = 0; property < propertyCount; property++) {
33
+ const acquiredAt = propertyAcquiredAt(spec.seed, property)
34
+ for (let unit = unitStarts[property]!; unit < unitStarts[property + 1]!; unit++) {
35
+ unitProperty[unit] = property
36
+ leaseStarts[unit + 1] = leaseStarts[unit]! + tenanciesOf(spec.seed, unit, acquiredAt).length
37
+ }
38
+ }
39
+ const leaseCount = leaseStarts[unitCount]!
40
+
41
+ // Per lease: where its tenants start; a co-signed lease has two.
42
+ const tenantStarts = new Uint32Array(leaseCount + 1)
43
+ for (let lease = 0; lease < leaseCount; lease++) tenantStarts[lease + 1] = tenantStarts[lease]! + (coSigned(spec.seed, lease) ? 2 : 1)
44
+
45
+ const users = spec.users.map(([first, initial, role], position) => ({
46
+ id: next.user + position,
47
+ name: `${first} ${initial}`,
48
+ email: `${first.toLowerCase()}@${spec.slug}.example`,
49
+ role,
50
+ }))
51
+ const vendors = spec.vendors.map(([vendorName, trade, rate], position) => ({ id: next.vendor + position, name: vendorName, trade, rate }))
52
+ const ticketCount = Math.round(unitCount * scale.ticketsPerUnit)
53
+ const org = {
54
+ ...spec,
55
+ users,
56
+ managers: users.filter((user) => user.role === 'manager'),
57
+ maintenanceUsers: users.filter((user) => user.role === 'maintenance'),
58
+ inspectors: users.filter((user) => user.role === 'manager' || user.role === 'maintenance'),
59
+ vendors,
60
+ ownerCount: Math.max(3, Math.round(propertyCount / 6)),
61
+ ownerFirstId: next.owner,
62
+ propertyCount,
63
+ propertyFirstId: next.property,
64
+ unitStarts,
65
+ unitCount,
66
+ unitProperty,
67
+ unitFirstId: next.unit,
68
+ leaseStarts,
69
+ leaseCount,
70
+ leaseFirstId: next.lease,
71
+ tenantStarts,
72
+ tenantFirstId: next.tenant,
73
+ ticketCount,
74
+ ticketFirstId: next.ticket,
75
+ ticketTimes: timestamps(ticketCount, spec.seed + 10),
76
+ }
77
+ next.user += users.length
78
+ next.owner += org.ownerCount
79
+ next.property += propertyCount
80
+ next.unit += unitCount
81
+ next.lease += leaseCount
82
+ next.tenant += tenantStarts[leaseCount]!
83
+ next.vendor += vendors.length
84
+ next.ticket += ticketCount
85
+ return org
86
+ })
87
+
88
+ return { name, orgs }
89
+ }
90
+
91
+ export type World = ReturnType<typeof buildWorld>
92
+ export type Org = World['orgs'][number]
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "verbatimModuleSyntax": true,
11
+ "isolatedModules": true,
12
+ "skipLibCheck": true,
13
+ "noEmit": true
14
+ },
15
+ "exclude": ["node_modules", "dist"]
16
+ }
@@ -0,0 +1,4 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ // The unit tests next to the config and the seed; none of them needs a database.
4
+ export default defineConfig({ test: { include: ['config/**/*.test.ts', 'seed/**/*.test.ts'] } })
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ .env
@@ -0,0 +1,4 @@
1
+ # `bun run` runs scripts and package bins on Bun even when Node is on PATH, whose `#!/usr/bin/env node` would otherwise
2
+ # start Node.
3
+ [run]
4
+ bun = true
@@ -0,0 +1,30 @@
1
+ import { readdir, readFile } from 'node:fs/promises'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { connect } from './connection'
4
+
5
+ // Applies the migrations not applied yet, the app's and the auth schema's alike, in file name order and in one
6
+ // transaction: when one fails, none is applied and the database stays as the running version expects it. Run it before
7
+ // a new version serves, while nothing else writes. A statement Postgres runs only outside a transaction, such as
8
+ // `create index concurrently`, does not belong in these files.
9
+
10
+ const directory = fileURLToPath(new URL('./migrations/', import.meta.url))
11
+ // Any constant shared by every run of this script: two runs at once take turns.
12
+ const lockKey = 7_311_946_202
13
+
14
+ const sql = connect({ max: 1 })
15
+ const files = (await readdir(directory)).filter((file) => file.endsWith('.sql')).sort()
16
+
17
+ const applied = await sql.begin(async (tx) => {
18
+ await tx`select pg_advisory_xact_lock(${lockKey})`
19
+ await tx`create table if not exists public.schema_migrations (name text primary key, applied_at timestamptz not null default now())`
20
+ const done = new Set((await tx`select name from public.schema_migrations`).map((row) => row.name))
21
+ const pending = files.filter((file) => !done.has(file))
22
+ for (const file of pending) {
23
+ await tx.unsafe(await readFile(directory + file, 'utf8'))
24
+ await tx`insert into public.schema_migrations (name) values (${file})`
25
+ }
26
+ return pending
27
+ })
28
+ for (const file of applied) console.log(`applied ${file}`)
29
+
30
+ await sql.end()
@@ -0,0 +1,36 @@
1
+ -- The auth schema changes this version of Protobase needs, written by `protobase auth migration`.
2
+ -- Applied with the project's other migrations; edit or remove statements the project handles differently.
3
+
4
+ create schema if not exists "auth";
5
+
6
+ create table if not exists "auth"."user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "email_verified" boolean not null, "image" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null, "role" text, "banned" boolean, "ban_reason" text, "ban_expires" timestamptz, "two_factor_enabled" boolean);
7
+
8
+ create table if not exists "auth"."session" ("id" text not null primary key, "expires_at" timestamptz not null, "token" text not null unique, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null, "ip_address" text, "user_agent" text, "user_id" text not null references "auth"."user" ("id") on delete cascade, "impersonated_by" text);
9
+
10
+ create table if not exists "auth"."account" ("id" text not null primary key, "account_id" text not null, "provider_id" text not null, "user_id" text not null references "auth"."user" ("id") on delete cascade, "access_token" text, "refresh_token" text, "id_token" text, "access_token_expires_at" timestamptz, "refresh_token_expires_at" timestamptz, "scope" text, "password" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null);
11
+
12
+ create table if not exists "auth"."verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expires_at" timestamptz not null, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null);
13
+
14
+ create table if not exists "auth"."jwks" ("id" text not null primary key, "public_key" text not null, "private_key" text not null, "created_at" timestamptz not null, "expires_at" timestamptz, "alg" text, "crv" text);
15
+
16
+ create table if not exists "auth"."two_factor" ("id" text not null primary key, "secret" text not null, "backup_codes" text not null, "user_id" text not null references "auth"."user" ("id") on delete cascade, "verified" boolean, "failed_verification_count" integer, "locked_until" timestamptz);
17
+
18
+ create table if not exists "auth"."passkey" ("id" text not null primary key, "name" text, "public_key" text not null, "user_id" text not null references "auth"."user" ("id") on delete cascade, "credential_id" text not null, "counter" integer not null, "device_type" text not null, "backed_up" boolean not null, "transports" text, "created_at" timestamptz, "aaguid" text);
19
+
20
+ create table if not exists "auth"."sign_in_policy" ("id" text not null primary key, "password" text not null, "email_code" text not null, "passkey" text not null, "two_factor" text not null, "staff_access" text not null, "platform_sign_in" text not null, "created_at" timestamptz not null, "created_by" text);
21
+
22
+ create table if not exists "auth"."staff_sign_in" ("id" text not null primary key, "user_id" text not null, "user_email" text not null, "staff" text not null, "staff_name" text, "staff_subject" text not null, "issuer" text not null, "reason" text not null, "session_id" text not null, "started_at" timestamptz not null, "expires_at" timestamptz not null, "ended_at" timestamptz);
23
+
24
+ create index if not exists "session_user_id_idx" on "auth"."session" ("user_id");
25
+
26
+ create index if not exists "account_user_id_idx" on "auth"."account" ("user_id");
27
+
28
+ create index if not exists "verification_identifier_idx" on "auth"."verification" ("identifier");
29
+
30
+ create index if not exists "two_factor_secret_idx" on "auth"."two_factor" ("secret");
31
+
32
+ create index if not exists "two_factor_user_id_idx" on "auth"."two_factor" ("user_id");
33
+
34
+ create index if not exists "passkey_user_id_idx" on "auth"."passkey" ("user_id");
35
+
36
+ create index if not exists "passkey_credential_id_idx" on "auth"."passkey" ("credential_id");
@@ -0,0 +1,109 @@
1
+ -- The auth schema changes this version of Protobase needs, written by `protobase auth migration`.
2
+ -- Applied with the project's other migrations; edit or remove statements the project handles differently.
3
+
4
+ -- Renames the auth store of Protobase 0.6 to snake_case. A store set up with the snake_case names has no "emailVerified"
5
+ -- and is left as it is.
6
+ do $$
7
+ declare
8
+ constraint_row record;
9
+ begin
10
+ if not exists (select from information_schema.columns where table_schema = 'auth' and table_name = 'user' and column_name = 'emailVerified') then
11
+ return;
12
+ end if;
13
+
14
+ alter table "auth"."twoFactor" rename to two_factor;
15
+ alter table "auth"."signInPolicy" rename to sign_in_policy;
16
+ alter table "auth"."staffSignIn" rename to staff_sign_in;
17
+
18
+ alter table "auth"."user" rename column "emailVerified" to email_verified;
19
+ alter table "auth"."user" rename column "createdAt" to created_at;
20
+ alter table "auth"."user" rename column "updatedAt" to updated_at;
21
+ alter table "auth"."user" rename column "banReason" to ban_reason;
22
+ alter table "auth"."user" rename column "banExpires" to ban_expires;
23
+ alter table "auth"."user" rename column "twoFactorEnabled" to two_factor_enabled;
24
+
25
+ alter table "auth".session rename column "expiresAt" to expires_at;
26
+ alter table "auth".session rename column "createdAt" to created_at;
27
+ alter table "auth".session rename column "updatedAt" to updated_at;
28
+ alter table "auth".session rename column "ipAddress" to ip_address;
29
+ alter table "auth".session rename column "userAgent" to user_agent;
30
+ alter table "auth".session rename column "userId" to user_id;
31
+ alter table "auth".session rename column "impersonatedBy" to impersonated_by;
32
+
33
+ alter table "auth".account rename column "accountId" to account_id;
34
+ alter table "auth".account rename column "providerId" to provider_id;
35
+ alter table "auth".account rename column "userId" to user_id;
36
+ alter table "auth".account rename column "accessToken" to access_token;
37
+ alter table "auth".account rename column "refreshToken" to refresh_token;
38
+ alter table "auth".account rename column "idToken" to id_token;
39
+ alter table "auth".account rename column "accessTokenExpiresAt" to access_token_expires_at;
40
+ alter table "auth".account rename column "refreshTokenExpiresAt" to refresh_token_expires_at;
41
+ alter table "auth".account rename column "createdAt" to created_at;
42
+ alter table "auth".account rename column "updatedAt" to updated_at;
43
+
44
+ alter table "auth".verification rename column "expiresAt" to expires_at;
45
+ alter table "auth".verification rename column "createdAt" to created_at;
46
+ alter table "auth".verification rename column "updatedAt" to updated_at;
47
+
48
+ alter table "auth".jwks rename column "publicKey" to public_key;
49
+ alter table "auth".jwks rename column "privateKey" to private_key;
50
+ alter table "auth".jwks rename column "createdAt" to created_at;
51
+ alter table "auth".jwks rename column "expiresAt" to expires_at;
52
+
53
+ alter table "auth".two_factor rename column "backupCodes" to backup_codes;
54
+ alter table "auth".two_factor rename column "userId" to user_id;
55
+ alter table "auth".two_factor rename column "failedVerificationCount" to failed_verification_count;
56
+ alter table "auth".two_factor rename column "lockedUntil" to locked_until;
57
+
58
+ alter table "auth".passkey rename column "publicKey" to public_key;
59
+ alter table "auth".passkey rename column "userId" to user_id;
60
+ alter table "auth".passkey rename column "credentialID" to credential_id;
61
+ alter table "auth".passkey rename column "deviceType" to device_type;
62
+ alter table "auth".passkey rename column "backedUp" to backed_up;
63
+ alter table "auth".passkey rename column "createdAt" to created_at;
64
+
65
+ alter table "auth".sign_in_policy rename column "emailCode" to email_code;
66
+ alter table "auth".sign_in_policy rename column "twoFactor" to two_factor;
67
+ alter table "auth".sign_in_policy rename column "staffAccess" to staff_access;
68
+ alter table "auth".sign_in_policy rename column "platformSignIn" to platform_sign_in;
69
+ alter table "auth".sign_in_policy rename column "createdAt" to created_at;
70
+ alter table "auth".sign_in_policy rename column "createdBy" to created_by;
71
+
72
+ alter table "auth".staff_sign_in rename column "userId" to user_id;
73
+ alter table "auth".staff_sign_in rename column "userEmail" to user_email;
74
+ alter table "auth".staff_sign_in rename column "staffName" to staff_name;
75
+ alter table "auth".staff_sign_in rename column "staffSubject" to staff_subject;
76
+ alter table "auth".staff_sign_in rename column "sessionId" to session_id;
77
+ alter table "auth".staff_sign_in rename column "startedAt" to started_at;
78
+ alter table "auth".staff_sign_in rename column "expiresAt" to expires_at;
79
+ alter table "auth".staff_sign_in rename column "endedAt" to ended_at;
80
+
81
+ -- A primary key's index takes the new name with it.
82
+ alter table "auth".two_factor rename constraint "twoFactor_pkey" to two_factor_pkey;
83
+ alter table "auth".sign_in_policy rename constraint "signInPolicy_pkey" to sign_in_policy_pkey;
84
+ alter table "auth".staff_sign_in rename constraint "staffSignIn_pkey" to staff_sign_in_pkey;
85
+ alter table "auth".session rename constraint "session_userId_fkey" to session_user_id_fkey;
86
+ alter table "auth".account rename constraint "account_userId_fkey" to account_user_id_fkey;
87
+ alter table "auth".two_factor rename constraint "twoFactor_userId_fkey" to two_factor_user_id_fkey;
88
+ alter table "auth".passkey rename constraint "passkey_userId_fkey" to passkey_user_id_fkey;
89
+
90
+ alter index "auth"."session_userId_idx" rename to session_user_id_idx;
91
+ alter index "auth"."account_userId_idx" rename to account_user_id_idx;
92
+ alter index "auth"."twoFactor_secret_idx" rename to two_factor_secret_idx;
93
+ alter index "auth"."twoFactor_userId_idx" rename to two_factor_user_id_idx;
94
+ alter index "auth"."passkey_userId_idx" rename to passkey_user_id_idx;
95
+ alter index "auth"."passkey_credentialID_idx" rename to passkey_credential_id_idx;
96
+
97
+ -- Postgres 18 names each not-null constraint after its table and column, as "user_emailVerified_not_null"; earlier
98
+ -- versions keep no such constraints, so there is nothing to rename there.
99
+ for constraint_row in
100
+ select c.conrelid::regclass as table_name, c.conname as old_name, t.relname || '_' || a.attname || '_not_null' as new_name
101
+ from pg_constraint c
102
+ join pg_class t on t.oid = c.conrelid
103
+ join pg_attribute a on a.attrelid = c.conrelid and a.attnum = c.conkey[1]
104
+ where c.contype = 'n' and t.relnamespace = (select oid from pg_namespace where nspname = 'auth') and c.conname ~ '[A-Z]'
105
+ loop
106
+ execute format('alter table %s rename constraint %I to %I', constraint_row.table_name, constraint_row.old_name, constraint_row.new_name);
107
+ end loop;
108
+ end
109
+ $$;
@@ -0,0 +1,6 @@
1
+ -- The auth schema changes this version of Protobase needs, written by `protobase auth migration`.
2
+ -- Applied with the project's other migrations; edit or remove statements the project handles differently.
3
+
4
+ alter table "auth"."user" add column if not exists "last_role" text;
5
+
6
+ alter table "auth"."session" add column if not exists "active_role" text;
@@ -0,0 +1,4 @@
1
+ -- The auth schema changes this version of Protobase needs, written by `protobase migration`.
2
+ -- Applied with the project's other migrations; edit or remove statements the project handles differently.
3
+
4
+ alter table "auth"."passkey" add column if not exists "rp_id" text;
@@ -0,0 +1 @@
1
+ /// <reference types="node" />
@@ -13,11 +13,11 @@
13
13
  "dev": "protobase dev"
14
14
  },
15
15
  "dependencies": {
16
- "@protobase/cli": ">=0.23.13 <1",
17
- "@protobase/layout": ">=0.23.13 <1",
18
- "@protobase/schema": ">=0.23.13 <1",
19
- "@protobase/server": ">=0.23.13 <1",
20
- "@protobase/ui": ">=0.23.13 <1",
16
+ "@protobase/cli": ">=0.24.0 <1",
17
+ "@protobase/layout": ">=0.24.0 <1",
18
+ "@protobase/schema": ">=0.24.0 <1",
19
+ "@protobase/server": ">=0.24.0 <1",
20
+ "@protobase/ui": ">=0.24.0 <1",
21
21
  "better-auth": "^1.7.7",
22
22
  "jose": "^6.2.12",
23
23
  "kysely": "^0.29.6",
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "verbatimModuleSyntax": true,
11
+ "isolatedModules": true,
12
+ "skipLibCheck": true,
13
+ "noEmit": true
14
+ },
15
+ "exclude": ["node_modules", "dist"]
16
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-protobase",
3
- "version": "0.23.13",
4
- "description": "npm create protobase: starts a new Protobase project from scratch.",
3
+ "version": "0.24.0",
4
+ "description": "npm create protobase: starts a new Protobase project from one of its presets.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
File without changes
File without changes
File without changes
File without changes
File without changes