Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
// Generated, do not edit. sha256:09721f6e6374d0297156a1b8af0d4578d0bbb9c98d4ea7c96458dafbb2d56dcc
import { createHmac } from 'node:crypto';
const HANDLE_TEKENS = 6;
const BECH32 = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const HEX = '0123456789abcdef';
const SOORTEN = Object.freeze({
address: Object.freeze({ prefix: 'bc1q', lengte: 42, alfabet: BECH32, onmogelijk: 'o' }),
xpub: Object.freeze({ prefix: 'xpub', lengte: 111, alfabet: BASE58, onmogelijk: '0' }),
txid: Object.freeze({ prefix: '', lengte: 64, alfabet: HEX, onmogelijk: 'z' }),
});
export const ALIAS_SOORTEN = Object.freeze(Object.keys(SOORTEN));
function vul(alfabet, lengte, zaad) {
let s = '';
for (let i = 0; i < lengte; i++) {
const paar = parseInt(zaad.slice((i * 2) % zaad.length, (i * 2) % zaad.length + 2) || '0', 16);
s += alfabet[(paar + i) % alfabet.length];
}
return s;
}
export function schijnwaarde(soort, zaad) {
const s = SOORTEN[soort];
if (!s) throw new Error(`unknown alias kind: ${soort}`);
const romp = vul(s.alfabet, s.lengte - s.prefix.length, String(zaad));
const met = `${romp.slice(0, 2)}${s.onmogelijk}${romp.slice(3)}`;
return `${s.prefix}${met}`;
}
export function isSchijnwaarde(tekst) {
if (typeof tekst !== 'string') return false;
for (const [, s] of Object.entries(SOORTEN)) {
if (s.prefix && !tekst.startsWith(s.prefix)) continue;
if (tekst.length !== s.lengte) continue;
const data = tekst.slice(s.prefix.length);
if (data.includes(s.onmogelijk)) return true;
}
return false;
}
export function createAliasRegistry({ secret } = {}) {
if (typeof secret !== 'string' || secret.length < 32) {
throw new Error('An alias registry without a secret is not allowed.');
}
const heen = new Map();
const terug = new Map();
const afgeleid = (soort, waarde) =>
createHmac('sha256', secret).update(`${soort}\0${waarde}`).digest('hex');
function onthoud(sleutel, uit, soort, waarde) {
const bestaand = terug.get(uit);
if (bestaand && bestaand.waarde !== waarde) {
throw new Error(`Handle collision on '${uit}': two different values yield the same handle.`);
}
heen.set(sleutel, uit);
terug.set(uit, { soort, waarde });
return uit;
}
return {
alias(soort, waarde) {
const sleutel = `${soort} ${waarde}`;
if (heen.has(sleutel)) return heen.get(sleutel);
return onthoud(sleutel, schijnwaarde(soort, afgeleid(soort, waarde)), soort, waarde);
},
handle(soort, waarde) {
const sleutel = `${soort} ${waarde}`;
if (heen.has(sleutel)) return heen.get(sleutel);
return onthoud(sleutel, `${soort}:${afgeleid(soort, waarde).slice(0, HANDLE_TEKENS)}`, soort, waarde);
},
resolve(uit) {
const r = terug.get(uit);
return r ? r.waarde : null;
},
uitgegeven() {
return [...terug.entries()].map(([uit, r]) => ({ uit, soort: r.soort }));
},
};
}
+90
View File
@@ -0,0 +1,90 @@
// Generated, do not edit. sha256:74e4ed13a040cfe4e2dc4c23217c88607a06c14c8cc97aceaf7026b8d073ba91
import { CONFIG } from '../../shared/config.js';
const LADDERS = CONFIG.AI.BUCKET_LADDERS;
function bodemVan(ladderNaam) {
const min = CONFIG.AI.BUCKET_MIN_VALUES[ladderNaam] || 0;
if (!min) return 0;
const ladder = LADDERS[ladderNaam];
let smalst = Infinity;
for (let i = 0; i < ladder.length; i++) {
const onder = ladder[i];
const boven = i + 1 < ladder.length ? ladder[i + 1] : 10;
smalst = Math.min(smalst, (boven - onder) / onder);
}
return min / smalst;
}
export function bucket(waarde, ladderNaam = 'COARSE') {
const ladder = LADDERS[ladderNaam];
if (!ladder) throw new Error(`unknown ladder: ${ladderNaam}`);
if (typeof waarde !== 'number' || !Number.isFinite(waarde)) return null;
if (waarde === 0) return { from: 0, to: 0, exact: true };
const negatief = waarde < 0;
const abs = Math.abs(waarde);
const bodem = Number.isInteger(waarde) ? bodemVan(ladderNaam) : 0;
if (bodem && abs < bodem) {
return negatief
? { from: -bodem, to: 0, exact: false, floor: true }
: { from: 0, to: bodem, exact: false, floor: true };
}
const orde = Math.floor(Math.log10(abs));
const schaal = 10 ** orde;
const genormaliseerd = Math.min(Math.max(abs / schaal, 1), 9.999999999);
let onder = ladder[0];
let boven = 10;
for (let i = 0; i < ladder.length; i++) {
if (genormaliseerd >= ladder[i]) {
onder = ladder[i];
boven = i + 1 < ladder.length ? ladder[i + 1] : 10;
}
}
const van = onder * schaal;
const tot = boven * schaal;
return negatief ? { from: -tot, to: -van, exact: false } : { from: van, to: tot, exact: false };
}
export function bucketLabel(waarde, ladderNaam = 'COARSE') {
const b = bucket(waarde, ladderNaam);
if (!b) return null;
if (b.exact) return String(b.from);
if (b.floor) return b.to === 0 ? `meer dan ${getal(b.from)}` : `minder dan ${getal(b.to)}`;
return `${getal(b.from)} tot ${getal(b.to)}`;
}
function getal(n) {
return Number.isInteger(n) ? String(n) : String(Math.round(n * 100) / 100);
}
export function shareBucket(pct) {
const grenzen = CONFIG.AI.SHARE_BUCKETS;
if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < 0) return null;
if (pct === 0) return { from: 0, to: 0, below: false };
if (pct < grenzen[0]) return { from: 0, to: grenzen[0], below: true };
let onder = grenzen[0];
let boven = grenzen[grenzen.length - 1];
for (let i = 0; i < grenzen.length; i++) {
if (pct >= grenzen[i]) {
onder = grenzen[i];
boven = i + 1 < grenzen.length ? grenzen[i + 1] : grenzen[i];
}
}
if (onder === boven) return { from: 100, to: 100, below: false };
return { from: onder, to: boven, below: false };
}
export function shareLabel(pct) {
const b = shareBucket(pct);
if (!b) return null;
if (b.from === b.to) return `${b.from}%`;
if (b.below) return `minder dan ${b.to}%`;
return `${b.from} tot ${b.to}%`;
}
+105
View File
@@ -0,0 +1,105 @@
// Generated, do not edit. sha256:ec282b725055d62dd35348c8698b9fb220cb23c5aa15fc53463e42d07d187d42
import { getPref } from '../data/preferences.js';
import { ENUMS } from '../../shared/config.js';
import { hasFeature } from '../license.mjs';
export const AI_DISABLED = 'ai_disabled';
export const AI_WRITE_DENIED = 'ai_write_denied';
export const AI_CONTROL_DENIED = 'ai_control_denied';
export const AI_UNLICENSED = 'ai_unlicensed';
export const AI_UNLICENSED_TEXT =
'De AI-koppeling hoort bij de betaalde versie van AssetApp. De gratis versie laat de administratie '
+ 'niet lezen of wijzigen via een AI-koppeling. Er is niets gelezen en niets gewijzigd; deze poging '
+ 'staat wel in het activiteitenlogboek.';
export const AI_INACTIVE = 'ai_inactive';
export const AI_REFUSALS = Object.freeze([AI_UNLICENSED, AI_INACTIVE, AI_DISABLED, AI_WRITE_DENIED, AI_CONTROL_DENIED]);
export const AI_INACTIVE_TEXT =
'De AI-koppeling van AssetApp staat uit. Er wordt niets gelezen, niets gewijzigd en niets getoond, '
+ 'ook geen verbindingstest. De eigenaar van de app zet hem weer aan met de schuif "Actief" onder '
+ 'Systeem, tabblad AI-activiteit. Deze poging staat wel in het activiteitenlogboek.';
export const AI_DISABLED_TEXT =
'De AI-koppeling van AssetApp mag de administratie niet lezen. De koppeling zelf staat wel aan; '
+ 'lezen is een aparte schuif die de eigenaar van de app zelf aanzet onder Systeem, tabblad '
+ 'AI-activiteit. Er is niets gelezen en niets gewijzigd; deze poging staat wel in het '
+ 'activiteitenlogboek.';
export function isAiEnabled() {
try { return getPref('ai', 'allow_read') !== 0; }
catch { return false; }
}
export function isAiActive() {
try { return getPref('ai', 'enabled') !== 0; }
catch { return false; }
}
export function assertAiEnabled() {
if (isAiEnabled()) return;
const e = new Error(AI_DISABLED_TEXT);
e.code = AI_DISABLED;
throw e;
}
export const AI_WRITE_DENIED_TEXT =
'De AI-koppeling van AssetApp mag niets wijzigen. Lezen kan wel; schrijven is een aparte instelling '
+ 'die de eigenaar van de app zelf aanzet onder Systeem, tabblad AI-activiteit. Er is niets '
+ 'gewijzigd; deze poging staat wel in het activiteitenlogboek.';
export function isAiWriteAllowed() {
try { return getPref('ai', 'allow_write') === 1; }
catch { return false; }
}
export const AI_CONTROL_DENIED_TEXT =
'De AI-koppeling van AssetApp mag de app niet besturen. Lezen kan wel; navigeren is een aparte '
+ 'instelling die de eigenaar van de app zelf aanzet onder Systeem, tabblad AI-activiteit. Er is '
+ 'niets gewijzigd; deze poging staat wel in het activiteitenlogboek.';
export function isAiControlAllowed() {
try { return getPref('ai', 'allow_control') === 1; }
catch { return false; }
}
export function isAiRawAllowed() {
try { return getPref('ai', 'allow_raw') === 1; }
catch { return false; }
}
function weigering(scope) {
if (!hasFeature('ai')) return { code: AI_UNLICENSED, text: AI_UNLICENSED_TEXT };
if (!isAiActive()) return { code: AI_INACTIVE, text: AI_INACTIVE_TEXT };
if (scope === 'none') return null;
if (scope === 'read') {
return isAiEnabled() ? null : { code: AI_DISABLED, text: AI_DISABLED_TEXT };
}
if (scope === 'write') {
return isAiWriteAllowed() ? null : { code: AI_WRITE_DENIED, text: AI_WRITE_DENIED_TEXT };
}
if (scope === 'control') {
return isAiControlAllowed() ? null : { code: AI_CONTROL_DENIED, text: AI_CONTROL_DENIED_TEXT };
}
return {
code: AI_DISABLED,
text: `Onbekende scope '${scope}'; toegestaan is ${ENUMS.AI_SCOPE.join(', ')}.`,
};
}
export function assertScope(scope) {
const w = weigering(scope);
if (!w) return;
const e = new Error(w.text);
e.code = w.code;
throw e;
}
export function mayScope(scope) {
return weigering(scope) === null;
}
+25
View File
@@ -0,0 +1,25 @@
// Generated, do not edit. sha256:ffc38e35f09c2a28784811c7505bad194c65e207ac58b284c202aff9a6c08ec2
import { randomBytes } from 'node:crypto';
import { readSecretsStore, writeSecretsStore, secretsFile } from '../keys.mjs';
const SLEUTEL = 'ai_handle_secret';
const cache = new Map();
export async function handleSecret() {
const pad = secretsFile();
if (cache.has(pad)) return cache.get(pad);
const store = await readSecretsStore();
const bestaand = store[SLEUTEL];
if (typeof bestaand === 'string' && bestaand.length >= 32) {
cache.set(pad, bestaand);
return bestaand;
}
const nieuw = randomBytes(32).toString('hex');
await writeSecretsStore({ ...store, [SLEUTEL]: nieuw });
cache.set(pad, nieuw);
return nieuw;
}
export function _resetHandleSecret() { cache.clear(); }
+31
View File
@@ -0,0 +1,31 @@
// Generated, do not edit. sha256:ec3bd3327478b77670de174a8843f03ad4b292ec52893e4b4f0f49c508b72a5f
import { CONFIG } from '../../shared/config.js';
export function omittedNote(omitted, max) {
return ` ... en nog ${omitted} niet getoond (grens: ${max} per lijst).`;
}
export function limitRows(rows, max = CONFIG.AI.LIMITS.MAX_ROWS_PER_LIST) {
const alle = Array.isArray(rows) ? rows : [];
if (alle.length <= max) return { rows: alle, omitted: 0, capped: false };
return { rows: alle.slice(0, max), omitted: alle.length - max, capped: true };
}
export function limitedList(rows, render, max = CONFIG.AI.LIMITS.MAX_ROWS_PER_LIST) {
const { rows: zichtbaar, omitted, capped } = limitRows(rows, max);
const regels = zichtbaar.map(render);
if (capped) regels.push(omittedNote(omitted, max));
return { lines: regels, omitted, capped };
}
export function limitText(text, max = CONFIG.AI.LIMITS.MAX_TEXT_CHARS) {
const heel = String(text ?? '');
if (heel.length <= max) return { text: heel, omitted: 0, capped: false };
const markering = `\n[Afgekapt: dit antwoord was te lang en is ingekort op ${max} tekens.]`;
const ruimte = Math.max(0, max - markering.length);
return {
text: heel.slice(0, ruimte) + markering.slice(0, max),
omitted: heel.length - ruimte,
capped: true,
};
}
+231
View File
@@ -0,0 +1,231 @@
// Generated, do not edit. sha256:49025914ad8e6294f0b8c9d075be9b80de842094dd53941a1aaa93bc73409a0d
import { CONFIG } from '../../shared/config.js';
const RANG = new Map(CONFIG.AI.FIELD_CLASSES.map((k, i) => [k, i]));
export const FIELD_POLICY = Object.freeze({
rawData: 'drop',
settings: 'drop',
value: 'drop',
arguments: 'drop',
text: 'drop',
payload: 'drop',
messageParts: 'drop',
keyHeader: 'drop',
signatureHeader: 'drop',
timestampHeader: 'drop',
windowHeader: 'drop',
authenticationHeaderName: 'drop',
authenticationParameterName: 'drop',
address: 'chain',
externalAddress: 'chain',
automaticLabel: 'chain',
txid: 'chain',
transactionHash: 'chain',
blockHeight: 'chain',
blockTime: 'chain',
chainIndex: 'chain',
derivationIndex: 'chain',
scriptType: 'chain',
id: 'handle',
assetId: 'handle',
accountId: 'handle',
fromAccountId: 'handle',
toAccountId: 'handle',
productId: 'handle',
providerId: 'handle',
connectionId: 'handle',
sourceId: 'handle',
labelId: 'handle',
eventId: 'handle',
scheduleId: 'handle',
profileId: 'handle',
walletTransactionId: 'handle',
resolvedEventId: 'handle',
createdEventId: 'handle',
createdAccountId: 'handle',
createdValuationId: 'handle',
callId: 'handle',
claimedBy: 'handle',
principalAssetId: 'handle',
payoutAssetId: 'handle',
name: 'handle',
searchName: 'handle',
description: 'handle',
note: 'handle',
memo: 'handle',
body: 'handle',
fingerprint: 'handle',
summary: 'handle',
origin: 'handle',
filename: 'handle',
importReference: 'handle',
externalId: 'handle',
instanceKey: 'handle',
sessionId: 'handle',
client: 'handle',
detail: 'handle',
fieldsShared: 'handle',
url: 'handle',
baseUrl: 'handle',
scope: 'plain',
amount: 'quantity',
balance: 'quantity',
balanceSats: 'quantity',
balanceFound: 'quantity',
valueSats: 'quantity',
feeSats: 'quantity',
unitFee: 'quantity',
principalAmount: 'quantity',
fineGrams: 'quantity',
productCount: 'quantity',
fiatFee: 'quantityFiat',
fiatValue: 'quantityFiat',
fiatPrice: 'rate',
fiatRate: 'rate',
annualRate: 'rate',
key: 'plain',
portfolio: 'plain',
symbol: 'plain',
unit: 'plain',
subunit: 'plain',
displayUnit: 'plain',
decimals: 'plain',
kind: 'plain',
taxCategory: 'plain',
taxScope: 'plain',
wealthScope: 'plain',
balanceCapture: 'plain',
state: 'plain',
form: 'plain',
type: 'plain',
accountKind: 'plain',
eventType: 'plain',
source: 'plain',
priceOrigin: 'plain',
sourceName: 'plain',
status: 'plain',
lastStatus: 'plain',
direction: 'plain',
suggestedType: 'plain',
method: 'plain',
capability: 'plain',
resolution: 'plain',
transform: 'plain',
internalField: 'plain',
jsonPath: 'plain',
pathTemplate: 'plain',
queryTemplate: 'plain',
quoteCurrency: 'plain',
baseCurrency: 'plain',
currency: 'plain',
quoteUnit: 'plain',
minIntervalSeconds: 'plain',
callsPerMonth: 'plain',
hashAlgorithm: 'plain',
encoding: 'plain',
authenticationKind: 'plain',
authenticationRequired: 'plain',
rateKind: 'plain',
creditCadence: 'plain',
creditWeekday: 'plain',
creditDayOfMonth: 'plain',
compounding: 'plain',
outcome: 'plain',
tool: 'plain',
isActive: 'plain',
isSeed: 'plain',
isSystem: 'plain',
isCatchAll: 'plain',
isOwnCustody: 'plain',
scanEnabled: 'plain',
rank: 'plain',
sortOrder: 'plain',
version: 'plain',
serverVersion: 'plain',
count: 'plain',
highWater: 'plain',
rowCount: 'plain',
okCount: 'plain',
failCount: 'plain',
addressesFound: 'plain',
proposalsCreated: 'plain',
transactionCount: 'plain',
charactersOut: 'plain',
sumLatencyMs: 'plain',
maxLatencyMs: 'plain',
layer: 'plain',
target: 'plain',
providerKey: 'plain',
subject: 'plain',
httpStatus: 'plain',
errorKey: 'plain',
latencyMs: 'plain',
requestId: 'plain',
windowMs: 'plain',
gridX: 'plain',
gridY: 'plain',
spanWidth: 'plain',
spanHeight: 'plain',
bucketMinute: 'plain',
tableName: 'plain',
operation: 'plain',
rowId: 'handle',
originId: 'handle',
changes: 'drop',
occurredAt: 'plain',
claimedAt: 'plain',
expiresAt: 'plain',
resolvedAt: 'plain',
createdAt: 'plain',
updatedAt: 'plain',
closedAt: 'plain',
detectedAt: 'plain',
dismissedAt: 'plain',
gapAcceptedAt: 'plain',
importedAt: 'plain',
observedAt: 'plain',
scannedAt: 'plain',
lastScannedAt: 'plain',
lastCheckedAt: 'plain',
rateAt: 'plain',
valuedOn: 'plain',
fromDay: 'plain',
untilDay: 'plain',
day: 'plain',
startsOn: 'plain',
endsOn: 'plain',
periodStart: 'plain',
periodEnd: 'plain',
});
export const TABLE_POLICY = Object.freeze({
Preference: Object.freeze({ value: 'plain' }),
});
export function fieldClass(field, table = null) {
if (table == null && typeof field === 'string') {
const punt = field.indexOf('.');
if (punt > 0) return fieldClass(field.slice(punt + 1), field.slice(0, punt));
}
const basis = Object.hasOwn(FIELD_POLICY, field) ? FIELD_POLICY[field] : null;
if (basis === null || table == null) return basis;
if (!Object.hasOwn(TABLE_POLICY, table)) return basis;
const perTabel = TABLE_POLICY[table];
return Object.hasOwn(perTabel, field) ? perTabel[field] : basis;
}
export function strictestClass(a, b) {
if (a == null) return b;
if (b == null) return a;
return (RANG.get(a) ?? -1) >= (RANG.get(b) ?? -1) ? a : b;
}
+41
View File
@@ -0,0 +1,41 @@
// Generated, do not edit. sha256:5d115259dddad7958f7f0a20af1477db47898da5fb7fe4a5f958f0a3bc9b0df0
import { isAiRawAllowed } from './gate.js';
import { getPref } from '../data/preferences.js';
function bucketing(key) {
try { return getPref('ai', key) !== 0; }
catch { return true; }
}
export function precisionSettings() {
if (isAiRawAllowed()) {
return Object.freeze({
rawValues: true,
bucketShares: false,
bucketTotals: false,
bucketAmounts: false,
bucketQuantities: false,
});
}
return Object.freeze({
rawValues: false,
bucketShares: bucketing('bucket_shares'),
bucketTotals: bucketing('bucket_totals'),
bucketAmounts: bucketing('bucket_amounts'),
bucketQuantities: bucketing('bucket_quantities'),
});
}
export function shareDisclosure(precision) {
const p = precision ?? {};
if (p.rawValues) return 'exact';
if (!p.bucketAmounts) return 'exact';
if (p.bucketShares) return 'bucketed';
if (p.bucketTotals) return 'exact';
return 'omit';
}
export function precisionKey(precision) {
const p = precision ?? {};
return Object.keys(p).sort().map(k => `${k}=${String(p[k])}`).join(',') || 'leeg';
}
+139
View File
@@ -0,0 +1,139 @@
// Generated, do not edit. sha256:45a1e1f166c06347eecc16618109c15256ed61caf29cc462a558b68275a28363
import { fieldClass } from './policy.js';
import { createAliasRegistry, isSchijnwaarde } from './aliases.js';
import { bucketLabel } from './buckets.js';
const SCHIJN_SOORT = Object.freeze({
address: 'address',
externalAddress: 'address',
automaticLabel: 'address',
txid: 'txid',
transactionHash: 'txid',
});
const HANDLE_SOORT = Object.freeze({
assetId: 'asset',
accountId: 'account',
fromAccountId: 'account',
toAccountId: 'account',
productId: 'product',
providerId: 'provider',
connectionId: 'connection',
sourceId: 'walletSource',
labelId: 'label',
eventId: 'event',
scheduleId: 'schedule',
profileId: 'profile',
walletTransactionId: 'walletTransaction',
resolvedEventId: 'event',
createdEventId: 'event',
createdAccountId: 'account',
createdValuationId: 'valuation',
principalAssetId: 'asset',
payoutAssetId: 'asset',
});
export function createTranslator({ secret, precision } = {}) {
const stand = Object.freeze({ rawValues: false, ...(precision ?? {}) });
const raw = stand.rawValues === true;
const registry = createAliasRegistry({ secret });
const gedeeld = new Set();
const uitgaand = new Map();
function onthoudUit(uit, echt) {
uitgaand.set(uit, echt);
return uit;
}
function veld(naam, waarde, entiteit, tabel, rol) {
const klasse = fieldClass(naam, tabel);
const logNaam = (tabel != null && klasse !== fieldClass(naam)) ? `${tabel}.${naam}` : naam;
if (klasse === null || klasse === 'drop') return undefined;
if (waarde === null || waarde === undefined) {
gedeeld.add(logNaam);
return null;
}
switch (klasse) {
case 'plain':
case 'rate':
gedeeld.add(logNaam);
return waarde;
case 'quantity':
case 'quantityFiat': {
if (raw) { gedeeld.add(logNaam); return waarde; }
const emmeren = klasse === 'quantity'
? stand.bucketQuantities !== false
: (rol === 'total' ? stand.bucketTotals !== false : stand.bucketAmounts !== false);
if (!emmeren) { gedeeld.add(logNaam); return waarde; }
const label = bucketLabel(Number(waarde), klasse === 'quantityFiat' ? 'FINE' : 'COARSE');
if (label === null) return undefined;
gedeeld.add(logNaam);
return label;
}
case 'chain': {
const soort = SCHIJN_SOORT[naam];
if (!soort) return undefined;
gedeeld.add(logNaam);
if (raw) return waarde;
return onthoudUit(registry.alias(soort, String(waarde)), String(waarde));
}
case 'handle': {
if (raw) { gedeeld.add(logNaam); return waarde; }
const soort = naam === 'id' ? (entiteit || 'record') : (HANDLE_SOORT[naam] || 'text');
gedeeld.add(logNaam);
return onthoudUit(registry.handle(soort, String(waarde)), String(waarde));
}
default:
throw new Error(`no handling for class '${klasse}' (field '${naam}')`);
}
}
function toAi(waarde, opties = {}) {
const entiteit = opties.entity || null;
const tabel = opties.table || null;
const rol = opties.role || 'row';
if (Array.isArray(waarde)) return waarde.map(v => toAi(v, opties));
if (waarde === null || typeof waarde !== 'object') return waarde;
const uit = {};
for (const [naam, v] of Object.entries(waarde)) {
const vertaald = (v !== null && typeof v === 'object')
? (fieldClass(naam, tabel) === null ? undefined : toAi(v, {}))
: veld(naam, v, entiteit, tabel, rol);
if (vertaald !== undefined) uit[naam] = vertaald;
}
return uit;
}
function fromAi(tekst) {
if (typeof tekst !== 'string') return { text: tekst, unresolved: [] };
let uit = tekst;
const sleutels = [...uitgaand.keys()].sort((a, b) => b.length - a.length);
for (const s of sleutels) uit = uit.split(s).join(uitgaand.get(s));
const unresolved = [];
for (const m of uit.matchAll(/\b(?:bc1q[0-9a-z]{38}|xpub[0-9A-Za-z]{107}|[0-9a-z]{64})\b/g)) {
if (isSchijnwaarde(m[0])) unresolved.push(m[0]);
}
return { text: uit, unresolved };
}
return {
toAi,
fromAi,
beginCall() { gedeeld.clear(); },
fieldsShared: () => [...gedeeld].sort(),
resolve: uit => registry.resolve(uit),
precision: stand,
};
}