alepha 0.20.2 → 0.20.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 (304) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.js +49 -0
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +2 -61
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/keys/index.d.ts +4 -4
  13. package/dist/api/keys/index.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +1 -10
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/parameters/index.browser.js +37 -0
  17. package/dist/api/parameters/index.browser.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +12 -68
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/parameters/index.js +57 -4
  21. package/dist/api/parameters/index.js.map +1 -1
  22. package/dist/api/payments/index.js.map +1 -1
  23. package/dist/api/users/index.browser.js +6 -0
  24. package/dist/api/users/index.browser.js.map +1 -1
  25. package/dist/api/users/index.d.ts +148 -227
  26. package/dist/api/users/index.d.ts.map +1 -1
  27. package/dist/api/users/index.js +60 -14
  28. package/dist/api/users/index.js.map +1 -1
  29. package/dist/api/verifications/index.d.ts.map +1 -1
  30. package/dist/api/verifications/index.js +2 -1
  31. package/dist/api/verifications/index.js.map +1 -1
  32. package/dist/bucket/index.d.ts +77 -107
  33. package/dist/bucket/index.d.ts.map +1 -1
  34. package/dist/bucket/index.js +153 -5
  35. package/dist/bucket/index.js.map +1 -1
  36. package/dist/bucket/index.workerd.js +12 -2
  37. package/dist/bucket/index.workerd.js.map +1 -1
  38. package/dist/cache/core/index.d.ts +26 -0
  39. package/dist/cache/core/index.d.ts.map +1 -1
  40. package/dist/cache/core/index.js +11 -1
  41. package/dist/cache/core/index.js.map +1 -1
  42. package/dist/cache/core/index.workerd.js +11 -1
  43. package/dist/cache/core/index.workerd.js.map +1 -1
  44. package/dist/captcha/index.js.map +1 -1
  45. package/dist/cli/config/index.d.ts +7 -5
  46. package/dist/cli/config/index.d.ts.map +1 -1
  47. package/dist/cli/config/index.js +2 -3
  48. package/dist/cli/config/index.js.map +1 -1
  49. package/dist/cli/core/index.d.ts +637 -11660
  50. package/dist/cli/core/index.d.ts.map +1 -1
  51. package/dist/cli/core/index.js +707 -532
  52. package/dist/cli/core/index.js.map +1 -1
  53. package/dist/cli/devtools/index.d.ts +4 -8
  54. package/dist/cli/devtools/index.d.ts.map +1 -1
  55. package/dist/cli/devtools/index.js +20 -16
  56. package/dist/cli/devtools/index.js.map +1 -1
  57. package/dist/cli/platform/index.d.ts +51 -77
  58. package/dist/cli/platform/index.d.ts.map +1 -1
  59. package/dist/cli/platform/index.js +65 -15
  60. package/dist/cli/platform/index.js.map +1 -1
  61. package/dist/cli/vendor/index.d.ts +10 -13
  62. package/dist/cli/vendor/index.d.ts.map +1 -1
  63. package/dist/cli/vendor/index.js +30 -12
  64. package/dist/cli/vendor/index.js.map +1 -1
  65. package/dist/command/index.js +1 -1
  66. package/dist/command/index.js.map +1 -1
  67. package/dist/core/index.browser.js +27 -3
  68. package/dist/core/index.browser.js.map +1 -1
  69. package/dist/core/index.d.ts +8 -11
  70. package/dist/core/index.d.ts.map +1 -1
  71. package/dist/core/index.js +27 -3
  72. package/dist/core/index.js.map +1 -1
  73. package/dist/core/index.native.js +27 -3
  74. package/dist/core/index.native.js.map +1 -1
  75. package/dist/core/index.workerd.js +27 -3
  76. package/dist/core/index.workerd.js.map +1 -1
  77. package/dist/crypto/index.js.map +1 -1
  78. package/dist/datetime/index.d.ts +69 -10
  79. package/dist/datetime/index.d.ts.map +1 -1
  80. package/dist/datetime/index.js +135 -13
  81. package/dist/datetime/index.js.map +1 -1
  82. package/dist/email/core/index.js.map +1 -1
  83. package/dist/email/smtp/index.js +130 -16
  84. package/dist/email/smtp/index.js.map +1 -1
  85. package/dist/fake/index.js.map +1 -1
  86. package/dist/lock/core/index.d.ts +30 -2
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/lock/core/index.js +35 -12
  89. package/dist/lock/core/index.js.map +1 -1
  90. package/dist/lock/redis/index.js.map +1 -1
  91. package/dist/logger/index.js +32 -1
  92. package/dist/logger/index.js.map +1 -1
  93. package/dist/mcp/index.d.ts +238 -31
  94. package/dist/mcp/index.d.ts.map +1 -1
  95. package/dist/mcp/index.js +198 -67
  96. package/dist/mcp/index.js.map +1 -1
  97. package/dist/orm/core/index.browser.js +2 -362
  98. package/dist/orm/core/index.browser.js.map +1 -1
  99. package/dist/orm/core/index.bun.js +18 -409
  100. package/dist/orm/core/index.bun.js.map +1 -1
  101. package/dist/orm/core/index.d.ts +41 -194
  102. package/dist/orm/core/index.d.ts.map +1 -1
  103. package/dist/orm/core/index.js +27 -422
  104. package/dist/orm/core/index.js.map +1 -1
  105. package/dist/orm/postgres/index.bun.js +17 -20
  106. package/dist/orm/postgres/index.bun.js.map +1 -1
  107. package/dist/orm/postgres/index.d.ts +1 -5
  108. package/dist/orm/postgres/index.d.ts.map +1 -1
  109. package/dist/orm/postgres/index.js +17 -20
  110. package/dist/orm/postgres/index.js.map +1 -1
  111. package/dist/react/core/index.d.ts +102 -1
  112. package/dist/react/core/index.d.ts.map +1 -1
  113. package/dist/react/core/index.js +65 -1
  114. package/dist/react/core/index.js.map +1 -1
  115. package/dist/react/form/index.d.ts +6 -0
  116. package/dist/react/form/index.d.ts.map +1 -1
  117. package/dist/react/form/index.js +7 -7
  118. package/dist/react/form/index.js.map +1 -1
  119. package/dist/react/i18n/index.d.ts +7 -1
  120. package/dist/react/i18n/index.d.ts.map +1 -1
  121. package/dist/react/i18n/index.js +6 -0
  122. package/dist/react/i18n/index.js.map +1 -1
  123. package/dist/react/intro/index.js +22 -17
  124. package/dist/react/intro/index.js.map +1 -1
  125. package/dist/react/router/index.browser.js +98 -4
  126. package/dist/react/router/index.browser.js.map +1 -1
  127. package/dist/react/router/index.d.ts +58 -5
  128. package/dist/react/router/index.d.ts.map +1 -1
  129. package/dist/react/router/index.js +122 -6
  130. package/dist/react/router/index.js.map +1 -1
  131. package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
  132. package/dist/react/testing/index.js +1 -1
  133. package/dist/react/testing/index.js.map +1 -1
  134. package/dist/react/ui/index.d.ts +195 -1
  135. package/dist/react/ui/index.d.ts.map +1 -1
  136. package/dist/react/ui/index.js +64 -1
  137. package/dist/react/ui/index.js.map +1 -1
  138. package/dist/react/websocket/index.js.map +1 -1
  139. package/dist/redis/index.js.map +1 -1
  140. package/dist/scheduler/index.d.ts +1 -2
  141. package/dist/scheduler/index.d.ts.map +1 -1
  142. package/dist/scheduler/index.js +1 -1
  143. package/dist/scheduler/index.js.map +1 -1
  144. package/dist/scheduler/index.workerd.js +1 -1
  145. package/dist/scheduler/index.workerd.js.map +1 -1
  146. package/dist/security/index.browser.js.map +1 -1
  147. package/dist/security/index.d.ts.map +1 -1
  148. package/dist/security/index.js +2 -2
  149. package/dist/security/index.js.map +1 -1
  150. package/dist/server/auth/index.d.ts.map +1 -1
  151. package/dist/server/auth/index.js +24 -10
  152. package/dist/server/auth/index.js.map +1 -1
  153. package/dist/server/cookies/index.js.map +1 -1
  154. package/dist/server/core/index.browser.js +10 -3
  155. package/dist/server/core/index.browser.js.map +1 -1
  156. package/dist/server/core/index.d.ts +1 -4
  157. package/dist/server/core/index.d.ts.map +1 -1
  158. package/dist/server/core/index.js +47 -9
  159. package/dist/server/core/index.js.map +1 -1
  160. package/dist/server/links/index.browser.js.map +1 -1
  161. package/dist/server/links/index.js.map +1 -1
  162. package/dist/server/metrics/index.js +19 -1
  163. package/dist/server/metrics/index.js.map +1 -1
  164. package/dist/server/rate-limit/index.js.map +1 -1
  165. package/dist/server/static/index.js.map +1 -1
  166. package/dist/server/swagger/index.d.ts.map +1 -1
  167. package/dist/server/swagger/index.js +4 -5
  168. package/dist/server/swagger/index.js.map +1 -1
  169. package/dist/sms/index.js.map +1 -1
  170. package/dist/system/index.browser.js.map +1 -1
  171. package/dist/system/index.js.map +1 -1
  172. package/dist/system/index.workerd.js.map +1 -1
  173. package/dist/topic/core/index.js.map +1 -1
  174. package/dist/websocket/index.browser.js +32 -5
  175. package/dist/websocket/index.browser.js.map +1 -1
  176. package/dist/websocket/index.d.ts +3 -1
  177. package/dist/websocket/index.d.ts.map +1 -1
  178. package/dist/websocket/index.js +42 -6
  179. package/dist/websocket/index.js.map +1 -1
  180. package/package.json +685 -274
  181. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  182. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  183. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  184. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  185. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  186. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  187. package/src/api/users/entities/sessions.ts +6 -0
  188. package/src/api/users/jobs/UserJobs.ts +44 -17
  189. package/src/api/users/providers/RealmProvider.ts +4 -0
  190. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  191. package/src/api/users/services/SessionService.ts +27 -0
  192. package/src/api/users/services/UserService.ts +1 -5
  193. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  194. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  195. package/src/api/verifications/services/VerificationService.ts +1 -0
  196. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  197. package/src/bucket/index.ts +19 -2
  198. package/src/bucket/primitives/$bucket.ts +9 -1
  199. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  200. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  201. package/src/cache/core/index.ts +29 -0
  202. package/src/cache/core/primitives/$cache.ts +14 -1
  203. package/src/cli/config/defineConfig.ts +13 -15
  204. package/src/cli/core/__tests__/init.spec.ts +214 -7
  205. package/src/cli/core/commands/init.ts +12 -0
  206. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  207. package/src/cli/core/services/ProjectScaffolder.ts +315 -33
  208. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  209. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  210. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  211. package/src/cli/core/templates/agentMd.ts +2 -10
  212. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  213. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  214. package/src/cli/core/templates/mainCss.ts +1 -0
  215. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  216. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  217. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  218. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  219. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  220. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  221. package/src/cli/core/templates/webIndexTs.ts +23 -1
  222. package/src/cli/devtools/index.ts +12 -26
  223. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  224. package/src/cli/platform/index.ts +15 -24
  225. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  226. package/src/cli/vendor/index.ts +14 -23
  227. package/src/command/providers/CliProvider.ts +1 -1
  228. package/src/core/Alepha.ts +11 -1
  229. package/src/core/helpers/ref.ts +18 -0
  230. package/src/core/index.shared.ts +1 -0
  231. package/src/core/interfaces/Service.ts +3 -1
  232. package/src/core/providers/SchemaValidator.ts +9 -1
  233. package/src/core/providers/TypeProvider.ts +2 -3
  234. package/src/datetime/REFACTORING.md +118 -0
  235. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  236. package/src/lock/core/index.ts +31 -0
  237. package/src/lock/core/primitives/$lock.ts +14 -1
  238. package/src/logger/services/Logger.ts +1 -1
  239. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  240. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  241. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  242. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  243. package/src/mcp/helpers/jsonrpc.ts +26 -1
  244. package/src/mcp/index.ts +10 -5
  245. package/src/mcp/interfaces/McpTypes.ts +83 -6
  246. package/src/mcp/primitives/$prompt.ts +18 -1
  247. package/src/mcp/primitives/$resource.ts +18 -1
  248. package/src/mcp/primitives/$tool.ts +83 -7
  249. package/src/mcp/providers/McpServerProvider.ts +74 -16
  250. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  251. package/src/orm/REFACTORING.md +330 -0
  252. package/src/orm/__tests__/$repository-tests.ts +1 -0
  253. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  254. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  255. package/src/orm/core/index.shared.ts +0 -2
  256. package/src/orm/core/index.ts +1 -2
  257. package/src/orm/core/primitives/$repository.ts +3 -6
  258. package/src/orm/core/primitives/$transactional.ts +11 -0
  259. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  260. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  261. package/src/orm/core/schemas/updateSchema.ts +1 -1
  262. package/src/orm/core/services/ModelBuilder.ts +1 -13
  263. package/src/orm/core/services/PgRelationManager.ts +4 -2
  264. package/src/orm/core/services/Repository.ts +1 -42
  265. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  266. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  267. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  268. package/src/react/core/hooks/useQuery.ts +153 -0
  269. package/src/react/core/index.ts +1 -0
  270. package/src/react/form/services/FormModel.ts +15 -6
  271. package/src/react/form/services/parseField.ts +8 -0
  272. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  273. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  274. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  275. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  276. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  277. package/src/react/router/primitives/$page.ts +28 -4
  278. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  279. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  280. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  281. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  282. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  283. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  284. package/src/react/ui/index.ts +6 -0
  285. package/src/react/ui/services/SchemaControl.ts +209 -0
  286. package/src/scheduler/providers/CronProvider.ts +1 -1
  287. package/src/security/primitives/$basicAuth.ts +1 -1
  288. package/src/security/primitives/$issuer.ts +6 -3
  289. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  290. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  291. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  292. package/src/server/core/errors/ValidationError.ts +13 -1
  293. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  294. package/src/server/core/primitives/$action.ts +16 -5
  295. package/src/server/core/providers/ServerProvider.ts +1 -1
  296. package/src/server/core/providers/ServerRouterProvider.ts +28 -6
  297. package/src/server/core/services/HttpClient.ts +1 -1
  298. package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
  299. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  300. package/src/websocket/services/WebSocketClient.ts +11 -5
  301. package/src/mcp/transports/SseMcpTransport.ts +0 -182
  302. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  303. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  304. package/src/orm/core/primitives/$view.ts +0 -88
