@mybuildex/pkg 0.10.3 → 0.12.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.
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.METHOD_ACTIONS = exports.ROUTE_KEY_ACTIONS = void 0;
4
+ exports.routeTable = routeTable;
5
+ exports.routeAction = routeAction;
6
+ exports.createNetworkPlugin = createNetworkPlugin;
7
+ // Plugin da governança de rede (ver context.ts e replication.ts).
8
+ //
9
+ // Regra (22/10/2026): sem governança, a ACL decide sozinha; com governança, a
10
+ // ACL valida PRIMEIRO e a governança em seguida — se a rede não libera, barra
11
+ // mesmo com a permissão da ACL. A governança nunca barra sozinha.
12
+ //
13
+ // guard da rota instalado no onRoute, DEPOIS do guard da ACL (o
14
+ // useModuleAcl põe o dele na frente da lista). Resolve o
15
+ // contexto da escola (cache curto). Filial: nega com 403 a
16
+ // ação que a rede não libera na tabela da rota — só quando a
17
+ // ACL comanda a rota (enforce). Matriz: troca request.prisma
18
+ // pelo client que anota as escritas.
19
+ // onSend matriz: replica nas filiais o que foi escrito, ANTES da
20
+ // resposta sair (no Cloud Run a CPU é cortada depois dela).
21
+ //
22
+ // O guard vem antes dos preHandlers da própria rota (setRequest dos
23
+ // controllers), que já recebem o client trocado. Registrar DEPOIS do
24
+ // createAclPlugin e ANTES do plugin de compressão e dos módulos: o onSend
25
+ // daqui pode trocar o corpo por um erro, e o onRoute só vale para as rotas
26
+ // registradas depois dele.
27
+ //
28
+ // NETWORK_GOVERNANCE=off desliga tudo (chave de emergência, como ACL_MODE).
29
+ const context_1 = require("./context");
30
+ const replication_1 = require("./replication");
31
+ const plugin_1 = require("../acl/plugin");
32
+ const NET_GUARD = Symbol.for("@mybuildex/pkg/networkGuard");
33
+ /** Ação da rede para cada constante do router (useModuleAcl grava em config.routeAcl.key). */
34
+ exports.ROUTE_KEY_ACTIONS = {
35
+ ALL: "read",
36
+ FIND: "read",
37
+ PAGINATE: "read",
38
+ TRASH: "read",
39
+ CREATE: "add",
40
+ CLONE: "add",
41
+ CREATE_FILE: "add",
42
+ UPDATE: "edit",
43
+ UPDATE_FILE: "edit",
44
+ UPDATE_ITENS: "edit",
45
+ SAVE: "edit",
46
+ SYNC: "edit",
47
+ REMOVE: "remove",
48
+ RESTORE: "restore",
49
+ DELETE: "delete",
50
+ };
51
+ /** Rota sem constante conhecida: decide pelo método. */
52
+ exports.METHOD_ACTIONS = {
53
+ GET: "read",
54
+ HEAD: "read",
55
+ POST: "add",
56
+ PUT: "edit",
57
+ PATCH: "edit",
58
+ DELETE: "delete",
59
+ };
60
+ const ACTION_LABELS = {
61
+ read: "consultar",
62
+ add: "incluir",
63
+ edit: "editar",
64
+ remove: "remover",
65
+ restore: "restaurar",
66
+ delete: "excluir",
67
+ };
68
+ /** Tabela da rota: o segmento depois de /v1/:id_school (o NAME do router). */
69
+ function routeTable(url) {
70
+ const m = /^\/v1\/:id_school\/([A-Za-z0-9_]+)/.exec(String(url ?? ""));
71
+ return m ? m[1] : null;
72
+ }
73
+ function routeAction(method, key) {
74
+ if (key && exports.ROUTE_KEY_ACTIONS[key])
75
+ return exports.ROUTE_KEY_ACTIONS[key];
76
+ return exports.METHOD_ACTIONS[String(method).toUpperCase()] ?? null;
77
+ }
78
+ function defaultLogger(event, data) {
79
+ const severity = event.endsWith("failed") ? "ERROR" : event === "network_replicated" ? "INFO" : "WARNING";
80
+ const line = JSON.stringify({ severity, event, ...data });
81
+ if (severity === "ERROR")
82
+ console.error(line);
83
+ else if (severity === "INFO")
84
+ console.log(line);
85
+ else
86
+ console.warn(line);
87
+ }
88
+ function createNetworkPlugin(options = {}) {
89
+ const enabled = options.enabled ?? process.env.NETWORK_GOVERNANCE !== "off";
90
+ const ttlMs = options.ttlMs ?? Number(process.env.NETWORK_CACHE_TTL_MS ?? 30_000);
91
+ const log = options.logger ?? defaultLogger;
92
+ const contexts = (0, context_1.createNetworkContextCache)(Number.isFinite(ttlMs) && ttlMs >= 0 ? ttlMs : 30_000, options.load);
93
+ const tableMeta = (0, replication_1.createTableMetaCache)();
94
+ const plugin = async function networkPlugin(fastify) {
95
+ if (!enabled)
96
+ return;
97
+ const unavailable = (reply) => reply.code(503).send({ statusCode: 503, error: "Service Unavailable", message: "Não foi possível validar as regras da rede de escolas. Tente novamente." });
98
+ const guard = async function networkGuard(request, reply) {
99
+ const idSchool = request.id_school;
100
+ if (!idSchool || !request.prisma || request.networkPending)
101
+ return;
102
+ const url = request.routeOptions?.url;
103
+ const table = routeTable(url);
104
+ // rota sem escola na URL (documentação, audit/archive, templates)
105
+ if (!table)
106
+ return;
107
+ // a governança só barra junto com a ACL (que já validou: o guard
108
+ // dela roda antes deste)
109
+ const withAcl = (0, plugin_1.aclEnforces)(request);
110
+ let ctx;
111
+ try {
112
+ ctx = await contexts.get(request.prisma, String(idSchool));
113
+ }
114
+ catch (e) {
115
+ // a escola É de rede (a flag foi lida) e o core não respondeu
116
+ const stale = contexts.stale(request.prisma, String(idSchool));
117
+ log("network_context_failed", { id_school: idSchool, route: url, table, stale: !!stale, error: e?.message ?? String(e) });
118
+ if (stale && !stale.moduleOf.has(table)) {
119
+ // tabela fora do catálogo (último catálogo conhecido): sem
120
+ // governança, a ACL decide sozinha
121
+ ctx = stale;
122
+ }
123
+ else if (!withAcl) {
124
+ // a governança não barra sozinha
125
+ return;
126
+ }
127
+ else {
128
+ // tabela governada (ou catálogo nunca carregado): sem as
129
+ // regras não dá para liberar (fail-closed)
130
+ return unavailable(reply);
131
+ }
132
+ }
133
+ request.network = ctx;
134
+ if (ctx.role === "filial") {
135
+ if (!withAcl)
136
+ return;
137
+ const key = request.routeOptions?.config?.routeAcl?.key;
138
+ const action = routeAction(request.method, key);
139
+ if (action && !(0, context_1.networkAllows)(ctx, table, action)) {
140
+ request.networkDenied = true;
141
+ log("network_denied", {
142
+ id_school: idSchool, id_network: ctx.idNetwork, method: request.method, route: url,
143
+ table, action, uid: request.user_id ?? request.user?.uid,
144
+ });
145
+ return reply.code(403).send({
146
+ statusCode: 403,
147
+ error: "Forbidden",
148
+ message: `A governança da rede não permite ${ACTION_LABELS[action]} ${table} nesta escola. Alterações são feitas pela escola matriz.`,
149
+ });
150
+ }
151
+ return;
152
+ }
153
+ // matriz: a réplica não barra ninguém, só mantém as filiais iguais
154
+ // à matriz — vale mesmo com a ACL fora do enforce
155
+ if (ctx.role === "matriz" && ctx.filiais.length > 0 && ctx.moduleOf.size > 0) {
156
+ const base = request.prisma;
157
+ const writes = new replication_1.NetworkWrites();
158
+ const pending = { ctx, base, writes };
159
+ request.networkPending = pending;
160
+ request.prisma = (0, replication_1.trackWrites)(base, {
161
+ tables: new Set(ctx.moduleOf.keys()),
162
+ base,
163
+ writes,
164
+ meta: (t) => tableMeta(base, String(base.$schema), t),
165
+ });
166
+ }
167
+ };
168
+ guard[NET_GUARD] = true;
169
+ // Na frente dos preHandlers da rota. O useModuleAcl (onRoute do
170
+ // controller, roda depois deste) põe o guard da ACL na frente de tudo:
171
+ // a ordem final é ACL -> rede -> preHandlers da rota.
172
+ fastify.addHook("onRoute", (routeOptions) => {
173
+ const existing = routeOptions.preHandler;
174
+ const list = existing == null ? [] : Array.isArray(existing) ? existing : [existing];
175
+ // HEAD automático do GET herda a lista já montada: não duplica
176
+ if (list.some((fn) => fn?.[NET_GUARD]))
177
+ return;
178
+ routeOptions.preHandler = [guard, ...list];
179
+ });
180
+ fastify.addHook("onSend", async (request, reply, payload) => {
181
+ const pending = request.networkPending;
182
+ if (!pending || pending.writes.empty)
183
+ return payload;
184
+ request.networkPending = undefined;
185
+ const { ctx, base, writes } = pending;
186
+ const source = String(base.$schema);
187
+ try {
188
+ const result = await (0, replication_1.syncWrites)({
189
+ db: base,
190
+ base,
191
+ source,
192
+ targets: ctx.filiais.map(id => (0, context_1.schoolDatabaseOf)(source, id)),
193
+ writes,
194
+ meta: (t) => tableMeta(base, source, t),
195
+ });
196
+ log("network_replicated", {
197
+ id_school: ctx.idSchool, id_network: ctx.idNetwork, route: request.routeOptions?.url,
198
+ filiais: ctx.filiais, tables: result.tables, statements: result.statements,
199
+ });
200
+ return payload;
201
+ }
202
+ catch (e) {
203
+ log("network_replication_failed", {
204
+ id_school: ctx.idSchool, id_network: ctx.idNetwork, route: request.routeOptions?.url,
205
+ filiais: ctx.filiais, tables: writes.tables, error: e?.message ?? String(e),
206
+ });
207
+ // resposta de erro original fica como está; sucesso vira erro:
208
+ // a matriz gravou, mas as filiais não (a réplica é atômica)
209
+ if (reply.statusCode >= 400)
210
+ return payload;
211
+ reply.code(500);
212
+ reply.header("content-type", "application/json; charset=utf-8");
213
+ return JSON.stringify({
214
+ statusCode: 500,
215
+ message: `Salvo na escola matriz, mas a replicação para as escolas da rede (${ctx.filiais.join(", ")}) falhou e nenhuma delas foi alterada: ${e?.message ?? e}`,
216
+ });
217
+ }
218
+ });
219
+ };
220
+ return Object.assign(plugin, { clearCache: () => contexts.clear() });
221
+ }
222
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/network/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /** Escritas do Prisma que a réplica acompanha (as mesmas da auditoria). */
2
+ export declare const NETWORK_WRITE_OPERATIONS: Set<string>;
3
+ /** Tabelas nunca replicadas, mesmo no catálogo. */
4
+ export declare const NEVER_REPLICATED: Set<string>;
5
+ /**
6
+ * Linhas que pertencem a cada escola e nunca são copiadas nem apagadas pela
7
+ * réplica: a flag REDE é gravada pelo core em cada membro.
8
+ */
9
+ export declare const PROTECTED_ROWS: Readonly<Record<string, {
10
+ column: string;
11
+ value: string;
12
+ }>>;
13
+ /** Teto de linhas por escrita *Many acompanhada. */
14
+ export declare const MAX_TRACKED_ROWS = 5000;
15
+ export interface TableMeta {
16
+ /** Colunas graváveis (sem as GENERATED), na ordem da tabela. */
17
+ columns: string[];
18
+ /** Chave primária. */
19
+ pk: string[];
20
+ }
21
+ type Row = Record<string, any>;
22
+ /**
23
+ * Colunas e chave primária da tabela (information_schema). As tabelas são as
24
+ * mesmas em todas as escolas: cache por tabela, para a vida da instância.
25
+ */
26
+ export declare function createTableMetaCache(): (db: any, schema: string, table: string) => Promise<TableMeta>;
27
+ /**
28
+ * where do Prisma -> where de findMany: chave única composta
29
+ * (`{a_b: {a, b}}`) vira `{a, b}`; o resto passa igual.
30
+ */
31
+ export declare function flattenWhere(where: Row | undefined, meta: TableMeta): Row;
32
+ /** Linhas tocadas pela request na matriz, por tabela, na ordem da primeira escrita. */
33
+ export declare class NetworkWrites {
34
+ readonly tables: string[];
35
+ readonly keys: Map<string, Map<string, Row>>;
36
+ readonly lookups: Map<string, Row[]>;
37
+ private touch;
38
+ addKey(table: string, key: Row, pk: string[]): void;
39
+ addLookup(table: string, where: Row): void;
40
+ get empty(): boolean;
41
+ }
42
+ export interface TrackOptions {
43
+ /** Tabelas replicadas (catálogo da rede). */
44
+ tables: ReadonlySet<string>;
45
+ /** Client SEM o proxy, do mesmo schema: lê as chaves antes da escrita. */
46
+ base: any;
47
+ meta: (table: string) => Promise<TableMeta>;
48
+ writes: NetworkWrites;
49
+ }
50
+ /**
51
+ * Client da matriz que anota as escritas nas tabelas replicadas. Leitura, SQL
52
+ * cru e tabelas fora do catálogo passam direto. $transaction interativa
53
+ * recebe um tx também acompanhado.
54
+ */
55
+ export declare function trackWrites<T extends object>(client: T, opts: TrackOptions): T;
56
+ export interface SyncStatement {
57
+ sql: string;
58
+ params: any[];
59
+ }
60
+ /**
61
+ * SQL que deixa as linhas `keys` de `table` na filial iguais às da matriz:
62
+ * apaga as que sumiram da matriz e insere/atualiza as demais com o mesmo id.
63
+ */
64
+ export declare function buildSyncStatements(source: string, target: string, table: string, meta: TableMeta, keys: Row[]): {
65
+ deletes: SyncStatement[];
66
+ upserts: SyncStatement[];
67
+ };
68
+ export interface SyncResult {
69
+ tables: Record<string, number>;
70
+ filiais: string[];
71
+ statements: number;
72
+ }
73
+ /**
74
+ * Copia para cada filial as linhas anotadas em `writes`, numa transação só.
75
+ * `db` executa o SQL (qualquer client do pool serve: todo nome é qualificado)
76
+ * e `base` reencontra as linhas do createMany na matriz.
77
+ */
78
+ export declare function syncWrites(input: {
79
+ db: any;
80
+ base: any;
81
+ source: string;
82
+ targets: string[];
83
+ writes: NetworkWrites;
84
+ meta: (table: string) => Promise<TableMeta>;
85
+ }): Promise<SyncResult>;
86
+ export {};
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ // Replicação matriz -> filiais (governança de rede; ver context.ts).
3
+ //
4
+ // Tudo o que a MATRIZ grava numa tabela do catálogo de módulos da rede é
5
+ // espelhado nas filiais — os bancos das escolas têm as mesmas tabelas. Em vez
6
+ // de repetir cada operação do Prisma em cada filial (ids divergiriam, e
7
+ // update/delete por id acertariam a linha errada), a réplica copia o ESTADO
8
+ // final das linhas tocadas, pela chave primária:
9
+ //
10
+ // 1. durante a request, o client da matriz (proxy de trackWrites) anota a
11
+ // chave de cada linha escrita: pelo resultado (create/update/upsert/
12
+ // delete), por uma leitura antes da escrita (where de update/delete e
13
+ // das variantes *Many) ou, no createMany (o MySQL não devolve os ids),
14
+ // por uma busca depois com os mesmos valores gravados;
15
+ // 2. no fim da request (onSend, ANTES da resposta sair), syncWrites roda numa
16
+ // ÚNICA transação, para cada filial:
17
+ // DELETE das linhas que não existem mais na matriz
18
+ // INSERT ... SELECT da matriz ... ON DUPLICATE KEY UPDATE
19
+ // com o mesmo id da matriz. Falha em qualquer filial desfaz a réplica em
20
+ // todas (a escrita na matriz já foi gravada).
21
+ //
22
+ // Por copiar o estado (e não a operação), a réplica é idempotente e também
23
+ // cobre escrita dentro de $transaction (o estado lido já é o confirmado).
24
+ //
25
+ // Limitações: SQL cru ($executeRaw) na matriz não é capturado; escrita
26
+ // aninhada em relação (data: {filhos: {create}}) só replica a tabela do model
27
+ // chamado.
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.NetworkWrites = exports.MAX_TRACKED_ROWS = exports.PROTECTED_ROWS = exports.NEVER_REPLICATED = exports.NETWORK_WRITE_OPERATIONS = void 0;
30
+ exports.createTableMetaCache = createTableMetaCache;
31
+ exports.flattenWhere = flattenWhere;
32
+ exports.trackWrites = trackWrites;
33
+ exports.buildSyncStatements = buildSyncStatements;
34
+ exports.syncWrites = syncWrites;
35
+ /** Escritas do Prisma que a réplica acompanha (as mesmas da auditoria). */
36
+ exports.NETWORK_WRITE_OPERATIONS = new Set([
37
+ "create", "createMany", "createManyAndReturn",
38
+ "update", "updateMany", "updateManyAndReturn",
39
+ "upsert", "delete", "deleteMany",
40
+ ]);
41
+ /** Leitura da chave antes de escrever (where de update/delete/upsert/*Many). */
42
+ const READ_KEYS_BEFORE = new Set(["update", "updateMany", "updateManyAndReturn", "upsert", "delete", "deleteMany"]);
43
+ /** Tabelas nunca replicadas, mesmo no catálogo. */
44
+ exports.NEVER_REPLICATED = new Set(["audit_logs", "migrations"]);
45
+ /**
46
+ * Linhas que pertencem a cada escola e nunca são copiadas nem apagadas pela
47
+ * réplica: a flag REDE é gravada pelo core em cada membro.
48
+ */
49
+ exports.PROTECTED_ROWS = {
50
+ configs: { column: "key", value: "REDE" },
51
+ };
52
+ /** Teto de linhas por escrita *Many acompanhada. */
53
+ exports.MAX_TRACKED_ROWS = 5000;
54
+ const CHUNK = 500;
55
+ const IDENT_RE = /^[A-Za-z0-9_]+$/;
56
+ function q(ident) {
57
+ if (!IDENT_RE.test(ident))
58
+ throw new Error(`identificador inválido: ${ident}`);
59
+ return `\`${ident}\``;
60
+ }
61
+ function isPlainObject(v) {
62
+ return !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date) && Object.getPrototypeOf(v) === Object.prototype;
63
+ }
64
+ function isPrimitive(v) {
65
+ return v === null || ["string", "number", "bigint", "boolean"].includes(typeof v);
66
+ }
67
+ /**
68
+ * Colunas e chave primária da tabela (information_schema). As tabelas são as
69
+ * mesmas em todas as escolas: cache por tabela, para a vida da instância.
70
+ */
71
+ function createTableMetaCache() {
72
+ const cache = new Map();
73
+ return function tableMeta(db, schema, table) {
74
+ let hit = cache.get(table);
75
+ if (!hit) {
76
+ hit = (async () => {
77
+ const rows = await db.$queryRawUnsafe("SELECT COLUMN_NAME AS name, COLUMN_KEY AS col_key, EXTRA AS extra FROM information_schema.COLUMNS" +
78
+ " WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION", schema, table);
79
+ const columns = rows.filter(r => !/GENERATED/i.test(String(r.extra ?? ""))).map(r => String(r.name));
80
+ const pk = rows.filter(r => String(r.col_key) === "PRI").map(r => String(r.name));
81
+ if (!columns.length)
82
+ throw new Error(`tabela ${schema}.${table} não encontrada`);
83
+ if (!pk.length)
84
+ throw new Error(`tabela ${table} sem chave primária: não dá para replicar`);
85
+ return { columns, pk };
86
+ })();
87
+ cache.set(table, hit);
88
+ hit.catch(() => cache.delete(table));
89
+ }
90
+ return hit;
91
+ };
92
+ }
93
+ function keyOf(row, pk) {
94
+ if (!row || typeof row !== "object")
95
+ return null;
96
+ const key = {};
97
+ for (const c of pk) {
98
+ const v = row[c];
99
+ if (v === undefined || v === null || typeof v === "object")
100
+ return null;
101
+ key[c] = v;
102
+ }
103
+ return key;
104
+ }
105
+ function keyString(key, pk) {
106
+ return pk.map(c => String(key[c])).join("\u0000");
107
+ }
108
+ /**
109
+ * where do Prisma -> where de findMany: chave única composta
110
+ * (`{a_b: {a, b}}`) vira `{a, b}`; o resto passa igual.
111
+ */
112
+ function flattenWhere(where, meta) {
113
+ const out = {};
114
+ for (const [k, v] of Object.entries(where ?? {})) {
115
+ if (!meta.columns.includes(k) && isPlainObject(v) && Object.keys(v).length > 0 && Object.keys(v).every(c => meta.columns.includes(c))) {
116
+ Object.assign(out, v);
117
+ }
118
+ else {
119
+ out[k] = v;
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+ /** Valores simples gravados numa linha (para reencontrá-la depois do createMany). */
125
+ function lookupWhere(data, meta) {
126
+ const where = {};
127
+ for (const [k, v] of Object.entries(data ?? {})) {
128
+ if (meta.columns.includes(k) && isPrimitive(v))
129
+ where[k] = v;
130
+ }
131
+ return Object.keys(where).length ? where : null;
132
+ }
133
+ /** Linhas tocadas pela request na matriz, por tabela, na ordem da primeira escrita. */
134
+ class NetworkWrites {
135
+ tables = [];
136
+ keys = new Map();
137
+ lookups = new Map();
138
+ touch(table) {
139
+ if (!this.keys.has(table)) {
140
+ this.tables.push(table);
141
+ this.keys.set(table, new Map());
142
+ this.lookups.set(table, []);
143
+ }
144
+ }
145
+ addKey(table, key, pk) {
146
+ this.touch(table);
147
+ this.keys.get(table).set(keyString(key, pk), key);
148
+ }
149
+ addLookup(table, where) {
150
+ this.touch(table);
151
+ this.lookups.get(table).push(where);
152
+ }
153
+ get empty() {
154
+ return this.tables.length === 0;
155
+ }
156
+ }
157
+ exports.NetworkWrites = NetworkWrites;
158
+ async function trackOperation(table, op, args, run, opts) {
159
+ const meta = await opts.meta(table);
160
+ const { writes, base } = opts;
161
+ if (READ_KEYS_BEFORE.has(op)) {
162
+ const where = flattenWhere(args?.where, meta);
163
+ const select = Object.fromEntries(meta.pk.map(c => [c, true]));
164
+ const before = await base[table].findMany({ where, select, take: exports.MAX_TRACKED_ROWS });
165
+ for (const row of before) {
166
+ const key = keyOf(row, meta.pk);
167
+ if (key)
168
+ writes.addKey(table, key, meta.pk);
169
+ }
170
+ }
171
+ const result = await run(args);
172
+ // *Many devolve {count}: sem a chave, keyOf descarta
173
+ const rows = Array.isArray(result) ? result : result && typeof result === "object" ? [result] : [];
174
+ let found = false;
175
+ for (const row of rows) {
176
+ const key = keyOf(row, meta.pk);
177
+ if (key) {
178
+ writes.addKey(table, key, meta.pk);
179
+ found = true;
180
+ }
181
+ }
182
+ if (!found && (op === "create" || op === "createMany")) {
183
+ // createMany não devolve linhas; create com select sem a chave também não
184
+ const list = op === "create" ? [args?.data] : Array.isArray(args?.data) ? args.data : [args?.data];
185
+ for (const data of list) {
186
+ const key = keyOf(data, meta.pk);
187
+ if (key) {
188
+ writes.addKey(table, key, meta.pk);
189
+ continue;
190
+ }
191
+ const where = lookupWhere(data, meta);
192
+ if (where)
193
+ writes.addLookup(table, where);
194
+ }
195
+ }
196
+ return result;
197
+ }
198
+ function bindValue(target, prop) {
199
+ const v = Reflect.get(target, prop);
200
+ return typeof v === "function" ? v.bind(target) : v;
201
+ }
202
+ function trackDelegate(delegate, table, opts) {
203
+ return new Proxy(delegate, {
204
+ get(target, prop) {
205
+ const fn = bindValue(target, prop);
206
+ if (typeof prop !== "string" || typeof fn !== "function" || !exports.NETWORK_WRITE_OPERATIONS.has(prop))
207
+ return fn;
208
+ return (args) => trackOperation(table, prop, args, fn, opts);
209
+ },
210
+ });
211
+ }
212
+ /**
213
+ * Client da matriz que anota as escritas nas tabelas replicadas. Leitura, SQL
214
+ * cru e tabelas fora do catálogo passam direto. $transaction interativa
215
+ * recebe um tx também acompanhado.
216
+ */
217
+ function trackWrites(client, opts) {
218
+ return new Proxy(client, {
219
+ get(target, prop) {
220
+ if (prop === "$transaction") {
221
+ const tx = bindValue(target, prop);
222
+ return (arg, txOptions) => typeof arg === "function"
223
+ ? tx((inner) => arg(trackWrites(inner, opts)), txOptions)
224
+ : tx(arg, txOptions);
225
+ }
226
+ if (typeof prop === "string" && opts.tables.has(prop) && !exports.NEVER_REPLICATED.has(prop)) {
227
+ return trackDelegate(Reflect.get(target, prop), prop, opts);
228
+ }
229
+ return bindValue(target, prop);
230
+ },
231
+ });
232
+ }
233
+ function keyCondition(alias, pk, keys) {
234
+ if (pk.length === 1) {
235
+ return { sql: `${alias}.${q(pk[0])} IN (${keys.map(() => "?").join(", ")})`, params: keys.map(k => k[pk[0]]) };
236
+ }
237
+ const tuple = `(${pk.map(() => "?").join(", ")})`;
238
+ return {
239
+ sql: `(${pk.map(c => `${alias}.${q(c)}`).join(", ")}) IN (${keys.map(() => tuple).join(", ")})`,
240
+ params: keys.flatMap(k => pk.map(c => k[c])),
241
+ };
242
+ }
243
+ function samePk(a, b, pk) {
244
+ return pk.map(c => `${a}.${q(c)} = ${b}.${q(c)}`).join(" AND ");
245
+ }
246
+ /**
247
+ * SQL que deixa as linhas `keys` de `table` na filial iguais às da matriz:
248
+ * apaga as que sumiram da matriz e insere/atualiza as demais com o mesmo id.
249
+ */
250
+ function buildSyncStatements(source, target, table, meta, keys) {
251
+ const src = `${q(source)}.${q(table)}`;
252
+ const dst = `${q(target)}.${q(table)}`;
253
+ const cols = meta.columns.map(q);
254
+ const update = meta.columns.filter(c => !meta.pk.includes(c)).map(c => `${q(c)} = VALUES(${q(c)})`);
255
+ const guard = exports.PROTECTED_ROWS[table];
256
+ const deletes = [];
257
+ const upserts = [];
258
+ for (let i = 0; i < keys.length; i += CHUNK) {
259
+ const chunk = keys.slice(i, i + CHUNK);
260
+ const dk = keyCondition("dst", meta.pk, chunk);
261
+ // DELETE de tabela única com alias (MySQL 8.0.16+): o multi-tabela
262
+ // (`DELETE dst FROM ... AS dst`) exige database default, e o pool não tem
263
+ const del = {
264
+ sql: `DELETE FROM ${dst} AS dst WHERE ${dk.sql}` +
265
+ (guard ? ` AND NOT (dst.${q(guard.column)} <=> ?)` : "") +
266
+ ` AND NOT EXISTS (SELECT 1 FROM ${src} AS src WHERE ${samePk("src", "dst", meta.pk)})`,
267
+ params: [...dk.params, ...(guard ? [guard.value] : [])],
268
+ };
269
+ deletes.push(del);
270
+ const sk = keyCondition("src", meta.pk, chunk);
271
+ const head = update.length ? `INSERT INTO ${dst}` : `INSERT IGNORE INTO ${dst}`;
272
+ upserts.push({
273
+ sql: `${head} (${cols.join(", ")}) SELECT ${cols.map(c => `src.${c}`).join(", ")} FROM ${src} AS src WHERE ${sk.sql}` +
274
+ (guard
275
+ ? ` AND NOT (src.${q(guard.column)} <=> ?)` +
276
+ ` AND NOT EXISTS (SELECT 1 FROM ${dst} AS cur WHERE ${samePk("cur", "src", meta.pk)} AND cur.${q(guard.column)} <=> ?)`
277
+ : "") +
278
+ (update.length ? ` ON DUPLICATE KEY UPDATE ${update.join(", ")}` : ""),
279
+ params: [...sk.params, ...(guard ? [guard.value, guard.value] : [])],
280
+ });
281
+ }
282
+ return { deletes, upserts };
283
+ }
284
+ /**
285
+ * Copia para cada filial as linhas anotadas em `writes`, numa transação só.
286
+ * `db` executa o SQL (qualquer client do pool serve: todo nome é qualificado)
287
+ * e `base` reencontra as linhas do createMany na matriz.
288
+ */
289
+ async function syncWrites(input) {
290
+ const { db, base, source, targets, writes, meta } = input;
291
+ const result = { tables: {}, filiais: targets, statements: 0 };
292
+ const perTable = [];
293
+ for (const table of writes.tables) {
294
+ const m = await meta(table);
295
+ const lookups = writes.lookups.get(table) ?? [];
296
+ for (let i = 0; i < lookups.length; i += CHUNK) {
297
+ const select = Object.fromEntries(m.pk.map(c => [c, true]));
298
+ const found = await base[table].findMany({ where: { OR: lookups.slice(i, i + CHUNK) }, select, take: exports.MAX_TRACKED_ROWS });
299
+ for (const row of found) {
300
+ const key = keyOf(row, m.pk);
301
+ if (key)
302
+ writes.addKey(table, key, m.pk);
303
+ }
304
+ }
305
+ const keys = [...(writes.keys.get(table)?.values() ?? [])];
306
+ if (keys.length)
307
+ perTable.push({ table, meta: m, keys });
308
+ result.tables[table] = keys.length;
309
+ }
310
+ if (!perTable.length || !targets.length)
311
+ return result;
312
+ const statements = [];
313
+ for (const target of targets) {
314
+ const built = perTable.map(t => buildSyncStatements(source, target, t.table, t.meta, t.keys));
315
+ // filhos antes dos pais ao apagar; pais antes dos filhos ao inserir (FKs)
316
+ for (const b of [...built].reverse())
317
+ statements.push(...b.deletes);
318
+ for (const b of built)
319
+ statements.push(...b.upserts);
320
+ }
321
+ await db.$transaction(async (tx) => {
322
+ for (const s of statements)
323
+ await tx.$executeRawUnsafe(s.sql, ...s.params);
324
+ });
325
+ result.statements = statements.length;
326
+ return result;
327
+ }
328
+ //# sourceMappingURL=replication.js.map