@softize/opus 8.6.6

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 (286) hide show
  1. package/CHANGELOG.md +1616 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/bin/cli.mjs +528 -0
  5. package/bin/lib/check.mjs +307 -0
  6. package/bin/lib/components.mjs +151 -0
  7. package/bin/lib/create.mjs +208 -0
  8. package/bin/lib/db-check-runner.mjs +86 -0
  9. package/bin/lib/db-migrate-runner.mjs +89 -0
  10. package/bin/lib/db-scaffold-runner.mjs +84 -0
  11. package/bin/lib/db.mjs +261 -0
  12. package/bin/lib/docs-include.mjs +48 -0
  13. package/bin/lib/gen-dicts.mjs +134 -0
  14. package/bin/lib/gen-docs.mjs +288 -0
  15. package/bin/lib/gen-manifest.mjs +102 -0
  16. package/bin/lib/gen-openapi.mjs +195 -0
  17. package/bin/lib/gen-runner.mjs +472 -0
  18. package/bin/lib/gen-stubs.mjs +463 -0
  19. package/bin/lib/gen.mjs +311 -0
  20. package/bin/lib/init.mjs +514 -0
  21. package/bin/lib/introspect.mjs +107 -0
  22. package/bin/lib/mcp.mjs +85 -0
  23. package/bin/lib/postinstall.mjs +56 -0
  24. package/docs/chat-event-protocol.md +85 -0
  25. package/docs/code-style.md +16 -0
  26. package/docs/data-layer.md +246 -0
  27. package/docs/ownership-vs-shadcn-lock.md +102 -0
  28. package/docs/protocol.md +2053 -0
  29. package/docs/releasing.md +110 -0
  30. package/docs/shellnav.md +131 -0
  31. package/package.json +338 -0
  32. package/registry/hooks/hooks.json +26 -0
  33. package/registry/hooks/link-memory-on-start.mjs +46 -0
  34. package/registry/hooks/opus-check-on-stop.mjs +114 -0
  35. package/registry/skills/create-action/SKILL.md +49 -0
  36. package/registry/skills/create-action/scaffold.mjs +122 -0
  37. package/registry/templates/app/_gitignore +3 -0
  38. package/registry/templates/app/_npmrc +1 -0
  39. package/registry/templates/app/_opus/_gitignore +5 -0
  40. package/registry/templates/app/_prettierrc.json +6 -0
  41. package/registry/templates/app/index.html +13 -0
  42. package/registry/templates/app/opus.config.ts +16 -0
  43. package/registry/templates/app/package.json +43 -0
  44. package/registry/templates/app/pnpm-workspace.yaml +11 -0
  45. package/registry/templates/app/public/favicon.svg +4 -0
  46. package/registry/templates/app/src/App.tsx +37 -0
  47. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
  48. package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
  49. package/registry/templates/app/src/domains/tasks/index.ts +13 -0
  50. package/registry/templates/app/src/index.css +18 -0
  51. package/registry/templates/app/src/main.tsx +25 -0
  52. package/registry/templates/app/tsconfig.json +20 -0
  53. package/registry/templates/app/vite.config.ts +46 -0
  54. package/registry/templates/monorepo/_gitignore +3 -0
  55. package/registry/templates/monorepo/_npmrc +1 -0
  56. package/registry/templates/monorepo/package.json +9 -0
  57. package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
  58. package/src/ai/ask.ts +64 -0
  59. package/src/ai/drivers/anthropic.ts +309 -0
  60. package/src/ai/index.ts +17 -0
  61. package/src/audit/drivers/console.ts +117 -0
  62. package/src/audit/drivers/pg.ts +172 -0
  63. package/src/audit/index.ts +51 -0
  64. package/src/auth/drivers/better-auth.ts +103 -0
  65. package/src/auth/drivers/jwt.ts +188 -0
  66. package/src/auth/index.ts +9 -0
  67. package/src/client/drivers/fetch.ts +202 -0
  68. package/src/client/index.ts +22 -0
  69. package/src/core/actions.ts +110 -0
  70. package/src/core/audit.ts +239 -0
  71. package/src/core/contracts.ts +137 -0
  72. package/src/core/domain.ts +310 -0
  73. package/src/core/errors.ts +181 -0
  74. package/src/core/index.ts +174 -0
  75. package/src/core/logical-type.ts +31 -0
  76. package/src/core/reactions.ts +81 -0
  77. package/src/core/runtime.ts +1167 -0
  78. package/src/core/schedules.ts +41 -0
  79. package/src/core/types.ts +1356 -0
  80. package/src/data/drivers/kysely.ts +389 -0
  81. package/src/data/index.ts +10 -0
  82. package/src/data/readonly-pool.ts +160 -0
  83. package/src/dsl/eval.ts +136 -0
  84. package/src/dsl/index.ts +29 -0
  85. package/src/dsl/kysely.ts +230 -0
  86. package/src/dsl/loads.ts +123 -0
  87. package/src/dsl/parser.ts +423 -0
  88. package/src/dsl/types.ts +113 -0
  89. package/src/events/drivers/mitt.ts +70 -0
  90. package/src/events/index.ts +9 -0
  91. package/src/log/drivers/pino.ts +57 -0
  92. package/src/log/index.ts +9 -0
  93. package/src/mcp/index.ts +62 -0
  94. package/src/queue/drivers/bullmq.ts +190 -0
  95. package/src/queue/index.ts +9 -0
  96. package/src/scheduler/drivers/node-cron.ts +93 -0
  97. package/src/scheduler/every.ts +45 -0
  98. package/src/scheduler/index.ts +9 -0
  99. package/src/schema/drivers/zod.ts +765 -0
  100. package/src/schema/entity.ts +439 -0
  101. package/src/schema/format/locale.ts +144 -0
  102. package/src/schema/index.ts +65 -0
  103. package/src/schema/openapi.ts +302 -0
  104. package/src/schema/scaffold.ts +160 -0
  105. package/src/server/drivers/fastify.ts +224 -0
  106. package/src/server/drivers/node.ts +386 -0
  107. package/src/server/index.ts +142 -0
  108. package/src/storage/drivers/fs.ts +90 -0
  109. package/src/storage/drivers/s3.ts +117 -0
  110. package/src/storage/index.ts +27 -0
  111. package/src/testing/fake.ts +298 -0
  112. package/src/testing/index.ts +324 -0
  113. package/src/ui/components/patterns/action-form-card.tsx +48 -0
  114. package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
  115. package/src/ui/components/patterns/app-shell.tsx +227 -0
  116. package/src/ui/components/patterns/confirm.tsx +226 -0
  117. package/src/ui/components/patterns/data-state.tsx +75 -0
  118. package/src/ui/components/patterns/form-dialog.tsx +64 -0
  119. package/src/ui/components/patterns/form.tsx +584 -0
  120. package/src/ui/components/patterns/list.tsx +1488 -0
  121. package/src/ui/components/patterns/page.tsx +46 -0
  122. package/src/ui/components/patterns/section-shell.tsx +246 -0
  123. package/src/ui/components/patterns/shell-nav.tsx +150 -0
  124. package/src/ui/components/patterns/sidebar.tsx +89 -0
  125. package/src/ui/components/patterns/split.tsx +93 -0
  126. package/src/ui/components/patterns/trigger.tsx +196 -0
  127. package/src/ui/components/patterns/view.tsx +84 -0
  128. package/src/ui/components/primitives/accordion.tsx +64 -0
  129. package/src/ui/components/primitives/alert-dialog.tsx +190 -0
  130. package/src/ui/components/primitives/alert.tsx +116 -0
  131. package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
  132. package/src/ui/components/primitives/avatar.tsx +107 -0
  133. package/src/ui/components/primitives/badge.tsx +37 -0
  134. package/src/ui/components/primitives/breadcrumb.tsx +109 -0
  135. package/src/ui/components/primitives/button-group.tsx +83 -0
  136. package/src/ui/components/primitives/button.tsx +102 -0
  137. package/src/ui/components/primitives/calendar.tsx +218 -0
  138. package/src/ui/components/primitives/card.tsx +56 -0
  139. package/src/ui/components/primitives/carousel.tsx +239 -0
  140. package/src/ui/components/primitives/chat.tsx +407 -0
  141. package/src/ui/components/primitives/checkbox.tsx +30 -0
  142. package/src/ui/components/primitives/collapsible.tsx +31 -0
  143. package/src/ui/components/primitives/command.tsx +182 -0
  144. package/src/ui/components/primitives/composer.tsx +121 -0
  145. package/src/ui/components/primitives/copyable.tsx +50 -0
  146. package/src/ui/components/primitives/dialog.tsx +147 -0
  147. package/src/ui/components/primitives/drawer.tsx +141 -0
  148. package/src/ui/components/primitives/empty.tsx +104 -0
  149. package/src/ui/components/primitives/field.tsx +246 -0
  150. package/src/ui/components/primitives/icon-picker.tsx +180 -0
  151. package/src/ui/components/primitives/input-group.tsx +168 -0
  152. package/src/ui/components/primitives/input-otp.tsx +75 -0
  153. package/src/ui/components/primitives/input.tsx +72 -0
  154. package/src/ui/components/primitives/item.tsx +193 -0
  155. package/src/ui/components/primitives/kbd.tsx +28 -0
  156. package/src/ui/components/primitives/label.tsx +22 -0
  157. package/src/ui/components/primitives/markdown.tsx +35 -0
  158. package/src/ui/components/primitives/menu.tsx +255 -0
  159. package/src/ui/components/primitives/pagination.tsx +127 -0
  160. package/src/ui/components/primitives/popover.tsx +87 -0
  161. package/src/ui/components/primitives/progress.tsx +29 -0
  162. package/src/ui/components/primitives/radio-group.tsx +43 -0
  163. package/src/ui/components/primitives/resizable.tsx +51 -0
  164. package/src/ui/components/primitives/scroll-area.tsx +56 -0
  165. package/src/ui/components/primitives/select.tsx +479 -0
  166. package/src/ui/components/primitives/separator.tsx +26 -0
  167. package/src/ui/components/primitives/skeleton.tsx +13 -0
  168. package/src/ui/components/primitives/slider.tsx +61 -0
  169. package/src/ui/components/primitives/sonner.tsx +46 -0
  170. package/src/ui/components/primitives/spinner.tsx +29 -0
  171. package/src/ui/components/primitives/switch.tsx +33 -0
  172. package/src/ui/components/primitives/table.tsx +114 -0
  173. package/src/ui/components/primitives/tabs.tsx +104 -0
  174. package/src/ui/components/primitives/textarea.tsx +18 -0
  175. package/src/ui/components/primitives/toggle-group.tsx +81 -0
  176. package/src/ui/components/primitives/toggle.tsx +45 -0
  177. package/src/ui/components/primitives/tooltip.tsx +55 -0
  178. package/src/ui/components/primitives/truncate.tsx +49 -0
  179. package/src/ui/docs/DocBrowser.tsx +90 -0
  180. package/src/ui/docs/changelog.tsx +80 -0
  181. package/src/ui/docs/content/accordion.md +86 -0
  182. package/src/ui/docs/content/action-form-card.md +24 -0
  183. package/src/ui/docs/content/action-form-dialog.md +30 -0
  184. package/src/ui/docs/content/action-form.md +125 -0
  185. package/src/ui/docs/content/action-list-dialog.md +68 -0
  186. package/src/ui/docs/content/action-list.md +194 -0
  187. package/src/ui/docs/content/action-trigger.md +72 -0
  188. package/src/ui/docs/content/action-view.md +47 -0
  189. package/src/ui/docs/content/actions.md +138 -0
  190. package/src/ui/docs/content/ai.md +112 -0
  191. package/src/ui/docs/content/alert-dialog.md +73 -0
  192. package/src/ui/docs/content/alert.md +69 -0
  193. package/src/ui/docs/content/app-shell.md +155 -0
  194. package/src/ui/docs/content/aspect-ratio.md +66 -0
  195. package/src/ui/docs/content/audit.md +84 -0
  196. package/src/ui/docs/content/auth.md +70 -0
  197. package/src/ui/docs/content/avatar.md +94 -0
  198. package/src/ui/docs/content/badge.md +48 -0
  199. package/src/ui/docs/content/breadcrumb.md +87 -0
  200. package/src/ui/docs/content/button-group.md +71 -0
  201. package/src/ui/docs/content/button.md +60 -0
  202. package/src/ui/docs/content/calendar.md +62 -0
  203. package/src/ui/docs/content/card.md +49 -0
  204. package/src/ui/docs/content/carousel.md +85 -0
  205. package/src/ui/docs/content/chat.md +69 -0
  206. package/src/ui/docs/content/checkbox.md +75 -0
  207. package/src/ui/docs/content/cli.md +58 -0
  208. package/src/ui/docs/content/collapsible.md +64 -0
  209. package/src/ui/docs/content/command.md +56 -0
  210. package/src/ui/docs/content/composer.md +50 -0
  211. package/src/ui/docs/content/confirm.md +120 -0
  212. package/src/ui/docs/content/copyable.md +30 -0
  213. package/src/ui/docs/content/customization.md +110 -0
  214. package/src/ui/docs/content/cycle.md +34 -0
  215. package/src/ui/docs/content/data-state.md +47 -0
  216. package/src/ui/docs/content/data.md +99 -0
  217. package/src/ui/docs/content/dialog.md +60 -0
  218. package/src/ui/docs/content/drawer.md +55 -0
  219. package/src/ui/docs/content/empty.md +66 -0
  220. package/src/ui/docs/content/events.md +61 -0
  221. package/src/ui/docs/content/field.md +58 -0
  222. package/src/ui/docs/content/getting-started.md +109 -0
  223. package/src/ui/docs/content/icon-picker.md +51 -0
  224. package/src/ui/docs/content/input-group.md +78 -0
  225. package/src/ui/docs/content/input-otp.md +72 -0
  226. package/src/ui/docs/content/input.md +78 -0
  227. package/src/ui/docs/content/item.md +84 -0
  228. package/src/ui/docs/content/kbd.md +62 -0
  229. package/src/ui/docs/content/label.md +32 -0
  230. package/src/ui/docs/content/log.md +55 -0
  231. package/src/ui/docs/content/markdown.md +41 -0
  232. package/src/ui/docs/content/mcp.md +44 -0
  233. package/src/ui/docs/content/menu.md +114 -0
  234. package/src/ui/docs/content/microcopy.md +83 -0
  235. package/src/ui/docs/content/page.md +34 -0
  236. package/src/ui/docs/content/pagination.md +99 -0
  237. package/src/ui/docs/content/popover.md +49 -0
  238. package/src/ui/docs/content/progress.md +69 -0
  239. package/src/ui/docs/content/queue.md +62 -0
  240. package/src/ui/docs/content/radio-group.md +77 -0
  241. package/src/ui/docs/content/resizable.md +86 -0
  242. package/src/ui/docs/content/router.md +56 -0
  243. package/src/ui/docs/content/runtime.md +77 -0
  244. package/src/ui/docs/content/scheduler.md +66 -0
  245. package/src/ui/docs/content/scroll-area.md +89 -0
  246. package/src/ui/docs/content/section-shell.md +121 -0
  247. package/src/ui/docs/content/select.md +342 -0
  248. package/src/ui/docs/content/separator.md +33 -0
  249. package/src/ui/docs/content/sidebar.md +38 -0
  250. package/src/ui/docs/content/skeleton.md +34 -0
  251. package/src/ui/docs/content/slider.md +64 -0
  252. package/src/ui/docs/content/spinner.md +37 -0
  253. package/src/ui/docs/content/split.md +33 -0
  254. package/src/ui/docs/content/storage.md +69 -0
  255. package/src/ui/docs/content/switch.md +69 -0
  256. package/src/ui/docs/content/table.md +102 -0
  257. package/src/ui/docs/content/tabs.md +94 -0
  258. package/src/ui/docs/content/testing.md +89 -0
  259. package/src/ui/docs/content/textarea.md +30 -0
  260. package/src/ui/docs/content/toast.md +67 -0
  261. package/src/ui/docs/content/toggle-group.md +81 -0
  262. package/src/ui/docs/content/toggle.md +72 -0
  263. package/src/ui/docs/content/tokens.md +171 -0
  264. package/src/ui/docs/content/tooltip.md +50 -0
  265. package/src/ui/docs/content/truncate.md +37 -0
  266. package/src/ui/docs/content/ui.md +40 -0
  267. package/src/ui/docs/content/upgrading.md +48 -0
  268. package/src/ui/docs/doc-client.tsx +214 -0
  269. package/src/ui/docs/doc.tsx +301 -0
  270. package/src/ui/docs/folder.tsx +149 -0
  271. package/src/ui/docs/index.ts +21 -0
  272. package/src/ui/docs/markdown.tsx +130 -0
  273. package/src/ui/docs/md-raw.d.ts +4 -0
  274. package/src/ui/docs/plugin.ts +104 -0
  275. package/src/ui/docs/registry.tsx +424 -0
  276. package/src/ui/docs/standalone.tsx +107 -0
  277. package/src/ui/drivers/react.tsx +627 -0
  278. package/src/ui/index.ts +92 -0
  279. package/src/ui/lib/cn.ts +10 -0
  280. package/src/ui/lib/zod-pt-br.ts +38 -0
  281. package/src/ui/meta.ts +412 -0
  282. package/src/ui/react.tsx +235 -0
  283. package/src/ui/router.ts +96 -0
  284. package/src/ui/theme.css +234 -0
  285. package/src/vite/design.ts +652 -0
  286. package/src/vite/index.ts +8 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @softize/opus/dsl — Public entry.