@@ -1,502 +0,0 @@
1
- import { AlephaError, type TObject } from "alepha";
2
- import type { PgQueryWhere } from "../interfaces/PgQueryWhere.ts";
3
-
4
- /**
5
- * Parse a string query into a PgQueryWhere object.
6
- *
7
- * Supported syntax:
8
- * - Simple equality: "name=John"
9
- * - Wildcard patterns: "name=John*" (startsWith), "name=*John" (endsWith), "name=*John*" (contains)
10
- * - Operators: "age>18", "age>=18", "age<65", "age<=65", "status!=active"
11
- * - NULL checks: "deletedAt=null", "email!=null"
12
- * - IN arrays: "status=[pending,active]"
13
- * - AND conditions: "name=John&age>18"
14
- * - OR conditions: "name=John|email=john@example.com"
15
- * - Nested AND/OR: "(name=John|name=Jane)&age>18"
16
- * - JSONB nested: "profile.city=Paris"
17
- *
18
- * @example
19
- * ```ts
20
- * // Simple equality
21
- * parseQueryString("name=John")
22
- * // => { name: { eq: "John" } }
23
- *
24
- * // Wildcard patterns
25
- * parseQueryString("name=John*") // startsWith
26
- * // => { name: { startsWith: "John" } }
27
- * parseQueryString("name=*Smith") // endsWith
28
- * // => { name: { endsWith: "Smith" } }
29
- * parseQueryString("name=*oh*") // contains
30
- * // => { name: { contains: "oh" } }
31
- *
32
- * // Multiple conditions
33
- * parseQueryString("name=John&age>18")
34
- * // => { and: [{ name: { eq: "John" } }, { age: { gt: 18 } }] }
35
- *
36
- * // OR conditions
37
- * parseQueryString("status=active|status=pending")
38
- * // => { or: [{ status: { eq: "active" } }, { status: { eq: "pending" } }] }
39
- *
40
- * // Complex nested
41
- * parseQueryString("(name=John|name=Jane)&age>18&status!=archived")
42
- * // => { and: [
43
- * // { or: [{ name: { eq: "John" } }, { name: { eq: "Jane" } }] },
44
- * // { age: { gt: 18 } },
45
- * // { status: { ne: "archived" } }
46
- * // ] }
47
- *
48
- * // JSONB nested query
49
- * parseQueryString("profile.city=Paris&profile.age>25")
50
- * // => { profile: { city: { eq: "Paris" }, age: { gt: 25 } } }
51
- * ```
52
- */
53
- export function parseQueryString<T extends TObject>(
54
- query: string,
55
- ): PgQueryWhere<T> {
56
- if (!query || query.trim() === "") {
57
- return {};
58
- }
59
-
60
- const parser = new QueryStringParser(query);
61
- return parser.parse() as PgQueryWhere<T>;
62
- }
63
-
64
- // ---------------------------------------------------------------------------------------------------------------------
65
-
66
- class QueryStringParser {
67
- protected pos = 0;
68
- protected readonly query: string;
69
-
70
- constructor(query: string) {
71
- this.query = query.trim();
72
- }
73
-
74
- parse(): PgQueryWhere<any> {
75
- return this.parseExpression();
76
- }
77
-
78
- protected parseExpression(): PgQueryWhere<any> {
79
- return this.parseOr();
80
- }
81
-
82
- protected parseOr(): any {
83
- const left = this.parseAnd();
84
-
85
- // Check for OR operator (|)
86
- if (this.peek() === "|") {
87
- const conditions = [left];
88
-
89
- while (this.peek() === "|") {
90
- this.consume("|");
91
- conditions.push(this.parseAnd());
92
- }
93
-
94
- return { or: conditions };
95
- }
96
-
97
- return left;
98
- }
99
-
100
- protected parseAnd(): any {
101
- const left = this.parsePrimary();
102
-
103
- // Check for AND operator (&)
104
- if (this.peek() === "&") {
105
- const conditions = [left];
106
-
107
- while (this.peek() === "&") {
108
- this.consume("&");
109
- conditions.push(this.parsePrimary());
110
- }
111
-
112
- return { and: conditions };
113
- }
114
-
115
- return left;
116
- }
117
-
118
- protected parsePrimary(): any {
119
- this.skipWhitespace();
120
-
121
- // Handle parentheses
122
- if (this.peek() === "(") {
123
- this.consume("(");
124
- const expr = this.parseExpression();
125
- this.consume(")");
126
- return expr;
127
- }
128
-
129
- // Parse field condition
130
- return this.parseCondition();
131
- }
132
-
133
- protected parseCondition(): any {
134
- const field = this.parseFieldPath();
135
- this.skipWhitespace();
136
-
137
- // Get operator
138
- const operator = this.parseOperator();
139
- this.skipWhitespace();
140
-
141
- // Get value
142
- const value = this.parseValue();
143
-
144
- if (value === "") {
145
- throw new AlephaError(`Expected value for field '${field.join(".")}'`);
146
- }
147
-
148
- // Build the condition object
149
- return this.buildCondition(field, operator, value);
150
- }
151
-
152
- protected parseFieldPath(): string[] {
153
- const path: string[] = [];
154
- let current = "";
155
-
156
- while (this.pos < this.query.length) {
157
- const ch = this.query[this.pos];
158
-
159
- if (ch === "." && current) {
160
- path.push(current);
161
- current = "";
162
- this.pos++;
163
- continue;
164
- }
165
-
166
- if (ch === "=" || ch === "!" || ch === ">" || ch === "<" || ch === " ") {
167
- break;
168
- }
169
-
170
- current += ch;
171
- this.pos++;
172
- }
173
-
174
- if (current) {
175
- path.push(current);
176
- }
177
-
178
- return path;
179
- }
180
-
181
- protected parseOperator(): string {
182
- this.skipWhitespace();
183
-
184
- const remaining = this.query.slice(this.pos);
185
-
186
- // Two-character operators
187
- if (remaining.startsWith(">=")) {
188
- this.pos += 2;
189
- return ">=";
190
- }
191
- if (remaining.startsWith("<=")) {
192
- this.pos += 2;
193
- return "<=";
194
- }
195
- if (remaining.startsWith("!=")) {
196
- this.pos += 2;
197
- return "!=";
198
- }
199
-
200
- // Single-character operators
201
- const ch = this.query[this.pos];
202
- if (ch === "=" || ch === ">" || ch === "<") {
203
- this.pos++;
204
- return ch;
205
- }
206
-
207
- throw new AlephaError(`Expected operator at position ${this.pos}`);
208
- }
209
-
210
- protected parseValue(): any {
211
- this.skipWhitespace();
212
-
213
- // Handle null
214
- if (this.query.slice(this.pos, this.pos + 4).toLowerCase() === "null") {
215
- this.pos += 4;
216
- return null;
217
- }
218
-
219
- // Handle arrays [value1,value2,...]
220
- if (this.query[this.pos] === "[") {
221
- return this.parseArray();
222
- }
223
-
224
- // Handle quoted strings
225
- if (this.query[this.pos] === '"' || this.query[this.pos] === "'") {
226
- return this.parseQuotedString();
227
- }
228
-
229
- // Parse unquoted value (until &, |, or ))
230
- let value = "";
231
- while (this.pos < this.query.length) {
232
- const ch = this.query[this.pos];
233
- if (ch === "&" || ch === "|" || ch === ")") {
234
- break;
235
- }
236
- value += ch;
237
- this.pos++;
238
- }
239
-
240
- return this.coerceValue(value.trim());
241
- }
242
-
243
- protected parseArray(): any[] {
244
- this.consume("[");
245
- const values: any[] = [];
246
-
247
- while (this.pos < this.query.length && this.query[this.pos] !== "]") {
248
- this.skipWhitespace();
249
-
250
- // Handle quoted values
251
- if (this.query[this.pos] === '"' || this.query[this.pos] === "'") {
252
- values.push(this.parseQuotedString());
253
- } else {
254
- // Parse until comma or ]
255
- let value = "";
256
- while (
257
- this.pos < this.query.length &&
258
- this.query[this.pos] !== "," &&
259
- this.query[this.pos] !== "]"
260
- ) {
261
- value += this.query[this.pos];
262
- this.pos++;
263
- }
264
- values.push(this.coerceValue(value.trim()));
265
- }
266
-
267
- this.skipWhitespace();
268
- if (this.query[this.pos] === ",") {
269
- this.pos++;
270
- }
271
- }
272
-
273
- this.consume("]");
274
- return values;
275
- }
276
-
277
- protected parseQuotedString(): string {
278
- const quote = this.query[this.pos];
279
- this.pos++; // Skip opening quote
280
-
281
- let value = "";
282
- let escaped = false;
283
-
284
- while (this.pos < this.query.length) {
285
- const ch = this.query[this.pos];
286
-
287
- if (escaped) {
288
- value += ch;
289
- escaped = false;
290
- this.pos++;
291
- continue;
292
- }
293
-
294
- if (ch === "\\") {
295
- escaped = true;
296
- this.pos++;
297
- continue;
298
- }
299
-
300
- if (ch === quote) {
301
- this.pos++; // Skip closing quote
302
- break;
303
- }
304
-
305
- value += ch;
306
- this.pos++;
307
- }
308
-
309
- return value;
310
- }
311
-
312
- protected coerceValue(value: string): any {
313
- // Try to parse as number
314
- if (/^-?\d+$/.test(value)) {
315
- return parseInt(value, 10);
316
- }
317
-
318
- if (/^-?\d+\.\d+$/.test(value)) {
319
- return parseFloat(value);
320
- }
321
-
322
- // Try to parse as boolean
323
- if (value.toLowerCase() === "true") {
324
- return true;
325
- }
326
- if (value.toLowerCase() === "false") {
327
- return false;
328
- }
329
-
330
- return value;
331
- }
332
-
333
- protected buildCondition(path: string[], operator: string, value: any): any {
334
- // Map operator to filter operator
335
- let filterOp: any;
336
-
337
- if (operator === "=") {
338
- if (value === null) {
339
- filterOp = { isNull: true };
340
- } else if (Array.isArray(value)) {
341
- // Arrays should be treated as inArray regardless of content
342
- filterOp = { inArray: value };
343
- } else if (typeof value === "string" && value.includes("*")) {
344
- // Handle wildcard patterns
345
- const startsWithAsterisk = value.startsWith("*");
346
- const endsWithAsterisk = value.endsWith("*");
347
- const cleanValue = value.replace(/^\*|\*$/g, ""); // Remove leading/trailing asterisks
348
-
349
- if (startsWithAsterisk && endsWithAsterisk) {
350
- // *text* -> contains
351
- filterOp = { contains: cleanValue };
352
- } else if (startsWithAsterisk) {
353
- // *text -> endsWith
354
- filterOp = { endsWith: cleanValue };
355
- } else if (endsWithAsterisk) {
356
- // text* -> startsWith
357
- filterOp = { startsWith: cleanValue };
358
- } else {
359
- // Has asterisk in the middle, treat as literal
360
- filterOp = { eq: value };
361
- }
362
- } else {
363
- filterOp = { eq: value };
364
- }
365
- } else if (operator === "!=") {
366
- if (value === null) {
367
- filterOp = { isNotNull: true };
368
- } else {
369
- filterOp = { ne: value };
370
- }
371
- } else if (operator === ">") {
372
- filterOp = { gt: value };
373
- } else if (operator === ">=") {
374
- filterOp = { gte: value };
375
- } else if (operator === "<") {
376
- filterOp = { lt: value };
377
- } else if (operator === "<=") {
378
- filterOp = { lte: value };
379
- } else {
380
- throw new AlephaError(`Unsupported operator: ${operator}`);
381
- }
382
-
383
- // Build nested object for path
384
- if (path.length === 1) {
385
- return { [path[0]]: filterOp };
386
- }
387
-
388
- // Handle nested paths (JSONB)
389
- let result: any = filterOp;
390
- for (let i = path.length - 1; i >= 0; i--) {
391
- result = { [path[i]]: result };
392
- }
393
-
394
- return result;
395
- }
396
-
397
- protected peek(): string {
398
- this.skipWhitespace();
399
- return this.query[this.pos] || "";
400
- }
401
-
402
- protected consume(expected: string): void {
403
- this.skipWhitespace();
404
- if (this.query[this.pos] !== expected) {
405
- throw new AlephaError(
406
- `Expected '${expected}' at position ${this.pos}, got '${this.query[this.pos]}'`,
407
- );
408
- }
409
- this.pos++;
410
- }
411
-
412
- protected skipWhitespace(): void {
413
- while (this.pos < this.query.length && /\s/.test(this.query[this.pos])) {
414
- this.pos++;
415
- }
416
- }
417
- }
418
-
419
- // ---------------------------------------------------------------------------------------------------------------------
420
-
421
- /**
422
- * Helper function to build query strings programmatically
423
- *
424
- * @example
425
- * ```ts
426
- * buildQueryString({
427
- * and: [
428
- * { name: "eq:John" },
429
- * { age: "gt:18" }
430
- * ]
431
- * })
432
- * // => "name=John&age>18"
433
- * ```
434
- */
435
- export function buildQueryString(where: any): string {
436
- if (!where || typeof where !== "object") {
437
- return "";
438
- }
439
-
440
- // Handle logical operators
441
- if ("and" in where && Array.isArray(where.and)) {
442
- return where.and.map((w: any) => buildQueryString(w)).join("&");
443
- }
444
-
445
- if ("or" in where && Array.isArray(where.or)) {
446
- const parts = where.or.map((w: any) => buildQueryString(w));
447
- return parts.length > 1 ? `(${parts.join("|")})` : parts[0];
448
- }
449
-
450
- if ("not" in where) {
451
- // Not operator is harder to represent in string form
452
- // For now, we'll skip it or you could add a syntax like "!field=value"
453
- return "";
454
- }
455
-
456
- // Handle field conditions
457
- const parts: string[] = [];
458
-
459
- for (const [field, condition] of Object.entries(where)) {
460
- if (typeof condition !== "object" || condition === null) {
461
- parts.push(`${field}=${condition}`);
462
- continue;
463
- }
464
-
465
- if ("eq" in condition) {
466
- parts.push(`${field}=${condition.eq}`);
467
- } else if ("ne" in condition) {
468
- parts.push(`${field}!=${condition.ne}`);
469
- } else if ("gt" in condition) {
470
- parts.push(`${field}>${condition.gt}`);
471
- } else if ("gte" in condition) {
472
- parts.push(`${field}>=${condition.gte}`);
473
- } else if ("lt" in condition) {
474
- parts.push(`${field}<${condition.lt}`);
475
- } else if ("lte" in condition) {
476
- parts.push(`${field}<=${condition.lte}`);
477
- } else if ("contains" in condition) {
478
- parts.push(`${field}=*${condition.contains}*`);
479
- } else if ("startsWith" in condition) {
480
- parts.push(`${field}=${condition.startsWith}*`);
481
- } else if ("endsWith" in condition) {
482
- parts.push(`${field}=*${condition.endsWith}`);
483
- } else if ("isNull" in condition && condition.isNull) {
484
- parts.push(`${field}=null`);
485
- } else if ("isNotNull" in condition && condition.isNotNull) {
486
- parts.push(`${field}!=null`);
487
- } else if ("inArray" in condition && Array.isArray(condition.inArray)) {
488
- const values = condition.inArray.map((v: any) =>
489
- typeof v === "string" ? `"${v}"` : v,
490
- );
491
- parts.push(`${field}=[${values.join(",")}]`);
492
- } else {
493
- // Nested object (JSONB)
494
- const nested = buildQueryString(condition);
495
- if (nested) {
496
- parts.push(`${field}.${nested}`);
497
- }
498
- }
499
- }
500
-
501
- return parts.join("&");
502
- }
@@ -1,88 +0,0 @@
1
- import { KIND, type TObject } from "alepha";
2
- import type { SQL } from "drizzle-orm";
3
-
4
- /**
5
- * Creates a database view primitive from a TypeBox schema and SQL query.
6
- *
7
- * Views are read-only: Repository blocks all write operations.
8
- *
9
- * @example
10
- * ```ts
11
- * import { t } from "alepha";
12
- * import { $view } from "alepha/orm";
13
- * import { sql } from "drizzle-orm";
14
- *
15
- * const userSummary = $view({
16
- * name: "user_summary",
17
- * schema: t.object({
18
- * id: t.uuid(),
19
- * fullName: t.text(),
20
- * orderCount: t.integer(),
21
- * }),
22
- * query: sql`SELECT u.id, u.first_name || ' ' || u.last_name AS full_name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id`,
23
- * });
24
- *
25
- * // Materialized view (PostgreSQL only)
26
- * const monthlyStats = $view({
27
- * name: "monthly_stats",
28
- * schema: t.object({ ... }),
29
- * query: sql`...`,
30
- * materialized: true,
31
- * });
32
- * ```
33
- */
34
- export const $view = <TSchema extends TObject>(
35
- options: ViewPrimitiveOptions<TSchema>,
36
- ): ViewPrimitive<TSchema> => {
37
- return new ViewPrimitive<TSchema>(options);
38
- };
39
-
40
- // ---------------------------------------------------------------------------------------------------------------------
41
-
42
- export interface ViewPrimitiveOptions<T extends TObject> {
43
- /**
44
- * The database view name.
45
- */
46
- name: string;
47
-
48
- /**
49
- * TypeBox schema defining the view's columns.
50
- */
51
- schema: T;
52
-
53
- /**
54
- * SQL query that defines the view.
55
- */
56
- query: SQL;
57
-
58
- /**
59
- * Whether this is a materialized view (PostgreSQL only).
60
- * Materialized views store results on disk and can be refreshed.
61
- */
62
- materialized?: boolean;
63
- }
64
-
65
- // ---------------------------------------------------------------------------------------------------------------------
66
-
67
- export class ViewPrimitive<T extends TObject = TObject> {
68
- public readonly options: ViewPrimitiveOptions<T>;
69
- public readonly isView = true;
70
-
71
- constructor(options: ViewPrimitiveOptions<T>) {
72
- this.options = options;
73
- }
74
-
75
- get name(): string {
76
- return this.options.name;
77
- }
78
-
79
- get schema(): T {
80
- return this.options.schema;
81
- }
82
-
83
- get materialized(): boolean {
84
- return this.options.materialized ?? false;
85
- }
86
- }
87
-
88
- $view[KIND] = ViewPrimitive;