@sincpro/mobile 0.4.4 → 1.0.0

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 (165) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/JsonSerializer.adapter.d.ts +1 -1
  3. package/dist/adapters/JsonSerializer.adapter.js +2 -2
  4. package/dist/adapters/repositories/database_table.repository.d.ts +3 -2
  5. package/dist/adapters/repositories/database_table.repository.js +1 -1
  6. package/dist/adapters/repositories/document.repository.d.ts +81 -0
  7. package/dist/adapters/repositories/document.repository.js +653 -0
  8. package/dist/adapters/repositories/domain_event.repository.d.ts +17 -17
  9. package/dist/adapters/repositories/domain_event.repository.js +25 -31
  10. package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +10 -10
  11. package/dist/adapters/repositories/domain_event_dead_letter.repository.js +14 -13
  12. package/dist/adapters/repositories/event_row.d.ts +13 -0
  13. package/dist/adapters/repositories/event_row.js +22 -0
  14. package/dist/adapters/repositories/sqlite_document_store.d.ts +33 -0
  15. package/dist/adapters/repositories/sqlite_document_store.js +201 -0
  16. package/dist/domain/connectivity/events.d.ts +5 -5
  17. package/dist/domain/connectivity/events.js +3 -3
  18. package/dist/domain/database/criteria.d.ts +19 -0
  19. package/dist/domain/database/criteria.js +36 -0
  20. package/dist/domain/database/criteria_clock.d.ts +8 -0
  21. package/dist/domain/database/criteria_clock.js +305 -0
  22. package/dist/domain/database/criteria_sql.d.ts +61 -0
  23. package/dist/domain/database/criteria_sql.js +476 -0
  24. package/dist/domain/database/database.d.ts +1 -0
  25. package/dist/domain/database/index.d.ts +3 -1
  26. package/dist/domain/database/index.js +3 -1
  27. package/dist/domain/database/migration_plan.d.ts +14 -0
  28. package/dist/domain/database/migration_plan.js +44 -0
  29. package/dist/domain/entity/change_tracking.d.ts +21 -0
  30. package/dist/domain/entity/change_tracking.js +71 -0
  31. package/dist/domain/entity/checks.d.ts +8 -0
  32. package/dist/domain/entity/checks.js +82 -0
  33. package/dist/domain/entity/entity.d.ts +39 -32
  34. package/dist/domain/entity/entity.js +94 -105
  35. package/dist/domain/entity/entity_collection.d.ts +15 -30
  36. package/dist/domain/entity/entity_collection.js +30 -121
  37. package/dist/domain/entity/index.d.ts +5 -0
  38. package/dist/domain/entity/index.js +5 -0
  39. package/dist/domain/entity/reconcile.d.ts +7 -0
  40. package/dist/domain/entity/reconcile.js +12 -0
  41. package/dist/domain/entity/relations.d.ts +27 -0
  42. package/dist/domain/entity/relations.js +69 -0
  43. package/dist/domain/entity/remote_sync.d.ts +4 -0
  44. package/dist/domain/entity/remote_sync.js +19 -0
  45. package/dist/domain/entity/values.d.ts +6 -0
  46. package/dist/domain/entity/values.js +13 -0
  47. package/dist/domain/events/catalog.d.ts +29 -0
  48. package/dist/domain/{events.js → events/catalog.js} +7 -11
  49. package/dist/domain/events/chain.d.ts +5 -0
  50. package/dist/domain/events/chain.js +6 -0
  51. package/dist/domain/{event_sourcing → events}/domain_event.d.ts +20 -22
  52. package/dist/domain/events/domain_event.js +227 -0
  53. package/dist/domain/events/index.d.ts +8 -0
  54. package/dist/domain/events/index.js +8 -0
  55. package/dist/domain/events/integration_event.d.ts +103 -0
  56. package/dist/domain/events/integration_event.js +72 -0
  57. package/dist/domain/events/label.d.ts +2 -0
  58. package/dist/domain/events/label.js +26 -0
  59. package/dist/domain/index.d.ts +0 -1
  60. package/dist/domain/index.js +0 -1
  61. package/dist/domain/print/events.d.ts +8 -4
  62. package/dist/domain/print/events.js +9 -4
  63. package/dist/domain/repositories/document_store.d.ts +33 -0
  64. package/dist/domain/repositories/document_store.js +5 -0
  65. package/dist/domain/repositories/hooks.d.ts +23 -0
  66. package/dist/domain/repositories/hooks.js +32 -0
  67. package/dist/domain/repositories/index.d.ts +4 -0
  68. package/dist/domain/repositories/index.js +4 -0
  69. package/dist/domain/{database → repositories}/repository.d.ts +9 -3
  70. package/dist/domain/settings.d.ts +4 -1
  71. package/dist/domain/settings.js +3 -0
  72. package/dist/domain/webview/events.d.ts +2 -2
  73. package/dist/domain/webview/events.js +2 -2
  74. package/dist/entrypoints/db/migrations.js +10 -10
  75. package/dist/entrypoints/queue/QueueProcessor.d.ts +1 -1
  76. package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +2 -2
  77. package/dist/entrypoints/queue/activateDomain.subscriber.js +2 -2
  78. package/dist/entrypoints/queue/newAppSettings.handler.d.ts +2 -2
  79. package/dist/entrypoints/queue/newAppSettings.handler.js +1 -1
  80. package/dist/entrypoints/queue/printImage.subscriber.d.ts +1 -1
  81. package/dist/entrypoints/queue/printImage.subscriber.js +2 -2
  82. package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +1 -1
  83. package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +1 -1
  84. package/dist/entrypoints/ui/AppShell.js +2 -2
  85. package/dist/entrypoints/ui/common_provider.d.ts +0 -12
  86. package/dist/entrypoints/ui/common_provider.js +2 -131
  87. package/dist/event_driven/index.d.ts +1 -0
  88. package/dist/event_driven/index.js +1 -0
  89. package/dist/{domain/event_sourcing/event_handler.d.ts → event_driven/subscriber.d.ts} +1 -1
  90. package/dist/exceptions.d.ts +21 -0
  91. package/dist/exceptions.js +30 -0
  92. package/dist/framework/base_module.d.ts +1 -1
  93. package/dist/framework/createApp.d.ts +3 -3
  94. package/dist/framework/domain_module.d.ts +1 -1
  95. package/dist/framework/kernel.d.ts +1 -1
  96. package/dist/framework/kernel.js +17 -8
  97. package/dist/infrastructure/database/connector.d.ts +2 -0
  98. package/dist/infrastructure/database/connector.js +22 -0
  99. package/dist/infrastructure/database/index.d.ts +1 -0
  100. package/dist/infrastructure/database/index.js +1 -0
  101. package/dist/infrastructure/database/schema.d.ts +9 -0
  102. package/dist/infrastructure/database/schema.js +61 -0
  103. package/dist/infrastructure/database/utils.d.ts +2 -0
  104. package/dist/infrastructure/database/utils.js +12 -4
  105. package/dist/infrastructure/event_chain.d.ts +6 -0
  106. package/dist/infrastructure/event_chain.js +25 -0
  107. package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +8 -5
  108. package/dist/infrastructure/ui/UIEventBus.d.ts +3 -1
  109. package/dist/infrastructure/ui/UIEventBus.js +8 -1
  110. package/dist/infrastructure/ui/activity.d.ts +37 -0
  111. package/dist/infrastructure/ui/activity.js +84 -0
  112. package/dist/infrastructure/workers/CronWorker.d.ts +6 -4
  113. package/dist/infrastructure/workers/CronWorker.js +24 -18
  114. package/dist/infrastructure/workers/EventBus.d.ts +9 -5
  115. package/dist/infrastructure/workers/EventBus.js +68 -33
  116. package/dist/services/bluetooth.service.js +16 -14
  117. package/dist/services/dead_letter_queue.service.d.ts +7 -7
  118. package/dist/services/dead_letter_queue.service.js +0 -2
  119. package/dist/services/event.service.d.ts +3 -3
  120. package/dist/services/event.service.js +1 -1
  121. package/dist/services/network.service.d.ts +1 -1
  122. package/dist/services/network.service.js +17 -15
  123. package/dist/services/printer.service.d.ts +1 -1
  124. package/dist/services/printer.service.js +50 -44
  125. package/dist/tools/utils/date.js +4 -3
  126. package/dist/tools/utils/monetary.d.ts +6 -3
  127. package/dist/tools/utils/monetary.js +33 -21
  128. package/dist/tools/utils/serializer.d.ts +1 -1
  129. package/dist/tools/utils/serializer.js +32 -35
  130. package/dist/ui/activity/DeviceNotices.d.ts +1 -0
  131. package/dist/ui/activity/DeviceNotices.js +29 -0
  132. package/dist/ui/activity/ProcessToast.d.ts +2 -0
  133. package/dist/ui/activity/ProcessToast.js +71 -0
  134. package/dist/ui/activity/index.d.ts +4 -0
  135. package/dist/ui/activity/index.js +4 -0
  136. package/dist/ui/activity/useActivity.d.ts +9 -0
  137. package/dist/ui/activity/useActivity.js +48 -0
  138. package/dist/ui/components/molecules/EventTimelineItem.d.ts +2 -2
  139. package/dist/ui/components/molecules/EventTimelineItem.js +1 -1
  140. package/dist/ui/components/molecules/index.d.ts +1 -2
  141. package/dist/ui/components/molecules/index.js +1 -2
  142. package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +3 -3
  143. package/dist/ui/components/organisms/EventRow.d.ts +3 -3
  144. package/dist/ui/components/organisms/EventRow.js +1 -1
  145. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +5 -5
  146. package/dist/ui/screens/events/events.context.d.ts +4 -4
  147. package/package.json +13 -8
  148. package/dist/domain/event_sourcing/domain_event.js +0 -164
  149. package/dist/domain/event_sourcing/event.d.ts +0 -25
  150. package/dist/domain/event_sourcing/event.js +0 -25
  151. package/dist/domain/event_sourcing/index.d.ts +0 -2
  152. package/dist/domain/event_sourcing/index.js +0 -2
  153. package/dist/domain/events.d.ts +0 -33
  154. package/dist/infrastructure/ui/ToastHost.d.ts +0 -6
  155. package/dist/infrastructure/ui/ToastHost.js +0 -28
  156. package/dist/infrastructure/ui/events.d.ts +0 -1
  157. package/dist/infrastructure/ui/events.js +0 -1
  158. package/dist/ui/components/molecules/ProcessToast.d.ts +0 -8
  159. package/dist/ui/components/molecules/ProcessToast.js +0 -52
  160. /package/dist/domain/{repositories.d.ts → repositories/catalog.d.ts} +0 -0
  161. /package/dist/domain/{repositories.js → repositories/catalog.js} +0 -0
  162. /package/dist/domain/{database → repositories}/repository.js +0 -0
  163. /package/dist/{domain/event_sourcing/event_handler.js → event_driven/subscriber.js} +0 -0
  164. /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.d.ts +0 -0
  165. /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.js +0 -0
