@ketvietlab/ketjs 0.1.1

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 (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/agent/capabilities.d.ts +62 -0
  4. package/dist/agent/capabilities.d.ts.map +1 -0
  5. package/dist/agent/capabilities.js +89 -0
  6. package/dist/agent/capabilities.js.map +1 -0
  7. package/dist/agent/permissions.d.ts +56 -0
  8. package/dist/agent/permissions.d.ts.map +1 -0
  9. package/dist/agent/permissions.js +209 -0
  10. package/dist/agent/permissions.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +698 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/codegen/dts.d.ts +3 -0
  16. package/dist/codegen/dts.d.ts.map +1 -0
  17. package/dist/codegen/dts.js +94 -0
  18. package/dist/codegen/dts.js.map +1 -0
  19. package/dist/data/adapter.d.ts +4 -0
  20. package/dist/data/adapter.d.ts.map +1 -0
  21. package/dist/data/adapter.js +18 -0
  22. package/dist/data/adapter.js.map +1 -0
  23. package/dist/data/changeset.d.ts +49 -0
  24. package/dist/data/changeset.d.ts.map +1 -0
  25. package/dist/data/changeset.js +206 -0
  26. package/dist/data/changeset.js.map +1 -0
  27. package/dist/data/expr.d.ts +59 -0
  28. package/dist/data/expr.d.ts.map +1 -0
  29. package/dist/data/expr.js +61 -0
  30. package/dist/data/expr.js.map +1 -0
  31. package/dist/data/fleet.d.ts +28 -0
  32. package/dist/data/fleet.d.ts.map +1 -0
  33. package/dist/data/fleet.js +82 -0
  34. package/dist/data/fleet.js.map +1 -0
  35. package/dist/data/list-search.d.ts +93 -0
  36. package/dist/data/list-search.d.ts.map +1 -0
  37. package/dist/data/list-search.js +268 -0
  38. package/dist/data/list-search.js.map +1 -0
  39. package/dist/data/migrate.d.ts +75 -0
  40. package/dist/data/migrate.d.ts.map +1 -0
  41. package/dist/data/migrate.js +136 -0
  42. package/dist/data/migrate.js.map +1 -0
  43. package/dist/data/pool.d.ts +20 -0
  44. package/dist/data/pool.d.ts.map +1 -0
  45. package/dist/data/pool.js +92 -0
  46. package/dist/data/pool.js.map +1 -0
  47. package/dist/data/query.d.ts +111 -0
  48. package/dist/data/query.d.ts.map +1 -0
  49. package/dist/data/query.js +268 -0
  50. package/dist/data/query.js.map +1 -0
  51. package/dist/data/sqlite.d.ts +3 -0
  52. package/dist/data/sqlite.d.ts.map +1 -0
  53. package/dist/data/sqlite.js +107 -0
  54. package/dist/data/sqlite.js.map +1 -0
  55. package/dist/data/time.d.ts +9 -0
  56. package/dist/data/time.d.ts.map +1 -0
  57. package/dist/data/time.js +107 -0
  58. package/dist/data/time.js.map +1 -0
  59. package/dist/index.d.ts +79 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +51 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/kernel/apps.d.ts +43 -0
  64. package/dist/kernel/apps.d.ts.map +1 -0
  65. package/dist/kernel/apps.js +234 -0
  66. package/dist/kernel/apps.js.map +1 -0
  67. package/dist/kernel/compose.d.ts +6 -0
  68. package/dist/kernel/compose.d.ts.map +1 -0
  69. package/dist/kernel/compose.js +976 -0
  70. package/dist/kernel/compose.js.map +1 -0
  71. package/dist/kernel/define.d.ts +9 -0
  72. package/dist/kernel/define.d.ts.map +1 -0
  73. package/dist/kernel/define.js +140 -0
  74. package/dist/kernel/define.js.map +1 -0
  75. package/dist/kernel/diff.d.ts +11 -0
  76. package/dist/kernel/diff.d.ts.map +1 -0
  77. package/dist/kernel/diff.js +106 -0
  78. package/dist/kernel/diff.js.map +1 -0
  79. package/dist/kernel/errors.d.ts +17 -0
  80. package/dist/kernel/errors.d.ts.map +1 -0
  81. package/dist/kernel/errors.js +43 -0
  82. package/dist/kernel/errors.js.map +1 -0
  83. package/dist/kernel/graph.d.ts +3 -0
  84. package/dist/kernel/graph.d.ts.map +1 -0
  85. package/dist/kernel/graph.js +56 -0
  86. package/dist/kernel/graph.js.map +1 -0
  87. package/dist/kernel/i18n.d.ts +43 -0
  88. package/dist/kernel/i18n.d.ts.map +1 -0
  89. package/dist/kernel/i18n.js +79 -0
  90. package/dist/kernel/i18n.js.map +1 -0
  91. package/dist/kernel/index.d.ts +7 -0
  92. package/dist/kernel/index.d.ts.map +1 -0
  93. package/dist/kernel/index.js +7 -0
  94. package/dist/kernel/index.js.map +1 -0
  95. package/dist/kernel/layout.d.ts +17 -0
  96. package/dist/kernel/layout.d.ts.map +1 -0
  97. package/dist/kernel/layout.js +77 -0
  98. package/dist/kernel/layout.js.map +1 -0
  99. package/dist/kernel/menu.d.ts +27 -0
  100. package/dist/kernel/menu.d.ts.map +1 -0
  101. package/dist/kernel/menu.js +65 -0
  102. package/dist/kernel/menu.js.map +1 -0
  103. package/dist/kernel/modules.d.ts +31 -0
  104. package/dist/kernel/modules.d.ts.map +1 -0
  105. package/dist/kernel/modules.js +322 -0
  106. package/dist/kernel/modules.js.map +1 -0
  107. package/dist/kernel/routes.d.ts +24 -0
  108. package/dist/kernel/routes.d.ts.map +1 -0
  109. package/dist/kernel/routes.js +98 -0
  110. package/dist/kernel/routes.js.map +1 -0
  111. package/dist/kernel/types.d.ts +8 -0
  112. package/dist/kernel/types.d.ts.map +1 -0
  113. package/dist/kernel/types.js +67 -0
  114. package/dist/kernel/types.js.map +1 -0
  115. package/dist/kernel/workspace.d.ts +62 -0
  116. package/dist/kernel/workspace.d.ts.map +1 -0
  117. package/dist/kernel/workspace.js +156 -0
  118. package/dist/kernel/workspace.js.map +1 -0
  119. package/dist/scaffold/index.d.ts +2 -0
  120. package/dist/scaffold/index.d.ts.map +1 -0
  121. package/dist/scaffold/index.js +49 -0
  122. package/dist/scaffold/index.js.map +1 -0
  123. package/dist/scaffold/templates/app.test.ts.tmpl +17 -0
  124. package/dist/scaffold/templates/biome.json.tmpl +30 -0
  125. package/dist/scaffold/templates/dev.mjs.tmpl +16 -0
  126. package/dist/scaffold/templates/gitignore.tmpl +24 -0
  127. package/dist/scaffold/templates/ket.workspace.ts.tmpl +18 -0
  128. package/dist/scaffold/templates/module.ts.tmpl +27 -0
  129. package/dist/scaffold/templates/package.json.tmpl +25 -0
  130. package/dist/scaffold/templates/tsconfig.json.tmpl +15 -0
  131. package/dist/server/boot.d.ts +173 -0
  132. package/dist/server/boot.d.ts.map +1 -0
  133. package/dist/server/boot.js +659 -0
  134. package/dist/server/boot.js.map +1 -0
  135. package/dist/server/config.d.ts +48 -0
  136. package/dist/server/config.d.ts.map +1 -0
  137. package/dist/server/config.js +82 -0
  138. package/dist/server/config.js.map +1 -0
  139. package/dist/server/ctx.d.ts +13 -0
  140. package/dist/server/ctx.d.ts.map +1 -0
  141. package/dist/server/ctx.js +496 -0
  142. package/dist/server/ctx.js.map +1 -0
  143. package/dist/server/fn.d.ts +34 -0
  144. package/dist/server/fn.d.ts.map +1 -0
  145. package/dist/server/fn.js +170 -0
  146. package/dist/server/fn.js.map +1 -0
  147. package/dist/server/http.d.ts +79 -0
  148. package/dist/server/http.d.ts.map +1 -0
  149. package/dist/server/http.js +459 -0
  150. package/dist/server/http.js.map +1 -0
  151. package/dist/server/idem.d.ts +31 -0
  152. package/dist/server/idem.d.ts.map +1 -0
  153. package/dist/server/idem.js +68 -0
  154. package/dist/server/idem.js.map +1 -0
  155. package/dist/server/jobs.d.ts +5 -0
  156. package/dist/server/jobs.d.ts.map +1 -0
  157. package/dist/server/jobs.js +24 -0
  158. package/dist/server/jobs.js.map +1 -0
  159. package/dist/server/multipart.d.ts +18 -0
  160. package/dist/server/multipart.d.ts.map +1 -0
  161. package/dist/server/multipart.js +183 -0
  162. package/dist/server/multipart.js.map +1 -0
  163. package/dist/server/project.d.ts +9 -0
  164. package/dist/server/project.d.ts.map +1 -0
  165. package/dist/server/project.js +67 -0
  166. package/dist/server/project.js.map +1 -0
  167. package/dist/server/queue.d.ts +80 -0
  168. package/dist/server/queue.d.ts.map +1 -0
  169. package/dist/server/queue.js +494 -0
  170. package/dist/server/queue.js.map +1 -0
  171. package/dist/server/respond.d.ts +91 -0
  172. package/dist/server/respond.d.ts.map +1 -0
  173. package/dist/server/respond.js +99 -0
  174. package/dist/server/respond.js.map +1 -0
  175. package/dist/server/runtime.d.ts +14 -0
  176. package/dist/server/runtime.d.ts.map +1 -0
  177. package/dist/server/runtime.js +25 -0
  178. package/dist/server/runtime.js.map +1 -0
  179. package/dist/server/session.d.ts +55 -0
  180. package/dist/server/session.d.ts.map +1 -0
  181. package/dist/server/session.js +172 -0
  182. package/dist/server/session.js.map +1 -0
  183. package/dist/server/sessionstore.d.ts +54 -0
  184. package/dist/server/sessionstore.d.ts.map +1 -0
  185. package/dist/server/sessionstore.js +232 -0
  186. package/dist/server/sessionstore.js.map +1 -0
  187. package/dist/server/storage/index.d.ts +11 -0
  188. package/dist/server/storage/index.d.ts.map +1 -0
  189. package/dist/server/storage/index.js +31 -0
  190. package/dist/server/storage/index.js.map +1 -0
  191. package/dist/server/storage/local.d.ts +5 -0
  192. package/dist/server/storage/local.d.ts.map +1 -0
  193. package/dist/server/storage/local.js +165 -0
  194. package/dist/server/storage/local.js.map +1 -0
  195. package/dist/server/storage/s3.d.ts +11 -0
  196. package/dist/server/storage/s3.d.ts.map +1 -0
  197. package/dist/server/storage/s3.js +133 -0
  198. package/dist/server/storage/s3.js.map +1 -0
  199. package/dist/server/storage/sigv4.d.ts +23 -0
  200. package/dist/server/storage/sigv4.d.ts.map +1 -0
  201. package/dist/server/storage/sigv4.js +79 -0
  202. package/dist/server/storage/sigv4.js.map +1 -0
  203. package/dist/server/storage/types.d.ts +37 -0
  204. package/dist/server/storage/types.d.ts.map +1 -0
  205. package/dist/server/storage/types.js +82 -0
  206. package/dist/server/storage/types.js.map +1 -0
  207. package/dist/server/stream.d.ts +43 -0
  208. package/dist/server/stream.d.ts.map +1 -0
  209. package/dist/server/stream.js +117 -0
  210. package/dist/server/stream.js.map +1 -0
  211. package/dist/server/streamstore.d.ts +29 -0
  212. package/dist/server/streamstore.d.ts.map +1 -0
  213. package/dist/server/streamstore.js +140 -0
  214. package/dist/server/streamstore.js.map +1 -0
  215. package/dist/server/tenants.d.ts +108 -0
  216. package/dist/server/tenants.d.ts.map +1 -0
  217. package/dist/server/tenants.js +172 -0
  218. package/dist/server/tenants.js.map +1 -0
  219. package/dist/server/transport/index.d.ts +5 -0
  220. package/dist/server/transport/index.d.ts.map +1 -0
  221. package/dist/server/transport/index.js +3 -0
  222. package/dist/server/transport/index.js.map +1 -0
  223. package/dist/server/transport/memory.d.ts +14 -0
  224. package/dist/server/transport/memory.d.ts.map +1 -0
  225. package/dist/server/transport/memory.js +36 -0
  226. package/dist/server/transport/memory.js.map +1 -0
  227. package/dist/server/transport/types.d.ts +44 -0
  228. package/dist/server/transport/types.d.ts.map +1 -0
  229. package/dist/server/transport/types.js +79 -0
  230. package/dist/server/transport/types.js.map +1 -0
  231. package/dist/server/worker.d.ts +33 -0
  232. package/dist/server/worker.d.ts.map +1 -0
  233. package/dist/server/worker.js +428 -0
  234. package/dist/server/worker.js.map +1 -0
  235. package/dist/testing.d.ts +146 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +406 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme/contracts.d.ts +4 -0
  240. package/dist/theme/contracts.d.ts.map +1 -0
  241. package/dist/theme/contracts.js +87 -0
  242. package/dist/theme/contracts.js.map +1 -0
  243. package/dist/theme/index.d.ts +8 -0
  244. package/dist/theme/index.d.ts.map +1 -0
  245. package/dist/theme/index.js +7 -0
  246. package/dist/theme/index.js.map +1 -0
  247. package/dist/theme/joints.d.ts +19 -0
  248. package/dist/theme/joints.d.ts.map +1 -0
  249. package/dist/theme/joints.js +126 -0
  250. package/dist/theme/joints.js.map +1 -0
  251. package/dist/theme/ktl/compile.d.ts +26 -0
  252. package/dist/theme/ktl/compile.d.ts.map +1 -0
  253. package/dist/theme/ktl/compile.js +226 -0
  254. package/dist/theme/ktl/compile.js.map +1 -0
  255. package/dist/theme/ktl/lexer.d.ts +20 -0
  256. package/dist/theme/ktl/lexer.d.ts.map +1 -0
  257. package/dist/theme/ktl/lexer.js +56 -0
  258. package/dist/theme/ktl/lexer.js.map +1 -0
  259. package/dist/theme/ktl/parser.d.ts +64 -0
  260. package/dist/theme/ktl/parser.d.ts.map +1 -0
  261. package/dist/theme/ktl/parser.js +220 -0
  262. package/dist/theme/ktl/parser.js.map +1 -0
  263. package/dist/theme/render.d.ts +19 -0
  264. package/dist/theme/render.d.ts.map +1 -0
  265. package/dist/theme/render.js +246 -0
  266. package/dist/theme/render.js.map +1 -0
  267. package/dist/theme/templates.d.ts +2 -0
  268. package/dist/theme/templates.d.ts.map +1 -0
  269. package/dist/theme/templates.js +40 -0
  270. package/dist/theme/templates.js.map +1 -0
  271. package/dist/theme/tokens.d.ts +4 -0
  272. package/dist/theme/tokens.d.ts.map +1 -0
  273. package/dist/theme/tokens.js +15 -0
  274. package/dist/theme/tokens.js.map +1 -0
  275. package/dist/theme/viewmodel.d.ts +6 -0
  276. package/dist/theme/viewmodel.d.ts.map +1 -0
  277. package/dist/theme/viewmodel.js +40 -0
  278. package/dist/theme/viewmodel.js.map +1 -0
  279. package/dist/types.d.ts +614 -0
  280. package/dist/types.d.ts.map +1 -0
  281. package/dist/types.js +3 -0
  282. package/dist/types.js.map +1 -0
  283. package/package.json +60 -0
@@ -0,0 +1,136 @@
1
+ // Schema is derived from the manifest, never hand-written; migrations are generated
2
+ // as reviewable operations, never applied silently.
3
+ //
4
+ // The Odoo lesson encoded as a rule: destructive operations are still generated so
5
+ // you can see them, but refused unless explicitly allowed. "Don't drop fields"
6
+ // stops being discipline someone has to remember and becomes something the tool enforces.
7
+ import { sqlTypeOf } from '../kernel/types.js';
8
+ export const tableNameFor = (modelKey) => modelKey
9
+ .replace('.', '_')
10
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
11
+ .toLowerCase();
12
+ export function schemaFromManifest(manifest) {
13
+ const tables = {};
14
+ for (const [modelKey, model] of Object.entries(manifest.models)) {
15
+ const columns = {};
16
+ for (const [fname, f] of Object.entries(model.fields)) {
17
+ columns[fname] = {
18
+ sql: sqlTypeOf(f),
19
+ base: f.base,
20
+ optional: !!f.optional,
21
+ by: f.by,
22
+ target: f.target ?? null,
23
+ };
24
+ }
25
+ tables[tableNameFor(modelKey)] = { model: modelKey, owner: model.owner, columns, indexes: model.indexes };
26
+ }
27
+ return { version: 1, tables };
28
+ }
29
+ export class DestructiveMigrationError extends Error {
30
+ code = 'E_DESTRUCTIVE_MIGRATION';
31
+ ops;
32
+ constructor(message, ops) {
33
+ super(message);
34
+ this.ops = ops;
35
+ }
36
+ }
37
+ export function planMigration(prev, next, opts = {}) {
38
+ const ops = [];
39
+ const prevTables = prev?.tables ?? {};
40
+ for (const [t, def] of Object.entries(next.tables)) {
41
+ const before = prevTables[t];
42
+ if (!before) {
43
+ ops.push({ op: 'CREATE_TABLE', table: t, columns: def.columns, destructive: false });
44
+ for (const [name, index] of Object.entries(def.indexes ?? {}))
45
+ ops.push({ op: 'CREATE_INDEX', table: t, name, def: index, destructive: false });
46
+ continue;
47
+ }
48
+ for (const [c, cd] of Object.entries(def.columns)) {
49
+ const bc = before.columns[c];
50
+ if (!bc) {
51
+ ops.push({ op: 'ADD_COLUMN', table: t, column: c, def: cd, destructive: false });
52
+ continue;
53
+ }
54
+ if (bc.base !== cd.base)
55
+ ops.push({
56
+ op: 'ALTER_COLUMN_TYPE',
57
+ table: t,
58
+ column: c,
59
+ from: bc.base,
60
+ to: cd.base,
61
+ destructive: true,
62
+ });
63
+ }
64
+ // Indexes are dropped before the columns and created after them. Postgres
65
+ // drops any index covering a dropped column as part of ALTER TABLE, so a
66
+ // DROP_INDEX emitted afterwards would fail on an index that is already gone
67
+ // and abort the migration halfway through.
68
+ const creates = [];
69
+ for (const [name, index] of Object.entries(def.indexes ?? {})) {
70
+ const old = before.indexes?.[name];
71
+ if (!old)
72
+ creates.push({ op: 'CREATE_INDEX', table: t, name, def: index, destructive: false });
73
+ else if (old.unique !== index.unique || old.fields.join('\0') !== index.fields.join('\0')) {
74
+ ops.push({ op: 'DROP_INDEX', table: t, name, destructive: true });
75
+ creates.push({ op: 'CREATE_INDEX', table: t, name, def: index, destructive: false });
76
+ }
77
+ }
78
+ for (const name of Object.keys(before.indexes ?? {})) {
79
+ if (!def.indexes?.[name])
80
+ ops.push({ op: 'DROP_INDEX', table: t, name, destructive: true });
81
+ }
82
+ for (const [c, bc] of Object.entries(before.columns)) {
83
+ if (!def.columns[c])
84
+ ops.push({ op: 'DROP_COLUMN', table: t, column: c, by: bc.by, destructive: true });
85
+ }
86
+ ops.push(...creates);
87
+ }
88
+ for (const t of Object.keys(prevTables)) {
89
+ if (!next.tables[t])
90
+ ops.push({ op: 'DROP_TABLE', table: t, destructive: true });
91
+ }
92
+ const destructive = ops.filter((o) => o.destructive);
93
+ if (destructive.length && !opts.allowDestructive) {
94
+ const list = destructive
95
+ .map((o) => {
96
+ const col = 'column' in o ? '.' + o.column : '';
97
+ const by = 'by' in o ? ` (contributed by ${o.by})` : '';
98
+ return ` - ${o.op} ${o.table}${col}${by}`;
99
+ })
100
+ .join('\n');
101
+ throw new DestructiveMigrationError(`migration contains ${destructive.length} destructive operation(s):\n${list}\n\n` +
102
+ `Re-run with --allow-destructive if this is intended. Data in these columns is lost.`, destructive);
103
+ }
104
+ return ops;
105
+ }
106
+ export function renderSql(ops, adapter) {
107
+ const q = (s) => adapter.quoteIdent(s);
108
+ const out = [];
109
+ for (const o of ops) {
110
+ if (o.op === 'CREATE_TABLE') {
111
+ const cols = Object.entries(o.columns).map(([n, c]) => `${q(n)} ${adapter.columnSql(c)}${c.optional || c.base === 'id' ? '' : ' NOT NULL'}`);
112
+ out.push(`CREATE TABLE ${q(o.table)} (\n ${cols.join(',\n ')}\n)`);
113
+ }
114
+ else if (o.op === 'ADD_COLUMN') {
115
+ out.push(`ALTER TABLE ${q(o.table)} ADD COLUMN ${q(o.column)} ${adapter.columnSql(o.def)}`);
116
+ }
117
+ else if (o.op === 'CREATE_INDEX') {
118
+ const name = `${o.table}__${o.name}`;
119
+ out.push(`CREATE ${o.def.unique ? 'UNIQUE ' : ''}INDEX ${q(name)} ON ${q(o.table)} (${o.def.fields.map(q).join(', ')})`);
120
+ }
121
+ else if (o.op === 'DROP_INDEX') {
122
+ out.push(`DROP INDEX ${q(`${o.table}__${o.name}`)}`);
123
+ }
124
+ else if (o.op === 'DROP_COLUMN') {
125
+ out.push(`ALTER TABLE ${q(o.table)} DROP COLUMN ${q(o.column)}`);
126
+ }
127
+ else if (o.op === 'ALTER_COLUMN_TYPE') {
128
+ out.push(`-- type change ${o.table}.${o.column}: ${o.from} -> ${o.to} needs a hand-written data migration`);
129
+ }
130
+ else {
131
+ out.push(`DROP TABLE ${q(o.table)}`);
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../../../../packages/ketjs/src/data/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Adapter } from '../types.ts';
2
+ export type PoolOptions = {
3
+ create(key: string): Adapter;
4
+ /** How many databases may stay open at once. */
5
+ max?: number;
6
+ /** Close an idle database after this long. */
7
+ idleMs?: number;
8
+ now?: () => number;
9
+ };
10
+ export type AdapterPool = {
11
+ acquire(key: string): Promise<Adapter>;
12
+ release(key: string): void;
13
+ with<T>(key: string, fn: (adapter: Adapter) => Promise<T>): Promise<T>;
14
+ evictIdle(): Promise<number>;
15
+ close(): Promise<void>;
16
+ readonly open: string[];
17
+ readonly size: number;
18
+ };
19
+ export declare function createAdapterPool(o: PoolOptions): AdapterPool;
20
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../../../packages/ketjs/src/data/pool.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC5B,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAID,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACtE,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,GAAG,WAAW,CAiF7D"}
@@ -0,0 +1,92 @@
1
+ // One adapter per database, kept warm and bounded.
2
+ //
3
+ // This is where the Odoo model hurts most in practice: a database per tenant means
4
+ // connections multiply by tenant, and a Postgres cluster has a hard ceiling on them.
5
+ // So the pool caps how many databases stay open at once, evicts the least recently
6
+ // used, and refuses to close one that a request is still holding.
7
+ export function createAdapterPool(o) {
8
+ const max = o.max ?? 32;
9
+ const idleMs = o.idleMs ?? 5 * 60_000;
10
+ const now = o.now ?? (() => Date.now());
11
+ const entries = new Map();
12
+ const evictOne = async () => {
13
+ let oldest = null;
14
+ for (const [k, e] of entries) {
15
+ if (e.leases > 0)
16
+ continue;
17
+ if (!oldest || e.lastUsed < oldest[1].lastUsed)
18
+ oldest = [k, e];
19
+ }
20
+ if (!oldest)
21
+ return false;
22
+ entries.delete(oldest[0]);
23
+ await oldest[1].adapter.close();
24
+ return true;
25
+ };
26
+ const acquire = async (key) => {
27
+ let e = entries.get(key);
28
+ if (!e) {
29
+ // Make room before opening, never after: the cap is on connections actually held.
30
+ while (entries.size >= max) {
31
+ if (!(await evictOne())) {
32
+ throw new Error(`adapter pool is full (${entries.size}/${max}) and every database is in use`);
33
+ }
34
+ }
35
+ const adapter = o.create(key);
36
+ e = { adapter, leases: 0, lastUsed: now(), opening: null };
37
+ entries.set(key, e);
38
+ e.opening = adapter.open();
39
+ }
40
+ if (e.opening) {
41
+ await e.opening;
42
+ e.opening = null;
43
+ }
44
+ e.leases++;
45
+ e.lastUsed = now();
46
+ return e.adapter;
47
+ };
48
+ const release = (key) => {
49
+ const e = entries.get(key);
50
+ if (!e)
51
+ return;
52
+ e.leases = Math.max(0, e.leases - 1);
53
+ e.lastUsed = now();
54
+ };
55
+ return {
56
+ acquire,
57
+ release,
58
+ async with(key, fn) {
59
+ const adapter = await acquire(key);
60
+ try {
61
+ return await fn(adapter);
62
+ }
63
+ finally {
64
+ release(key);
65
+ }
66
+ },
67
+ async evictIdle() {
68
+ const cutoff = now() - idleMs;
69
+ let n = 0;
70
+ for (const [k, e] of [...entries]) {
71
+ if (e.leases > 0 || e.lastUsed > cutoff)
72
+ continue;
73
+ entries.delete(k);
74
+ await e.adapter.close();
75
+ n++;
76
+ }
77
+ return n;
78
+ },
79
+ async close() {
80
+ for (const [, e] of entries)
81
+ await e.adapter.close();
82
+ entries.clear();
83
+ },
84
+ get open() {
85
+ return [...entries.keys()];
86
+ },
87
+ get size() {
88
+ return entries.size;
89
+ },
90
+ };
91
+ }
92
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../../../../packages/ketjs/src/data/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import type { Col, Expr } from './expr.ts';
2
+ import type { Manifest } from '../types.ts';
3
+ import type { GroupInterval } from './time.ts';
4
+ export type Dialect = 'sqlite' | 'postgres';
5
+ export type Sql = {
6
+ text: string;
7
+ params: unknown[];
8
+ touches: string[];
9
+ };
10
+ export type Order = {
11
+ col: Col;
12
+ dir: 'asc' | 'desc';
13
+ };
14
+ export type GroupSpec = {
15
+ col: Col;
16
+ interval?: GroupInterval;
17
+ timezone?: string;
18
+ };
19
+ export type AggregateSpec = {
20
+ fn: 'count';
21
+ as: string;
22
+ } | {
23
+ fn: 'countDistinct' | 'sum' | 'avg' | 'min' | 'max';
24
+ col: Col;
25
+ as: string;
26
+ };
27
+ export type GroupOrder = {
28
+ by: 'key' | 'count' | string;
29
+ dir: 'asc' | 'desc';
30
+ };
31
+ export type GroupRow = {
32
+ key: unknown[];
33
+ count: number;
34
+ aggregates: Record<string, unknown>;
35
+ };
36
+ export type Table<T = Record<string, Col>> = T & {
37
+ readonly $model: string;
38
+ readonly $columns: string[];
39
+ };
40
+ export declare function table(manifest: Manifest, model: string): Table;
41
+ export type QueryKind = 'select' | 'count' | 'delete' | 'group';
42
+ /** Related rows a query has asked for. Never populated by touching a property. */
43
+ export type Preload = {
44
+ name: string;
45
+ };
46
+ export declare class Query {
47
+ readonly kind: QueryKind;
48
+ readonly model: string;
49
+ readonly columns: readonly string[] | null;
50
+ readonly condition: Expr | null;
51
+ readonly order: readonly Order[];
52
+ readonly limitN: number | null;
53
+ readonly offsetN: number | null;
54
+ readonly preloads: readonly Preload[];
55
+ readonly groups: readonly GroupSpec[];
56
+ readonly aggregates: readonly AggregateSpec[];
57
+ readonly groupOrder: readonly GroupOrder[];
58
+ constructor(init: {
59
+ kind: QueryKind;
60
+ model: string;
61
+ columns?: readonly string[] | null;
62
+ condition?: Expr | null;
63
+ order?: readonly Order[];
64
+ limitN?: number | null;
65
+ offsetN?: number | null;
66
+ preloads?: readonly Preload[];
67
+ groups?: readonly GroupSpec[];
68
+ aggregates?: readonly AggregateSpec[];
69
+ groupOrder?: readonly GroupOrder[];
70
+ });
71
+ private with;
72
+ select(...cols: Col[]): Query;
73
+ /** Additional conditions are ANDed, so a query can be narrowed by several callers. */
74
+ where(...parts: Expr[]): Query;
75
+ orderBy(...order: Order[]): Query;
76
+ limit(n: number): Query;
77
+ offset(n: number): Query;
78
+ count(): Query;
79
+ groupBy(...groups: GroupSpec[]): Query;
80
+ aggregate(...aggregates: AggregateSpec[]): Query;
81
+ orderGroupsBy(...order: GroupOrder[]): Query;
82
+ /**
83
+ * Ask for a declared relation alongside the rows. Two queries, not one per row:
84
+ * the parents, then the children by id. There is no lazy alternative, which is
85
+ * exactly why an accidental N+1 cannot be written here.
86
+ */
87
+ preload(...names: string[]): Query;
88
+ /** Every model this query reads or writes. Checked against declared effects. */
89
+ get touches(): string[];
90
+ get effect(): 'read' | 'write';
91
+ toSQL(dialect?: Dialect): Sql;
92
+ toJSON(): {
93
+ kind: QueryKind;
94
+ model: string;
95
+ columns: readonly string[] | null;
96
+ where: Expr | null;
97
+ order: readonly Order[];
98
+ limit: number | null;
99
+ offset: number | null;
100
+ preloads: string[];
101
+ groups: readonly GroupSpec[];
102
+ aggregates: readonly AggregateSpec[];
103
+ groupOrder: readonly GroupOrder[];
104
+ touches: string[];
105
+ };
106
+ }
107
+ export declare const from: (t: Table) => Query;
108
+ export declare const deleteFrom: (t: Table) => Query;
109
+ export declare const asc: (col: Col) => Order;
110
+ export declare const desc: (col: Col) => Order;
111
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../../packages/ketjs/src/data/query.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAG1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9C,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAA;AAC3C,MAAM,MAAM,GAAG,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AACxE,MAAM,MAAM,KAAK,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAA;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;IAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAA;AAC9E,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAA;AAE7F,MAAM,MAAM,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAIzG,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAc9D;AAED,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE/D,kFAAkF;AAClF,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,qBAAa,KAAK;IAChB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAC1C,QAAQ,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAA;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,UAAU,EAAE,CAAA;IAE1C,YAAY,IAAI,EAAE;QAChB,IAAI,EAAE,SAAS,CAAA;QACf,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;QAClC,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;QACvB,KAAK,CAAC,EAAE,SAAS,KAAK,EAAE,CAAA;QACxB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACvB,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;QAC7B,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;QAC7B,UAAU,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;QACrC,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;KACnC,EAaA;IAED,OAAO,CAAC,IAAI;IAiBZ,MAAM,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAE5B;IACD,sFAAsF;IACtF,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,CAG7B;IACD,OAAO,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAEhC;IACD,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAEtB;IACD,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAEvB;IACD,KAAK,IAAI,KAAK,CAEb;IACD,OAAO,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAWrC;IACD,SAAS,CAAC,GAAG,UAAU,EAAE,aAAa,EAAE,GAAG,KAAK,CAc/C;IACD,aAAa,CAAC,GAAG,KAAK,EAAE,UAAU,EAAE,GAAG,KAAK,CAE3C;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAEjC;IAED,gFAAgF;IAChF,IAAI,OAAO,IAAI,MAAM,EAAE,CAMtB;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,OAAO,CAE7B;IAED,KAAK,CAAC,OAAO,GAAE,OAAkB,GAAG,GAAG,CAkGtC;IAED,MAAM;QAEF,IAAI;QACJ,KAAK;QACL,OAAO;QACP,KAAK;QACL,KAAK;QACL,KAAK;QACL,MAAM;QACN,QAAQ;QACR,MAAM;QACN,UAAU;QACV,UAAU;QACV,OAAO;MAEV;CACF;AAED,eAAO,MAAM,IAAI,MAAO,KAAK,KAAG,KAAuD,CAAA;AACvF,eAAO,MAAM,UAAU,MAAO,KAAK,KAAG,KAAuD,CAAA;AAC7F,eAAO,MAAM,GAAG,QAAS,GAAG,KAAG,KAA8B,CAAA;AAC7D,eAAO,MAAM,IAAI,QAAS,GAAG,KAAG,KAA+B,CAAA"}
@@ -0,0 +1,268 @@
1
+ // A query is an immutable value. Builder methods return a new query rather than
2
+ // mutating one, so a query can be passed through several modules and refined by
3
+ // each without any of them stepping on the others — Ecto's composability with a
4
+ // chainable surface.
5
+ import { exprTouches, and } from './expr.js';
6
+ import { tableNameFor } from './migrate.js';
7
+ import { KetError } from '../kernel/errors.js';
8
+ import { assertTimezone } from './time.js';
9
+ // Column handles are built once from the manifest. No proxies, no magic: a column
10
+ // that does not exist simply is not there, and the expression helpers reject it.
11
+ export function table(manifest, model) {
12
+ const def = manifest.models[model];
13
+ if (!def) {
14
+ throw new KetError({
15
+ code: 'E_UNKNOWN_MODEL',
16
+ message: `no model "${model}"`,
17
+ hint: `known models: ${Object.keys(manifest.models).join(', ') || '(none)'}`,
18
+ });
19
+ }
20
+ const t = Object.create(null);
21
+ for (const name of Object.keys(def.fields))
22
+ t[name] = Object.freeze({ model, name });
23
+ t['$model'] = model;
24
+ t['$columns'] = Object.keys(def.fields);
25
+ return Object.freeze(t);
26
+ }
27
+ export class Query {
28
+ kind;
29
+ model;
30
+ columns;
31
+ condition;
32
+ order;
33
+ limitN;
34
+ offsetN;
35
+ preloads;
36
+ groups;
37
+ aggregates;
38
+ groupOrder;
39
+ constructor(init) {
40
+ this.kind = init.kind;
41
+ this.model = init.model;
42
+ this.columns = init.columns ?? null;
43
+ this.condition = init.condition ?? null;
44
+ this.order = init.order ?? [];
45
+ this.limitN = init.limitN ?? null;
46
+ this.offsetN = init.offsetN ?? null;
47
+ this.preloads = init.preloads ?? [];
48
+ this.groups = init.groups ?? [];
49
+ this.aggregates = init.aggregates ?? [];
50
+ this.groupOrder = init.groupOrder ?? [];
51
+ Object.freeze(this);
52
+ }
53
+ with(patch) {
54
+ return new Query({
55
+ kind: this.kind,
56
+ model: this.model,
57
+ columns: this.columns,
58
+ condition: this.condition,
59
+ order: this.order,
60
+ limitN: this.limitN,
61
+ offsetN: this.offsetN,
62
+ preloads: this.preloads,
63
+ groups: this.groups,
64
+ aggregates: this.aggregates,
65
+ groupOrder: this.groupOrder,
66
+ ...patch,
67
+ });
68
+ }
69
+ select(...cols) {
70
+ return this.with({ columns: cols.map((c) => c.name) });
71
+ }
72
+ /** Additional conditions are ANDed, so a query can be narrowed by several callers. */
73
+ where(...parts) {
74
+ const next = parts.length === 1 ? parts[0] : and(...parts);
75
+ return this.with({ condition: this.condition ? and(this.condition, next) : next });
76
+ }
77
+ orderBy(...order) {
78
+ return this.with({ order: [...this.order, ...order] });
79
+ }
80
+ limit(n) {
81
+ return this.with({ limitN: n });
82
+ }
83
+ offset(n) {
84
+ return this.with({ offsetN: n });
85
+ }
86
+ count() {
87
+ return this.with({ kind: 'count', columns: null, order: [] });
88
+ }
89
+ groupBy(...groups) {
90
+ if (!groups.length)
91
+ throw new KetError({ code: 'E_GROUP_EMPTY', message: 'groupBy requires a field' });
92
+ for (const group of groups) {
93
+ if (group.col.model !== this.model)
94
+ throw new KetError({
95
+ code: 'E_GROUP_MODEL',
96
+ message: `cannot group ${this.model} by ${group.col.model}`,
97
+ });
98
+ if (group.interval && group.timezone)
99
+ assertTimezone(group.timezone);
100
+ }
101
+ return this.with({ kind: 'group', columns: null, groups: [...this.groups, ...groups] });
102
+ }
103
+ aggregate(...aggregates) {
104
+ for (const aggregate of aggregates) {
105
+ if (aggregate.fn !== 'count' && aggregate.col.model !== this.model)
106
+ throw new KetError({
107
+ code: 'E_GROUP_MODEL',
108
+ message: `cannot aggregate ${aggregate.col.model} on ${this.model}`,
109
+ });
110
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(aggregate.as) || aggregate.as === 'count')
111
+ throw new KetError({
112
+ code: 'E_AGGREGATE_ALIAS',
113
+ message: `invalid aggregate alias "${aggregate.as}"`,
114
+ });
115
+ }
116
+ return this.with({ aggregates: [...this.aggregates, ...aggregates] });
117
+ }
118
+ orderGroupsBy(...order) {
119
+ return this.with({ groupOrder: [...this.groupOrder, ...order] });
120
+ }
121
+ /**
122
+ * Ask for a declared relation alongside the rows. Two queries, not one per row:
123
+ * the parents, then the children by id. There is no lazy alternative, which is
124
+ * exactly why an accidental N+1 cannot be written here.
125
+ */
126
+ preload(...names) {
127
+ return this.with({ preloads: [...this.preloads, ...names.map((name) => ({ name }))] });
128
+ }
129
+ /** Every model this query reads or writes. Checked against declared effects. */
130
+ get touches() {
131
+ const s = exprTouches(this.condition);
132
+ s.add(this.model);
133
+ for (const group of this.groups)
134
+ s.add(group.col.model);
135
+ for (const aggregate of this.aggregates)
136
+ if (aggregate.fn !== 'count')
137
+ s.add(aggregate.col.model);
138
+ return [...s].sort();
139
+ }
140
+ get effect() {
141
+ return this.kind === 'delete' ? 'write' : 'read';
142
+ }
143
+ toSQL(dialect = 'sqlite') {
144
+ const params = [];
145
+ const q = (s) => `"${s.replace(/"/g, '""')}"`;
146
+ const ph = () => (dialect === 'postgres' ? `$${params.length}` : '?');
147
+ const bind = (v) => {
148
+ params.push(v);
149
+ return ph();
150
+ };
151
+ const colSql = (c) => `${q(tableNameFor(c.model))}.${q(c.name)}`;
152
+ const render = (e) => {
153
+ if (e.op === 'and' || e.op === 'or') {
154
+ if (!e.parts.length)
155
+ return e.op === 'and' ? '1 = 1' : '1 = 0';
156
+ return '(' + e.parts.map(render).join(e.op === 'and' ? ' AND ' : ' OR ') + ')';
157
+ }
158
+ if (e.op === 'not')
159
+ return `NOT (${render(e.expr)})`;
160
+ if (e.op === 'null')
161
+ return `${colSql(e.col)} IS ${e.negated ? 'NOT ' : ''}NULL`;
162
+ if (e.op === 'bucket') {
163
+ const column = colSql(e.col);
164
+ if (dialect === 'postgres') {
165
+ const format = e.interval === 'quarter'
166
+ ? 'YYYY-"Q"Q'
167
+ : e.interval === 'year'
168
+ ? 'YYYY'
169
+ : e.interval === 'month'
170
+ ? 'YYYY-MM'
171
+ : 'YYYY-MM-DD';
172
+ return `TO_CHAR(DATE_TRUNC('${e.interval}', ${column} AT TIME ZONE ${bind(e.timezone)}), '${format}') = ${bind(e.value)}`;
173
+ }
174
+ return `ket_date_bucket(${column}, ${bind(e.interval)}, ${bind(e.timezone)}) = ${bind(e.value)}`;
175
+ }
176
+ if (e.op === 'like')
177
+ return `${colSql(e.col)} ${e.insensitive && dialect === 'postgres' ? 'ILIKE' : 'LIKE'} ${bind(e.value)}${e.escape ? ` ESCAPE '\\'` : ''}`;
178
+ if (e.op === 'in') {
179
+ if (!e.values.length)
180
+ return '1 = 0';
181
+ return `${colSql(e.col)} IN (${e.values.map(bind).join(', ')})`;
182
+ }
183
+ const compared = e.numeric && dialect === 'sqlite' ? `CAST(${colSql(e.col)} AS REAL)` : colSql(e.col);
184
+ return `${compared} ${e.cmp} ${bind(e.value)}`;
185
+ };
186
+ const t = q(tableNameFor(this.model));
187
+ let text;
188
+ if (this.kind === 'delete')
189
+ text = `DELETE FROM ${t}`;
190
+ else if (this.kind === 'count')
191
+ text = `SELECT COUNT(*) AS count FROM ${t}`;
192
+ else if (this.kind === 'group') {
193
+ const groupSql = this.groups.map((group) => {
194
+ const column = colSql(group.col);
195
+ if (!group.interval)
196
+ return column;
197
+ const timezone = group.timezone ?? 'UTC';
198
+ if (dialect === 'postgres') {
199
+ if (group.interval === 'quarter')
200
+ return `TO_CHAR(DATE_TRUNC('quarter', ${column} AT TIME ZONE ${bind(timezone)}), 'YYYY-"Q"Q')`;
201
+ const format = group.interval === 'year' ? 'YYYY' : group.interval === 'month' ? 'YYYY-MM' : 'YYYY-MM-DD';
202
+ return `TO_CHAR(DATE_TRUNC('${group.interval}', ${column} AT TIME ZONE ${bind(timezone)}), '${format}')`;
203
+ }
204
+ return `ket_date_bucket(${column}, ${bind(group.interval)}, ${bind(timezone)})`;
205
+ });
206
+ const aggregateSql = this.aggregates.map((aggregate) => {
207
+ if (aggregate.fn === 'count')
208
+ return `COUNT(*) AS ${q(aggregate.as)}`;
209
+ const fn = aggregate.fn === 'countDistinct' ? 'COUNT' : aggregate.fn.toUpperCase();
210
+ const body = aggregate.fn === 'countDistinct' ? `DISTINCT ${colSql(aggregate.col)}` : colSql(aggregate.col);
211
+ return `${fn}(${body}) AS ${q(aggregate.as)}`;
212
+ });
213
+ text = `SELECT ${groupSql.map((sql, i) => `${sql} AS ${q(`__group${i}`)}`).join(', ')}, COUNT(*) AS ${q('__count')}${aggregateSql.length ? `, ${aggregateSql.join(', ')}` : ''} FROM ${t}`;
214
+ }
215
+ else
216
+ text = `SELECT ${this.columns ? this.columns.map((c) => `${t}.${q(c)}`).join(', ') : `${t}.*`} FROM ${t}`;
217
+ if (this.condition)
218
+ text += ` WHERE ${render(this.condition)}`;
219
+ if (this.kind === 'group') {
220
+ text += ` GROUP BY ${this.groups.map((_, i) => String(i + 1)).join(', ')}`;
221
+ if (this.groupOrder.length) {
222
+ const aliases = new Set(this.aggregates.map((a) => a.as));
223
+ text += ` ORDER BY ${this.groupOrder
224
+ .map((order) => {
225
+ const by = order.by === 'key'
226
+ ? q('__group0')
227
+ : order.by === 'count'
228
+ ? q('__count')
229
+ : aliases.has(order.by)
230
+ ? q(order.by)
231
+ : null;
232
+ if (!by)
233
+ throw new KetError({ code: 'E_GROUP_ORDER', message: `unknown group order "${order.by}"` });
234
+ return `${by} ${order.dir.toUpperCase()}`;
235
+ })
236
+ .join(', ')}`;
237
+ }
238
+ }
239
+ else if (this.order.length)
240
+ text += ` ORDER BY ${this.order.map((o) => `${colSql(o.col)} ${o.dir.toUpperCase()}`).join(', ')}`;
241
+ if (this.limitN != null)
242
+ text += ` LIMIT ${bind(this.limitN)}`;
243
+ if (this.offsetN != null)
244
+ text += ` OFFSET ${bind(this.offsetN)}`;
245
+ return { text, params, touches: this.touches };
246
+ }
247
+ toJSON() {
248
+ return {
249
+ kind: this.kind,
250
+ model: this.model,
251
+ columns: this.columns,
252
+ where: this.condition,
253
+ order: this.order,
254
+ limit: this.limitN,
255
+ offset: this.offsetN,
256
+ preloads: this.preloads.map((p) => p.name),
257
+ groups: this.groups,
258
+ aggregates: this.aggregates,
259
+ groupOrder: this.groupOrder,
260
+ touches: this.touches,
261
+ };
262
+ }
263
+ }
264
+ export const from = (t) => new Query({ kind: 'select', model: t.$model });
265
+ export const deleteFrom = (t) => new Query({ kind: 'delete', model: t.$model });
266
+ export const asc = (col) => ({ col, dir: 'asc' });
267
+ export const desc = (col) => ({ col, dir: 'desc' });
268
+ //# sourceMappingURL=query.js.map