alepha 0.19.3 → 0.19.4

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 (215) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/audits/index.d.ts +8 -8
  3. package/dist/api/invitations/index.d.ts +790 -0
  4. package/dist/api/invitations/index.d.ts.map +1 -0
  5. package/dist/api/invitations/index.js +665 -0
  6. package/dist/api/invitations/index.js.map +1 -0
  7. package/dist/api/jobs/index.browser.js +8 -9
  8. package/dist/api/jobs/index.browser.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +99 -43
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js +257 -40
  12. package/dist/api/jobs/index.js.map +1 -1
  13. package/dist/api/keys/index.d.ts +5 -5
  14. package/dist/api/notifications/index.browser.js +0 -1
  15. package/dist/api/notifications/index.browser.js.map +1 -1
  16. package/dist/api/notifications/index.d.ts +3 -3
  17. package/dist/api/notifications/index.d.ts.map +1 -1
  18. package/dist/api/notifications/index.js +0 -1
  19. package/dist/api/notifications/index.js.map +1 -1
  20. package/dist/api/parameters/index.browser.js +112 -1
  21. package/dist/api/parameters/index.browser.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +90 -3
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +79 -12
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/{billing → api/payments}/index.d.ts +67 -49
  27. package/dist/api/payments/index.d.ts.map +1 -0
  28. package/dist/{billing → api/payments}/index.js +108 -74
  29. package/dist/api/payments/index.js.map +1 -0
  30. package/dist/api/subscriptions/index.d.ts +1692 -0
  31. package/dist/api/subscriptions/index.d.ts.map +1 -0
  32. package/dist/api/subscriptions/index.js +1870 -0
  33. package/dist/api/subscriptions/index.js.map +1 -0
  34. package/dist/api/users/index.d.ts +18 -2
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +167 -34
  37. package/dist/api/users/index.js.map +1 -1
  38. package/dist/api/verifications/index.d.ts +13 -13
  39. package/dist/api/workflows/index.browser.js +246 -0
  40. package/dist/api/workflows/index.browser.js.map +1 -0
  41. package/dist/api/workflows/index.d.ts +1618 -0
  42. package/dist/api/workflows/index.d.ts.map +1 -0
  43. package/dist/api/workflows/index.js +1504 -0
  44. package/dist/api/workflows/index.js.map +1 -0
  45. package/dist/cli/core/index.d.ts +44 -28
  46. package/dist/cli/core/index.d.ts.map +1 -1
  47. package/dist/cli/core/index.js +16 -61
  48. package/dist/cli/core/index.js.map +1 -1
  49. package/dist/cli/vendor/index.d.ts +31 -8
  50. package/dist/cli/vendor/index.d.ts.map +1 -1
  51. package/dist/cli/vendor/index.js +79 -24
  52. package/dist/cli/vendor/index.js.map +1 -1
  53. package/dist/core/index.browser.js +21 -2
  54. package/dist/core/index.browser.js.map +1 -1
  55. package/dist/core/index.d.ts +33 -2
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js +21 -2
  58. package/dist/core/index.js.map +1 -1
  59. package/dist/core/index.native.js +21 -2
  60. package/dist/core/index.native.js.map +1 -1
  61. package/dist/core/index.workerd.js +21 -2
  62. package/dist/core/index.workerd.js.map +1 -1
  63. package/dist/email/smtp/index.js +24 -8
  64. package/dist/email/smtp/index.js.map +1 -1
  65. package/dist/orm/core/index.browser.js +0 -18
  66. package/dist/orm/core/index.browser.js.map +1 -1
  67. package/dist/orm/core/index.bun.js +0 -17
  68. package/dist/orm/core/index.bun.js.map +1 -1
  69. package/dist/orm/core/index.d.ts +1 -13
  70. package/dist/orm/core/index.d.ts.map +1 -1
  71. package/dist/orm/core/index.js +0 -17
  72. package/dist/orm/core/index.js.map +1 -1
  73. package/dist/orm/postgres/index.bun.js +3 -3
  74. package/dist/orm/postgres/index.bun.js.map +1 -1
  75. package/dist/orm/postgres/index.d.ts.map +1 -1
  76. package/dist/orm/postgres/index.js +3 -3
  77. package/dist/orm/postgres/index.js.map +1 -1
  78. package/dist/react/router/index.browser.js +25 -3
  79. package/dist/react/router/index.browser.js.map +1 -1
  80. package/dist/react/router/index.d.ts +16 -1
  81. package/dist/react/router/index.d.ts.map +1 -1
  82. package/dist/react/router/index.js +25 -3
  83. package/dist/react/router/index.js.map +1 -1
  84. package/dist/security/index.d.ts +28 -0
  85. package/dist/security/index.d.ts.map +1 -1
  86. package/dist/security/index.js +28 -0
  87. package/dist/security/index.js.map +1 -1
  88. package/package.json +37 -20
  89. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  90. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  91. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  92. package/src/api/invitations/entities/invitations.ts +33 -0
  93. package/src/api/invitations/index.ts +65 -0
  94. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  95. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  96. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  97. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  98. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  99. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  100. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  101. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  102. package/src/api/invitations/services/InvitationService.ts +556 -0
  103. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  104. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  105. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  106. package/src/api/jobs/index.ts +0 -3
  107. package/src/api/jobs/primitives/$job.ts +22 -11
  108. package/src/api/jobs/providers/JobProvider.ts +229 -19
  109. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  110. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  111. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  112. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  113. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  114. package/src/api/jobs/services/JobService.ts +51 -12
  115. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  116. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  117. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  118. package/src/api/parameters/index.browser.ts +12 -0
  119. package/src/api/parameters/primitives/$parameter.ts +20 -3
  120. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  121. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  122. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  123. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  124. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  125. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  126. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  127. package/src/{billing → api/payments}/index.ts +31 -25
  128. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  129. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  130. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  131. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  132. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  133. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  134. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  135. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  136. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  137. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  138. package/src/api/subscriptions/index.ts +144 -0
  139. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  140. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  141. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  142. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  143. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  144. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  145. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  146. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  147. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  148. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  149. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  150. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  151. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  152. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  153. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  154. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  155. package/src/api/subscriptions/services/BillingService.ts +437 -0
  156. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  157. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  158. package/src/api/subscriptions/services/UsageService.ts +118 -0
  159. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  160. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  161. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  162. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  163. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  164. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  165. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  166. package/src/api/users/controllers/UserController.ts +3 -8
  167. package/src/api/users/notifications/UserNotifications.ts +23 -0
  168. package/src/api/users/schemas/loginSchema.ts +1 -1
  169. package/src/api/users/services/CredentialService.ts +51 -4
  170. package/src/api/users/services/RegistrationService.ts +38 -9
  171. package/src/api/users/services/SessionService.ts +62 -9
  172. package/src/api/users/services/UserService.ts +21 -12
  173. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  174. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  175. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  176. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  177. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  178. package/src/api/workflows/index.browser.ts +22 -0
  179. package/src/api/workflows/index.ts +124 -0
  180. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  181. package/src/api/workflows/primitives/$workflow.ts +202 -0
  182. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  183. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  184. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  185. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  186. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  187. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  188. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  189. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  190. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  191. package/src/api/workflows/services/WorkflowService.ts +382 -0
  192. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  193. package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
  194. package/src/cli/vendor/services/VendorService.ts +126 -27
  195. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  196. package/src/core/providers/SchemaValidator.ts +1 -1
  197. package/src/core/providers/TypeProvider.ts +46 -3
  198. package/src/orm/__tests__/enums.spec.ts +22 -29
  199. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  200. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  201. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  202. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  203. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  204. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  205. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  206. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  207. package/src/security/primitives/$secure.ts +28 -0
  208. package/dist/billing/index.d.ts.map +0 -1
  209. package/dist/billing/index.js.map +0 -1
  210. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  211. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  212. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  213. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  214. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  215. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -50,6 +50,13 @@ export interface VendorDiffResult {
50
50
  totalChanges: number;
51
51
  }