3
+ */
4
+
5
+ export type {
6
+ AstNode,
7
+ LiteralNode,
8
+ ArrayNode,
9
+ IdentifierNode,
10
+ MemberAccessNode,
11
+ UnaryOpNode,
12
+ BinaryOpNode,
13
+ BinaryOp,
14
+ InNode,
15
+ BetweenNode,
16
+ LikeNode,
17
+ IsNullNode,
18
+ FunctionCallNode,
19
+ } from './types.ts'
20
+
21
+ export { parseExpression } from './parser.ts'
22
+ export { evalExpression, type EvalContext } from './eval.ts'
23
+ export { compileToKysely, compileWhere, type KyselyBindings } from './kysely.ts'
24
+ export {
25
+ parseLoad,
26
+ resolveLoadArgs,
27
+ type LoadSpec,
28
+ type LoadArg,
29
+ } from './loads.ts'
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @softize/opus/dsl — AST → Kysely where callback.
3
+ *
4
+ * Compila uma AST de expressão em `(eb) => Expression` consumível por
5
+ * `query.where(...)` do Kysely.
6
+ *
7
+ * Pra que isso valha, o DSL precisa estar no escopo da tabela (identifiers
8
+ * raiz são colunas; ex: `status == 'open'` vira `eb('status', '=', 'open')`).
9
+ *
10
+ * Contexto adicional (`user`, `now`, etc) é injetado via `bindings` na hora
11
+ * da compilação — esses identifiers viram literais resolvidos.
12
+ */
13
+
14
+ /* eslint-disable @typescript-eslint/no-explicit-any */
15
+ import type { AstNode } from './types.ts'
16
+ import { parseExpression } from './parser.ts'
17
+
18
+ export interface KyselyBindings {
19
+ /**
20
+ * Map de identifier raiz → valor. Quando AST referencia
21
+ * `user.kind`, e bindings.user = { kind: 'employee' }, o compiler
22
+ * inline o valor como literal.
23
+ */
24
+ [name: string]: unknown
25
+ }
26
+
27
+ interface KyselyEb {
28
+ // Subset usado. Tipos estrito do Kysely são complexos; usamos any onde
29
+ // não acrescentam segurança pro caller (Kysely valida em runtime/typecheck
30
+ // no consumer).
31
+ and: (exprs: any[]) => any
32
+ or: (exprs: any[]) => any
33
+ not: (expr: any) => any
34
+ (column: string, op: string, value: unknown): any
35
+ fn: { coalesce: (...args: any[]) => any }
36
+ lit: (v: unknown) => any
37
+ }
38
+
39
+ /**
40
+ * Compila AST → callback Kysely. Identifiers raiz desconhecidos viram
41
+ * referências a coluna (string). Identifiers em bindings viram literais.
42
+ */
43
+ export function compileToKysely(
44
+ node: AstNode,
45
+ bindings: KyselyBindings = {},
46
+ ): (eb: any) => any {
47
+ return (eb: any) => compile(node, eb as KyselyEb, bindings)
48
+ }
49
+
50
+ /**
51
+ * Atalho pra repository: parsa a string DSL e devolve callback Kysely.
52
+ *
53
+ * query.where(compileWhere('NOT archived', { user }))
54
+ *
55
+ * Equivale a `compileToKysely(parseExpression(src), bindings)` — existe pra
56
+ * que consumidores não precisem importar `parseExpression` separado.
57
+ */
58
+ export function compileWhere(
59
+ src: string,
60
+ bindings: KyselyBindings = {},
61
+ ): (eb: any) => any {
62
+ return compileToKysely(parseExpression(src), bindings)
63
+ }
64
+
65
+ function compile(node: AstNode, eb: KyselyEb, bindings: KyselyBindings): any {
66
+ switch (node.kind) {
67
+ case 'literal':
68
+ return node.value
69
+ case 'array':
70
+ return node.items.map((i) => compile(i, eb, bindings))
71
+ case 'identifier': {
72
+ if (Object.prototype.hasOwnProperty.call(bindings, node.name)) {
73
+ return bindings[node.name]
74
+ }
75
+ // identifier raiz desconhecido → nome de coluna
76
+ return { __column: node.name }
77
+ }
78
+ case 'member': {
79
+ // a.b.c → resolve a partir do binding se existir; senão erro
80
+ const root = compile(node.object, eb, bindings)
81
+ if (
82
+ root !== null &&
83
+ typeof root === 'object' &&
84
+ !('__column' in (root as object))
85
+ ) {
86
+ return (root as Record<string, unknown>)[node.property]
87
+ }
88
+ throw new Error(
89
+ `DSL kysely: member access em coluna não suportado ('${node.property}')`,
90
+ )
91
+ }
92
+ case 'unary': {
93
+ const operand = compile(node.operand, eb, bindings)
94
+ /* v8 ignore next */
95
+ return node.op === 'NOT' ? eb.not(operand) : -(operand as number)
96
+ }
97
+ case 'binary': {
98
+ if (node.op === 'AND') {
99
+ return eb.and([
100
+ compile(node.left, eb, bindings),
101
+ compile(node.right, eb, bindings),
102
+ ])
103
+ }
104
+ if (node.op === 'OR') {
105
+ return eb.or([
106
+ compile(node.left, eb, bindings),
107
+ compile(node.right, eb, bindings),
108
+ ])
109
+ }
110
+ const left = compile(node.left, eb, bindings)
111
+ const right = compile(node.right, eb, bindings)
112
+ if (isColumn(left)) {
113
+ return eb(columnOf(left), sqlOp(node.op), right)
114
+ }
115
+ // ambos literais: avaliar direto
116
+ switch (node.op) {
117
+ case '==':
118
+ return left === right
119
+ case '!=':
120
+ return left !== right
121
+ case '<':
122
+ return (left as number) < (right as number)
123
+ case '<=':
124
+ return (left as number) <= (right as number)
125
+ case '>':
126
+ return (left as number) > (right as number)
127
+ case '>=':
128
+ return (left as number) >= (right as number)
129
+ case '+':
130
+ return (left as number) + (right as number)
131
+ case '-':
132
+ return (left as number) - (right as number)
133
+ case '*':
134
+ return (left as number) * (right as number)
135
+ case '/':
136
+ return (left as number) / (right as number)
137
+ /* v8 ignore next 2 */
138
+ default:
139
+ throw new Error(`DSL kysely: unhandled binary ${node.op as string}`)
140
+ }
141
+ }
142
+ case 'in': {
143
+ const subj = compile(node.left, eb, bindings)
144
+ const arr = compile(node.values, eb, bindings) as unknown[]
145
+ if (isColumn(subj)) {
146
+ return eb(columnOf(subj), node.negated ? 'not in' : 'in', arr)
147
+ }
148
+ const has = arr.includes(subj)
149
+ return node.negated ? !has : has
150
+ }
151
+ case 'between': {
152
+ const subj = compile(node.subject, eb, bindings)
153
+ const lo = compile(node.low, eb, bindings)
154
+ const hi = compile(node.high, eb, bindings)
155
+ if (isColumn(subj)) {
156
+ return eb.and([
157
+ eb(columnOf(subj), node.negated ? '<' : '>=', lo),
158
+ eb(columnOf(subj), node.negated ? '>' : '<=', hi),
159
+ ])
160
+ }
161
+ const within = (subj as number) >= (lo as number) && (subj as number) <= (hi as number)
162
+ return node.negated ? !within : within
163
+ }
164
+ case 'like': {
165
+ const subj = compile(node.subject, eb, bindings)
166
+ const pat = compile(node.pattern, eb, bindings)
167
+ if (isColumn(subj)) {
168
+ const op = node.caseInsensitive ? 'ilike' : 'like'
169
+ return eb(columnOf(subj), node.negated ? `not ${op}` : op, pat)
170
+ }
171
+ throw new Error('DSL kysely: LIKE em literal não suportado')
172
+ }
173
+ case 'isNull': {
174
+ const subj = compile(node.subject, eb, bindings)
175
+ if (isColumn(subj)) {
176
+ return eb(columnOf(subj), node.negated ? 'is not' : 'is', null)
177
+ }
178
+ /* v8 ignore next */
179
+ const isNullish = subj === null || subj === undefined
180
+ return node.negated ? !isNullish : isNullish
181
+ }
182
+ case 'call': {
183
+ const args = node.args.map((a) => compile(a, eb, bindings))
184
+ switch (node.name) {
185
+ case 'NOW':
186
+ return new Date().toISOString()
187
+ case 'TODAY':
188
+ return new Date().toISOString().slice(0, 10)
189
+ case 'COALESCE':
190
+ return eb.fn.coalesce(...args)
191
+ case 'LOWER':
192
+ case 'UPPER':
193
+ case 'LENGTH':
194
+ throw new Error(
195
+ `DSL kysely: function '${node.name}' precisa de mapping de coluna explícito (use eb.fn)`,
196
+ )
197
+ }
198
+ throw new Error(`DSL kysely: unknown function '${node.name}'`)
199
+ }
200
+ }
201
+ }
202
+
203
+ function isColumn(v: unknown): v is { __column: string } {
204
+ return (
205
+ typeof v === 'object' &&
206
+ v !== null &&
207
+ '__column' in (v as object) &&
208
+ typeof (v as { __column: unknown }).__column === 'string'
209
+ )
210
+ }
211
+
212
+ function columnOf(v: { __column: string }): string {
213
+ return v.__column
214
+ }
215
+
216
+ function sqlOp(op: string): string {
217
+ switch (op) {
218
+ case '==':
219
+ return '='
220
+ case '!=':
221
+ return '!='
222
+ case '<':
223
+ case '<=':
224
+ case '>':
225
+ case '>=':
226
+ return op
227
+ default:
228
+ throw new Error(`DSL kysely: op ${op} não tem SQL equivalente`)
229
+ }
230
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @softize/opus/dsl — Loads parser.
3
+ *
4
+ * Sintaxe simples (não usa o parser de expressão):
5
+ *
6
+ * ticket(:id)
7
+ * shipment(:shipmentId)
8
+ * customer(:customerId, status="active")
9
+ *
10
+ * Resolve em { entity, args: { paramKey: inputKey | literalValue } }.
11
+ * Quem executa loads é o runtime (mapeia entity → repository.findX, passa
12
+ * args resolvidos contra input).
13
+ */
14
+
15
+ export interface LoadSpec {
16
+ entity: string
17
+ /**
18
+ * Mapeia parâmetro → fonte. Se string começa com ':', é referência ao
19
+ * input do action (`:id` → `input.id`). Senão é literal.
20
+ */
21
+ args: Record<string, LoadArg>
22
+ }
23
+
24
+ export type LoadArg =
25
+ | { source: 'input'; key: string }
26
+ | { source: 'literal'; value: string | number | boolean | null }
27
+
28
+ const LOAD_RE = /^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/
29
+
30
+ export function parseLoad(src: string): LoadSpec {
31
+ const trimmed = src.trim()
32
+ const m = LOAD_RE.exec(trimmed)
33
+ if (m === null) {
34
+ throw new Error(`DSL loads: invalid syntax '${src}'`)
35
+ }
36
+ const entity = m[1]!
37
+ const argsRaw = m[2]!.trim()
38
+ const args: Record<string, LoadArg> = {}
39
+ if (argsRaw.length > 0) {
40
+ const parts = splitArgs(argsRaw)
41
+ let idx = 0
42
+ for (const part of parts) {
43
+ const eq = part.indexOf('=')
44
+ if (eq === -1) {
45
+ // forma posicional: `:id` ou `"valor"` — chave é o primeiro arg = `id`
46
+ const value = parseArgValue(part.trim())
47
+ const key = value.source === 'input' ? value.key : `arg${idx}`
48
+ args[key] = value
49
+ } else {
50
+ const key = part.slice(0, eq).trim()
51
+ const value = parseArgValue(part.slice(eq + 1).trim())
52
+ args[key] = value
53
+ }
54
+ idx += 1
55
+ }
56
+ }
57
+ return { entity, args }
58
+ }
59
+
60
+ function splitArgs(src: string): string[] {
61
+ const parts: string[] = []
62
+ let depth = 0
63
+ let inStr: '"' | "'" | null = null
64
+ let buf = ''
65
+ for (let i = 0; i < src.length; i += 1) {
66
+ const c = src[i]!
67
+ if (inStr !== null) {
68
+ buf += c
69
+ if (c === inStr) inStr = null
70
+ continue
71
+ }
72
+ if (c === '"' || c === "'") {
73
+ inStr = c
74
+ buf += c
75
+ continue
76
+ }
77
+ if (c === '(' || c === '[') depth += 1
78
+ if (c === ')' || c === ']') depth -= 1
79
+ if (c === ',' && depth === 0) {
80
+ parts.push(buf)
81
+ buf = ''
82
+ continue
83
+ }
84
+ buf += c
85
+ }
86
+ if (buf.trim().length > 0) parts.push(buf)
87
+ return parts
88
+ }
89
+
90
+ function parseArgValue(raw: string): LoadArg {
91
+ if (raw.startsWith(':')) {
92
+ return { source: 'input', key: raw.slice(1) }
93
+ }
94
+ if (raw.startsWith('"') || raw.startsWith("'")) {
95
+ return { source: 'literal', value: raw.slice(1, -1) }
96
+ }
97
+ if (raw === 'true') return { source: 'literal', value: true }
98
+ if (raw === 'false') return { source: 'literal', value: false }
99
+ if (raw === 'null') return { source: 'literal', value: null }
100
+ const n = Number(raw)
101
+ if (!Number.isNaN(n)) return { source: 'literal', value: n }
102
+ // Sem aspas — trata como string crua
103
+ return { source: 'literal', value: raw }
104
+ }
105
+
106
+ /**
107
+ * Resolve um LoadSpec contra `input` em args concretos.
108
+ * Devolve `Record<string, any>` pronto pra passar pro repository.findBy.
109
+ */
110
+ export function resolveLoadArgs(
111
+ spec: LoadSpec,
112
+ input: Record<string, unknown>,
113
+ ): Record<string, unknown> {
114
+ const resolved: Record<string, unknown> = {}
115
+ for (const [k, arg] of Object.entries(spec.args)) {
116
+ if (arg.source === 'input') {
117
+ resolved[k] = input[arg.key]
118
+ } else {
119
+ resolved[k] = arg.value
120
+ }
121
+ }
122
+ return resolved
123
+ }