firebird-wasm 0.7.0 → 0.8.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 (72) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +1 -1
  3. package/dist/.tsbuildinfo.build +1 -1
  4. package/dist/browser/engine-transport.d.ts.map +1 -1
  5. package/dist/browser/engine-transport.js +5 -8
  6. package/dist/browser/engine-transport.js.map +1 -1
  7. package/dist/browser/firebird-browser.d.ts.map +1 -1
  8. package/dist/browser/firebird-browser.js +59 -20
  9. package/dist/browser/firebird-browser.js.map +1 -1
  10. package/dist/browser/index.d.ts +2 -2
  11. package/dist/browser/index.d.ts.map +1 -1
  12. package/dist/browser/index.js +4 -2
  13. package/dist/browser/index.js.map +1 -1
  14. package/dist/browser/serve-engine.d.ts +30 -0
  15. package/dist/browser/serve-engine.d.ts.map +1 -0
  16. package/dist/browser/serve-engine.js +93 -0
  17. package/dist/browser/serve-engine.js.map +1 -0
  18. package/dist/browser/worker-entry.d.ts +8 -22
  19. package/dist/browser/worker-entry.d.ts.map +1 -1
  20. package/dist/browser/worker-entry.js +18 -79
  21. package/dist/browser/worker-entry.js.map +1 -1
  22. package/dist/electric/apply.d.ts +53 -0
  23. package/dist/electric/apply.d.ts.map +1 -0
  24. package/dist/electric/apply.js +88 -0
  25. package/dist/electric/apply.js.map +1 -0
  26. package/dist/electric/index.d.ts +83 -0
  27. package/dist/electric/index.d.ts.map +1 -0
  28. package/dist/electric/index.js +210 -0
  29. package/dist/electric/index.js.map +1 -0
  30. package/dist/electric/values.d.ts +65 -0
  31. package/dist/electric/values.d.ts.map +1 -0
  32. package/dist/electric/values.js +243 -0
  33. package/dist/electric/values.js.map +1 -0
  34. package/dist/electric-server/capture.d.ts +36 -0
  35. package/dist/electric-server/capture.d.ts.map +1 -0
  36. package/dist/electric-server/capture.js +148 -0
  37. package/dist/electric-server/capture.js.map +1 -0
  38. package/dist/electric-server/catalog.d.ts +59 -0
  39. package/dist/electric-server/catalog.d.ts.map +1 -0
  40. package/dist/electric-server/catalog.js +122 -0
  41. package/dist/electric-server/catalog.js.map +1 -0
  42. package/dist/electric-server/index.d.ts +58 -0
  43. package/dist/electric-server/index.d.ts.map +1 -0
  44. package/dist/electric-server/index.js +348 -0
  45. package/dist/electric-server/index.js.map +1 -0
  46. package/dist/firebird.d.ts +3 -1
  47. package/dist/firebird.d.ts.map +1 -1
  48. package/dist/firebird.js +250 -22
  49. package/dist/firebird.js.map +1 -1
  50. package/dist/frameworks/query-store.d.ts +99 -0
  51. package/dist/frameworks/query-store.d.ts.map +1 -0
  52. package/dist/frameworks/query-store.js +166 -0
  53. package/dist/frameworks/query-store.js.map +1 -0
  54. package/dist/react/index.d.ts +68 -0
  55. package/dist/react/index.d.ts.map +1 -0
  56. package/dist/react/index.js +92 -0
  57. package/dist/react/index.js.map +1 -0
  58. package/dist/repl/index.d.ts +76 -0
  59. package/dist/repl/index.d.ts.map +1 -0
  60. package/dist/repl/index.js +328 -0
  61. package/dist/repl/index.js.map +1 -0
  62. package/dist/types.d.ts +2 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/dist/vue/index.d.ts +68 -0
  67. package/dist/vue/index.d.ts.map +1 -0
  68. package/dist/vue/index.js +107 -0
  69. package/dist/vue/index.js.map +1 -0
  70. package/dist/wasm/firebird-embedded-st.wasm +0 -0
  71. package/dist/wasm/firebird-embedded.wasm +0 -0
  72. package/package.json +50 -3
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * values.ts – Postgres values, as Electric sends them, into Firebird.
4
+ *
5
+ * Electric sends every value as Postgres's text output and describes the
6
+ * columns once, in the `electric-schema` response header. This turns one
7
+ * into a parameter Firebird converts exactly, and refuses what it does not
8
+ * map by name rather than storing something that looks right and is not.
9
+ *
10
+ * Observed from Electric 1.8 (not all of it in the documentation): a boolean
11
+ * is `"true"` in the initial snapshot and `"f"` in a live update; a
12
+ * `timestamptz` arrives in UTC as `2024-05-01 10:00:00+00`; arrays are marked
13
+ * `dims`, and the primary key `pk_index`.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.checkSchema = checkSchema;
17
+ exports.toFirebird = toFirebird;
18
+ exports.firebirdColumn = firebirdColumn;
19
+ exports.firebirdName = firebirdName;
20
+ exports.tableFromSchema = tableFromSchema;
21
+ exports.primaryKeyOf = primaryKeyOf;
22
+ const asText = (text) => text;
23
+ const asInteger = (text) => Number(text);
24
+ // As text, which Firebird converts to BIGINT exactly; the Node driver cannot
25
+ // bind a JavaScript bigint at all.
26
+ const asBigInt = (text, column) => {
27
+ if (!/^-?\d+$/.test(text)) {
28
+ throw new RangeError(`Column ${column} holds ${text}, which is not a whole number`);
29
+ }
30
+ return text;
31
+ };
32
+ const asFloat = (text, column) => {
33
+ const value = Number(text);
34
+ if (!Number.isFinite(value)) {
35
+ // Postgres has NaN and ±Infinity; Firebird has no literal for them.
36
+ throw new RangeError(`Column ${column} holds ${text}, which Firebird cannot store`);
37
+ }
38
+ return value;
39
+ };
40
+ const asNumeric = (text, column) => {
41
+ if (!/^-?\d+(\.\d+)?$/.test(text)) {
42
+ throw new RangeError(`Column ${column} holds ${text}, which Firebird cannot store`);
43
+ }
44
+ // Exact: the text goes to a NUMERIC column, which converts it digit for digit.
45
+ return text;
46
+ };
47
+ const asBoolean = (text, column) => {
48
+ // Both forms occur: the snapshot says "true", the replication stream "t".
49
+ if (text === 't' || text === 'true')
50
+ return true;
51
+ if (text === 'f' || text === 'false')
52
+ return false;
53
+ throw new RangeError(`Column ${column} holds ${text}, which is not a boolean`);
54
+ };
55
+ /** Firebird keeps time to 1/10000 s; Postgres sends up to six digits. */
56
+ function fourDigitFraction(text) {
57
+ return text.replace(/(\.\d{4})\d+/, '$1');
58
+ }
59
+ /** `+05:30` from `+05:30`, `+05` or `+0530`, as Firebird reads an offset. */
60
+ function offset(sign, hours, minutes = '00') {
61
+ return ` ${sign}${hours}:${minutes}`;
62
+ }
63
+ const asDate = (text, column) => {
64
+ if (!/^\d{4}-\d\d-\d\d$/.test(text)) {
65
+ // 'infinity', BC dates and years past 9999 have no Firebird DATE.
66
+ throw new RangeError(`Column ${column} holds ${text}, which Firebird cannot store`);
67
+ }
68
+ return text;
69
+ };
70
+ const asTime = (text) => fourDigitFraction(text);
71
+ const asTimestamp = (text, column) => {
72
+ if (!/^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(\.\d+)?$/.test(text)) {
73
+ throw new RangeError(`Column ${column} holds ${text}, which Firebird cannot store`);
74
+ }
75
+ return fourDigitFraction(text);
76
+ };
77
+ const ZONED = /^(.*?)([+-])(\d\d):?(\d\d)?(?::\d\d)?$/;
78
+ const asZoned = (text, column) => {
79
+ const match = ZONED.exec(text);
80
+ if (!match || !/^(\d{4}-\d\d-\d\d )?\d\d:\d\d:\d\d(\.\d+)?$/.test(match[1])) {
81
+ throw new RangeError(`Column ${column} holds ${text}, which Firebird cannot store`);
82
+ }
83
+ return fourDigitFraction(match[1]) + offset(match[2], match[3], match[4]);
84
+ };
85
+ /** Postgres type name to its conversion, for every type this maps. */
86
+ const CONVERSIONS = {
87
+ int2: asInteger,
88
+ int4: asInteger,
89
+ int8: asBigInt,
90
+ numeric: asNumeric,
91
+ float4: asFloat,
92
+ float8: asFloat,
93
+ bool: asBoolean,
94
+ text: asText,
95
+ varchar: asText,
96
+ bpchar: asText,
97
+ name: asText,
98
+ citext: asText,
99
+ uuid: asText,
100
+ json: asText,
101
+ jsonb: asText,
102
+ date: asDate,
103
+ time: asTime,
104
+ timestamp: asTimestamp,
105
+ timetz: asZoned,
106
+ timestamptz: asZoned,
107
+ };
108
+ /** Why a type is not mapped, where there is more to say than "not yet". */
109
+ const REFUSED = {
110
+ bytea: 'bytea is not mapped yet: the WASM build binds parameters as text, and bytes are not text',
111
+ };
112
+ /**
113
+ * Check that every column of `schema` can be stored, before any row is.
114
+ * @throws naming each column and type that cannot.
115
+ */
116
+ function checkSchema(schema) {
117
+ const problems = [];
118
+ for (const [column, info] of Object.entries(schema)) {
119
+ if ((info.dims ?? 0) > 0)
120
+ continue; // arrays: stored as JSON text
121
+ if (CONVERSIONS[info.type])
122
+ continue;
123
+ problems.push(`${column} (${info.type}): ${REFUSED[info.type] ?? 'this Postgres type is not mapped to Firebird'}`);
124
+ }
125
+ if (problems.length > 0) {
126
+ throw new TypeError(`This shape has columns firebird-wasm/electric cannot store:\n ${problems.join('\n ')}\n` +
127
+ 'Leave them out with the shape\'s columns parameter, or cast them to text in a view');
128
+ }
129
+ }
130
+ /**
131
+ * One value from Electric as a Firebird parameter.
132
+ *
133
+ * `value` is Electric's text, `null`, or for an array column the array the
134
+ * client parsed (of texts), which is stored as JSON.
135
+ */
136
+ function toFirebird(value, column, info) {
137
+ if (value === null || value === undefined)
138
+ return null;
139
+ if ((info.dims ?? 0) > 0)
140
+ return JSON.stringify(value);
141
+ const convert = CONVERSIONS[info.type];
142
+ if (!convert) {
143
+ throw new TypeError(`Column ${column} has type ${info.type}, which is not mapped to Firebird`);
144
+ }
145
+ return convert(String(value), column);
146
+ }
147
+ /**
148
+ * The Firebird identifier for a Postgres column, quoted.
149
+ *
150
+ * Postgres folds unquoted names to lower case and Firebird to upper, so a
151
+ * plain lower-case Postgres name maps to the upper-case Firebird name that
152
+ * the same unquoted word finds (`title` → `"TITLE"`). Anything else was
153
+ * quoted in Postgres to exist and stays exactly as it is (`"camelCase"`).
154
+ */
155
+ function firebirdColumn(postgresName) {
156
+ if (/^[a-z_][a-z0-9_$]*$/.test(postgresName))
157
+ return quote(postgresName.toUpperCase());
158
+ return quote(postgresName);
159
+ }
160
+ /**
161
+ * A table or column name as written in Firebird SQL, quoted: `items` and
162
+ * `ITEMS` are `"ITEMS"`; a name given in double quotes keeps its case.
163
+ */
164
+ function firebirdName(name) {
165
+ const quoted = /^"((?:[^"]|"")+)"$/.exec(name);
166
+ if (quoted)
167
+ return name;
168
+ if (/^[A-Za-z][A-Za-z0-9_$]*$/.test(name))
169
+ return quote(name.toUpperCase());
170
+ throw new TypeError(`${name} is not a Firebird name; write it in double quotes to use it as it is`);
171
+ }
172
+ function quote(name) {
173
+ return `"${name.replace(/"/g, '""')}"`;
174
+ }
175
+ /** The Firebird column type for one Postgres column, for `tableFromSchema`. */
176
+ function firebirdType(info) {
177
+ if ((info.dims ?? 0) > 0)
178
+ return 'BLOB SUB_TYPE TEXT';
179
+ switch (info.type) {
180
+ case 'int2': return 'SMALLINT';
181
+ case 'int4': return 'INTEGER';
182
+ case 'int8': return 'BIGINT';
183
+ case 'numeric':
184
+ return info.precision !== undefined
185
+ ? `NUMERIC(${Math.min(info.precision, 38)}, ${info.scale ?? 0})`
186
+ : 'DECFLOAT(34)';
187
+ case 'float4': return 'FLOAT';
188
+ case 'float8': return 'DOUBLE PRECISION';
189
+ case 'bool': return 'BOOLEAN';
190
+ case 'varchar': return info.max_length ? `VARCHAR(${info.max_length})` : 'BLOB SUB_TYPE TEXT';
191
+ case 'bpchar': return `CHAR(${info.length ?? 1})`;
192
+ // Not CHAR(36): a CHAR column reads back padded to its byte length.
193
+ case 'uuid': return 'VARCHAR(36)';
194
+ case 'date': return 'DATE';
195
+ case 'time': return 'TIME';
196
+ case 'timetz': return 'TIME WITH TIME ZONE';
197
+ case 'timestamp': return 'TIMESTAMP';
198
+ case 'timestamptz': return 'TIMESTAMP WITH TIME ZONE';
199
+ case 'text':
200
+ case 'name':
201
+ case 'citext':
202
+ case 'json':
203
+ case 'jsonb':
204
+ return 'BLOB SUB_TYPE TEXT';
205
+ default:
206
+ throw new TypeError(`${info.type} is not mapped to Firebird`);
207
+ }
208
+ }
209
+ /**
210
+ * `CREATE TABLE` for a shape's columns, to read and adjust before running.
211
+ *
212
+ * Unbounded text becomes `BLOB SUB_TYPE TEXT`, which cannot be part of a key
213
+ * or an index; a text primary key gets `VARCHAR(255)` instead, and is worth
214
+ * a look. Nothing here runs the statement: what types a table should have
215
+ * is the app's call, and a later change to the Postgres table does not
216
+ * migrate this one.
217
+ */
218
+ function tableFromSchema(table, schema) {
219
+ checkSchema(schema);
220
+ const columns = Object.entries(schema);
221
+ const key = columns
222
+ .filter(([, info]) => info.pk_index !== undefined)
223
+ .sort(([, a], [, b]) => a.pk_index - b.pk_index)
224
+ .map(([name]) => name);
225
+ const lines = columns.map(([name, info]) => {
226
+ let type = firebirdType(info);
227
+ if (key.includes(name) && type === 'BLOB SUB_TYPE TEXT')
228
+ type = 'VARCHAR(255)';
229
+ const notNull = info.not_null || key.includes(name) ? ' NOT NULL' : '';
230
+ return ` ${firebirdColumn(name)} ${type}${notNull}`;
231
+ });
232
+ if (key.length > 0)
233
+ lines.push(` PRIMARY KEY (${key.map(firebirdColumn).join(', ')})`);
234
+ return `CREATE TABLE ${firebirdName(table)} (\n${lines.join(',\n')}\n)`;
235
+ }
236
+ /** The shape's primary key, as Postgres column names in key order. */
237
+ function primaryKeyOf(schema) {
238
+ return Object.entries(schema)
239
+ .filter(([, info]) => info.pk_index !== undefined)
240
+ .sort(([, a], [, b]) => a.pk_index - b.pk_index)
241
+ .map(([name]) => name);
242
+ }
243
+ //# sourceMappingURL=values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.js","sourceRoot":"","sources":["../../src/electric/values.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * capture.ts – the change log a shape is served from, kept by triggers.
3
+ *
4
+ * See docs/plans/firebird-shape-server.md. Per synced table, an AFTER
5
+ * trigger logs each changed row's key. The server numbers the log itself
6
+ * (stampCommitted): a change is numbered once its transaction has committed,
7
+ * which is the order the stream follows.
8
+ */
9
+ import type { Attachment } from 'node-firebird-driver';
10
+ import type { TablePlan } from './catalog';
11
+ export declare const CHANGES = "ELECTRIC_CHANGES";
12
+ export declare const META = "ELECTRIC_META";
13
+ /** How many key columns the log has room for. */
14
+ export declare const KEY_COLUMNS = 4;
15
+ /** A short, stable trigger name for a table: Firebird 3 allows 31 characters. */
16
+ export declare function triggerName(table: string): string;
17
+ /**
18
+ * Create what the log needs (once) and a trigger for each of `tables`.
19
+ * Safe to run again: existing objects are kept, triggers replaced.
20
+ */
21
+ export declare function installCapture(attachment: Attachment, tables: TablePlan[]): Promise<void>;
22
+ /** The trigger that logs `table`'s changed keys. */
23
+ export declare function tableTrigger(table: TablePlan): string;
24
+ /**
25
+ * Give every change committed since the last call one new number, and
26
+ * return it (or 0 when there was nothing to number).
27
+ *
28
+ * Numbering after the commit, not in a commit trigger: a database trigger
29
+ * is loaded when a connection attaches, so an application connected before
30
+ * the capture was installed would never run it. A change still being
31
+ * written is invisible to this READ COMMITTED update and is numbered by a
32
+ * later call, after its commit; so numbers follow commits, and one stamper
33
+ * at a time (the server's queue) leaves no number to arrive late.
34
+ */
35
+ export declare function stampCommitted(attachment: Attachment): Promise<number>;
36
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/electric-server/capture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,sBAAsB,CAAC;AAEpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,eAAO,MAAM,OAAO,qBAAqB,CAAC;AAC1C,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAIpC,iDAAiD;AACjD,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAYD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/F;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAkBrD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB5E"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * capture.ts – the change log a shape is served from, kept by triggers.
4
+ *
5
+ * See docs/plans/firebird-shape-server.md. Per synced table, an AFTER
6
+ * trigger logs each changed row's key. The server numbers the log itself
7
+ * (stampCommitted): a change is numbered once its transaction has committed,
8
+ * which is the order the stream follows.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.KEY_COLUMNS = exports.META = exports.CHANGES = void 0;
12
+ exports.triggerName = triggerName;
13
+ exports.installCapture = installCapture;
14
+ exports.tableTrigger = tableTrigger;
15
+ exports.stampCommitted = stampCommitted;
16
+ const node_firebird_driver_1 = require("node-firebird-driver");
17
+ const catalog_1 = require("./catalog");
18
+ exports.CHANGES = 'ELECTRIC_CHANGES';
19
+ exports.META = 'ELECTRIC_META';
20
+ const CHANGE_SEQ = 'ELECTRIC_CHANGE_SEQ';
21
+ const COMMIT_SEQ = 'ELECTRIC_COMMIT_SEQ';
22
+ /** How many key columns the log has room for. */
23
+ exports.KEY_COLUMNS = 4;
24
+ /** A short, stable trigger name for a table: Firebird 3 allows 31 characters. */
25
+ function triggerName(table) {
26
+ let hash = 0x811c9dc5;
27
+ for (const ch of table) {
28
+ hash ^= ch.codePointAt(0);
29
+ hash = Math.imul(hash, 0x01000193) >>> 0;
30
+ }
31
+ return `ELECTRIC$${hash.toString(16).padStart(8, '0').toUpperCase()}`;
32
+ }
33
+ async function exists(attachment, tx, sql, name) {
34
+ // Not executeSingleton: it fails on a query that finds no row.
35
+ const rs = await attachment.executeQuery(tx, sql, [name]);
36
+ try {
37
+ return (await rs.fetch({ fetchSize: 1 })).length > 0;
38
+ }
39
+ finally {
40
+ await rs.close();
41
+ }
42
+ }
43
+ /**
44
+ * Create what the log needs (once) and a trigger for each of `tables`.
45
+ * Safe to run again: existing objects are kept, triggers replaced.
46
+ */
47
+ async function installCapture(attachment, tables) {
48
+ // DDL commits before its objects are usable, so each step is its own
49
+ // transaction.
50
+ const ddl = async (sql) => {
51
+ const tx = await attachment.startTransaction();
52
+ try {
53
+ await attachment.execute(tx, sql);
54
+ await tx.commit();
55
+ }
56
+ catch (err) {
57
+ await tx.rollback().catch(() => undefined);
58
+ throw err;
59
+ }
60
+ };
61
+ const has = async (sql, name) => {
62
+ const tx = await attachment.startTransaction();
63
+ try {
64
+ return await exists(attachment, tx, sql, name);
65
+ }
66
+ finally {
67
+ await tx.commit();
68
+ }
69
+ };
70
+ const generator = 'SELECT 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = ?';
71
+ const relation = 'SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?';
72
+ if (!(await has(generator, CHANGE_SEQ)))
73
+ await ddl(`CREATE SEQUENCE ${CHANGE_SEQ}`);
74
+ if (!(await has(generator, COMMIT_SEQ)))
75
+ await ddl(`CREATE SEQUENCE ${COMMIT_SEQ}`);
76
+ if (!(await has(relation, exports.CHANGES))) {
77
+ const keys = Array.from({ length: exports.KEY_COLUMNS }, (_, i) => `K${i + 1} VARCHAR(255)`).join(', ');
78
+ await ddl(`CREATE TABLE ${exports.CHANGES} (CHANGE_ID BIGINT NOT NULL PRIMARY KEY, TX_ID BIGINT NOT NULL, ` +
79
+ `COMMIT_SEQ BIGINT, TABLE_NAME VARCHAR(63) NOT NULL, OP CHAR(1) NOT NULL, ${keys})`);
80
+ await ddl(`CREATE INDEX ${exports.CHANGES}_TX ON ${exports.CHANGES} (TX_ID)`);
81
+ await ddl(`CREATE INDEX ${exports.CHANGES}_SEQ ON ${exports.CHANGES} (COMMIT_SEQ)`);
82
+ }
83
+ if (!(await has(relation, exports.META))) {
84
+ await ddl(`CREATE TABLE ${exports.META} (EPOCH VARCHAR(40) NOT NULL)`);
85
+ const tx = await attachment.startTransaction();
86
+ const epoch = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
87
+ await attachment.execute(tx, `INSERT INTO ${exports.META} (EPOCH) VALUES (?)`, [epoch]);
88
+ await tx.commit();
89
+ }
90
+ for (const table of tables) {
91
+ await ddl(tableTrigger(table));
92
+ }
93
+ }
94
+ /** The trigger that logs `table`'s changed keys. */
95
+ function tableTrigger(table) {
96
+ const keys = table.key.map((_, i) => `K${i + 1}`).join(', ');
97
+ const values = (row) => table.key.map((k) => `${row}.${(0, catalog_1.quote)(k)}`).join(', ');
98
+ const name = table.name.replace(/'/g, "''");
99
+ const log = (op, row) => `INSERT INTO ${exports.CHANGES} (CHANGE_ID, TX_ID, TABLE_NAME, OP, ${keys}) ` +
100
+ `VALUES (GEN_ID(${CHANGE_SEQ}, 1), CURRENT_TRANSACTION, '${name}', '${op}', ${values(row)});`;
101
+ const keyChanged = table.key.map((k) => `OLD.${(0, catalog_1.quote)(k)} IS DISTINCT FROM NEW.${(0, catalog_1.quote)(k)}`).join(' OR ');
102
+ return (`CREATE OR ALTER TRIGGER ${triggerName(table.name)} FOR ${(0, catalog_1.quote)(table.name)}\n` +
103
+ 'ACTIVE AFTER INSERT OR UPDATE OR DELETE POSITION 32767 AS\n' +
104
+ 'BEGIN\n' +
105
+ // A changed key is the old row gone and a new one there.
106
+ ` IF (DELETING OR (UPDATING AND (${keyChanged}))) THEN ${log('D', 'OLD')}\n` +
107
+ ` IF (INSERTING) THEN ${log('I', 'NEW')}\n` +
108
+ ` IF (UPDATING) THEN ${log('U', 'NEW')}\n` +
109
+ 'END');
110
+ }
111
+ /**
112
+ * Give every change committed since the last call one new number, and
113
+ * return it (or 0 when there was nothing to number).
114
+ *
115
+ * Numbering after the commit, not in a commit trigger: a database trigger
116
+ * is loaded when a connection attaches, so an application connected before
117
+ * the capture was installed would never run it. A change still being
118
+ * written is invisible to this READ COMMITTED update and is numbered by a
119
+ * later call, after its commit; so numbers follow commits, and one stamper
120
+ * at a time (the server's queue) leaves no number to arrive late.
121
+ */
122
+ async function stampCommitted(attachment) {
123
+ const tx = await attachment.startTransaction({
124
+ isolation: node_firebird_driver_1.TransactionIsolation.READ_COMMITTED,
125
+ readCommittedMode: 'RECORD_VERSION',
126
+ waitMode: 'NO_WAIT',
127
+ });
128
+ try {
129
+ const pending = await attachment.executeQuery(tx, `SELECT FIRST 1 1 FROM ${exports.CHANGES} WHERE COMMIT_SEQ IS NULL`);
130
+ const any = (await pending.fetch({ fetchSize: 1 })).length > 0;
131
+ await pending.close();
132
+ if (!any) {
133
+ await tx.commit();
134
+ return 0;
135
+ }
136
+ const seq = await attachment.executeQuery(tx, `SELECT GEN_ID(${COMMIT_SEQ}, 1) FROM RDB$DATABASE`);
137
+ const n = Number((await seq.fetch())[0][0]);
138
+ await seq.close();
139
+ await attachment.execute(tx, `UPDATE ${exports.CHANGES} SET COMMIT_SEQ = ? WHERE COMMIT_SEQ IS NULL`, [n]);
140
+ await tx.commit();
141
+ return n;
142
+ }
143
+ catch (err) {
144
+ await tx.rollback().catch(() => undefined);
145
+ throw err;
146
+ }
147
+ }
148
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/electric-server/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * catalog.ts – a Firebird table described as an Electric shape: its columns
3
+ * in Postgres's type names, its key, and the SQL that renders each value as
4
+ * the text the client parses.
5
+ */
6
+ import type { ElectricColumn, ElectricSchema } from '../electric/values';
7
+ /** A column as the system tables describe it. */
8
+ export interface FirebirdColumn {
9
+ /** The Firebird name, exactly (upper case unless it was quoted). */
10
+ name: string;
11
+ /** RDB$FIELD_TYPE. */
12
+ type: number;
13
+ subType: number;
14
+ scale: number;
15
+ precision: number;
16
+ charLength: number;
17
+ notNull: boolean;
18
+ }
19
+ /** How one column is read and described. */
20
+ export interface ColumnPlan {
21
+ /** The Firebird name. */
22
+ name: string;
23
+ /** The name the shape uses: lower case where Firebird folded it. */
24
+ shapeName: string;
25
+ /** The SELECT expression that yields the value. */
26
+ select: string;
27
+ /** How the fetched value becomes text. */
28
+ render: 'text' | 'number' | 'boolean' | 'blob' | 'utc';
29
+ /** The column in `electric-schema`. */
30
+ schema: ElectricColumn;
31
+ }
32
+ export declare function quote(name: string): string;
33
+ /**
34
+ * The shape's name for a Firebird column: a name Firebird folded to upper
35
+ * case is lower case, as Postgres would fold it, and the client folds it back
36
+ * (`TITLE` → `title` → `"TITLE"`); any other name is kept as it is.
37
+ */
38
+ export declare function shapeName(firebirdName: string): string;
39
+ /** The plan for one column, from its system-table description. */
40
+ export declare function planColumn(column: FirebirdColumn): ColumnPlan;
41
+ /** A table as the server serves it. */
42
+ export interface TablePlan {
43
+ /** The Firebird name, exactly. */
44
+ name: string;
45
+ columns: ColumnPlan[];
46
+ /** Key columns, Firebird names, in key order. */
47
+ key: string[];
48
+ schema: ElectricSchema;
49
+ }
50
+ /** Combine a table's columns and key into its plan. */
51
+ export declare function planTable(name: string, columns: FirebirdColumn[], key: string[]): TablePlan;
52
+ /** Render one fetched value as the text Electric sends, or null. */
53
+ export declare function renderValue(plan: ColumnPlan, value: unknown): string | null;
54
+ /**
55
+ * A table name as a client gives it (`items`, `ITEMS` or `"Mixed"`) as the
56
+ * name in the system tables.
57
+ */
58
+ export declare function relationName(given: string): string;
59
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/electric-server/catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzE,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;IACvD,uCAAuC;IACvC,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,UAAU,CAkD7D;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACxB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,iDAAiD;IACjD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,uDAAuD;AACvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAS,CAW3F;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc3E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlD"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * catalog.ts – a Firebird table described as an Electric shape: its columns
4
+ * in Postgres's type names, its key, and the SQL that renders each value as
5
+ * the text the client parses.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.quote = quote;
9
+ exports.shapeName = shapeName;
10
+ exports.planColumn = planColumn;
11
+ exports.planTable = planTable;
12
+ exports.renderValue = renderValue;
13
+ exports.relationName = relationName;
14
+ function quote(name) {
15
+ return `"${name.replace(/"/g, '""')}"`;
16
+ }
17
+ /**
18
+ * The shape's name for a Firebird column: a name Firebird folded to upper
19
+ * case is lower case, as Postgres would fold it, and the client folds it back
20
+ * (`TITLE` → `title` → `"TITLE"`); any other name is kept as it is.
21
+ */
22
+ function shapeName(firebirdName) {
23
+ return /^[A-Z][A-Z0-9_$]*$/.test(firebirdName) ? firebirdName.toLowerCase() : firebirdName;
24
+ }
25
+ /** The plan for one column, from its system-table description. */
26
+ function planColumn(column) {
27
+ const q = quote(column.name);
28
+ const base = { name: column.name, shapeName: shapeName(column.name) };
29
+ const schema = (type, extra = {}) => ({
30
+ type,
31
+ ...(column.notNull ? { not_null: true } : {}),
32
+ ...extra,
33
+ });
34
+ const exact = (type, extra = {}) => ({
35
+ ...base, select: `CAST(${q} AS VARCHAR(64))`, render: 'text', schema: schema(type, extra),
36
+ });
37
+ switch (column.type) {
38
+ case 7: // SMALLINT, or NUMERIC/DECIMAL stored in one
39
+ case 8: // INTEGER, likewise
40
+ case 16: // BIGINT, likewise
41
+ case 26: { // INT128, likewise
42
+ if (column.scale < 0 || column.subType === 1 || column.subType === 2) {
43
+ return exact('numeric', { precision: column.precision, scale: -column.scale });
44
+ }
45
+ if (column.type === 7)
46
+ return { ...base, select: q, render: 'number', schema: schema('int2') };
47
+ if (column.type === 8)
48
+ return { ...base, select: q, render: 'number', schema: schema('int4') };
49
+ if (column.type === 16)
50
+ return exact('int8');
51
+ return exact('numeric', { precision: 38, scale: 0 });
52
+ }
53
+ case 10: return { ...base, select: q, render: 'number', schema: schema('float4') };
54
+ case 27: return { ...base, select: q, render: 'number', schema: schema('float8') };
55
+ case 23: return { ...base, select: q, render: 'boolean', schema: schema('bool') };
56
+ case 14: return { ...base, select: q, render: 'text', schema: schema('bpchar', { length: column.charLength }) };
57
+ case 37: return { ...base, select: q, render: 'text', schema: schema('varchar', { max_length: column.charLength }) };
58
+ case 261:
59
+ return column.subType === 1
60
+ ? { ...base, select: q, render: 'blob', schema: schema('text') }
61
+ : { ...base, select: 'NULL', render: 'text', schema: schema('bytea') };
62
+ case 12: return { ...base, select: `CAST(${q} AS VARCHAR(10))`, render: 'text', schema: schema('date') };
63
+ case 13: return { ...base, select: `CAST(${q} AS VARCHAR(13))`, render: 'text', schema: schema('time') };
64
+ case 35: return { ...base, select: `CAST(${q} AS VARCHAR(24))`, render: 'text', schema: schema('timestamp') };
65
+ // The zoned types, in UTC: Firebird renders the zone as a name, the
66
+ // client wants an offset.
67
+ case 28:
68
+ return { ...base, select: `CAST(${q} AT TIME ZONE 'UTC' AS VARCHAR(40))`, render: 'utc', schema: schema('timetz') };
69
+ case 29:
70
+ return { ...base, select: `CAST(${q} AT TIME ZONE 'UTC' AS VARCHAR(48))`, render: 'utc', schema: schema('timestamptz') };
71
+ case 24:
72
+ case 25:
73
+ // DECFLOAT renders in exponent notation, which numeric does not take.
74
+ return { ...base, select: 'NULL', render: 'text', schema: schema('decfloat') };
75
+ default:
76
+ return { ...base, select: 'NULL', render: 'text', schema: schema(`firebird_type_${column.type}`) };
77
+ }
78
+ }
79
+ /** Combine a table's columns and key into its plan. */
80
+ function planTable(name, columns, key) {
81
+ if (columns.length === 0)
82
+ throw new Error(`Table ${name} does not exist or has no columns`);
83
+ if (key.length === 0)
84
+ throw new Error(`Table ${name} has no primary key, which a shape needs`);
85
+ if (key.length > 4)
86
+ throw new Error(`Table ${name} has a key of ${key.length} columns; at most 4 are supported`);
87
+ const plans = columns.map(planColumn);
88
+ const schema = {};
89
+ for (const plan of plans) {
90
+ const index = key.indexOf(plan.name);
91
+ schema[plan.shapeName] = index >= 0 ? { ...plan.schema, pk_index: index } : plan.schema;
92
+ }
93
+ return { name, columns: plans, key, schema };
94
+ }
95
+ /** Render one fetched value as the text Electric sends, or null. */
96
+ function renderValue(plan, value) {
97
+ if (value === null || value === undefined)
98
+ return null;
99
+ switch (plan.render) {
100
+ case 'number':
101
+ // String() of a double is the shortest text that reads back as it.
102
+ return String(value);
103
+ case 'boolean':
104
+ return value ? 'true' : 'false';
105
+ case 'utc':
106
+ // '2024-05-01 10:00:00.0000 UTC' → '2024-05-01 10:00:00.0000+00'
107
+ return String(value).replace(/ (UTC|GMT|\+00:00)$/, '+00');
108
+ default:
109
+ return String(value);
110
+ }
111
+ }
112
+ /**
113
+ * A table name as a client gives it (`items`, `ITEMS` or `"Mixed"`) as the
114
+ * name in the system tables.
115
+ */
116
+ function relationName(given) {
117
+ const quoted = /^"((?:[^"]|"")+)"$/.exec(given);
118
+ if (quoted)
119
+ return quoted[1].replace(/""/g, '"');
120
+ return given.toUpperCase();
121
+ }
122
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/electric-server/catalog.ts"],"names":[],"mappings":"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"}