@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,423 @@
1
+ /**
2
+ * @softize/opus/dsl — Tokenizer + parser de expressões.
3
+ *
4
+ * Gramática (descendente recursiva, precedência: NOT > comp > AND > OR):
5
+ *
6
+ * expr = orExpr
7
+ * orExpr = andExpr ('OR' andExpr)*
8
+ * andExpr = notExpr ('AND' notExpr)*
9
+ * notExpr = 'NOT' notExpr | compExpr
10
+ * compExpr = addExpr (compOp compExpr)?
11
+ * compOp = '==' | '!=' | '<' | '<=' | '>' | '>=' |
12
+ * ('NOT')? 'IN' | ('NOT')? 'BETWEEN' |
13
+ * ('NOT')? ('I')? 'LIKE' | 'IS' ('NOT')? 'NULL'
14
+ * addExpr = mulExpr (('+'|'-') mulExpr)*
15
+ * mulExpr = unary (('*'|'/') unary)*
16
+ * unary = '-' unary | primary
17
+ * primary = literal | array | functionCall | identifier ('.' ident)* | '(' expr ')'
18
+ * array = '[' (expr (',' expr)*)? ']'
19
+ * functionCall= ident '(' (expr (',' expr)*)? ')'
20
+ * literal = STRING | NUMBER | 'true' | 'false' | 'null'
21
+ *
22
+ * Identifiers válidos: [a-zA-Z_][a-zA-Z0-9_]*. Mas NÃO podem ser keyword
23
+ * reservada (AND, OR, NOT, IN, BETWEEN, LIKE, ILIKE, IS, NULL, true, false).
24
+ */
25
+
26
+ import type { AstNode } from './types.ts'
27
+
28
+ // =============================================================================
29
+ // Tokens
30
+ // =============================================================================
31
+
32
+ type TokenType =
33
+ | 'NUMBER'
34
+ | 'STRING'
35
+ | 'IDENT'
36
+ | 'KEYWORD'
37
+ | 'OP'
38
+ | 'PUNCT'
39
+ | 'EOF'
40
+
41
+ interface Token {
42
+ type: TokenType
43
+ value: string
44
+ pos: number
45
+ }
46
+
47
+ const KEYWORDS = new Set([
48
+ 'AND',
49
+ 'OR',
50
+ 'NOT',
51
+ 'IN',
52
+ 'BETWEEN',
53
+ 'LIKE',
54
+ 'ILIKE',
55
+ 'IS',
56
+ 'NULL',
57
+ 'TRUE',
58
+ 'FALSE',
59
+ ])
60
+
61
+ const MULTI_CHAR_OPS = ['==', '!=', '<=', '>='] as const
62
+ const SINGLE_CHAR_OPS = ['<', '>', '+', '-', '*', '/'] as const
63
+ const PUNCTS = ['(', ')', '[', ']', ',', '.'] as const
64
+
65
+ function tokenize(src: string): Token[] {
66
+ const tokens: Token[] = []
67
+ let i = 0
68
+ while (i < src.length) {
69
+ const c = src[i]!
70
+ // whitespace
71
+ if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
72
+ i += 1
73
+ continue
74
+ }
75
+ const startPos = i
76
+
77
+ // string literals (single or double quote)
78
+ if (c === '"' || c === "'") {
79
+ const quote = c
80
+ i += 1
81
+ let val = ''
82
+ while (i < src.length && src[i] !== quote) {
83
+ if (src[i] === '\\' && i + 1 < src.length) {
84
+ val += src[i + 1]
85
+ i += 2
86
+ } else {
87
+ val += src[i]!
88
+ i += 1
89
+ }
90
+ }
91
+ if (i >= src.length) {
92
+ throw new Error(`DSL parse error: unterminated string at ${startPos}`)
93
+ }
94
+ i += 1 // skip closing quote
95
+ tokens.push({ type: 'STRING', value: val, pos: startPos })
96
+ continue
97
+ }
98
+
99
+ // numbers
100
+ if (c >= '0' && c <= '9') {
101
+ let val = ''
102
+ while (i < src.length && /[0-9.]/.test(src[i]!)) {
103
+ val += src[i]!
104
+ i += 1
105
+ }
106
+ tokens.push({ type: 'NUMBER', value: val, pos: startPos })
107
+ continue
108
+ }
109
+
110
+ // multi-char operators
111
+ let matched = false
112
+ for (const op of MULTI_CHAR_OPS) {
113
+ if (src.startsWith(op, i)) {
114
+ tokens.push({ type: 'OP', value: op, pos: startPos })
115
+ i += op.length
116
+ matched = true
117
+ break
118
+ }
119
+ }
120
+ if (matched) continue
121
+
122
+ // single-char operators
123
+ if (SINGLE_CHAR_OPS.includes(c as (typeof SINGLE_CHAR_OPS)[number])) {
124
+ tokens.push({ type: 'OP', value: c, pos: startPos })
125
+ i += 1
126
+ continue
127
+ }
128
+
129
+ // punctuation
130
+ if (PUNCTS.includes(c as (typeof PUNCTS)[number])) {
131
+ tokens.push({ type: 'PUNCT', value: c, pos: startPos })
132
+ i += 1
133
+ continue
134
+ }
135
+
136
+ // identifiers / keywords
137
+ if (/[a-zA-Z_]/.test(c)) {
138
+ let val = ''
139
+ while (i < src.length && /[a-zA-Z0-9_]/.test(src[i]!)) {
140
+ val += src[i]!
141
+ i += 1
142
+ }
143
+ const upper = val.toUpperCase()
144
+ if (KEYWORDS.has(upper)) {
145
+ tokens.push({ type: 'KEYWORD', value: upper, pos: startPos })
146
+ } else {
147
+ tokens.push({ type: 'IDENT', value: val, pos: startPos })
148
+ }
149
+ continue
150
+ }
151
+
152
+ throw new Error(
153
+ `DSL parse error: unexpected character '${c}' at position ${i}`,
154
+ )
155
+ }
156
+ tokens.push({ type: 'EOF', value: '', pos: src.length })
157
+ return tokens
158
+ }
159
+
160
+ // =============================================================================
161
+ // Parser (recursive descent)
162
+ // =============================================================================
163
+
164
+ class Parser {
165
+ private pos = 0
166
+ constructor(private tokens: Token[]) {}
167
+
168
+ private peek(offset = 0): Token {
169
+ return this.tokens[this.pos + offset]!
170
+ }
171
+
172
+ private consume(): Token {
173
+ return this.tokens[this.pos++]!
174
+ }
175
+
176
+ private match(type: TokenType, value?: string): boolean {
177
+ const t = this.peek()
178
+ if (t.type !== type) return false
179
+ if (value !== undefined && t.value !== value) return false
180
+ return true
181
+ }
182
+
183
+ private expect(type: TokenType, value?: string): Token {
184
+ if (!this.match(type, value)) {
185
+ const t = this.peek()
186
+ throw new Error(
187
+ `DSL parse error: expected ${value ?? type}, got '${t.value}' at ${t.pos}`,
188
+ )
189
+ }
190
+ return this.consume()
191
+ }
192
+
193
+ parse(): AstNode {
194
+ const expr = this.orExpr()
195
+ this.expect('EOF')
196
+ return expr
197
+ }
198
+
199
+ private orExpr(): AstNode {
200
+ let left = this.andExpr()
201
+ while (this.match('KEYWORD', 'OR')) {
202
+ this.consume()
203
+ const right = this.andExpr()
204
+ left = { kind: 'binary', op: 'OR', left, right }
205
+ }
206
+ return left
207
+ }
208
+
209
+ private andExpr(): AstNode {
210
+ let left = this.notExpr()
211
+ while (this.match('KEYWORD', 'AND')) {
212
+ this.consume()
213
+ const right = this.notExpr()
214
+ left = { kind: 'binary', op: 'AND', left, right }
215
+ }
216
+ return left
217
+ }
218
+
219
+ private notExpr(): AstNode {
220
+ if (this.match('KEYWORD', 'NOT')) {
221
+ this.consume()
222
+ const operand = this.notExpr()
223
+ return { kind: 'unary', op: 'NOT', operand }
224
+ }
225
+ return this.compExpr()
226
+ }
227
+
228
+ private compExpr(): AstNode {
229
+ const left = this.addExpr()
230
+
231
+ // Detecta operadores de comparação
232
+ // ==, !=, <, <=, >, >=
233
+ if (
234
+ this.match('OP', '==') ||
235
+ this.match('OP', '!=') ||
236
+ this.match('OP', '<') ||
237
+ this.match('OP', '<=') ||
238
+ this.match('OP', '>') ||
239
+ this.match('OP', '>=')
240
+ ) {
241
+ const op = this.consume().value as
242
+ | '=='
243
+ | '!='
244
+ | '<'
245
+ | '<='
246
+ | '>'
247
+ | '>='
248
+ const right = this.addExpr()
249
+ return { kind: 'binary', op, left, right }
250
+ }
251
+
252
+ // IS NULL / IS NOT NULL
253
+ if (this.match('KEYWORD', 'IS')) {
254
+ this.consume()
255
+ let negated = false
256
+ if (this.match('KEYWORD', 'NOT')) {
257
+ this.consume()
258
+ negated = true
259
+ }
260
+ this.expect('KEYWORD', 'NULL')
261
+ return { kind: 'isNull', negated, subject: left }
262
+ }
263
+
264
+ // NOT IN / NOT BETWEEN / NOT LIKE / NOT ILIKE
265
+ let negated = false
266
+ if (this.match('KEYWORD', 'NOT')) {
267
+ const save = this.pos
268
+ this.consume()
269
+ if (
270
+ this.match('KEYWORD', 'IN') ||
271
+ this.match('KEYWORD', 'BETWEEN') ||
272
+ this.match('KEYWORD', 'LIKE') ||
273
+ this.match('KEYWORD', 'ILIKE')
274
+ ) {
275
+ negated = true
276
+ } else {
277
+ // não era NOT-de-comparison; foi unary NOT após primary? não é gramaticalmente
278
+ // possível aqui. Restaura.
279
+ this.pos = save
280
+ }
281
+ }
282
+
283
+ if (this.match('KEYWORD', 'IN')) {
284
+ this.consume()
285
+ const values = this.primary()
286
+ return { kind: 'in', negated, left, values }
287
+ }
288
+
289
+ if (this.match('KEYWORD', 'BETWEEN')) {
290
+ this.consume()
291
+ const low = this.addExpr()
292
+ this.expect('KEYWORD', 'AND')
293
+ const high = this.addExpr()
294
+ return { kind: 'between', negated, subject: left, low, high }
295
+ }
296
+
297
+ if (this.match('KEYWORD', 'LIKE') || this.match('KEYWORD', 'ILIKE')) {
298
+ const tok = this.consume()
299
+ const caseInsensitive = tok.value === 'ILIKE'
300
+ const pattern = this.addExpr()
301
+ return { kind: 'like', caseInsensitive, negated, subject: left, pattern }
302
+ }
303
+
304
+ return left
305
+ }
306
+
307
+ private addExpr(): AstNode {
308
+ let left = this.mulExpr()
309
+ while (this.match('OP', '+') || this.match('OP', '-')) {
310
+ const op = this.consume().value as '+' | '-'
311
+ const right = this.mulExpr()
312
+ left = { kind: 'binary', op, left, right }
313
+ }
314
+ return left
315
+ }
316
+
317
+ private mulExpr(): AstNode {
318
+ let left = this.unary()
319
+ while (this.match('OP', '*') || this.match('OP', '/')) {
320
+ const op = this.consume().value as '*' | '/'
321
+ const right = this.unary()
322
+ left = { kind: 'binary', op, left, right }
323
+ }
324
+ return left
325
+ }
326
+
327
+ private unary(): AstNode {
328
+ if (this.match('OP', '-')) {
329
+ this.consume()
330
+ return { kind: 'unary', op: '-', operand: this.unary() }
331
+ }
332
+ return this.primary()
333
+ }
334
+
335
+ private primary(): AstNode {
336
+ // STRING
337
+ if (this.match('STRING')) {
338
+ const t = this.consume()
339
+ return { kind: 'literal', value: t.value }
340
+ }
341
+ // NUMBER
342
+ if (this.match('NUMBER')) {
343
+ const t = this.consume()
344
+ const n = Number(t.value)
345
+ if (Number.isNaN(n)) {
346
+ throw new Error(`DSL parse error: invalid number '${t.value}'`)
347
+ }
348
+ return { kind: 'literal', value: n }
349
+ }
350
+ // booleans / null
351
+ if (this.match('KEYWORD', 'TRUE')) {
352
+ this.consume()
353
+ return { kind: 'literal', value: true }
354
+ }
355
+ if (this.match('KEYWORD', 'FALSE')) {
356
+ this.consume()
357
+ return { kind: 'literal', value: false }
358
+ }
359
+ if (this.match('KEYWORD', 'NULL')) {
360
+ this.consume()
361
+ return { kind: 'literal', value: null }
362
+ }
363
+ // array
364
+ if (this.match('PUNCT', '[')) {
365
+ this.consume()
366
+ const items: AstNode[] = []
367
+ if (!this.match('PUNCT', ']')) {
368
+ items.push(this.orExpr())
369
+ while (this.match('PUNCT', ',')) {
370
+ this.consume()
371
+ items.push(this.orExpr())
372
+ }
373
+ }
374
+ this.expect('PUNCT', ']')
375
+ return { kind: 'array', items }
376
+ }
377
+ // parenthesized
378
+ if (this.match('PUNCT', '(')) {
379
+ this.consume()
380
+ const inner = this.orExpr()
381
+ this.expect('PUNCT', ')')
382
+ return inner
383
+ }
384
+ // identifier / function call / member access
385
+ if (this.match('IDENT')) {
386
+ const name = this.consume().value
387
+ // function call?
388
+ if (this.match('PUNCT', '(')) {
389
+ this.consume()
390
+ const args: AstNode[] = []
391
+ if (!this.match('PUNCT', ')')) {
392
+ args.push(this.orExpr())
393
+ while (this.match('PUNCT', ',')) {
394
+ this.consume()
395
+ args.push(this.orExpr())
396
+ }
397
+ }
398
+ this.expect('PUNCT', ')')
399
+ return { kind: 'call', name: name.toUpperCase(), args }
400
+ }
401
+ // member access (a.b.c)
402
+ let node: AstNode = { kind: 'identifier', name }
403
+ while (this.match('PUNCT', '.')) {
404
+ this.consume()
405
+ const prop = this.expect('IDENT').value
406
+ node = { kind: 'member', object: node, property: prop }
407
+ }
408
+ return node
409
+ }
410
+ const t = this.peek()
411
+ throw new Error(
412
+ `DSL parse error: unexpected token '${t.value}' at ${t.pos}`,
413
+ )
414
+ }
415
+ }
416
+
417
+ // =============================================================================
418
+ // Public
419
+ // =============================================================================
420
+
421
+ export function parseExpression(src: string): AstNode {
422
+ return new Parser(tokenize(src)).parse()
423
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @softize/opus/dsl — AST de expressões DSL.
3
+ *
4
+ * Mesma AST consumida por 3 compilers:
5
+ * - evalExpression() — runtime boolean evaluation (autorize)
6
+ * - compileToKysely() — SQL where clause builder
7
+ * - compileToFilterTree() — UI filter builder (futuro)
8
+ *
9
+ * Inspirada em SQL + JS literals. Ver README do módulo pra gramática
10
+ * completa.
11
+ */
12
+
13
+ // =============================================================================
14
+ // AST nodes (discriminated union por `kind`)
15
+ // =============================================================================
16
+
17
+ export type AstNode =
18
+ | LiteralNode
19
+ | ArrayNode
20
+ | IdentifierNode
21
+ | MemberAccessNode
22
+ | UnaryOpNode
23
+ | BinaryOpNode
24
+ | InNode
25
+ | BetweenNode
26
+ | LikeNode
27
+ | IsNullNode
28
+ | FunctionCallNode
29
+
30
+ export interface LiteralNode {
31
+ kind: 'literal'
32
+ value: string | number | boolean | null
33
+ }
34
+
35
+ export interface ArrayNode {
36
+ kind: 'array'
37
+ items: AstNode[]
38
+ }
39
+
40
+ export interface IdentifierNode {
41
+ kind: 'identifier'
42
+ name: string
43
+ }
44
+
45
+ /** `a.b.c` — encadeamento de access. */
46
+ export interface MemberAccessNode {
47
+ kind: 'member'
48
+ object: AstNode
49
+ property: string
50
+ }
51
+
52
+ export interface UnaryOpNode {
53
+ kind: 'unary'
54
+ op: 'NOT' | '-'
55
+ operand: AstNode
56
+ }
57
+
58
+ export type BinaryOp =
59
+ | '=='
60
+ | '!='
61
+ | '<'
62
+ | '<='
63
+ | '>'
64
+ | '>='
65
+ | 'AND'
66
+ | 'OR'
67
+ | '+'
68
+ | '-'
69
+ | '*'
70
+ | '/'
71
+
72
+ export interface BinaryOpNode {
73
+ kind: 'binary'
74
+ op: BinaryOp
75
+ left: AstNode
76
+ right: AstNode
77
+ }
78
+
79
+ /** `x IN (a, b, c)` — array implícito do lado direito. */
80
+ export interface InNode {
81
+ kind: 'in'
82
+ negated: boolean
83
+ left: AstNode
84
+ values: AstNode // tipicamente ArrayNode
85
+ }
86
+
87
+ export interface BetweenNode {
88
+ kind: 'between'
89
+ negated: boolean
90
+ subject: AstNode
91
+ low: AstNode
92
+ high: AstNode
93
+ }
94
+
95
+ export interface LikeNode {
96
+ kind: 'like'
97
+ caseInsensitive: boolean // ILIKE
98
+ negated: boolean
99
+ subject: AstNode
100
+ pattern: AstNode
101
+ }
102
+
103
+ export interface IsNullNode {
104
+ kind: 'isNull'
105
+ negated: boolean // IS NOT NULL
106
+ subject: AstNode
107
+ }
108
+
109
+ export interface FunctionCallNode {
110
+ kind: 'call'
111
+ name: string // NOW, LOWER, COALESCE, etc
112
+ args: AstNode[]
113
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @softize/opus/events/mitt — in-process EventBus driver
3
+ *
4
+ * Wrappa `mitt` (200 bytes, zero-dep) implementando `EventBusAdapter`.
5
+ * Pra monolito / dev / testes. Distribuição cross-service usa
6
+ * `/redis` ou `/nats` (a virem).
7
+ *
8
+ * Pattern matching:
9
+ * - Nome literal: `subscribe('deal.archived', h)` recebe só esse evento.
10
+ * - Wildcard global: `subscribe('*', h)` recebe **todos** os eventos
11
+ * (mitt nativo). Útil pra audit secundário, analytics fan-out.
12
+ * - Padrões parciais (ex: `'crm.*'`) **não** suportados em v0 — exige
13
+ * wrapper customizado. Use `*` + filtro no handler quando preciso.
14
+ *
15
+ * Uso:
16
+ * import { mittEvents } from '@softize/opus/events/mitt'
17
+ *
18
+ * const bus = mittEvents()
19
+ * bus.subscribe('deal.archived', (event) => console.log(event))
20
+ * createRuntime({ eventBus: bus, ... })
21
+ */
22
+
23
+ import mitt, { type Emitter } from 'mitt'
24
+ import type {
25
+ DomainEvent,
26
+ EventBusAdapter,
27
+ Unsubscribe,
28
+ } from '../../core/index.ts'
29
+
30
+ export interface MittEventsOptions {
31
+ /** Nome do adapter (default 'mitt'). */
32
+ name?: string
33
+ }
34
+
35
+ /**
36
+ * Map de tipos pra mitt: chave é o nome do evento, valor é `DomainEvent`.
37
+ * Usamos `Record<string, DomainEvent>` pra mitt aceitar qualquer string como
38
+ * type (não limita a um union literal).
39
+ */
40
+ type EventMap = Record<string, DomainEvent>
41
+
42
+ export function mittEvents(options: MittEventsOptions = {}): EventBusAdapter {
43
+ const { name = 'mitt' } = options
44
+ const emitter: Emitter<EventMap> = mitt<EventMap>()
45
+
46
+ return {
47
+ name,
48
+ kind: 'eventbus',
49
+
50
+ publish(event: DomainEvent) {
51
+ emitter.emit(event.type, event)
52
+ },
53
+
54
+ subscribe(pattern: string, listener: (event: DomainEvent) => void): Unsubscribe {
55
+ if (pattern === '*') {
56
+ const wildcardHandler = (_type: string | symbol, event: DomainEvent): void => {
57
+ listener(event)
58
+ }
59
+ emitter.on('*', wildcardHandler)
60
+ return () => emitter.off('*', wildcardHandler)
61
+ }
62
+ emitter.on(pattern, listener)
63
+ return () => emitter.off(pattern, listener)
64
+ },
65
+
66
+ healthCheck() {
67
+ return Promise.resolve({ ok: true })
68
+ },
69
+ }
70
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @softize/opus/events — shared helpers/types for event-bus drivers.
3
+ *
4
+ * Drivers (mitt, futuro: redis, nats) implementam EventBusAdapter do core.
5
+ * Espaço reservado pra helpers comuns (wildcard matcher universal,
6
+ * envelope normalization, etc).
7
+ */
8
+
9
+ export {}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @softize/opus/log/pino — Pino logger driver
3
+ *
4
+ * Adapta uma instância `pino.Logger` à interface `LoggerAdapter`.
5
+ *
6
+ * Pino assina `logger.info(obj, msg)` (objeto primeiro, mensagem depois);
7
+ * o LoggerAdapter assina `info(msg, meta?)`. Wrapper reordena.
8
+ *
9
+ * Uso:
10
+ * import pino from 'pino'
11
+ * import { pinoLogger } from '@softize/opus/log/pino'
12
+ *
13
+ * const logger = pino({ level: 'info' })
14
+ * createRuntime({ logger: pinoLogger({ logger }) })
15
+ */
16
+
17
+ import type { LoggerAdapter } from '../../core/index.ts'
18
+
19
+ /**
20
+ * Subset de `pino.Logger` que precisamos. Permite passar qualquer logger
21
+ * pino-compatível (pino, pino-pretty, custom destination).
22
+ */
23
+ export interface PinoCompatible {
24
+ trace(obj: object, msg: string): void
25
+ debug(obj: object, msg: string): void
26
+ info(obj: object, msg: string): void
27
+ warn(obj: object, msg: string): void
28
+ error(obj: object, msg: string): void
29
+ fatal(obj: object, msg: string): void
30
+ child(bindings: object): PinoCompatible
31
+ }
32
+
33
+ export interface PinoLoggerOptions {
34
+ /** Instância pino (ou pino-compatível) já configurada. */
35
+ logger: PinoCompatible
36
+
37
+ /** Nome do adapter (default 'pino'). */
38
+ name?: string
39
+ }
40
+
41
+ export function pinoLogger(options: PinoLoggerOptions): LoggerAdapter {
42
+ return makeAdapter(options.logger, options.name ?? 'pino')
43
+ }
44
+
45
+ function makeAdapter(logger: PinoCompatible, name: string): LoggerAdapter {
46
+ return {
47
+ name,
48
+ kind: 'logger',
49
+ trace: (msg, meta) => logger.trace(meta ?? {}, msg),
50
+ debug: (msg, meta) => logger.debug(meta ?? {}, msg),
51
+ info: (msg, meta) => logger.info(meta ?? {}, msg),
52
+ warn: (msg, meta) => logger.warn(meta ?? {}, msg),
53
+ error: (msg, meta) => logger.error(meta ?? {}, msg),
54
+ fatal: (msg, meta) => logger.fatal(meta ?? {}, msg),
55
+ child: (bindings) => makeAdapter(logger.child(bindings), name),
56
+ }
57
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @softize/opus/log — shared helpers/types for logger drivers.
3
+ *
4
+ * Drivers (pino) são thin wrappers que implementam LoggerAdapter.
5
+ * Espaço reservado pra helpers comuns no futuro (formatters, redactors,
6
+ * sampling).
7
+ */
8
+
9
+ export {}