@ekwo-ai/mcp 0.4.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.
- package/README.md +253 -0
- package/dist/backend.d.ts +24 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +105 -0
- package/dist/backend.js.map +1 -0
- package/dist/bin.d.ts +15 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +75 -0
- package/dist/bin.js.map +1 -0
- package/dist/columns.d.ts +59 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +422 -0
- package/dist/columns.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +49 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/postgrest.d.ts +35 -0
- package/dist/postgrest.d.ts.map +1 -0
- package/dist/postgrest.js +152 -0
- package/dist/postgrest.js.map +1 -0
- package/dist/rounding.d.ts +25 -0
- package/dist/rounding.d.ts.map +1 -0
- package/dist/rounding.js +35 -0
- package/dist/rounding.js.map +1 -0
- package/dist/schema.d.ts +28 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +51 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +521 -0
- package/dist/server.js.map +1 -0
- package/dist/sql.d.ts +36 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +214 -0
- package/dist/sql.js.map +1 -0
- package/dist/tools/modules.d.ts +61 -0
- package/dist/tools/modules.d.ts.map +1 -0
- package/dist/tools/modules.js +335 -0
- package/dist/tools/modules.js.map +1 -0
- package/dist/tools/read.d.ts +233 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +853 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +343 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +814 -0
- package/dist/tools/write.js.map +1 -0
- package/package.json +69 -0
package/dist/columns.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each tool reads back, written once.
|
|
3
|
+
*
|
|
4
|
+
* Two rules run through every list. A `numeric` column is asked for as
|
|
5
|
+
* `amount::text`, so the exact decimal arrives rather than a float; a `date`
|
|
6
|
+
* or a timestamp likewise, so `2026-06-15` is a string on both routes instead
|
|
7
|
+
* of whatever a driver decided a date object should be. Everything else —
|
|
8
|
+
* uuid, text, boolean, integer — crosses unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export const COMPANY = [
|
|
11
|
+
'id',
|
|
12
|
+
'name',
|
|
13
|
+
'trade_name',
|
|
14
|
+
'legal_name',
|
|
15
|
+
'legal_form',
|
|
16
|
+
'country',
|
|
17
|
+
'fiscal_country',
|
|
18
|
+
'vat_number',
|
|
19
|
+
'registration_number',
|
|
20
|
+
'address_line1',
|
|
21
|
+
'address_line2',
|
|
22
|
+
'postal_code',
|
|
23
|
+
'city',
|
|
24
|
+
'country',
|
|
25
|
+
'region',
|
|
26
|
+
'email',
|
|
27
|
+
'phone',
|
|
28
|
+
'website',
|
|
29
|
+
'logo_url',
|
|
30
|
+
'share_capital::text',
|
|
31
|
+
'share_capital_currency',
|
|
32
|
+
'activity_code',
|
|
33
|
+
'activity_scheme',
|
|
34
|
+
'document_template',
|
|
35
|
+
'default_bank_account_id',
|
|
36
|
+
'language',
|
|
37
|
+
'currency_code',
|
|
38
|
+
'lock_date::text',
|
|
39
|
+
'tax_lock_date::text',
|
|
40
|
+
'receivable_account_id',
|
|
41
|
+
'payable_account_id',
|
|
42
|
+
'suspense_account_id',
|
|
43
|
+
'retained_earnings_account_id',
|
|
44
|
+
'sales_journal_id',
|
|
45
|
+
'purchase_journal_id',
|
|
46
|
+
'miscellaneous_journal_id',
|
|
47
|
+
];
|
|
48
|
+
export const FISCAL_YEAR = ['id', 'name', 'start_date::text', 'end_date::text', 'is_closed'];
|
|
49
|
+
export const ACCOUNT = [
|
|
50
|
+
'id',
|
|
51
|
+
'code',
|
|
52
|
+
'name',
|
|
53
|
+
'account_type',
|
|
54
|
+
'internal_group',
|
|
55
|
+
'reconcilable',
|
|
56
|
+
'currency_code',
|
|
57
|
+
'deprecated',
|
|
58
|
+
];
|
|
59
|
+
export const JOURNAL = ['id', 'code', 'name', 'journal_type', 'active'];
|
|
60
|
+
export const CONTACT = [
|
|
61
|
+
'id',
|
|
62
|
+
'name',
|
|
63
|
+
'contact_type',
|
|
64
|
+
'vat_number',
|
|
65
|
+
'auxiliary_code',
|
|
66
|
+
'email',
|
|
67
|
+
'country',
|
|
68
|
+
'payment_terms_days',
|
|
69
|
+
'receivable_account_id',
|
|
70
|
+
'payable_account_id',
|
|
71
|
+
'active',
|
|
72
|
+
];
|
|
73
|
+
export const TAX = [
|
|
74
|
+
'id',
|
|
75
|
+
'code',
|
|
76
|
+
'name',
|
|
77
|
+
'amount::text',
|
|
78
|
+
'amount_type',
|
|
79
|
+
'applies_to',
|
|
80
|
+
'treatment',
|
|
81
|
+
'vat_category',
|
|
82
|
+
'valid_from::text',
|
|
83
|
+
'valid_to::text',
|
|
84
|
+
'legal_reference',
|
|
85
|
+
// The generalised engine. `tax_kind` and `jurisdiction` say what a tax is
|
|
86
|
+
// where the code alone would have to be guessed at; `recoverable` and
|
|
87
|
+
// `price_include` change what a client should show; `cash_basis` is here so
|
|
88
|
+
// a reader is not surprised by it once cash-basis VAT starts writing it.
|
|
89
|
+
'tax_kind',
|
|
90
|
+
'recoverable',
|
|
91
|
+
'jurisdiction',
|
|
92
|
+
'price_include',
|
|
93
|
+
'cash_basis',
|
|
94
|
+
'active',
|
|
95
|
+
];
|
|
96
|
+
export const DOCUMENT = [
|
|
97
|
+
'id',
|
|
98
|
+
'company_id',
|
|
99
|
+
'doc_type',
|
|
100
|
+
'state',
|
|
101
|
+
'payment_state',
|
|
102
|
+
'number',
|
|
103
|
+
'supplier_reference',
|
|
104
|
+
'contact_id',
|
|
105
|
+
'journal_id',
|
|
106
|
+
'document_date::text',
|
|
107
|
+
'accounting_date::text',
|
|
108
|
+
'due_date::text',
|
|
109
|
+
'currency_code',
|
|
110
|
+
'amount_untaxed::text',
|
|
111
|
+
'amount_tax::text',
|
|
112
|
+
'amount_total::text',
|
|
113
|
+
'amount_paid::text',
|
|
114
|
+
'amount_residual::text',
|
|
115
|
+
'entry_id',
|
|
116
|
+
'sent_at::text',
|
|
117
|
+
];
|
|
118
|
+
/**
|
|
119
|
+
* The mentions the law of a country puts on one document, from the view that
|
|
120
|
+
* decides which of them apply. `text_i18n` crosses as it is: a renderer picks
|
|
121
|
+
* the language, and the server has no business choosing one.
|
|
122
|
+
*/
|
|
123
|
+
export const DOCUMENT_LEGAL_MENTION = [
|
|
124
|
+
'code',
|
|
125
|
+
'applies_when',
|
|
126
|
+
'text',
|
|
127
|
+
'text_i18n',
|
|
128
|
+
'sequence',
|
|
129
|
+
'legal_reference',
|
|
130
|
+
];
|
|
131
|
+
/**
|
|
132
|
+
* What the country of a document requires of it, beside the mentions: the
|
|
133
|
+
* payment term the law sets, how the number is built, when the tax falls due
|
|
134
|
+
* and how the document is exchanged. Read from `country_defaults`, which
|
|
135
|
+
* holds null wherever the pack has said nothing — and null is the answer, not
|
|
136
|
+
* an invitation to substitute another country's.
|
|
137
|
+
*/
|
|
138
|
+
export const COUNTRY_DOCUMENT_RULES = [
|
|
139
|
+
'country',
|
|
140
|
+
'numbering_gapless',
|
|
141
|
+
'number_format',
|
|
142
|
+
'legal_payment_days',
|
|
143
|
+
'late_payment_reference',
|
|
144
|
+
'tax_point_rule',
|
|
145
|
+
'einvoice_profile',
|
|
146
|
+
'einvoice_mandatory_from::text',
|
|
147
|
+
'party_scheme',
|
|
148
|
+
'vat_scheme',
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* The header of a document, from the view that assembles it: the seller, the
|
|
152
|
+
* buyer, the amounts, where it is paid and what the country of the document
|
|
153
|
+
* requires. `document_line_items` is the lines and `document_legal_mentions`
|
|
154
|
+
* the sentences — three reads, and no client holding a copy of the
|
|
155
|
+
* letterhead.
|
|
156
|
+
*/
|
|
157
|
+
export const DOCUMENT_HEADER = [
|
|
158
|
+
'document_id',
|
|
159
|
+
'company_id',
|
|
160
|
+
'doc_type',
|
|
161
|
+
'state',
|
|
162
|
+
'payment_state',
|
|
163
|
+
'number',
|
|
164
|
+
'supplier_reference',
|
|
165
|
+
'document_date::text',
|
|
166
|
+
'accounting_date::text',
|
|
167
|
+
'due_date::text',
|
|
168
|
+
'delivery_date::text',
|
|
169
|
+
'currency_code',
|
|
170
|
+
'amount_untaxed::text',
|
|
171
|
+
'amount_tax::text',
|
|
172
|
+
'amount_total::text',
|
|
173
|
+
'amount_paid::text',
|
|
174
|
+
'amount_residual::text',
|
|
175
|
+
'payment_terms',
|
|
176
|
+
'payment_means_code',
|
|
177
|
+
'payment_reference',
|
|
178
|
+
'buyer_reference',
|
|
179
|
+
'order_reference',
|
|
180
|
+
'contract_reference',
|
|
181
|
+
'project_reference',
|
|
182
|
+
'note',
|
|
183
|
+
'payee_iban',
|
|
184
|
+
'payee_bic',
|
|
185
|
+
'seller_name',
|
|
186
|
+
'seller_legal_name',
|
|
187
|
+
'seller_legal_form',
|
|
188
|
+
'seller_vat_number',
|
|
189
|
+
'seller_registration_number',
|
|
190
|
+
'seller_address_line1',
|
|
191
|
+
'seller_address_line2',
|
|
192
|
+
'seller_postal_code',
|
|
193
|
+
'seller_city',
|
|
194
|
+
'seller_country',
|
|
195
|
+
'seller_email',
|
|
196
|
+
'seller_phone',
|
|
197
|
+
'seller_website',
|
|
198
|
+
'seller_logo_url',
|
|
199
|
+
'seller_share_capital::text',
|
|
200
|
+
'seller_share_capital_currency',
|
|
201
|
+
'seller_activity_code',
|
|
202
|
+
'seller_activity_scheme',
|
|
203
|
+
'document_template',
|
|
204
|
+
'buyer_id',
|
|
205
|
+
'buyer_name',
|
|
206
|
+
'buyer_vat_number',
|
|
207
|
+
'buyer_registration_number',
|
|
208
|
+
'buyer_address_line1',
|
|
209
|
+
'buyer_address_line2',
|
|
210
|
+
'buyer_postal_code',
|
|
211
|
+
'buyer_city',
|
|
212
|
+
'buyer_country',
|
|
213
|
+
'buyer_email',
|
|
214
|
+
'country',
|
|
215
|
+
'number_format',
|
|
216
|
+
'numbering_gapless',
|
|
217
|
+
'legal_payment_days',
|
|
218
|
+
'late_payment_reference',
|
|
219
|
+
'tax_point_rule',
|
|
220
|
+
'einvoice_profile',
|
|
221
|
+
'einvoice_mandatory_from::text',
|
|
222
|
+
'party_scheme',
|
|
223
|
+
'vat_scheme',
|
|
224
|
+
];
|
|
225
|
+
export const PRODUCT = [
|
|
226
|
+
'id',
|
|
227
|
+
'company_id',
|
|
228
|
+
'code',
|
|
229
|
+
'name',
|
|
230
|
+
'description',
|
|
231
|
+
'kind',
|
|
232
|
+
'unit_code',
|
|
233
|
+
'currency_code',
|
|
234
|
+
'sale_price::text',
|
|
235
|
+
'purchase_price::text',
|
|
236
|
+
'sale_account_id',
|
|
237
|
+
'purchase_account_id',
|
|
238
|
+
'sale_tax_id',
|
|
239
|
+
'purchase_tax_id',
|
|
240
|
+
'active',
|
|
241
|
+
];
|
|
242
|
+
export const DOCUMENT_LINE = [
|
|
243
|
+
'id',
|
|
244
|
+
'document_id',
|
|
245
|
+
'sequence',
|
|
246
|
+
'line_type',
|
|
247
|
+
'name',
|
|
248
|
+
'description',
|
|
249
|
+
'product_id',
|
|
250
|
+
'quantity::text',
|
|
251
|
+
'unit_code',
|
|
252
|
+
'unit_price::text',
|
|
253
|
+
'discount_percent::text',
|
|
254
|
+
'tax_id',
|
|
255
|
+
'account_id',
|
|
256
|
+
'vat_category',
|
|
257
|
+
'vat_rate::text',
|
|
258
|
+
'amount_untaxed::text',
|
|
259
|
+
];
|
|
260
|
+
export const ENTRY = [
|
|
261
|
+
'id',
|
|
262
|
+
'company_id',
|
|
263
|
+
'journal_id',
|
|
264
|
+
'number',
|
|
265
|
+
'entry_date::text',
|
|
266
|
+
'reference',
|
|
267
|
+
'description',
|
|
268
|
+
'state',
|
|
269
|
+
'kind',
|
|
270
|
+
'document_id',
|
|
271
|
+
'total_debit::text',
|
|
272
|
+
'total_credit::text',
|
|
273
|
+
'is_balanced',
|
|
274
|
+
'posted_at::text',
|
|
275
|
+
];
|
|
276
|
+
export const ENTRY_LINE = [
|
|
277
|
+
'id',
|
|
278
|
+
'entry_id',
|
|
279
|
+
'account_id',
|
|
280
|
+
'sequence',
|
|
281
|
+
'name',
|
|
282
|
+
'debit::text',
|
|
283
|
+
'credit::text',
|
|
284
|
+
'balance::text',
|
|
285
|
+
'contact_id',
|
|
286
|
+
'date_maturity::text',
|
|
287
|
+
'tax_id',
|
|
288
|
+
'tax_line',
|
|
289
|
+
'declaration_box',
|
|
290
|
+
'box_amount::text',
|
|
291
|
+
'matching_number',
|
|
292
|
+
'matched_amount::text',
|
|
293
|
+
// The currency of the line and its amount in it, written whenever that
|
|
294
|
+
// currency is not the company's. A client matching two foreign lines needs
|
|
295
|
+
// both: that is the scale the matching is worked out on.
|
|
296
|
+
'currency_code',
|
|
297
|
+
'amount_currency::text',
|
|
298
|
+
];
|
|
299
|
+
export const BANK_ACCOUNT = [
|
|
300
|
+
'id',
|
|
301
|
+
'name',
|
|
302
|
+
'iban',
|
|
303
|
+
'bic',
|
|
304
|
+
'bank_name',
|
|
305
|
+
'currency_code',
|
|
306
|
+
'account_id',
|
|
307
|
+
'journal_id',
|
|
308
|
+
'active',
|
|
309
|
+
];
|
|
310
|
+
export const BANK_TRANSACTION = [
|
|
311
|
+
'id',
|
|
312
|
+
'company_id',
|
|
313
|
+
'bank_account_id',
|
|
314
|
+
'statement_id',
|
|
315
|
+
'transaction_date::text',
|
|
316
|
+
'value_date::text',
|
|
317
|
+
'amount::text',
|
|
318
|
+
'currency_code',
|
|
319
|
+
'description',
|
|
320
|
+
'counterpart_name',
|
|
321
|
+
'counterpart_iban',
|
|
322
|
+
'reference',
|
|
323
|
+
'structured_reference',
|
|
324
|
+
'contact_id',
|
|
325
|
+
'entry_id',
|
|
326
|
+
'state',
|
|
327
|
+
];
|
|
328
|
+
export const PAYMENT = [
|
|
329
|
+
'id',
|
|
330
|
+
'company_id',
|
|
331
|
+
'direction',
|
|
332
|
+
'payment_date::text',
|
|
333
|
+
'amount::text',
|
|
334
|
+
'currency_code',
|
|
335
|
+
'contact_id',
|
|
336
|
+
'journal_id',
|
|
337
|
+
'bank_account_id',
|
|
338
|
+
'entry_id',
|
|
339
|
+
'reference',
|
|
340
|
+
'memo',
|
|
341
|
+
'state',
|
|
342
|
+
];
|
|
343
|
+
/**
|
|
344
|
+
* An invitation, without its secret. `token_hash` is deliberately absent: it
|
|
345
|
+
* is of no use to a client, and a list of hashes is a thing to leak rather
|
|
346
|
+
* than a thing to show.
|
|
347
|
+
*/
|
|
348
|
+
/** What one person chose for themselves. Null everywhere is the fresh state. */
|
|
349
|
+
export const USER_PREFERENCES = [
|
|
350
|
+
'user_id',
|
|
351
|
+
'preferred_company_id',
|
|
352
|
+
'language',
|
|
353
|
+
'timezone',
|
|
354
|
+
'date_display_format',
|
|
355
|
+
'number_display_format',
|
|
356
|
+
'theme',
|
|
357
|
+
'updated_at::text',
|
|
358
|
+
];
|
|
359
|
+
export const INVITATION = [
|
|
360
|
+
'id',
|
|
361
|
+
'company_id',
|
|
362
|
+
'email',
|
|
363
|
+
'role',
|
|
364
|
+
'capabilities_granted',
|
|
365
|
+
'invited_by',
|
|
366
|
+
'created_at::text',
|
|
367
|
+
'expires_at::text',
|
|
368
|
+
'accepted_at::text',
|
|
369
|
+
'accepted_by',
|
|
370
|
+
'revoked_at::text',
|
|
371
|
+
];
|
|
372
|
+
/** A machine key, without its hash. The secret is returned once, elsewhere. */
|
|
373
|
+
export const API_KEY = [
|
|
374
|
+
'id',
|
|
375
|
+
'company_id',
|
|
376
|
+
'name',
|
|
377
|
+
'prefix',
|
|
378
|
+
'capabilities',
|
|
379
|
+
'created_by',
|
|
380
|
+
'created_at::text',
|
|
381
|
+
'expires_at::text',
|
|
382
|
+
'last_used_at::text',
|
|
383
|
+
'revoked_at::text',
|
|
384
|
+
];
|
|
385
|
+
export const INSTANCE = [
|
|
386
|
+
'instance_id',
|
|
387
|
+
'organization_name',
|
|
388
|
+
'country',
|
|
389
|
+
'edition',
|
|
390
|
+
'schema_version',
|
|
391
|
+
'installed_at::text',
|
|
392
|
+
'registered_at::text',
|
|
393
|
+
];
|
|
394
|
+
export const RECONCILIATION = [
|
|
395
|
+
'id',
|
|
396
|
+
'debit_line_id',
|
|
397
|
+
'credit_line_id',
|
|
398
|
+
'amount::text',
|
|
399
|
+
'matching_number',
|
|
400
|
+
'matched_at::text',
|
|
401
|
+
// What the matching revealed, when it revealed something: the entry that
|
|
402
|
+
// booked the realised exchange difference, and the one that moved the share
|
|
403
|
+
// of a cash-basis tax the settlement made due. A client that shows a
|
|
404
|
+
// matching shows what it caused.
|
|
405
|
+
'fx_entry_id',
|
|
406
|
+
'tax_transfer_entry_id',
|
|
407
|
+
];
|
|
408
|
+
export const AUDIT_LOG = [
|
|
409
|
+
'id',
|
|
410
|
+
'occurred_at::text',
|
|
411
|
+
'actor_id',
|
|
412
|
+
'api_key_id',
|
|
413
|
+
'company_id',
|
|
414
|
+
'table_name',
|
|
415
|
+
'record_id',
|
|
416
|
+
'record_key',
|
|
417
|
+
'operation',
|
|
418
|
+
'action',
|
|
419
|
+
'old_values',
|
|
420
|
+
'new_values',
|
|
421
|
+
];
|
|
422
|
+
//# sourceMappingURL=columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns.js","sourceRoot":"","sources":["../src/columns.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the server gets its connection, and what it refuses.
|
|
3
|
+
*
|
|
4
|
+
* Two modes, and the order matters. **PostgREST with the user's own session**
|
|
5
|
+
* is the recommended one: `SUPABASE_URL` and `SUPABASE_ANON_KEY` with either a
|
|
6
|
+
* password or an access token, and row level security decides everything from
|
|
7
|
+
* there. **A direct Postgres connection** is the fallback for a self-hosted
|
|
8
|
+
* installation; it demands `EKWO_ACT_AS_USER_ID` precisely because a database
|
|
9
|
+
* connection is nobody, and a server acting as nobody would be acting as the
|
|
10
|
+
* owner.
|
|
11
|
+
*
|
|
12
|
+
* The one thing this file exists to refuse is a `service_role` key. It would
|
|
13
|
+
* work — that is the problem. Every policy in the schema would be bypassed,
|
|
14
|
+
* and an assistant that can read every company of an installation is not the
|
|
15
|
+
* thing anybody asked for. It is refused wherever it can arrive, and there are
|
|
16
|
+
* two doors, not one: `SUPABASE_ANON_KEY` is the obvious paste, and
|
|
17
|
+
* `EKWO_ACCESS_TOKEN` is the quiet one — it goes into the `Authorization`
|
|
18
|
+
* header, which is what PostgREST reads for the role, so a service_role key
|
|
19
|
+
* put there overrides a perfectly good anon key sitting next to it.
|
|
20
|
+
*/
|
|
21
|
+
import { isServiceRoleKey } from '@ekwo-ai/core';
|
|
22
|
+
import { type Backend } from './backend.js';
|
|
23
|
+
export interface Config {
|
|
24
|
+
mode: 'postgrest' | 'sql';
|
|
25
|
+
supabaseUrl?: string;
|
|
26
|
+
anonKey?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
password?: string;
|
|
29
|
+
accessToken?: string;
|
|
30
|
+
dbUrl?: string;
|
|
31
|
+
actAsUserId?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const ENV: {
|
|
34
|
+
readonly dbUrl: "EKWO_DB_URL";
|
|
35
|
+
readonly actAsUserId: "EKWO_ACT_AS_USER_ID";
|
|
36
|
+
readonly supabaseUrl: "SUPABASE_URL";
|
|
37
|
+
readonly anonKey: "SUPABASE_ANON_KEY";
|
|
38
|
+
readonly email: "EKWO_EMAIL";
|
|
39
|
+
readonly password: "EKWO_PASSWORD";
|
|
40
|
+
readonly accessToken: "EKWO_ACCESS_TOKEN";
|
|
41
|
+
};
|
|
42
|
+
export { isServiceRoleKey };
|
|
43
|
+
/** Reads the environment, and says what is missing rather than failing later. */
|
|
44
|
+
export declare function readConfig(env?: NodeJS.ProcessEnv): Config;
|
|
45
|
+
/** Opens the backend the configuration describes. The caller closes it. */
|
|
46
|
+
export declare function openBackend(config: Config): Promise<Backend>;
|
|
47
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAgB,gBAAgB,EAAsB,MAAM,eAAe,CAAC;AACnF,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAI1D,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,WAAW,GAAG,KAAK,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,GAAG;;;;;;;;CAIN,CAAC;AAUX,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAS5B,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CA2DvE;AAED,2EAA2E;AAC3E,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYlE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the server gets its connection, and what it refuses.
|
|
3
|
+
*
|
|
4
|
+
* Two modes, and the order matters. **PostgREST with the user's own session**
|
|
5
|
+
* is the recommended one: `SUPABASE_URL` and `SUPABASE_ANON_KEY` with either a
|
|
6
|
+
* password or an access token, and row level security decides everything from
|
|
7
|
+
* there. **A direct Postgres connection** is the fallback for a self-hosted
|
|
8
|
+
* installation; it demands `EKWO_ACT_AS_USER_ID` precisely because a database
|
|
9
|
+
* connection is nobody, and a server acting as nobody would be acting as the
|
|
10
|
+
* owner.
|
|
11
|
+
*
|
|
12
|
+
* The one thing this file exists to refuse is a `service_role` key. It would
|
|
13
|
+
* work — that is the problem. Every policy in the schema would be bypassed,
|
|
14
|
+
* and an assistant that can read every company of an installation is not the
|
|
15
|
+
* thing anybody asked for. It is refused wherever it can arrive, and there are
|
|
16
|
+
* two doors, not one: `SUPABASE_ANON_KEY` is the obvious paste, and
|
|
17
|
+
* `EKWO_ACCESS_TOKEN` is the quiet one — it goes into the `Authorization`
|
|
18
|
+
* header, which is what PostgREST reads for the role, so a service_role key
|
|
19
|
+
* put there overrides a perfectly good anon key sitting next to it.
|
|
20
|
+
*/
|
|
21
|
+
import { IDENTITY_ENV, isServiceRoleKey, serviceRoleRefusal } from '@ekwo-ai/core';
|
|
22
|
+
import { EkwoMcpError } from './backend.js';
|
|
23
|
+
import { postgrestBackend } from './postgrest.js';
|
|
24
|
+
import { connect, sqlBackend } from './sql.js';
|
|
25
|
+
export const ENV = {
|
|
26
|
+
...IDENTITY_ENV,
|
|
27
|
+
dbUrl: 'EKWO_DB_URL',
|
|
28
|
+
actAsUserId: 'EKWO_ACT_AS_USER_ID',
|
|
29
|
+
};
|
|
30
|
+
const SURFACE = 'this server';
|
|
31
|
+
const SIGN_IN = `sign in, or set ${IDENTITY_ENV.email} and ${IDENTITY_ENV.password} and let this server sign in for you`;
|
|
32
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33
|
+
// The test and the sentence moved to the core, where the command line reads
|
|
34
|
+
// them too: which key is refused cannot be decided twice. Still exported from
|
|
35
|
+
// here, as it always was.
|
|
36
|
+
export { isServiceRoleKey };
|
|
37
|
+
function trimmed(env, name) {
|
|
38
|
+
const value = env[name];
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const text = value.trim();
|
|
42
|
+
return text.length === 0 ? undefined : text;
|
|
43
|
+
}
|
|
44
|
+
/** Reads the environment, and says what is missing rather than failing later. */
|
|
45
|
+
export function readConfig(env = process.env) {
|
|
46
|
+
const dbUrl = trimmed(env, ENV.dbUrl);
|
|
47
|
+
if (dbUrl !== undefined) {
|
|
48
|
+
const actAsUserId = trimmed(env, ENV.actAsUserId);
|
|
49
|
+
if (actAsUserId === undefined) {
|
|
50
|
+
throw new EkwoMcpError(`missing_act_as_user: ${ENV.dbUrl} needs ${ENV.actAsUserId}. A database connection is nobody — auth.uid() is null and row level security is bypassed rather than satisfied — so this server refuses to run without the user it acts for.`);
|
|
51
|
+
}
|
|
52
|
+
if (!UUID.test(actAsUserId)) {
|
|
53
|
+
throw new EkwoMcpError(`bad_act_as_user: ${ENV.actAsUserId} must be the auth.users id of a real user, as a uuid.`);
|
|
54
|
+
}
|
|
55
|
+
return { mode: 'sql', dbUrl, actAsUserId };
|
|
56
|
+
}
|
|
57
|
+
const supabaseUrl = trimmed(env, ENV.supabaseUrl);
|
|
58
|
+
const anonKey = trimmed(env, ENV.anonKey);
|
|
59
|
+
if (supabaseUrl === undefined || anonKey === undefined) {
|
|
60
|
+
throw new EkwoMcpError(`missing_configuration: set ${ENV.supabaseUrl} and ${ENV.anonKey}, plus either ${ENV.email} and ${ENV.password} or ${ENV.accessToken}. For a self-hosted database, set ${ENV.dbUrl} and ${ENV.actAsUserId} instead.`);
|
|
61
|
+
}
|
|
62
|
+
if (isServiceRoleKey(anonKey)) {
|
|
63
|
+
throw new EkwoMcpError(serviceRoleRefusal(ENV.anonKey, 'apikey', SURFACE, SIGN_IN));
|
|
64
|
+
}
|
|
65
|
+
const accessToken = trimmed(env, ENV.accessToken);
|
|
66
|
+
// The second door. `postgrestBackend` puts this straight into
|
|
67
|
+
// `Authorization: Bearer`, and PostgREST takes the role from that header and
|
|
68
|
+
// not from `apikey` — so a service_role token here bypasses every policy
|
|
69
|
+
// exactly as it would in the slot above, while the anon key next to it makes
|
|
70
|
+
// the configuration look right.
|
|
71
|
+
if (accessToken !== undefined && isServiceRoleKey(accessToken)) {
|
|
72
|
+
throw new EkwoMcpError(serviceRoleRefusal(ENV.accessToken, 'authorization', SURFACE, SIGN_IN));
|
|
73
|
+
}
|
|
74
|
+
const email = trimmed(env, ENV.email);
|
|
75
|
+
const password = trimmed(env, ENV.password);
|
|
76
|
+
if (accessToken === undefined && (email === undefined || password === undefined)) {
|
|
77
|
+
throw new EkwoMcpError(`missing_credentials: set ${ENV.email} and ${ENV.password}, or ${ENV.accessToken}. This server has no identity of its own; it acts as the person using it.`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
mode: 'postgrest',
|
|
81
|
+
supabaseUrl,
|
|
82
|
+
anonKey,
|
|
83
|
+
...(accessToken !== undefined ? { accessToken } : {}),
|
|
84
|
+
...(email !== undefined ? { email } : {}),
|
|
85
|
+
...(password !== undefined ? { password } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Opens the backend the configuration describes. The caller closes it. */
|
|
89
|
+
export async function openBackend(config) {
|
|
90
|
+
if (config.mode === 'sql') {
|
|
91
|
+
const db = await connect(config.dbUrl);
|
|
92
|
+
return sqlBackend(db, { userId: config.actAsUserId });
|
|
93
|
+
}
|
|
94
|
+
return postgrestBackend({
|
|
95
|
+
supabaseUrl: config.supabaseUrl,
|
|
96
|
+
anonKey: config.anonKey,
|
|
97
|
+
accessToken: config.accessToken,
|
|
98
|
+
email: config.email,
|
|
99
|
+
password: config.password,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as decimal strings, everywhere, in both directions.
|
|
3
|
+
*
|
|
4
|
+
* Postgres holds `numeric` exactly and hands it over as text; a table read
|
|
5
|
+
* asks for `amount::text` so that is what arrives. The one place a float can
|
|
6
|
+
* appear is a function result crossing PostgREST as JSON, where `1210.00`
|
|
7
|
+
* becomes a number before any code of ours sees it. `money()` puts it back to
|
|
8
|
+
* two decimals, which is exact for every amount a ledger holds, and a model
|
|
9
|
+
* reading `"1210.00"` never has to wonder whether `1210.0000000001` was the
|
|
10
|
+
* VAT rounding or the transport.
|
|
11
|
+
*/
|
|
12
|
+
export type Decimal = string;
|
|
13
|
+
/** A numeric, as the decimal string the ledger holds. */
|
|
14
|
+
export declare function money(value: unknown): Decimal | null;
|
|
15
|
+
/** The same, for a quantity or a rate, where two decimals would be a lie. */
|
|
16
|
+
export declare function decimal(value: unknown): Decimal | null;
|
|
17
|
+
/** Applies `money()` to the named keys of every row. */
|
|
18
|
+
export declare function moneyFields<T extends Record<string, unknown>>(rows: T[], keys: string[]): T[];
|
|
19
|
+
/** An amount on its way into the database: `"1210.00"`, never 1210.0000001. */
|
|
20
|
+
export declare function amountIn(value: string | number): string;
|
|
21
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAKpD;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAItD;AAED,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAQ7F;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAMvD"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as decimal strings, everywhere, in both directions.
|
|
3
|
+
*
|
|
4
|
+
* Postgres holds `numeric` exactly and hands it over as text; a table read
|
|
5
|
+
* asks for `amount::text` so that is what arrives. The one place a float can
|
|
6
|
+
* appear is a function result crossing PostgREST as JSON, where `1210.00`
|
|
7
|
+
* becomes a number before any code of ours sees it. `money()` puts it back to
|
|
8
|
+
* two decimals, which is exact for every amount a ledger holds, and a model
|
|
9
|
+
* reading `"1210.00"` never has to wonder whether `1210.0000000001` was the
|
|
10
|
+
* VAT rounding or the transport.
|
|
11
|
+
*/
|
|
12
|
+
import { EkwoMcpError } from './backend.js';
|
|
13
|
+
import { roundCurrency } from './rounding.js';
|
|
14
|
+
/** A numeric, as the decimal string the ledger holds. */
|
|
15
|
+
export function money(value) {
|
|
16
|
+
if (value === null || value === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
if (typeof value === 'number')
|
|
19
|
+
return roundCurrency(value, 2).toFixed(2);
|
|
20
|
+
const text = String(value);
|
|
21
|
+
return text.length === 0 ? null : text;
|
|
22
|
+
}
|
|
23
|
+
/** The same, for a quantity or a rate, where two decimals would be a lie. */
|
|
24
|
+
export function decimal(value) {
|
|
25
|
+
if (value === null || value === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const text = String(value);
|
|
28
|
+
return text.length === 0 ? null : text;
|
|
29
|
+
}
|
|
30
|
+
/** Applies `money()` to the named keys of every row. */
|
|
31
|
+
export function moneyFields(rows, keys) {
|
|
32
|
+
return rows.map((row) => {
|
|
33
|
+
const out = { ...row };
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
if (key in out)
|
|
36
|
+
out[key] = money(out[key]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** An amount on its way into the database: `"1210.00"`, never 1210.0000001. */
|
|
42
|
+
export function amountIn(value) {
|
|
43
|
+
const text = typeof value === 'number' ? roundCurrency(value, 2).toFixed(2) : value.trim();
|
|
44
|
+
if (!/^-?\d+(\.\d+)?$/.test(text)) {
|
|
45
|
+
throw new EkwoMcpError(`bad_amount: ${String(value)} is not a decimal amount`);
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI9C,yDAAyD;AACzD,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,WAAW,CAAoC,IAAS,EAAE,IAAc;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,GAAG,GAA4B,EAAE,GAAG,GAAG,EAAE,CAAC;QAChD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,IAAI,GAAG;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,GAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,QAAQ,CAAC,KAAsB;IAC7C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3F,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,YAAY,CAAC,eAAe,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|