52
52
 
53
+ /**
54
+ * Shape of the <root>/.alepha/vendor.json lock file.
55
+ */
56
+ export interface VendorLock {
57
+ commit: string;
58
+ }
59
+
53
60
  /**
54
61
  * Handles syncing and diffing vendored packages from a remote git repository.
55
62
  */
@@ -61,29 +68,44 @@ export class VendorService {
61
68
  /**
62
69
  * Sync vendored packages from a remote repository.
63
70
  *
64
- * Shallow-clones the remote once, then:
65
- * - Without `force`: diffs first. If local modifications exist, aborts
66
- * and returns the diff result without touching local files.
67
- * - With `force` (or no changes): removes local copies and replaces them.
71
+ * Without `force`: checks for local modifications by comparing the local
72
+ * copy against the last-synced commit (stored in .alepha/vendor.json).
73
+ * If modifications are found, aborts without touching local files.
74
+ *
75
+ * With `force` (or first sync): replaces local copies unconditionally.
68
76
  */
69
77
  async sync(options: VendorSyncOptions): Promise<VendorSyncResult> {
70
78
  const synced: string[] = [];
71
79
  const errors: string[] = [];
72
- let tmpDir: string | undefined;
73
80
 
74
- try {
75
- tmpDir = await this.cloneRemote(options.remote, options.branch);
81
+ if (!options.force) {
82
+ const lock = await this.readLock(options.root);
83
+
84
+ if (lock) {
85
+ let baselineDir: string | undefined;
86
+ try {
87
+ baselineDir = await this.cloneAtCommit(options.remote, lock.commit);
88
+ const diffResult = await this.diffFromClone(
89
+ options.root,
90
+ baselineDir,
91
+ options.packages,
92
+ );
76
93
 
77
- if (!options.force) {
78
- const diffResult = await this.diffFromClone(
79
- options.root,
80
- tmpDir,
81
- options.packages,
82
- );
83
- if (diffResult.totalChanges > 0) {
84
- return { synced: [], errors: [], aborted: diffResult };
94
+ if (diffResult.totalChanges > 0) {
95
+ return { synced: [], errors: [], aborted: diffResult };
96
+ }
97
+ } finally {
98
+ if (baselineDir) {
99
+ await this.fs.rm(baselineDir, { recursive: true, force: true });
100
+ }
85
101
  }
86
102
  }
103
+ }
104
+
105
+ let tmpDir: string | undefined;
106
+
107
+ try {
108
+ tmpDir = await this.cloneRemote(options.remote, options.branch);
87
109
 
88
110
  for (const pkg of options.packages) {
89
111
  const remotePkgDir = this.fs.join(tmpDir, "packages", pkg);
@@ -103,6 +125,9 @@ export class VendorService {
103
125
 
104
126
  synced.push(pkg);
105
127
  }
128
+
129
+ const commit = await this.getCommitHash(tmpDir);
130
+ await this.writeLock(options.root, { commit });
106
131
  } finally {
107
132
  if (tmpDir) {
108
133
  await this.fs.rm(tmpDir, { recursive: true, force: true });
@@ -113,16 +138,21 @@ export class VendorService {
113
138
  }
114
139
 
115
140
  /**
116
- * Diff vendored packages against a remote repository.
141
+ * Diff vendored packages against the last-synced commit.
117
142
  *
118
- * Shallow-clones the remote, then for each package: recursively compares
119
- * files to identify added, modified, and removed files.
143
+ * Reads the commit hash from .alepha/vendor.json, clones at that commit,
144
+ * and compares local files to detect modifications since last sync.
120
145
  */
121
146
  async diff(options: VendorDiffOptions): Promise<VendorDiffResult> {
147
+ const lock = await this.readLock(options.root);
148
+ if (!lock) {
149
+ return { packages: [], totalChanges: 0 };
150
+ }
151
+
122
152
  let tmpDir: string | undefined;
123
153
 
124
154
  try {
125
- tmpDir = await this.cloneRemote(options.remote, options.branch);
155
+ tmpDir = await this.cloneAtCommit(options.remote, lock.commit);
126
156
  return await this.diffFromClone(options.root, tmpDir, options.packages);
127
157
  } finally {
128
158
  if (tmpDir) {
@@ -155,24 +185,26 @@ export class VendorService {
155
185
  }
156
186
 
157
187
  if (!remoteExists) {
188
+ // No baseline = everything local was added by user
158
189
  const localFiles = await this.fs.ls(localPkgDir, { recursive: true });
159
190
  results.push({
160
191
  name: pkg,
161
- added: [],
192
+ added: localFiles,
162
193
  modified: [],
163
- removed: localFiles,
194
+ removed: [],
164
195
  });
165
196
  totalChanges += localFiles.length;
166
197
  continue;
167
198
  }
168
199
 
169
200
  if (!localExists) {
201
+ // Baseline exists but local doesn't = user deleted everything
170
202
  const remoteFiles = await this.fs.ls(remotePkgDir, { recursive: true });
171
203
  results.push({
172
204
  name: pkg,
173
- added: remoteFiles,
205
+ added: [],
174
206
  modified: [],
175
- removed: [],
207
+ removed: remoteFiles,
176
208
  });
177
209
  totalChanges += remoteFiles.length;
178
210
  continue;
@@ -205,7 +237,8 @@ export class VendorService {
205
237
  if (
206
238
  file.endsWith(".spec.ts") ||
207
239
  file.endsWith(".spec.tsx") ||
208
- file === "LICENSE"
240
+ file === "LICENSE" ||
241
+ file === "tsdown.config.ts"
209
242
  ) {
210
243
  await this.fs.rm(this.fs.join(pkgDir, file), { force: true });
211
244
  }
@@ -255,6 +288,69 @@ export class VendorService {
255
288
  return tmpDir;
256
289
  }
257
290
 
291
+ /**
292
+ * Clone a remote repository at a specific commit hash.
293
+ */
294
+ protected async cloneAtCommit(
295
+ remote: string,
296
+ commit: string,
297
+ ): Promise<string> {
298
+ const tmpDir = this.fs.join(
299
+ process.env.TMPDIR || "/tmp",
300
+ `.alepha-vendor-${Date.now()}`,
301
+ );
302
+
303
+ this.log.debug(`Cloning ${remote}@${commit} into ${tmpDir}`);
304
+
305
+ await this.shell.run(`git init ${tmpDir}`, { capture: true });
306
+ await this.shell.run(`git -C ${tmpDir} remote add origin ${remote}`, {
307
+ capture: true,
308
+ });
309
+ await this.shell.run(`git -C ${tmpDir} fetch --depth 1 origin ${commit}`, {
310
+ capture: true,
311
+ });
312
+ await this.shell.run(`git -C ${tmpDir} checkout FETCH_HEAD`, {
313
+ capture: true,
314
+ });
315
+
316
+ return tmpDir;
317
+ }
318
+
319
+ /**
320
+ * Get the HEAD commit hash from a cloned repository.
321
+ */
322
+ protected async getCommitHash(repoDir: string): Promise<string> {
323
+ const hash = await this.shell.run(`git -C ${repoDir} rev-parse HEAD`, {
324
+ capture: true,
325
+ });
326
+ return hash.trim();
327
+ }
328
+
329
+ /**
330
+ * Read the vendor lock file.
331
+ */
332
+ protected async readLock(root: string): Promise<VendorLock | undefined> {
333
+ const lockPath = this.fs.join(root, ".alepha", "vendor.json");
334
+ const exists = await this.fs.exists(lockPath);
335
+ if (!exists) {
336
+ return undefined;
337
+ }
338
+ const content = await this.fs.readFile(lockPath);
339
+ return JSON.parse(content.toString());
340
+ }
341
+
342
+ /**
343
+ * Write the vendor lock file.
344
+ */
345
+ protected async writeLock(root: string, lock: VendorLock): Promise<void> {
346
+ const dir = this.fs.join(root, ".alepha");
347
+ await this.fs.mkdir(dir, { recursive: true });
348
+ await this.fs.writeFile(
349
+ this.fs.join(dir, "vendor.json"),
350
+ JSON.stringify(lock, null, 2),
351
+ );
352
+ }
353
+
258
354
  /**
259
355
  * Directories to ignore during diff comparisons.
260
356
  */
@@ -272,7 +368,8 @@ export class VendorService {
272
368
  if (
273
369
  filePath.endsWith(".spec.ts") ||
274
370
  filePath.endsWith(".spec.tsx") ||
275
- filePath === "LICENSE"
371
+ filePath === "LICENSE" ||
372
+ filePath === "tsdown.config.ts"
276
373
  ) {
277
374
  return true;
278
375
  }
@@ -307,9 +404,10 @@ export class VendorService {
307
404
  const localSet = new Set(filteredLocal);
308
405
  const remoteSet = new Set(filteredRemote);
309
406
 
407
+ // Files in baseline but not local = user deleted them
310
408
  for (const file of filteredRemote) {
311
409
  if (!localSet.has(file)) {
312
- added.push(file);
410
+ removed.push(file);
313
411
  continue;
314
412
  }
315
413
 
@@ -327,9 +425,10 @@ export class VendorService {
327
425
  }
328
426
  }
329
427
 
428
+ // Files in local but not baseline = user added them
330
429
  for (const file of filteredLocal) {
331
430
  if (!remoteSet.has(file)) {
332
- removed.push(file);
431
+ added.push(file);
333
432
  }
334
433
  }
335
434
 
@@ -850,12 +850,14 @@ describe("TypeProvider", () => {
850
850
  ).toEqual(["valid", "123", "also valid"]);
851
851
  });
852
852
 
853
- it("should reject non-array values", async () => {
853
+ it("should auto-cast non-array values to single-element array", async () => {
854
854
  const alepha = Alepha.create();
855
855
  await alepha.start();
856
856
  const schema = t.array(t.text());
857
857
 
858
- expect(() => alepha.codec.validate(schema, "not an array")).toThrow();
858
+ expect(alepha.codec.validate(schema, "not an array")).toEqual([
859
+ "not an array",
860
+ ]);
859
861
  });
860
862
 
861
863
  it("should support array of objects", async () => {
@@ -27,7 +27,7 @@ export class SchemaValidator {
27
27
  try {
28
28
  //
29
29
  if (!this.useEval) {
30
- return Value.Parse(schema, newValue);
30
+ return Value.Parse(schema, newValue) as Static<T>;
31
31
  }
32
32
  return this.getValidator(schema).Parse(newValue) as Static<T>;
33
33
  } catch (error: any) {
@@ -458,18 +458,43 @@ export class TypeProvider {
458
458
 
459
459
  /**
460
460
  * Create a schema for a string enum.
461
+ *
462
+ * By default, this creates a real PostgreSQL ENUM type in the database.
463
+ * Use `{ mode: "text" }` to store as a TEXT column instead.
464
+ *
465
+ * @example
466
+ * ```ts
467
+ * // PostgreSQL ENUM type (default)
468
+ * status: t.enum(["pending", "active", "archived"])
469
+ *
470
+ * // TEXT column
471
+ * status: t.enum(["pending", "active", "archived"], { mode: "text" })
472
+ *
473
+ * // Shared enum name across tables
474
+ * status: t.enum(["enabled", "disabled"], { name: "shared_status_enum" })
475
+ * ```
461
476
  */
462
477
  public enum<T extends string[]>(
463
478
  values: [...T],
464
- options?: TTextOptions,
479
+ options?: TEnumOptions,
465
480
  ): TUnsafe<T[number]> {
466
- return Type.Unsafe<T[number]>(
481
+ const { mode, name, ...textOptions } = options ?? {};
482
+
483
+ const schema = Type.Unsafe<T[number]>(
467
484
  t.text({
468
485
  enum: values,
469
486
  pattern: values.map((v) => `^${v}$`).join("|"),
470
- ...options,
487
+ ...textOptions,
471
488
  }),
472
489
  );
490
+
491
+ if (mode === "text") {
492
+ Object.assign(schema, { mode: "text" });
493
+ } else {
494
+ Object.assign(schema, { enumName: name });
495
+ }
496
+
497
+ return schema;
473
498
  }
474
499
 
475
500
  // -------------------------------------------------------------------------------------------------------------------
@@ -666,6 +691,24 @@ export class TypeProvider {
666
691
 
667
692
  export type TextLength = "short" | "regular" | "long" | "rich";
668
693
 
694
+ export interface TEnumOptions extends TTextOptions {
695
+ /**
696
+ * Storage mode for the enum.
697
+ *
698
+ * - `"text"` — stores as a TEXT column in the database.
699
+ * - When omitted, creates a real PostgreSQL ENUM type.
700
+ */
701
+ mode?: "text";
702
+
703
+ /**
704
+ * Custom name for the PostgreSQL ENUM type.
705
+ *
706
+ * Use this to share the same enum type across multiple tables.
707
+ * If not specified, the name is auto-generated from the table and column names.
708
+ */
709
+ name?: string;
710
+ }
711
+
669
712
  export interface TTextOptions extends TStringOptions {
670
713
  /**
671
714
  * Predefined size of the text.
@@ -3,33 +3,33 @@ import { describe, expect, it } from "vitest";
3
3
  import { $entity, $repository, DrizzleKitProvider, db } from "../core/index.ts";
4
4
  import { AlephaOrmPostgres } from "../postgres/index.ts";
5
5
 
6
- // Test 1: Basic enum using t.enum (should map to TEXT column)
6
+ // Test 1: t.enum with mode: "text" (should map to TEXT column)
7
7
  const textEnumEntity = $entity({
8
8
  name: "text_enum_test",
9
9
  schema: t.object({
10
10
  id: db.primaryKey(),
11
- status: t.enum(["pending", "active", "archived"]),
12
- role: t.enum(["user", "admin", "moderator"]),
11
+ status: t.enum(["pending", "active", "archived"], { mode: "text" }),
12
+ role: t.enum(["user", "admin", "moderator"], { mode: "text" }),
13
13
  }),
14
14
  });
15
15
 
16
- // Test 2: Basic enum using db.enum (should create real PG ENUM type)
16
+ // Test 2: t.enum without mode (should create real PG ENUM type)
17
17
  const pgEnumEntity = $entity({
18
18
  name: "pg_enum_test",
19
19
  schema: t.object({
20
20
  id: db.primaryKey(),
21
- status: db.enum(["draft", "published", "deleted"]),
22
- priority: db.enum(["low", "medium", "high"]),
21
+ status: t.enum(["draft", "published", "deleted"]),
22
+ priority: t.enum(["low", "medium", "high"]),
23
23
  }),
24
24
  });
25
25
 
26
- // Test 3: Mixed t.enum and db.enum in the same table
26
+ // Test 3: Mixed text and pg enum in the same table
27
27
  const mixedEnumEntity = $entity({
28
28
  name: "mixed_enum_test",
29
29
  schema: t.object({
30
30
  id: db.primaryKey(),
31
- textStatus: t.enum(["open", "closed"]),
32
- pgStatus: db.enum(["new", "in_progress", "done"]),
31
+ textStatus: t.enum(["open", "closed"], { mode: "text" }),
32
+ pgStatus: t.enum(["new", "in_progress", "done"]),
33
33
  }),
34
34
  });
35
35
 
@@ -38,7 +38,7 @@ const sharedEnumEntity1 = $entity({
38
38
  name: "shared_enum_test_1",
39
39
  schema: t.object({
40
40
  id: db.primaryKey(),
41
- status: db.enum(["enabled", "disabled"], { name: "shared_status_enum" }),
41
+ status: t.enum(["enabled", "disabled"], { name: "shared_status_enum" }),
42
42
  }),
43
43
  });
44
44
 
@@ -46,7 +46,7 @@ const sharedEnumEntity2 = $entity({
46
46
  name: "shared_enum_test_2",
47
47
  schema: t.object({
48
48
  id: db.primaryKey(),
49
- status: db.enum(["enabled", "disabled"], { name: "shared_status_enum" }),
49
+ status: t.enum(["enabled", "disabled"], { name: "shared_status_enum" }),
50
50
  }),
51
51
  });
52
52
 
@@ -55,7 +55,7 @@ const conflictEnumEntity1 = $entity({
55
55
  name: "conflict_enum_test_1",
56
56
  schema: t.object({
57
57
  id: db.primaryKey(),
58
- status: db.enum(["a", "b", "c"], { name: "conflict_status_enum" }),
58
+ status: t.enum(["a", "b", "c"], { name: "conflict_status_enum" }),
59
59
  }),
60
60
  });
61
61
 
@@ -63,12 +63,12 @@ const conflictEnumEntity2 = $entity({
63
63
  name: "conflict_enum_test_2",
64
64
  schema: t.object({
65
65
  id: db.primaryKey(),
66
- status: db.enum(["a", "b", "d"], { name: "conflict_status_enum" }), // Different value!
66
+ status: t.enum(["a", "b", "d"], { name: "conflict_status_enum" }), // Different value!
67
67
  }),
68
68
  });
69
69
 
70
- describe("enums - t.enum (TEXT column)", () => {
71
- it("should create TEXT columns for t.enum fields", async () => {
70
+ describe("enums - t.enum with mode: 'text' (TEXT column)", () => {
71
+ it("should create TEXT columns for t.enum fields with mode: 'text'", async () => {
72
72
  const alepha = Alepha.create().with(AlephaOrmPostgres);
73
73
 
74
74
  class App {
@@ -87,12 +87,12 @@ describe("enums - t.enum (TEXT column)", () => {
87
87
 
88
88
  expect(sql).toContainEqual(expect.stringContaining("CREATE TABLE"));
89
89
  expect(sql).toContainEqual(expect.stringContaining("text_enum_test"));
90
- // t.enum should map to TEXT, not a real ENUM type
90
+ // mode: "text" should map to TEXT, not a real ENUM type
91
91
  expect(sql.some((s) => s.includes('"status" text'))).toBe(true);
92
92
  expect(sql.some((s) => s.includes('"role" text'))).toBe(true);
93
93
  });
94
94
 
95
- it("should allow inserting and querying with t.enum values", async () => {
95
+ it("should allow inserting and querying with text enum values", async () => {
96
96
  const alepha = Alepha.create().with(AlephaOrmPostgres);
97
97
 
98
98
  class App {
@@ -117,8 +117,8 @@ describe("enums - t.enum (TEXT column)", () => {
117
117
  });
118
118
  });
119
119
 
120
- describe("enums - db.enum (real PG ENUM type)", () => {
121
- it("should create real PostgreSQL ENUM types for db.enum fields", async () => {
120
+ describe("enums - t.enum (real PG ENUM type)", () => {
121
+ it("should create real PostgreSQL ENUM types for t.enum fields", async () => {
122
122
  const alepha = Alepha.create().with(AlephaOrmPostgres);
123
123
 
124
124
  class App {
@@ -137,13 +137,13 @@ describe("enums - db.enum (real PG ENUM type)", () => {
137
137
 
138
138
  expect(sql).toContainEqual(expect.stringContaining("CREATE TABLE"));
139
139
  expect(sql).toContainEqual(expect.stringContaining("pg_enum_test"));
140
- // db.enum should create real ENUM types
140
+ // t.enum without mode should create real ENUM types
141
141
  expect(
142
142
  sql.some((s) => s.includes("CREATE TYPE") || s.includes("DO $$ BEGIN")),
143
143
  ).toBe(true);
144
144
  });
145
145
 
146
- it("should allow inserting and querying with db.enum values", async () => {
146
+ it("should allow inserting and querying with pg enum values", async () => {
147
147
  const alepha = Alepha.create().with(AlephaOrmPostgres);
148
148
 
149
149
  class App {
@@ -168,7 +168,7 @@ describe("enums - db.enum (real PG ENUM type)", () => {
168
168
  });
169
169
  });
170
170
 
171
- describe("enums - mixed t.enum and db.enum in same table", () => {
171
+ describe("enums - mixed text and pg enum in same table", () => {
172
172
  it("should handle both TEXT and PG ENUM types in the same table", async () => {
173
173
  const alepha = Alepha.create().with(AlephaOrmPostgres);
174
174
 
@@ -299,13 +299,6 @@ describe("enums - conflict detection with different values", () => {
299
299
  it("should throw error when same enum name has different values", async () => {
300
300
  const alepha = Alepha.create().with(AlephaOrmPostgres);
301
301
 
302
- const load = async () => {
303
- return alepha.with(() => ({
304
- r1: $repository(conflictEnumEntity1),
305
- r2: $repository(conflictEnumEntity2),
306
- }));
307
- };
308
-
309
302
  expect(() =>
310
303
  alepha.with(() => ({
311
304
  r1: $repository(conflictEnumEntity1),