@mybuildex/pkg 0.10.3 → 0.11.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 +24 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/network/context.d.ts +68 -0
- package/dist/network/context.js +189 -0
- package/dist/network/context.js.map +1 -0
- package/dist/network/plugin.d.ts +21 -0
- package/dist/network/plugin.js +179 -0
- package/dist/network/plugin.js.map +1 -0
- package/dist/network/replication.d.ts +86 -0
- package/dist/network/replication.js +328 -0
- package/dist/network/replication.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,30 @@ createAuthMiddleware({
|
|
|
51
51
|
createTenantMiddleware({getTenantClient, defaultClient, strictTenant: true});
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
## 0.11.0 — governança de rede de escolas (matriz/filial)
|
|
55
|
+
|
|
56
|
+
Módulo novo, opt-in: API que não registra o plugin não muda nada. Veio da `v1_settings` sem mudança de comportamento (19/10/2026), para as outras APIs ligarem com uma linha.
|
|
57
|
+
|
|
58
|
+
- **`createNetworkPlugin({ttlMs, enabled, logger})`** — a escola é de rede quando o PRÓPRIO banco tem `configs.key = 'REDE'` com `TRUE` (gravada pelo core Go). Sem a flag o core nem é consultado. Com ela, o papel e as regras vêm do banco core (`my_builder_<env>_core`, por SQL cru qualificado no mesmo pool; env `CORE_DATABASE` sobrescreve):
|
|
59
|
+
- **filial** — cada ação (`read/add/edit/remove/restore/delete`) numa tabela do catálogo de módulos passa pelas `networks_policies`, **acima** da ACL: nega com 403 mesmo com a permissão do usuário. A ação sai da constante do router (`routeAcl.key` gravada pelo `useModuleAcl`) ou, sem ela, do método HTTP. A tabela é o segmento depois de `/v1/:id_school/`.
|
|
60
|
+
- **matriz** — pode tudo; o que grava nas tabelas do catálogo é replicado nas filiais com o mesmo id, numa transação única, no `onSend` (antes da resposta). Falha na réplica vira 500 com mensagem clara (a matriz já gravou, nenhuma filial foi alterada).
|
|
61
|
+
- **standalone** — sem flag, sem vínculo ou rede removida: nada muda.
|
|
62
|
+
- Core fora do ar numa escola de rede: 503 (fail-closed). Contexto em cache por escola (`NETWORK_CACHE_TTL_MS`, default 30 s). `NETWORK_GOVERNANCE=off` desliga tudo.
|
|
63
|
+
- **Ordem no `loadPlugins`:** depois do `createAclPlugin` (usa a `routeAcl` dele) e **antes** do `createCompressPlugin` (o `onSend` da réplica pode trocar o corpo por erro).
|
|
64
|
+
- **Para o front:** `effectivePermissions(request.network ?? standaloneContext(id))` devolve `{tabela: {read, add, ...}}` — é o que o `GET /v1/:id_school/networks/effective` da `v1_settings` responde.
|
|
65
|
+
- Limitações (como antes): SQL cru na matriz não replica; escrita aninhada em relação só replica a tabela do model chamado; a réplica não entra no `audit_logs`.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import {createNetworkPlugin} from "@mybuildex/pkg";
|
|
69
|
+
|
|
70
|
+
const pluginNetwork = createNetworkPlugin();
|
|
71
|
+
// loadPlugins
|
|
72
|
+
await createAclPlugin({api: settings.PREFIX, mode: "enforce"})(fastify);
|
|
73
|
+
await pluginNetwork(fastify);
|
|
74
|
+
await pluginCors(fastify);
|
|
75
|
+
await pluginCompress(fastify);
|
|
76
|
+
```
|
|
77
|
+
|
|
54
78
|
## 0.10.3 — ACL_MAP com split de pagamento
|
|
55
79
|
|
|
56
80
|
- **`ACL_MAP` regerado do front** com os recursos `pay_split_recipients` (ações do CRUD + `sync`, para o botão "Sincronizar" que grava o recebedor no gateway) e `pay_split_rules`. Sem eles a `v1_payments` em enforce nega as rotas novas para todo mundo (10/10/2026). Nenhuma mudança de código.
|
package/dist/index.d.ts
CHANGED
|
@@ -47,3 +47,9 @@ export type { AclMode, AclPluginOptions, AclRouteInfo, UseModuleAclOptions } fro
|
|
|
47
47
|
export { createAuditPlugin, createAuditExtension, createAuditQueryHandler, withAudit, auditEnabled, auditRaw, runInAuditContext, getAuditContext, writeEntries, auditRow, auditDate, resolveAuditActor, clearAuditActorCache, createForbiddenThrottle, AUDIT_COLUMNS, AUDIT_WRITE_OPERATIONS, } from "./audit/audit";
|
|
48
48
|
export type { AuditOptions, AuditPluginOptions, AuditOrigin, AuditEntry, AuditBaseClient, AuditActor } from "./audit/audit";
|
|
49
49
|
export { sanitizeForAudit, auditJson, changedFields, onlyChanged, DEFAULT_MASK_KEYS } from "./audit/serialize";
|
|
50
|
+
export { createNetworkPlugin, routeTable, routeAction, ROUTE_KEY_ACTIONS, METHOD_ACTIONS, } from "./network/plugin";
|
|
51
|
+
export type { NetworkPluginOptions } from "./network/plugin";
|
|
52
|
+
export { NETWORK_ACTIONS, NETWORK_FLAG_KEY, idKey, standaloneContext, coreDatabaseOf, schoolDatabaseOf, hasNetworkFlag, loadNetworkContext, resolvePolicy, networkAllows, effectivePermissions, createNetworkContextCache, } from "./network/context";
|
|
53
|
+
export type { NetworkAction, NetworkRole, NetworkPolicy, NetworkContext, NetworkSqlClient, NetworkPermissions } from "./network/context";
|
|
54
|
+
export { NETWORK_WRITE_OPERATIONS, NEVER_REPLICATED, PROTECTED_ROWS, MAX_TRACKED_ROWS, createTableMetaCache, flattenWhere, NetworkWrites, trackWrites, buildSyncStatements, syncWrites, } from "./network/replication";
|
|
55
|
+
export type { TableMeta, TrackOptions, SyncStatement, SyncResult } from "./network/replication";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_REDIS_CONNECT_TIMEOUT_MS = exports.resolveRedisOptions = exports.createRedisClient = exports.permissionsVersionKey = exports.permissionsCacheKey = exports.invalidatePermissionsCache = exports.defaultPermissionsQuery = exports.createPermissionsResolver = exports.EMBED_MODEL = exports.GEMINI_MODEL = exports.EMBED_DIM = exports.GeminiHelp = exports.createGcfHandler = exports.TRACE_MAX_CHARS = exports.resumirTrace = exports.isInternalError = exports.createErrorHandler = exports.getBucket = exports.getDefaultAuth = exports.getTenantAuth = exports.initFirebaseAdmin = exports.getFirebaseStorage = exports.getFirebaseClientApp = exports.createFirebaseClientPlugin = exports.createSwaggerPlugin = exports.DEFAULT_CORS = exports.createCorsPlugin = exports.withTenantSchema = exports.createMultitenantPrismaMariaDb = exports.resolveStatementTimeout = exports.createMultitenantPrisma = exports.createAsyncSendGuard = exports.createCompressPlugin = exports.createSharedSecretGuard = exports.safeEqual = exports.isPublicRoute = exports.publicPath = exports.publicRoute = exports.RequestScopeError = exports.runInRequestScope = exports.getRequestScope = exports.requestScoped = exports.createRequestScopePlugin = exports.createTenantMembershipAssert = exports.canonicalRequestPath = exports.createAuthMiddleware = exports.InvalidTenantError = exports.createTenantMiddleware = exports.readRawTenantId = exports.resolveTenantId = void 0;
|
|
4
|
-
exports.DEFAULT_MASK_KEYS = exports.onlyChanged = exports.changedFields = exports.auditJson = exports.sanitizeForAudit = exports.AUDIT_WRITE_OPERATIONS = exports.AUDIT_COLUMNS = exports.createForbiddenThrottle = exports.clearAuditActorCache = exports.resolveAuditActor = exports.auditDate = exports.auditRow = exports.writeEntries = exports.getAuditContext = exports.runInAuditContext = exports.auditRaw = exports.auditEnabled = exports.withAudit = exports.createAuditQueryHandler = exports.createAuditExtension = exports.createAuditPlugin = exports.aclAllows = exports.defaultAclAction = exports.ACL_ACTION_FALLBACK = exports.DEFAULT_ACL_ACTIONS = exports.getAclRouteReport = exports.useModuleAcl = exports.createAclPlugin = exports.isFunctionRoleName = exports.FUNCTION_ROLE_ALIASES = exports.FUNCTION_ROLES = exports.FUNCTION_ROLE_PAIRS = exports.hasAnyOf = exports.hasAclName = exports.isSingleRoleList = exports.isRoleName = exports.ROLE_ALIASES = exports.RESPONSIBLE_ACLS = exports.STUDENT_ACLS = exports.TEACHER_ACLS = exports.AclConfigError = exports.evaluateAclRule = exports.resolveAclRule = exports.aclEntryIds = exports.checkAcl = exports.ACL_MAP = void 0;
|
|
4
|
+
exports.ROUTE_KEY_ACTIONS = exports.routeAction = exports.routeTable = exports.createNetworkPlugin = exports.DEFAULT_MASK_KEYS = exports.onlyChanged = exports.changedFields = exports.auditJson = exports.sanitizeForAudit = exports.AUDIT_WRITE_OPERATIONS = exports.AUDIT_COLUMNS = exports.createForbiddenThrottle = exports.clearAuditActorCache = exports.resolveAuditActor = exports.auditDate = exports.auditRow = exports.writeEntries = exports.getAuditContext = exports.runInAuditContext = exports.auditRaw = exports.auditEnabled = exports.withAudit = exports.createAuditQueryHandler = exports.createAuditExtension = exports.createAuditPlugin = exports.aclAllows = exports.defaultAclAction = exports.ACL_ACTION_FALLBACK = exports.DEFAULT_ACL_ACTIONS = exports.getAclRouteReport = exports.useModuleAcl = exports.createAclPlugin = exports.isFunctionRoleName = exports.FUNCTION_ROLE_ALIASES = exports.FUNCTION_ROLES = exports.FUNCTION_ROLE_PAIRS = exports.hasAnyOf = exports.hasAclName = exports.isSingleRoleList = exports.isRoleName = exports.ROLE_ALIASES = exports.RESPONSIBLE_ACLS = exports.STUDENT_ACLS = exports.TEACHER_ACLS = exports.AclConfigError = exports.evaluateAclRule = exports.resolveAclRule = exports.aclEntryIds = exports.checkAcl = exports.ACL_MAP = void 0;
|
|
5
|
+
exports.syncWrites = exports.buildSyncStatements = exports.trackWrites = exports.NetworkWrites = exports.flattenWhere = exports.createTableMetaCache = exports.MAX_TRACKED_ROWS = exports.PROTECTED_ROWS = exports.NEVER_REPLICATED = exports.NETWORK_WRITE_OPERATIONS = exports.createNetworkContextCache = exports.effectivePermissions = exports.networkAllows = exports.resolvePolicy = exports.loadNetworkContext = exports.hasNetworkFlag = exports.schoolDatabaseOf = exports.coreDatabaseOf = exports.standaloneContext = exports.idKey = exports.NETWORK_FLAG_KEY = exports.NETWORK_ACTIONS = exports.METHOD_ACTIONS = void 0;
|
|
5
6
|
var resolve_1 = require("./tenant/resolve");
|
|
6
7
|
Object.defineProperty(exports, "resolveTenantId", { enumerable: true, get: function () { return resolve_1.resolveTenantId; } });
|
|
7
8
|
Object.defineProperty(exports, "readRawTenantId", { enumerable: true, get: function () { return resolve_1.readRawTenantId; } });
|
|
@@ -128,4 +129,35 @@ Object.defineProperty(exports, "auditJson", { enumerable: true, get: function ()
|
|
|
128
129
|
Object.defineProperty(exports, "changedFields", { enumerable: true, get: function () { return serialize_1.changedFields; } });
|
|
129
130
|
Object.defineProperty(exports, "onlyChanged", { enumerable: true, get: function () { return serialize_1.onlyChanged; } });
|
|
130
131
|
Object.defineProperty(exports, "DEFAULT_MASK_KEYS", { enumerable: true, get: function () { return serialize_1.DEFAULT_MASK_KEYS; } });
|
|
132
|
+
// ---- governança de rede de escolas (matriz/filial; flag REDE + regras do core) ----
|
|
133
|
+
var plugin_4 = require("./network/plugin");
|
|
134
|
+
Object.defineProperty(exports, "createNetworkPlugin", { enumerable: true, get: function () { return plugin_4.createNetworkPlugin; } });
|
|
135
|
+
Object.defineProperty(exports, "routeTable", { enumerable: true, get: function () { return plugin_4.routeTable; } });
|
|
136
|
+
Object.defineProperty(exports, "routeAction", { enumerable: true, get: function () { return plugin_4.routeAction; } });
|
|
137
|
+
Object.defineProperty(exports, "ROUTE_KEY_ACTIONS", { enumerable: true, get: function () { return plugin_4.ROUTE_KEY_ACTIONS; } });
|
|
138
|
+
Object.defineProperty(exports, "METHOD_ACTIONS", { enumerable: true, get: function () { return plugin_4.METHOD_ACTIONS; } });
|
|
139
|
+
var context_1 = require("./network/context");
|
|
140
|
+
Object.defineProperty(exports, "NETWORK_ACTIONS", { enumerable: true, get: function () { return context_1.NETWORK_ACTIONS; } });
|
|
141
|
+
Object.defineProperty(exports, "NETWORK_FLAG_KEY", { enumerable: true, get: function () { return context_1.NETWORK_FLAG_KEY; } });
|
|
142
|
+
Object.defineProperty(exports, "idKey", { enumerable: true, get: function () { return context_1.idKey; } });
|
|
143
|
+
Object.defineProperty(exports, "standaloneContext", { enumerable: true, get: function () { return context_1.standaloneContext; } });
|
|
144
|
+
Object.defineProperty(exports, "coreDatabaseOf", { enumerable: true, get: function () { return context_1.coreDatabaseOf; } });
|
|
145
|
+
Object.defineProperty(exports, "schoolDatabaseOf", { enumerable: true, get: function () { return context_1.schoolDatabaseOf; } });
|
|
146
|
+
Object.defineProperty(exports, "hasNetworkFlag", { enumerable: true, get: function () { return context_1.hasNetworkFlag; } });
|
|
147
|
+
Object.defineProperty(exports, "loadNetworkContext", { enumerable: true, get: function () { return context_1.loadNetworkContext; } });
|
|
148
|
+
Object.defineProperty(exports, "resolvePolicy", { enumerable: true, get: function () { return context_1.resolvePolicy; } });
|
|
149
|
+
Object.defineProperty(exports, "networkAllows", { enumerable: true, get: function () { return context_1.networkAllows; } });
|
|
150
|
+
Object.defineProperty(exports, "effectivePermissions", { enumerable: true, get: function () { return context_1.effectivePermissions; } });
|
|
151
|
+
Object.defineProperty(exports, "createNetworkContextCache", { enumerable: true, get: function () { return context_1.createNetworkContextCache; } });
|
|
152
|
+
var replication_1 = require("./network/replication");
|
|
153
|
+
Object.defineProperty(exports, "NETWORK_WRITE_OPERATIONS", { enumerable: true, get: function () { return replication_1.NETWORK_WRITE_OPERATIONS; } });
|
|
154
|
+
Object.defineProperty(exports, "NEVER_REPLICATED", { enumerable: true, get: function () { return replication_1.NEVER_REPLICATED; } });
|
|
155
|
+
Object.defineProperty(exports, "PROTECTED_ROWS", { enumerable: true, get: function () { return replication_1.PROTECTED_ROWS; } });
|
|
156
|
+
Object.defineProperty(exports, "MAX_TRACKED_ROWS", { enumerable: true, get: function () { return replication_1.MAX_TRACKED_ROWS; } });
|
|
157
|
+
Object.defineProperty(exports, "createTableMetaCache", { enumerable: true, get: function () { return replication_1.createTableMetaCache; } });
|
|
158
|
+
Object.defineProperty(exports, "flattenWhere", { enumerable: true, get: function () { return replication_1.flattenWhere; } });
|
|
159
|
+
Object.defineProperty(exports, "NetworkWrites", { enumerable: true, get: function () { return replication_1.NetworkWrites; } });
|
|
160
|
+
Object.defineProperty(exports, "trackWrites", { enumerable: true, get: function () { return replication_1.trackWrites; } });
|
|
161
|
+
Object.defineProperty(exports, "buildSyncStatements", { enumerable: true, get: function () { return replication_1.buildSyncStatements; } });
|
|
162
|
+
Object.defineProperty(exports, "syncWrites", { enumerable: true, get: function () { return replication_1.syncWrites; } });
|
|
131
163
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type NetworkAction = "read" | "add" | "edit" | "remove" | "restore" | "delete";
|
|
2
|
+
export type NetworkRole = "standalone" | "matriz" | "filial";
|
|
3
|
+
export declare const NETWORK_ACTIONS: readonly NetworkAction[];
|
|
4
|
+
/** Regra normalizada (ids como string; "" = NULL). */
|
|
5
|
+
export interface NetworkPolicy {
|
|
6
|
+
school: string;
|
|
7
|
+
module: string;
|
|
8
|
+
resource: string;
|
|
9
|
+
action: string;
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface NetworkContext {
|
|
13
|
+
idSchool: string;
|
|
14
|
+
role: NetworkRole;
|
|
15
|
+
idNetwork: string | null;
|
|
16
|
+
idMatriz: string | null;
|
|
17
|
+
/** Só na matriz: as outras escolas da rede com banco próprio (is_plataform = 1). */
|
|
18
|
+
filiais: string[];
|
|
19
|
+
/** Catálogo: tabela -> módulo, só dos módulos ativos. */
|
|
20
|
+
moduleOf: Map<string, string>;
|
|
21
|
+
/** Só na filial: as regras da rede. */
|
|
22
|
+
policies: NetworkPolicy[];
|
|
23
|
+
}
|
|
24
|
+
/** O que o contexto precisa do client do tenant (Prisma real ou o falso dos testes). */
|
|
25
|
+
export interface NetworkSqlClient {
|
|
26
|
+
$schema?: string;
|
|
27
|
+
$queryRawUnsafe(sql: string, ...params: any[]): Promise<any>;
|
|
28
|
+
configs: {
|
|
29
|
+
findFirst(args: any): Promise<any>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare const NETWORK_FLAG_KEY = "REDE";
|
|
33
|
+
/** "1", 1n, null... -> "1" / "" (como o networks.IDKey do core). */
|
|
34
|
+
export declare function idKey(v: unknown): string;
|
|
35
|
+
export declare function standaloneContext(idSchool: string): NetworkContext;
|
|
36
|
+
/**
|
|
37
|
+
* Banco core do ambiente a partir do banco da escola: my_builder_dev_school_8
|
|
38
|
+
* -> my_builder_dev_core (dev, qa, sandbox) e my_builder_school_8 ->
|
|
39
|
+
* my_builder_core (prod). A env CORE_DATABASE sobrescreve.
|
|
40
|
+
*/
|
|
41
|
+
export declare function coreDatabaseOf(schoolSchema: string | undefined, env?: string | undefined): string;
|
|
42
|
+
/** Banco de outra escola do mesmo ambiente: troca o sufixo _school_N. */
|
|
43
|
+
export declare function schoolDatabaseOf(schoolSchema: string, idSchool: string): string;
|
|
44
|
+
/** Flag REDE ativa no banco da escola: existe e vale TRUE. */
|
|
45
|
+
export declare function hasNetworkFlag(prisma: NetworkSqlClient): Promise<boolean>;
|
|
46
|
+
/** Lê do core o papel da escola na rede, o catálogo e (filial) as regras. */
|
|
47
|
+
export declare function loadNetworkContext(prisma: NetworkSqlClient, idSchool: string): Promise<NetworkContext>;
|
|
48
|
+
/** A filial `school` pode `action` na tabela `resource` (do módulo `module`)? */
|
|
49
|
+
export declare function resolvePolicy(policies: NetworkPolicy[], school: string, resource: string, module: string, action: string): boolean;
|
|
50
|
+
/** Veredito da rede para a escola do contexto. Matriz, standalone e tabela fora do catálogo: permitido. */
|
|
51
|
+
export declare function networkAllows(ctx: NetworkContext, resource: string, action: NetworkAction): boolean;
|
|
52
|
+
/** Permissões de uma tabela do catálogo, uma por ação. */
|
|
53
|
+
export type NetworkPermissions = Record<NetworkAction, boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Permissões efetivas da escola em cada tabela do catálogo, no formato do
|
|
56
|
+
* GET /effective do core: {states: {read: true, add: false, ...}}. Tabela
|
|
57
|
+
* fora do mapa = permitida (standalone e matriz devolvem tudo true).
|
|
58
|
+
*/
|
|
59
|
+
export declare function effectivePermissions(ctx: NetworkContext): Record<string, NetworkPermissions>;
|
|
60
|
+
/**
|
|
61
|
+
* Cache do contexto por banco de escola (TTL curto: regra alterada no core
|
|
62
|
+
* vale em até ttlMs). Guarda a promise para requests simultâneas da mesma
|
|
63
|
+
* escola dividirem a consulta; falha não fica em cache.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createNetworkContextCache(ttlMs: number, load?: typeof loadNetworkContext): {
|
|
66
|
+
get(prisma: NetworkSqlClient, idSchool: string): Promise<NetworkContext>;
|
|
67
|
+
clear(): void;
|
|
68
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Governança de rede de escolas (cadastro no core: mybuilder-mysql
|
|
3
|
+
// 2026_10_04_100000_networks; telas no admin-front).
|
|
4
|
+
//
|
|
5
|
+
// A escola sabe que está numa rede pela flag REDE no PRÓPRIO banco
|
|
6
|
+
// (configs.key = 'REDE', value = 'TRUE', gravada pelo core Go). Sem a flag
|
|
7
|
+
// nada muda e o core nem é consultado. Com ela, as regras vêm do banco CORE,
|
|
8
|
+
// que esta API não mapeia no Prisma: SQL cru qualificado
|
|
9
|
+
// (`my_builder_<env>_core`.`tabela`) pelo mesmo pool das escolas.
|
|
10
|
+
//
|
|
11
|
+
// matriz pode tudo; o que ela grava nas tabelas do catálogo de módulos
|
|
12
|
+
// é replicado nas filiais (replication.ts)
|
|
13
|
+
// filial cada ação (read/add/edit/remove/restore/delete) em tabela do
|
|
14
|
+
// catálogo passa pelas networks_policies — ACIMA da ACL da
|
|
15
|
+
// escola: a regra da rede nega mesmo com a permissão do usuário
|
|
16
|
+
// standalone sem flag, sem vínculo ou rede removida (soft delete): nada muda
|
|
17
|
+
//
|
|
18
|
+
// Resolução (espelho de networks_policies.Resolve no core Go), a mais
|
|
19
|
+
// específica vence: escola+tabela > escola+módulo > rede+tabela > rede+módulo
|
|
20
|
+
// > permitido. Tabela fora do catálogo é sempre permitida.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.NETWORK_FLAG_KEY = exports.NETWORK_ACTIONS = void 0;
|
|
23
|
+
exports.idKey = idKey;
|
|
24
|
+
exports.standaloneContext = standaloneContext;
|
|
25
|
+
exports.coreDatabaseOf = coreDatabaseOf;
|
|
26
|
+
exports.schoolDatabaseOf = schoolDatabaseOf;
|
|
27
|
+
exports.hasNetworkFlag = hasNetworkFlag;
|
|
28
|
+
exports.loadNetworkContext = loadNetworkContext;
|
|
29
|
+
exports.resolvePolicy = resolvePolicy;
|
|
30
|
+
exports.networkAllows = networkAllows;
|
|
31
|
+
exports.effectivePermissions = effectivePermissions;
|
|
32
|
+
exports.createNetworkContextCache = createNetworkContextCache;
|
|
33
|
+
exports.NETWORK_ACTIONS = ["read", "add", "edit", "remove", "restore", "delete"];
|
|
34
|
+
exports.NETWORK_FLAG_KEY = "REDE";
|
|
35
|
+
const IDENT_RE = /^[A-Za-z0-9_]+$/;
|
|
36
|
+
/** "1", 1n, null... -> "1" / "" (como o networks.IDKey do core). */
|
|
37
|
+
function idKey(v) {
|
|
38
|
+
if (v === null || v === undefined)
|
|
39
|
+
return "";
|
|
40
|
+
return String(v).trim();
|
|
41
|
+
}
|
|
42
|
+
function standaloneContext(idSchool) {
|
|
43
|
+
return { idSchool, role: "standalone", idNetwork: null, idMatriz: null, filiais: [], moduleOf: new Map(), policies: [] };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Banco core do ambiente a partir do banco da escola: my_builder_dev_school_8
|
|
47
|
+
* -> my_builder_dev_core (dev, qa, sandbox) e my_builder_school_8 ->
|
|
48
|
+
* my_builder_core (prod). A env CORE_DATABASE sobrescreve.
|
|
49
|
+
*/
|
|
50
|
+
function coreDatabaseOf(schoolSchema, env = process.env.CORE_DATABASE) {
|
|
51
|
+
const name = env && env.trim() !== "" ? env.trim() : String(schoolSchema ?? "").replace(/_school_\d+$/, "_core");
|
|
52
|
+
if (!IDENT_RE.test(name) || !name.endsWith("_core")) {
|
|
53
|
+
throw new Error(`banco core não identificado a partir de "${schoolSchema}" (defina CORE_DATABASE)`);
|
|
54
|
+
}
|
|
55
|
+
return name;
|
|
56
|
+
}
|
|
57
|
+
/** Banco de outra escola do mesmo ambiente: troca o sufixo _school_N. */
|
|
58
|
+
function schoolDatabaseOf(schoolSchema, idSchool) {
|
|
59
|
+
if (!/^\d+$/.test(idSchool) || !/_school_\d+$/.test(schoolSchema)) {
|
|
60
|
+
throw new Error(`banco da escola ${idSchool} não identificado a partir de "${schoolSchema}"`);
|
|
61
|
+
}
|
|
62
|
+
return schoolSchema.replace(/_school_\d+$/, `_school_${idSchool}`);
|
|
63
|
+
}
|
|
64
|
+
/** Flag REDE ativa no banco da escola: existe e vale TRUE. */
|
|
65
|
+
async function hasNetworkFlag(prisma) {
|
|
66
|
+
const row = await prisma.configs.findFirst({
|
|
67
|
+
where: { key: exports.NETWORK_FLAG_KEY, deleted_at: null },
|
|
68
|
+
select: { value: true },
|
|
69
|
+
orderBy: { id: "asc" },
|
|
70
|
+
});
|
|
71
|
+
const value = String(row?.value ?? "").trim().toUpperCase();
|
|
72
|
+
return value === "TRUE" || value === "1";
|
|
73
|
+
}
|
|
74
|
+
function toAllowed(v) {
|
|
75
|
+
if (typeof v === "boolean")
|
|
76
|
+
return v;
|
|
77
|
+
if (Buffer.isBuffer(v))
|
|
78
|
+
return v[0] === 1;
|
|
79
|
+
return Number(v) === 1;
|
|
80
|
+
}
|
|
81
|
+
/** Lê do core o papel da escola na rede, o catálogo e (filial) as regras. */
|
|
82
|
+
async function loadNetworkContext(prisma, idSchool) {
|
|
83
|
+
const ctx = standaloneContext(idSchool);
|
|
84
|
+
if (!(await hasNetworkFlag(prisma)))
|
|
85
|
+
return ctx;
|
|
86
|
+
const core = `\`${coreDatabaseOf(prisma.$schema)}\``;
|
|
87
|
+
const q = async (sql, ...params) => (await prisma.$queryRawUnsafe(sql, ...params)) ?? [];
|
|
88
|
+
const [link] = await q(`SELECT t.* FROM ${core}.networks_schools t WHERE t.id_school = ?`, idSchool);
|
|
89
|
+
if (!link)
|
|
90
|
+
return ctx;
|
|
91
|
+
// rede removida (soft delete) não governa, como no /effective do core
|
|
92
|
+
const [network] = await q(`SELECT t.id, t.id_school_matriz FROM ${core}.networks t WHERE t.id = ? AND t.deleted_at IS NULL`, idKey(link.id_network));
|
|
93
|
+
if (!network)
|
|
94
|
+
return ctx;
|
|
95
|
+
ctx.idNetwork = idKey(network.id);
|
|
96
|
+
ctx.idMatriz = idKey(network.id_school_matriz) || null;
|
|
97
|
+
ctx.role = ctx.idMatriz === idSchool ? "matriz" : "filial";
|
|
98
|
+
const catalog = await q(`SELECT r.resource, r.id_module FROM ${core}.networks_modules_resources r` +
|
|
99
|
+
` INNER JOIN ${core}.networks_modules m ON m.id = r.id_module AND m.deleted_at IS NULL`);
|
|
100
|
+
for (const row of catalog)
|
|
101
|
+
ctx.moduleOf.set(String(row.resource), idKey(row.id_module));
|
|
102
|
+
if (ctx.role === "matriz") {
|
|
103
|
+
// só escola de plataforma tem banco próprio para receber a réplica
|
|
104
|
+
const members = await q(`SELECT t.id_school FROM ${core}.networks_schools t` +
|
|
105
|
+
` INNER JOIN ${core}.schools s ON s.id = t.id_school AND s.is_plataform = 1` +
|
|
106
|
+
` WHERE t.id_network = ? AND t.id_school <> ? ORDER BY t.id_school`, ctx.idNetwork, idSchool);
|
|
107
|
+
ctx.filiais = members.map(m => idKey(m.id_school)).filter(id => /^\d+$/.test(id));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const rows = await q(`SELECT t.* FROM ${core}.networks_policies t WHERE t.id_network = ?`, ctx.idNetwork);
|
|
111
|
+
ctx.policies = rows.map(r => ({
|
|
112
|
+
school: idKey(r.id_school),
|
|
113
|
+
module: idKey(r.id_module),
|
|
114
|
+
resource: idKey(r.resource),
|
|
115
|
+
action: String(r.action ?? ""),
|
|
116
|
+
allowed: toAllowed(r.allowed),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
return ctx;
|
|
120
|
+
}
|
|
121
|
+
/** A filial `school` pode `action` na tabela `resource` (do módulo `module`)? */
|
|
122
|
+
function resolvePolicy(policies, school, resource, module, action) {
|
|
123
|
+
let schoolRes;
|
|
124
|
+
let schoolMod;
|
|
125
|
+
let netRes;
|
|
126
|
+
let netMod;
|
|
127
|
+
for (const p of policies) {
|
|
128
|
+
if (p.action !== action)
|
|
129
|
+
continue;
|
|
130
|
+
if (p.school === school && p.resource !== "" && p.resource === resource)
|
|
131
|
+
schoolRes = p.allowed;
|
|
132
|
+
else if (p.school === school && p.module !== "" && p.module === module)
|
|
133
|
+
schoolMod = p.allowed;
|
|
134
|
+
else if (p.school === "" && p.resource !== "" && p.resource === resource)
|
|
135
|
+
netRes = p.allowed;
|
|
136
|
+
else if (p.school === "" && p.module !== "" && p.module === module)
|
|
137
|
+
netMod = p.allowed;
|
|
138
|
+
}
|
|
139
|
+
return schoolRes ?? schoolMod ?? netRes ?? netMod ?? true;
|
|
140
|
+
}
|
|
141
|
+
/** Veredito da rede para a escola do contexto. Matriz, standalone e tabela fora do catálogo: permitido. */
|
|
142
|
+
function networkAllows(ctx, resource, action) {
|
|
143
|
+
if (ctx.role !== "filial")
|
|
144
|
+
return true;
|
|
145
|
+
const module = ctx.moduleOf.get(resource);
|
|
146
|
+
if (module === undefined)
|
|
147
|
+
return true;
|
|
148
|
+
return resolvePolicy(ctx.policies, ctx.idSchool, resource, module, action);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Permissões efetivas da escola em cada tabela do catálogo, no formato do
|
|
152
|
+
* GET /effective do core: {states: {read: true, add: false, ...}}. Tabela
|
|
153
|
+
* fora do mapa = permitida (standalone e matriz devolvem tudo true).
|
|
154
|
+
*/
|
|
155
|
+
function effectivePermissions(ctx) {
|
|
156
|
+
const out = {};
|
|
157
|
+
for (const resource of [...ctx.moduleOf.keys()].sort()) {
|
|
158
|
+
out[resource] = Object.fromEntries(exports.NETWORK_ACTIONS.map(action => [action, networkAllows(ctx, resource, action)]));
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Cache do contexto por banco de escola (TTL curto: regra alterada no core
|
|
164
|
+
* vale em até ttlMs). Guarda a promise para requests simultâneas da mesma
|
|
165
|
+
* escola dividirem a consulta; falha não fica em cache.
|
|
166
|
+
*/
|
|
167
|
+
function createNetworkContextCache(ttlMs, load = loadNetworkContext) {
|
|
168
|
+
const entries = new Map();
|
|
169
|
+
return {
|
|
170
|
+
get(prisma, idSchool) {
|
|
171
|
+
const key = `${prisma.$schema ?? ""}#${idSchool}`;
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
const hit = entries.get(key);
|
|
174
|
+
if (hit && hit.expires > now)
|
|
175
|
+
return hit.value;
|
|
176
|
+
const value = load(prisma, idSchool);
|
|
177
|
+
entries.set(key, { expires: now + ttlMs, value });
|
|
178
|
+
value.catch(() => {
|
|
179
|
+
if (entries.get(key)?.value === value)
|
|
180
|
+
entries.delete(key);
|
|
181
|
+
});
|
|
182
|
+
return value;
|
|
183
|
+
},
|
|
184
|
+
clear() {
|
|
185
|
+
entries.clear();
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/network/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { loadNetworkContext, NetworkAction } from "./context";
|
|
2
|
+
/** Ação da rede para cada constante do router (useModuleAcl grava em config.routeAcl.key). */
|
|
3
|
+
export declare const ROUTE_KEY_ACTIONS: Readonly<Record<string, NetworkAction>>;
|
|
4
|
+
/** Rota sem constante conhecida: decide pelo método. */
|
|
5
|
+
export declare const METHOD_ACTIONS: Readonly<Record<string, NetworkAction>>;
|
|
6
|
+
/** Tabela da rota: o segmento depois de /v1/:id_school (o NAME do router). */
|
|
7
|
+
export declare function routeTable(url: string | undefined): string | null;
|
|
8
|
+
export declare function routeAction(method: string, key: string | undefined): NetworkAction | null;
|
|
9
|
+
export interface NetworkPluginOptions {
|
|
10
|
+
/** Validade do contexto em cache (default env NETWORK_CACHE_TTL_MS ou 30 s). */
|
|
11
|
+
ttlMs?: number;
|
|
12
|
+
/** false desliga (default: env NETWORK_GOVERNANCE !== "off"). */
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
/** Log estruturado (default: JSON no console, para o Cloud Logging). */
|
|
15
|
+
logger?: (event: string, data: Record<string, unknown>) => void;
|
|
16
|
+
/** Carga do contexto (injetável para teste). */
|
|
17
|
+
load?: typeof loadNetworkContext;
|
|
18
|
+
}
|
|
19
|
+
export declare function createNetworkPlugin(options?: NetworkPluginOptions): ((fastify: any) => Promise<void>) & {
|
|
20
|
+
clearCache: () => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
// preHandler resolve o contexto da escola do request (cache curto). Filial:
|
|
10
|
+
// nega com 403 a ação que a rede não libera na tabela da rota,
|
|
11
|
+
// mesmo que a ACL do usuário libere. Matriz: troca
|
|
12
|
+
// request.prisma pelo client que anota as escritas.
|
|
13
|
+
// onSend matriz: replica nas filiais o que foi escrito, ANTES da
|
|
14
|
+
// resposta sair (no Cloud Run a CPU é cortada depois dela).
|
|
15
|
+
//
|
|
16
|
+
// Hooks de instância rodam antes dos preHandlers das rotas, então o
|
|
17
|
+
// setRequest dos controllers já recebe o client trocado. Registrar ANTES do
|
|
18
|
+
// plugin de compressão: o onSend daqui pode trocar o corpo por um erro.
|
|
19
|
+
//
|
|
20
|
+
// NETWORK_GOVERNANCE=off desliga tudo (chave de emergência, como ACL_MODE).
|
|
21
|
+
const context_1 = require("./context");
|
|
22
|
+
const replication_1 = require("./replication");
|
|
23
|
+
/** Ação da rede para cada constante do router (useModuleAcl grava em config.routeAcl.key). */
|
|
24
|
+
exports.ROUTE_KEY_ACTIONS = {
|
|
25
|
+
ALL: "read",
|
|
26
|
+
FIND: "read",
|
|
27
|
+
PAGINATE: "read",
|
|
28
|
+
TRASH: "read",
|
|
29
|
+
CREATE: "add",
|
|
30
|
+
CLONE: "add",
|
|
31
|
+
CREATE_FILE: "add",
|
|
32
|
+
UPDATE: "edit",
|
|
33
|
+
UPDATE_FILE: "edit",
|
|
34
|
+
UPDATE_ITENS: "edit",
|
|
35
|
+
SAVE: "edit",
|
|
36
|
+
SYNC: "edit",
|
|
37
|
+
REMOVE: "remove",
|
|
38
|
+
RESTORE: "restore",
|
|
39
|
+
DELETE: "delete",
|
|
40
|
+
};
|
|
41
|
+
/** Rota sem constante conhecida: decide pelo método. */
|
|
42
|
+
exports.METHOD_ACTIONS = {
|
|
43
|
+
GET: "read",
|
|
44
|
+
HEAD: "read",
|
|
45
|
+
POST: "add",
|
|
46
|
+
PUT: "edit",
|
|
47
|
+
PATCH: "edit",
|
|
48
|
+
DELETE: "delete",
|
|
49
|
+
};
|
|
50
|
+
const ACTION_LABELS = {
|
|
51
|
+
read: "consultar",
|
|
52
|
+
add: "incluir",
|
|
53
|
+
edit: "editar",
|
|
54
|
+
remove: "remover",
|
|
55
|
+
restore: "restaurar",
|
|
56
|
+
delete: "excluir",
|
|
57
|
+
};
|
|
58
|
+
/** Tabela da rota: o segmento depois de /v1/:id_school (o NAME do router). */
|
|
59
|
+
function routeTable(url) {
|
|
60
|
+
const m = /^\/v1\/:id_school\/([A-Za-z0-9_]+)/.exec(String(url ?? ""));
|
|
61
|
+
return m ? m[1] : null;
|
|
62
|
+
}
|
|
63
|
+
function routeAction(method, key) {
|
|
64
|
+
if (key && exports.ROUTE_KEY_ACTIONS[key])
|
|
65
|
+
return exports.ROUTE_KEY_ACTIONS[key];
|
|
66
|
+
return exports.METHOD_ACTIONS[String(method).toUpperCase()] ?? null;
|
|
67
|
+
}
|
|
68
|
+
function defaultLogger(event, data) {
|
|
69
|
+
const severity = event.endsWith("failed") ? "ERROR" : event === "network_replicated" ? "INFO" : "WARNING";
|
|
70
|
+
const line = JSON.stringify({ severity, event, ...data });
|
|
71
|
+
if (severity === "ERROR")
|
|
72
|
+
console.error(line);
|
|
73
|
+
else if (severity === "INFO")
|
|
74
|
+
console.log(line);
|
|
75
|
+
else
|
|
76
|
+
console.warn(line);
|
|
77
|
+
}
|
|
78
|
+
function createNetworkPlugin(options = {}) {
|
|
79
|
+
const enabled = options.enabled ?? process.env.NETWORK_GOVERNANCE !== "off";
|
|
80
|
+
const ttlMs = options.ttlMs ?? Number(process.env.NETWORK_CACHE_TTL_MS ?? 30_000);
|
|
81
|
+
const log = options.logger ?? defaultLogger;
|
|
82
|
+
const contexts = (0, context_1.createNetworkContextCache)(Number.isFinite(ttlMs) && ttlMs >= 0 ? ttlMs : 30_000, options.load);
|
|
83
|
+
const tableMeta = (0, replication_1.createTableMetaCache)();
|
|
84
|
+
const plugin = async function networkPlugin(fastify) {
|
|
85
|
+
if (!enabled)
|
|
86
|
+
return;
|
|
87
|
+
fastify.addHook("preHandler", async (request, reply) => {
|
|
88
|
+
const idSchool = request.id_school;
|
|
89
|
+
if (!idSchool || !request.prisma || request.networkPending)
|
|
90
|
+
return;
|
|
91
|
+
const url = request.routeOptions?.url;
|
|
92
|
+
const table = routeTable(url);
|
|
93
|
+
// rota sem escola na URL (documentação, audit/archive, templates)
|
|
94
|
+
if (!table)
|
|
95
|
+
return;
|
|
96
|
+
let ctx;
|
|
97
|
+
try {
|
|
98
|
+
ctx = await contexts.get(request.prisma, String(idSchool));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// a escola É de rede (a flag foi lida) e o core não respondeu:
|
|
102
|
+
// sem as regras não dá para liberar nada (fail-closed)
|
|
103
|
+
log("network_context_failed", { id_school: idSchool, route: url, error: e?.message ?? String(e) });
|
|
104
|
+
return reply.code(503).send({ statusCode: 503, error: "Service Unavailable", message: "Não foi possível validar as regras da rede de escolas. Tente novamente." });
|
|
105
|
+
}
|
|
106
|
+
request.network = ctx;
|
|
107
|
+
if (ctx.role === "filial") {
|
|
108
|
+
const key = request.routeOptions?.config?.routeAcl?.key;
|
|
109
|
+
const action = routeAction(request.method, key);
|
|
110
|
+
if (action && !(0, context_1.networkAllows)(ctx, table, action)) {
|
|
111
|
+
request.networkDenied = true;
|
|
112
|
+
log("network_denied", {
|
|
113
|
+
id_school: idSchool, id_network: ctx.idNetwork, method: request.method, route: url,
|
|
114
|
+
table, action, uid: request.user_id ?? request.user?.uid,
|
|
115
|
+
});
|
|
116
|
+
return reply.code(403).send({
|
|
117
|
+
statusCode: 403,
|
|
118
|
+
error: "Forbidden",
|
|
119
|
+
message: `A governança da rede não permite ${ACTION_LABELS[action]} ${table} nesta escola. Alterações são feitas pela escola matriz.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (ctx.role === "matriz" && ctx.filiais.length > 0 && ctx.moduleOf.size > 0) {
|
|
125
|
+
const base = request.prisma;
|
|
126
|
+
const writes = new replication_1.NetworkWrites();
|
|
127
|
+
const pending = { ctx, base, writes };
|
|
128
|
+
request.networkPending = pending;
|
|
129
|
+
request.prisma = (0, replication_1.trackWrites)(base, {
|
|
130
|
+
tables: new Set(ctx.moduleOf.keys()),
|
|
131
|
+
base,
|
|
132
|
+
writes,
|
|
133
|
+
meta: (t) => tableMeta(base, String(base.$schema), t),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
fastify.addHook("onSend", async (request, reply, payload) => {
|
|
138
|
+
const pending = request.networkPending;
|
|
139
|
+
if (!pending || pending.writes.empty)
|
|
140
|
+
return payload;
|
|
141
|
+
request.networkPending = undefined;
|
|
142
|
+
const { ctx, base, writes } = pending;
|
|
143
|
+
const source = String(base.$schema);
|
|
144
|
+
try {
|
|
145
|
+
const result = await (0, replication_1.syncWrites)({
|
|
146
|
+
db: base,
|
|
147
|
+
base,
|
|
148
|
+
source,
|
|
149
|
+
targets: ctx.filiais.map(id => (0, context_1.schoolDatabaseOf)(source, id)),
|
|
150
|
+
writes,
|
|
151
|
+
meta: (t) => tableMeta(base, source, t),
|
|
152
|
+
});
|
|
153
|
+
log("network_replicated", {
|
|
154
|
+
id_school: ctx.idSchool, id_network: ctx.idNetwork, route: request.routeOptions?.url,
|
|
155
|
+
filiais: ctx.filiais, tables: result.tables, statements: result.statements,
|
|
156
|
+
});
|
|
157
|
+
return payload;
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
log("network_replication_failed", {
|
|
161
|
+
id_school: ctx.idSchool, id_network: ctx.idNetwork, route: request.routeOptions?.url,
|
|
162
|
+
filiais: ctx.filiais, tables: writes.tables, error: e?.message ?? String(e),
|
|
163
|
+
});
|
|
164
|
+
// resposta de erro original fica como está; sucesso vira erro:
|
|
165
|
+
// a matriz gravou, mas as filiais não (a réplica é atômica)
|
|
166
|
+
if (reply.statusCode >= 400)
|
|
167
|
+
return payload;
|
|
168
|
+
reply.code(500);
|
|
169
|
+
reply.header("content-type", "application/json; charset=utf-8");
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
statusCode: 500,
|
|
172
|
+
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}`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
return Object.assign(plugin, { clearCache: () => contexts.clear() });
|
|
178
|
+
}
|
|
179
|
+
//# 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replication.js","sourceRoot":"","sources":["../../src/network/replication.ts"],"names":[],"mappings":"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"}
|