@@ -0,0 +1,476 @@
1
+ import { InvalidCriteria, isOperator, } from "@sincpro/criteria";
2
+ import { criteriaOf } from "./criteria";
3
+ import { deviceClock, periodBounds, readCriteriaValue, } from "./criteria_clock";
4
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ const MAX_DEPTH = 8;
6
+ export { deviceClock };
7
+ export function compileCriteria(asked, target, clock = deviceClock()) {
8
+ assertName(target.table, "table");
9
+ if (!(clock.now instanceof Date) || Number.isNaN(clock.now.getTime())) {
10
+ throw new InvalidCriteria("the criteria clock has no instant");
11
+ }
12
+ const criteria = criteriaOf(asked);
13
+ if (criteria.grouping !== undefined) {
14
+ throw new InvalidCriteria("a document query answers a selection, not a grouping");
15
+ }
16
+ const params = [];
17
+ const compiled = criteria.selection
18
+ ? compileExpression(criteria.selection, target, params, 0, clock)
19
+ : { sql: "", predicate: { op: "true" } };
20
+ const order = (criteria.order ?? []).map((sort) => ({
21
+ ref: fieldRef(sort.field, target),
22
+ descending: sort.descending === true,
23
+ }));
24
+ const page = pageOf(criteria.pagination);
25
+ if ((page.limit !== undefined || page.offset > 0) &&
26
+ !order.some((item) => item.ref.name === "uuid")) {
27
+ order.push({
28
+ ref: fieldRef("uuid", { ...target, column: () => true }),
29
+ descending: false,
30
+ });
31
+ }
32
+ return {
33
+ whereSql: compiled.sql,
34
+ params,
35
+ orderSql: order
36
+ .map((item) => `${sqlOf(target.table, item.ref)} ${item.descending ? "DESC" : "ASC"}`)
37
+ .join(", "),
38
+ order,
39
+ limit: page.limit,
40
+ offset: page.offset,
41
+ predicate: compiled.predicate,
42
+ };
43
+ }
44
+ export function rowsFor(compiled, rows, others = () => []) {
45
+ const matched = rows.filter((row) => holds(row, compiled.predicate, others));
46
+ const ordered = compiled.order.length === 0
47
+ ? [...matched]
48
+ : [...matched].sort((left, right) => compareRows(left, right, compiled.order));
49
+ const start = compiled.offset;
50
+ const end = compiled.limit === undefined ? undefined : start + compiled.limit;
51
+ return ordered.slice(start, end);
52
+ }
53
+ function compileExpression(expression, target, params, depth, clock) {
54
+ if ("through" in expression && "selection" in expression) {
55
+ return compileThrough(expression, target, params, depth, clock);
56
+ }
57
+ if ("all" in expression)
58
+ return compileGroup("and", "AND", expression.all, target, params, depth, clock);
59
+ if ("any" in expression)
60
+ return compileGroup("or", "OR", expression.any, target, params, depth, clock);
61
+ if ("negate" in expression) {
62
+ const inner = compileExpression(expression.negate, target, params, depth, clock);
63
+ return {
64
+ sql: `NOT ((${inner.sql || "1"}) IS TRUE)`,
65
+ predicate: { op: "not", item: inner.predicate },
66
+ };
67
+ }
68
+ if (!("field" in expression) || !isOperator(expression.operator)) {
69
+ throw new InvalidCriteria("a criteria condition needs a field and an operator");
70
+ }
71
+ return compileCondition(expression.field, expression.operator, expression.value, target, params, clock);
72
+ }
73
+ function compileGroup(op, sqlOp, parts, target, params, depth, clock) {
74
+ if (parts.length === 0) {
75
+ const open = op === "and";
76
+ return { sql: open ? "(1)" : "(0)", predicate: { op: open ? "true" : "false" } };
77
+ }
78
+ const compiled = parts.map((part) => compileExpression(part, target, params, depth, clock));
79
+ return {
80
+ sql: `(${compiled.map((part) => part.sql).join(` ${sqlOp} `)})`,
81
+ predicate: { op, items: compiled.map((part) => part.predicate) },
82
+ };
83
+ }
84
+ function compileThrough(expression, parent, params, depth, clock) {
85
+ if (depth >= MAX_DEPTH) {
86
+ throw new InvalidCriteria("a criteria crosses too many relations");
87
+ }
88
+ assertName(expression.through, "relation");
89
+ const child = parent.relation?.(expression.through);
90
+ if (!child)
91
+ throw new InvalidCriteria(`unknown relation '${expression.through}'`);
92
+ assertName(child.table, "table");
93
+ const inner = compileExpression(expression.selection, child, params, depth + 1, clock);
94
+ const link = `json_extract(${parent.table}.data, '$.${expression.through}')`;
95
+ const each = `json_each(${parent.table}.data, '$.${expression.through}')`;
96
+ return {
97
+ sql: `EXISTS (SELECT 1 FROM ${child.table} WHERE (` +
98
+ `${child.table}.uuid = ${link} OR ${child.table}.uuid IN (SELECT value FROM ${each}))` +
99
+ ` AND (${inner.sql || "1"}))`,
100
+ predicate: {
101
+ op: "exists",
102
+ table: child.table,
103
+ link: expression.through,
104
+ item: inner.predicate,
105
+ },
106
+ };
107
+ }
108
+ function compileCondition(field, operator, value, target, params, clock) {
109
+ const left = fieldRef(field, target);
110
+ const expr = sqlOf(target.table, left);
111
+ if (isFieldValue(value)) {
112
+ const right = fieldRef(value.name, target);
113
+ return {
114
+ sql: compare(expr, operator, sqlOf(target.table, right), params, undefined, true),
115
+ predicate: { op: "cmp", left, operator, right: { field: right } },
116
+ };
117
+ }
118
+ if (isPeriodValue(value)) {
119
+ if (operator !== "between") {
120
+ throw new InvalidCriteria(`a period is a range and takes the operator 'between'; '${field}' asked it with '${operator}'`);
121
+ }
122
+ const [start, end] = periodBounds(value, clock);
123
+ const lower = compileCondition(field, ">=", start, target, params, clock);
124
+ const upper = compileCondition(field, "<", end, target, params, clock);
125
+ return {
126
+ sql: `(${lower.sql} AND ${upper.sql})`,
127
+ predicate: { op: "and", items: [lower.predicate, upper.predicate] },
128
+ };
129
+ }
130
+ const bound = literal(operator, readCriteriaValue(value, clock, field));
131
+ return {
132
+ sql: compare(expr, operator, "?", params, bound, false),
133
+ predicate: { op: "cmp", left, operator, right: { value: bound } },
134
+ };
135
+ }
136
+ function compare(left, operator, rightSql, params, bound, againstField) {
137
+ if (againstField) {
138
+ switch (operator) {
139
+ case "=":
140
+ case "!=":
141
+ case ">":
142
+ case ">=":
143
+ case "<":
144
+ case "<=":
145
+ return `${left} ${operator} ${rightSql}`;
146
+ default:
147
+ throw new InvalidCriteria(`'${operator}' compares against a value`);
148
+ }
149
+ }
150
+ if (operator === "=" && Array.isArray(bound) && bound.length === 0) {
151
+ return `(${left} IS NULL OR CAST(${left} AS TEXT) = '[]')`;
152
+ }
153
+ if (operator === "!=" && Array.isArray(bound) && bound.length === 0) {
154
+ return `(${left} IS NOT NULL AND CAST(${left} AS TEXT) != '[]')`;
155
+ }
156
+ if ((operator === "=" || operator === "!=") && bound === null) {
157
+ return operator === "=" ? `${left} IS NULL` : `${left} IS NOT NULL`;
158
+ }
159
+ switch (operator) {
160
+ case "=":
161
+ case "!=":
162
+ case ">":
163
+ case ">=":
164
+ case "<":
165
+ case "<=":
166
+ push(params, bound);
167
+ return `${left} ${operator} ${rightSql}`;
168
+ case "between": {
169
+ const pair = pairOf(bound);
170
+ params.push(pair[0], pair[1]);
171
+ return `${left} BETWEEN ? AND ?`;
172
+ }
173
+ case "in":
174
+ case "not in":
175
+ return membership(left, operator === "not in", listOf(bound), params);
176
+ case "is null":
177
+ if (bound !== 0 && bound !== 1) {
178
+ throw new InvalidCriteria("'is null' compares against true or false");
179
+ }
180
+ return bound === 0 ? `${left} IS NOT NULL` : `${left} IS NULL`;
181
+ case "like":
182
+ params.push(`%${escaped(text(bound).toLowerCase())}%`);
183
+ return `lower(${left}) LIKE ? ESCAPE '\\'`;
184
+ case "starts with":
185
+ params.push(`${escaped(text(bound).toLowerCase())}%`);
186
+ return `lower(${left}) LIKE ? ESCAPE '\\'`;
187
+ case "contains":
188
+ params.push(`%"${escaped(text(bound))}"%`);
189
+ return `CAST(${left} AS TEXT) LIKE ? ESCAPE '\\'`;
190
+ case "not contains":
191
+ params.push(`%"${escaped(text(bound))}"%`);
192
+ return `NOT ((CAST(${left} AS TEXT) LIKE ? ESCAPE '\\') IS TRUE)`;
193
+ default:
194
+ throw new InvalidCriteria(`no SQL translation for operator '${operator}'`);
195
+ }
196
+ }
197
+ function membership(left, negated, values, params) {
198
+ if (values.length === 0)
199
+ return negated ? "(1)" : "(0)";
200
+ params.push(...values);
201
+ const marks = values.map(() => "?").join(", ");
202
+ return `${left} ${negated ? "NOT IN" : "IN"} (${marks})`;
203
+ }
204
+ function pageOf(pagination) {
205
+ if (!pagination)
206
+ return { offset: 0 };
207
+ if (pagination.strategy && "token" in pagination.strategy) {
208
+ throw new InvalidCriteria("a document query pages by rows, not by a token");
209
+ }
210
+ const offset = pagination.strategy && "rows" in pagination.strategy ? pagination.strategy.rows : 0;
211
+ if (!Number.isInteger(offset) || offset < 0) {
212
+ throw new InvalidCriteria("a page offset is a whole number from zero");
213
+ }
214
+ if (pagination.limit === undefined)
215
+ return { offset };
216
+ if (!Number.isInteger(pagination.limit) || pagination.limit < 0) {
217
+ throw new InvalidCriteria("a page limit is a whole number from zero");
218
+ }
219
+ return { limit: pagination.limit, offset };
220
+ }
221
+ function fieldRef(field, target) {
222
+ const segments = field.split(".");
223
+ if (segments.length === 0 || segments.some((segment) => !IDENTIFIER.test(segment))) {
224
+ throw new InvalidCriteria(`invalid field '${field}'`);
225
+ }
226
+ if (target.column(field)) {
227
+ if (segments.length !== 1)
228
+ throw new InvalidCriteria(`'${field}' is not a column`);
229
+ return { kind: "column", name: field, path: "" };
230
+ }
231
+ return { kind: "json", name: field, path: `$.${segments.join(".")}` };
232
+ }
233
+ function sqlOf(table, ref) {
234
+ if (ref.kind === "column")
235
+ return `${table}.${ref.name}`;
236
+ return `json_extract(${table}.data, '${ref.path}')`;
237
+ }
238
+ function literal(operator, value) {
239
+ if (operator === "in" || operator === "not in" || operator === "between") {
240
+ if (!Array.isArray(value))
241
+ throw new InvalidCriteria(`'${operator}' compares against a list`);
242
+ return value.map((item) => {
243
+ if (isDynamic(item) || Array.isArray(item)) {
244
+ throw new InvalidCriteria(`'${operator}' compares against text, numbers, or null`);
245
+ }
246
+ return scalarParam(item);
247
+ });
248
+ }
249
+ if (operator === "=" && Array.isArray(value) && value.length === 0)
250
+ return [];
251
+ if (operator === "!=" && Array.isArray(value) && value.length === 0)
252
+ return [];
253
+ if (Array.isArray(value) || isDynamic(value)) {
254
+ throw new InvalidCriteria(`'${operator}' compares against one value`);
255
+ }
256
+ if (operator === "is null") {
257
+ if (typeof value !== "boolean") {
258
+ throw new InvalidCriteria("'is null' compares against true or false");
259
+ }
260
+ return value ? 1 : 0;
261
+ }
262
+ return scalarParam(value);
263
+ }
264
+ function scalarParam(value) {
265
+ if (value instanceof Date)
266
+ return value.toISOString();
267
+ if (typeof value === "boolean")
268
+ return value ? 1 : 0;
269
+ if (value === null || typeof value === "number" || typeof value === "string")
270
+ return value;
271
+ throw new InvalidCriteria("a criteria value must be text, a number, or null");
272
+ }
273
+ function pairOf(bound) {
274
+ if (!Array.isArray(bound) || bound.length !== 2) {
275
+ throw new InvalidCriteria("'between' compares against two values");
276
+ }
277
+ return [bound[0], bound[1]];
278
+ }
279
+ function listOf(bound) {
280
+ if (!Array.isArray(bound))
281
+ throw new InvalidCriteria("'in' compares against a list");
282
+ return bound;
283
+ }
284
+ function push(params, bound) {
285
+ if (bound === undefined || Array.isArray(bound)) {
286
+ throw new InvalidCriteria("a comparison needs one value");
287
+ }
288
+ params.push(bound);
289
+ }
290
+ function text(bound) {
291
+ if (typeof bound === "string" || typeof bound === "number")
292
+ return String(bound);
293
+ throw new InvalidCriteria("a text comparison needs text or a number");
294
+ }
295
+ function escaped(value) {
296
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
297
+ }
298
+ function isDynamic(value) {
299
+ return (value !== null &&
300
+ typeof value === "object" &&
301
+ !Array.isArray(value) &&
302
+ !(value instanceof Date) &&
303
+ "$" in value);
304
+ }
305
+ function isPeriodValue(value) {
306
+ return isDynamic(value) && value.$ === "period";
307
+ }
308
+ function isFieldValue(value) {
309
+ return isDynamic(value) && value.$ === "field";
310
+ }
311
+ function assertName(name, label) {
312
+ if (!IDENTIFIER.test(name))
313
+ throw new InvalidCriteria(`invalid ${label} '${name}'`);
314
+ }
315
+ function holds(row, predicate, others) {
316
+ switch (predicate.op) {
317
+ case "true":
318
+ return true;
319
+ case "false":
320
+ return false;
321
+ case "not":
322
+ return !holds(row, predicate.item, others);
323
+ case "and":
324
+ return predicate.items.every((item) => holds(row, item, others));
325
+ case "or":
326
+ return predicate.items.some((item) => holds(row, item, others));
327
+ case "exists": {
328
+ const ids = linkedIds(row, predicate.link);
329
+ if (ids.length === 0)
330
+ return false;
331
+ return others(predicate.table).some((child) => ids.includes(child.uuid) && holds(child, predicate.item, others));
332
+ }
333
+ case "cmp":
334
+ return compareValues(readField(row, predicate.left), predicate.operator, "field" in predicate.right
335
+ ? readField(row, predicate.right.field)
336
+ : predicate.right.value);
337
+ }
338
+ }
339
+ function linkedIds(row, field) {
340
+ const value = readJson(row.data, `$.${field}`);
341
+ if (typeof value === "string")
342
+ return [value];
343
+ if (Array.isArray(value))
344
+ return value.filter((item) => typeof item === "string");
345
+ return [];
346
+ }
347
+ function readField(row, ref) {
348
+ if (ref.kind === "column") {
349
+ if (ref.name === "uuid")
350
+ return row.uuid;
351
+ if (ref.name === "version")
352
+ return row.version;
353
+ return row.columns[ref.name] ?? null;
354
+ }
355
+ return readJson(row.data, ref.path);
356
+ }
357
+ function readJson(data, path) {
358
+ const segments = path.startsWith("$.") ? path.slice(2).split(".") : [];
359
+ let current = JSON.parse(data);
360
+ for (const segment of segments) {
361
+ if (current === null || typeof current !== "object")
362
+ return null;
363
+ current = current[segment];
364
+ }
365
+ return current === undefined ? null : current;
366
+ }
367
+ function compareValues(left, operator, right) {
368
+ if (operator === "=" && Array.isArray(right) && right.length === 0) {
369
+ return left == null || (Array.isArray(left) && left.length === 0);
370
+ }
371
+ if (operator === "!=" && Array.isArray(right) && right.length === 0) {
372
+ return left != null && !(Array.isArray(left) && left.length === 0);
373
+ }
374
+ if (operator === "=" && right === null)
375
+ return left == null;
376
+ if (operator === "!=" && right === null)
377
+ return left != null;
378
+ switch (operator) {
379
+ case "=":
380
+ return same(left, right);
381
+ case "!=":
382
+ return !same(left, right);
383
+ case ">":
384
+ case ">=":
385
+ case "<":
386
+ case "<=":
387
+ return ordered(left, right, operator);
388
+ case "between": {
389
+ if (!Array.isArray(right) || right.length !== 2)
390
+ return false;
391
+ return ordered(left, right[0], ">=") && ordered(left, right[1], "<=");
392
+ }
393
+ case "in":
394
+ return Array.isArray(right) && right.some((item) => same(left, item));
395
+ case "not in":
396
+ return Array.isArray(right) && !right.some((item) => same(left, item));
397
+ case "is null":
398
+ return right === 0 || right === false ? left != null : left == null;
399
+ case "like":
400
+ return (left != null &&
401
+ String(scalar(left)).toLowerCase().includes(String(right).toLowerCase()));
402
+ case "starts with":
403
+ return (left != null &&
404
+ String(scalar(left)).toLowerCase().startsWith(String(right).toLowerCase()));
405
+ case "contains":
406
+ return quoted(left, String(right));
407
+ case "not contains":
408
+ return left == null || !quoted(left, String(right));
409
+ default:
410
+ return false;
411
+ }
412
+ }
413
+ function quoted(value, needle) {
414
+ const mark = `"${needle}"`;
415
+ if (typeof value === "string")
416
+ return value.includes(mark);
417
+ if (value == null)
418
+ return false;
419
+ return JSON.stringify(value).includes(mark);
420
+ }
421
+ function same(left, right) {
422
+ return scalar(left) === scalar(right);
423
+ }
424
+ function ordered(left, right, operator) {
425
+ if (scalar(left) === null || scalar(right) === null)
426
+ return false;
427
+ const compared = compareUnknown(left, right);
428
+ if (operator === "<")
429
+ return compared < 0;
430
+ if (operator === "<=")
431
+ return compared <= 0;
432
+ if (operator === ">")
433
+ return compared > 0;
434
+ return compared >= 0;
435
+ }
436
+ function compareRows(left, right, order) {
437
+ for (const item of order) {
438
+ const compared = compareUnknown(readField(left, item.ref), readField(right, item.ref));
439
+ if (compared === 0)
440
+ continue;
441
+ return item.descending ? -compared : compared;
442
+ }
443
+ return 0;
444
+ }
445
+ function compareUnknown(left, right) {
446
+ const a = scalar(left);
447
+ const b = scalar(right);
448
+ if (a === null && b === null)
449
+ return 0;
450
+ if (a === null)
451
+ return -1;
452
+ if (b === null)
453
+ return 1;
454
+ if (typeof a === "number" && typeof b === "number")
455
+ return a - b;
456
+ const as = String(a);
457
+ const bs = String(b);
458
+ if (as < bs)
459
+ return -1;
460
+ if (as > bs)
461
+ return 1;
462
+ return 0;
463
+ }
464
+ function scalar(value) {
465
+ if (value == null)
466
+ return null;
467
+ if (typeof value === "boolean")
468
+ return value ? 1 : 0;
469
+ if (typeof value === "number")
470
+ return Number.isNaN(value) ? null : value;
471
+ if (typeof value === "string")
472
+ return value;
473
+ if (value instanceof Date)
474
+ return value.toISOString();
475
+ return null;
476
+ }
@@ -13,4 +13,5 @@ export interface ITable {
13
13
  export interface IMigration {
14
14
  name: string;
15
15
  migrationFn: () => Promise<void>;
16
+ tables?: readonly string[];
16
17
  }
@@ -1,2 +1,4 @@
1
+ export * from "../../domain/database/criteria";
2
+ export * from "../../domain/database/criteria_sql";
1
3
  export * from "../../domain/database/database";
2
- export * from "../../domain/database/repository";
4
+ export * from "../../domain/database/migration_plan";
@@ -1,2 +1,4 @@
1
+ export * from "../../domain/database/criteria";
2
+ export * from "../../domain/database/criteria_sql";
1
3
  export * from "../../domain/database/database";
2
- export * from "../../domain/database/repository";
4
+ export * from "../../domain/database/migration_plan";
@@ -0,0 +1,14 @@
1
+ import { type IMigration } from "./database";
2
+ export interface ColumnDefinition {
3
+ name: string;
4
+ definition: string;
5
+ }
6
+ export declare function migrationTables(migration: IMigration): string[];
7
+ export declare function migrationsNewerThanApp(applied: Iterable<string>, known: readonly string[]): string[];
8
+ export declare function applyMigrationStep(migration: IMigration, transaction: (work: () => Promise<void>) => Promise<void>, mark: (name: string) => Promise<void>): Promise<void>;
9
+ export declare function pendingMigrations(migrations: readonly IMigration[], applied: ReadonlySet<string>): IMigration[];
10
+ export declare function resetPlan(migrations: readonly IMigration[], persisted: readonly string[]): {
11
+ drop: string[];
12
+ forget: string[];
13
+ };
14
+ export declare function missingColumns(existing: readonly string[], wanted: readonly ColumnDefinition[]): ColumnDefinition[];
@@ -0,0 +1,44 @@
1
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
2
+ export function migrationTables(migration) {
3
+ const declared = migration.tables ?? [migration.name];
4
+ return declared.filter((name) => IDENTIFIER.test(name));
5
+ }
6
+ export function migrationsNewerThanApp(applied, known) {
7
+ const names = new Set(known);
8
+ return [...applied].filter((name) => !names.has(name)).sort();
9
+ }
10
+ export async function applyMigrationStep(migration, transaction, mark) {
11
+ await transaction(async () => {
12
+ await migration.migrationFn();
13
+ await mark(migration.name);
14
+ });
15
+ }
16
+ export function pendingMigrations(migrations, applied) {
17
+ const seen = new Set(applied);
18
+ const pending = [];
19
+ for (const migration of migrations) {
20
+ if (seen.has(migration.name))
21
+ continue;
22
+ seen.add(migration.name);
23
+ pending.push(migration);
24
+ }
25
+ return pending;
26
+ }
27
+ export function resetPlan(migrations, persisted) {
28
+ const keep = new Set(persisted);
29
+ const drop = new Set();
30
+ const forget = [];
31
+ for (const migration of migrations) {
32
+ const tables = migrationTables(migration);
33
+ const removed = tables.filter((table) => !keep.has(table));
34
+ for (const table of removed)
35
+ drop.add(table);
36
+ if (tables.length > 0 && removed.length === tables.length)
37
+ forget.push(migration.name);
38
+ }
39
+ return { drop: [...drop], forget };
40
+ }
41
+ export function missingColumns(existing, wanted) {
42
+ const have = new Set(existing);
43
+ return wanted.filter((column) => !have.has(column.name));
44
+ }
@@ -0,0 +1,21 @@
1
+ import { type Entity } from "../../domain/entity/entity";
2
+ import { DomainEvent } from "../../domain/events/domain_event";
3
+ import { type AnyClass } from "./relations";
4
+ export declare class EntityUpdated extends DomainEvent {
5
+ readonly name = "entity.updated";
6
+ readonly label = "Updated";
7
+ changes: Record<string, {
8
+ from: string;
9
+ to: string;
10
+ }>;
11
+ }
12
+ export declare function fieldSnapshot(entity: Entity, seen?: Set<object>): Record<string, string>;
13
+ type Tracked = {
14
+ takeSnapshot(): void;
15
+ recordChanges(): void;
16
+ };
17
+ export declare function withChangeTracking<T extends Entity, C extends AnyClass<T>>(Base: C): C & {
18
+ new (...args: any[]): T & Tracked;
19
+ };
20
+ export declare function isChangeTracked(entity: Entity): entity is Entity & Tracked;
21
+ export {};
@@ -0,0 +1,71 @@
1
+ import { DomainEvent } from "../../domain/events/domain_event";
2
+ import { heldEntities } from "./relations";
3
+ const SKIPPED = new Set(["uuid", "version", "createdAt", "updatedAt", "eventIds"]);
4
+ export class EntityUpdated extends DomainEvent {
5
+ name = "entity.updated";
6
+ label = "Updated";
7
+ changes = {};
8
+ }
9
+ export function fieldSnapshot(entity, seen = new Set()) {
10
+ seen.add(entity);
11
+ const snapshot = {};
12
+ for (const key of Object.keys(entity)) {
13
+ if (key.startsWith("_") || SKIPPED.has(key))
14
+ continue;
15
+ const value = entity[key];
16
+ if (typeof value === "function")
17
+ continue;
18
+ snapshot[key] = printed(value, seen);
19
+ }
20
+ return snapshot;
21
+ }
22
+ function printed(value, seen) {
23
+ const held = heldEntities(value);
24
+ if (held.length > 0) {
25
+ return JSON.stringify(held.map((one) => seen.has(one) ? { uuid: one.uuid } : fieldSnapshot(one, seen)));
26
+ }
27
+ if (Array.isArray(value))
28
+ return value.length === 0 ? "" : JSON.stringify(value);
29
+ if (value === undefined)
30
+ return "";
31
+ return JSON.stringify(value);
32
+ }
33
+ export function withChangeTracking(Base) {
34
+ class ChangeTracked extends Base {
35
+ _changeSnapshot = {};
36
+ _snapshotTaken = false;
37
+ // TS2545: a mixin class must declare a rest constructor.
38
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
39
+ constructor(...args) {
40
+ super(...args);
41
+ }
42
+ takeSnapshot() {
43
+ this._changeSnapshot = fieldSnapshot(this);
44
+ this._snapshotTaken = true;
45
+ }
46
+ recordChanges() {
47
+ if (!this._snapshotTaken)
48
+ return;
49
+ const next = fieldSnapshot(this);
50
+ const changes = {};
51
+ for (const key of new Set([
52
+ ...Object.keys(this._changeSnapshot),
53
+ ...Object.keys(next),
54
+ ])) {
55
+ if (this._changeSnapshot[key] !== next[key]) {
56
+ changes[key] = {
57
+ from: this._changeSnapshot[key] ?? "",
58
+ to: next[key] ?? "",
59
+ };
60
+ }
61
+ }
62
+ if (Object.keys(changes).length === 0)
63
+ return;
64
+ this.record(EntityUpdated, { changes });
65
+ }
66
+ }
67
+ return ChangeTracked;
68
+ }
69
+ export function isChangeTracked(entity) {
70
+ return typeof entity.recordChanges === "function";
71
+ }
@@ -0,0 +1,8 @@
1
+ export declare function check(watches: (entity: object) => unknown): (_target: object, _key: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
2
+ export interface CheckFailure {
3
+ rule: string;
4
+ message: string;
5
+ path: string[];
6
+ }
7
+ export declare function checkWarnings(entity: object, touched: readonly string[]): CheckFailure[];
8
+ export declare function assertChecks(entity: object): void;