nucleus-core-ts 0.9.821 → 0.9.823
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/dist/client.js +1 -1
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +5 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTransfer.d.ts +7 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTransfer.js +157 -0
- package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.d.ts +12 -0
- package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +214 -0
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +10 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +14 -3
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.d.ts +15 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.js +120 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.test.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.test.js +103 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +5 -0
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +4 -2
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +3 -1
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +14 -0
- package/dist/fe/components/IntegrationsPage/index.d.ts +3 -0
- package/dist/fe/components/IntegrationsPage/index.js +3 -0
- package/dist/fe/components/IntegrationsPage/types/actions.d.ts +18 -0
- package/dist/fe/components/IntegrationsPage/types/records.d.ts +26 -1
- package/dist/fe/components/IntegrationsPage/types/results.d.ts +2 -0
- package/dist/index.js +5 -5
- package/dist/src/ElysiaPlugin/routes/integrations/inspect.d.ts +1 -0
- package/dist/src/ElysiaPlugin/routes/integrations/repository.d.ts +36 -0
- package/dist/src/Services/Integrations/plan.d.ts +18 -1
- package/dist/src/Services/Integrations/runner.d.ts +13 -1
- package/dist/src/Services/Integrations/transfer.d.ts +123 -0
- package/dist/src/Services/Integrations/transfer.test.d.ts +1 -0
- package/dist/src/Services/Integrations/types.d.ts +47 -2
- package/dist/src/Services/Integrations/writer.d.ts +24 -0
- package/package.json +1 -1
- package/src/system.tables.json +12 -0
|
@@ -81,6 +81,7 @@ export declare function previewRun(chain: ResolvedChain, tokenCache: TokenCache,
|
|
|
81
81
|
readEntityColumn: (entity: string, field: string) => Promise<unknown[]>;
|
|
82
82
|
requiredFields: (entity: string) => string[];
|
|
83
83
|
uniqueFields: (entity: string) => string[];
|
|
84
|
+
readEntityIdsBy: (entity: string, field: string) => Promise<Record<string, string>>;
|
|
84
85
|
}, deps?: {
|
|
85
86
|
fetchImpl?: typeof fetch;
|
|
86
87
|
maxRecords?: number;
|
|
@@ -12,6 +12,7 @@ import type { EndpointSpec } from 'src/Services/Integrations/collect';
|
|
|
12
12
|
import type { SourceConnection } from 'src/Services/Integrations/fetcher';
|
|
13
13
|
import type { OAuthConfig } from 'src/Services/Integrations/oauth';
|
|
14
14
|
import type { MappingSpec, RunRepository } from 'src/Services/Integrations/runner';
|
|
15
|
+
import type { TransferDocument } from 'src/Services/Integrations/transfer';
|
|
15
16
|
import type { PreImage } from 'src/Services/Integrations/writer';
|
|
16
17
|
import type { Logger } from 'src/Services/Logger';
|
|
17
18
|
import type { NucleusTable } from 'src/types';
|
|
@@ -75,6 +76,41 @@ export declare function resolveEndpointContext(deps: RepositoryDeps, by: {
|
|
|
75
76
|
*/
|
|
76
77
|
export declare function cacheEndpointSample(deps: RepositoryDeps, endpointId: string, records: readonly unknown[]): Promise<void>;
|
|
77
78
|
/** Columns of a target that must carry a value, for pre-write validation. */
|
|
79
|
+
/**
|
|
80
|
+
* Everything one connection is made of, for an export.
|
|
81
|
+
*
|
|
82
|
+
* Read as three plain selects rather than through `resolveChain`, which starts
|
|
83
|
+
* from a mapping — a connection worth exporting may have endpoints and no
|
|
84
|
+
* mappings yet, and that is a legitimate thing to move.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readConnectionBundle(deps: RepositoryDeps, sourceId: string): Promise<{
|
|
87
|
+
source: Record<string, unknown>;
|
|
88
|
+
endpoints: Record<string, unknown>[];
|
|
89
|
+
mappings: Record<string, unknown>[];
|
|
90
|
+
} | null>;
|
|
91
|
+
/**
|
|
92
|
+
* Writes an exported connection into this installation.
|
|
93
|
+
*
|
|
94
|
+
* Matched by NAME throughout — the connection by its slug, endpoints and
|
|
95
|
+
* mappings by their name — so bringing the same file in twice updates what is
|
|
96
|
+
* here instead of leaving two of everything. That is the behaviour somebody
|
|
97
|
+
* actually wants: the file is how a configuration is kept in step between two
|
|
98
|
+
* environments, not a one-shot.
|
|
99
|
+
*
|
|
100
|
+
* Credentials are neither in the file nor invented here. A connection arrives
|
|
101
|
+
* unable to authenticate, and says so, rather than arriving with an empty token
|
|
102
|
+
* that looks configured.
|
|
103
|
+
*/
|
|
104
|
+
export declare function applyTransferDocument(deps: RepositoryDeps, document: TransferDocument, userId: string): Promise<{
|
|
105
|
+
ok: true;
|
|
106
|
+
sourceId: string;
|
|
107
|
+
created: boolean;
|
|
108
|
+
endpoints: number;
|
|
109
|
+
mappings: number;
|
|
110
|
+
} | {
|
|
111
|
+
ok: false;
|
|
112
|
+
error: string;
|
|
113
|
+
}>;
|
|
78
114
|
export declare function requiredFieldsOf(entities: NucleusTable[], targetEntity: string): string[];
|
|
79
115
|
/**
|
|
80
116
|
* Columns the target enforces as unique, single-column only.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* second implementation that drifts. And every rule about destroying data is
|
|
12
12
|
* testable here without a database.
|
|
13
13
|
*/
|
|
14
|
-
import type { MappedRecord, MissingRecordPolicy, ReferenceCheck, ReferenceInsert, RunIssue, RunPlan, WriteMode } from './types';
|
|
14
|
+
import type { AccountInsert, AccountRule, MappedRecord, MissingRecordPolicy, ReferenceCheck, ReferenceInsert, RunIssue, RunPlan, WriteMode } from './types';
|
|
15
15
|
/** A row already in the target table. */
|
|
16
16
|
export type ExistingRow = {
|
|
17
17
|
id: string;
|
|
@@ -35,6 +35,13 @@ export type WriteOperations = {
|
|
|
35
35
|
* them. Empty unless a reference is set to `create`.
|
|
36
36
|
*/
|
|
37
37
|
referenceInserts: ReferenceInsert[];
|
|
38
|
+
/**
|
|
39
|
+
* Accounts a run would create so its records have a login.
|
|
40
|
+
*
|
|
41
|
+
* Written before the records that point at them, in the same transaction: the
|
|
42
|
+
* person and their account both exist, or neither does.
|
|
43
|
+
*/
|
|
44
|
+
accountInserts: AccountInsert[];
|
|
38
45
|
updates: UpdateOp[];
|
|
39
46
|
/** Rows to remove — from `replace`, or from the `delete` missing policy. */
|
|
40
47
|
deletes: ExistingRow[];
|
|
@@ -76,6 +83,16 @@ export type PlanInput = {
|
|
|
76
83
|
* required field, because the column is filled — just not by the source.
|
|
77
84
|
*/
|
|
78
85
|
createDefaults?: Readonly<Record<string, unknown>>;
|
|
86
|
+
/** Creates an account alongside each new record, and links it. */
|
|
87
|
+
accountRule?: AccountRule | null;
|
|
88
|
+
/**
|
|
89
|
+
* Accounts that already exist, keyed by their match value.
|
|
90
|
+
*
|
|
91
|
+
* Loaded by the caller, like reference values: a record whose e-mail already
|
|
92
|
+
* has an account is LINKED to it rather than given a second one, which is what
|
|
93
|
+
* makes re-running an import safe.
|
|
94
|
+
*/
|
|
95
|
+
existingAccounts?: Readonly<Record<string, string>>;
|
|
79
96
|
/** Columns whose value has to already exist in another table. */
|
|
80
97
|
references?: readonly ReferenceCheck[];
|
|
81
98
|
/**
|
|
@@ -24,7 +24,7 @@ import { type EndpointSpec } from './collect';
|
|
|
24
24
|
import type { FetchImpl, SourceConnection } from './fetcher';
|
|
25
25
|
import { type OAuthConfig, type TokenCache } from './oauth';
|
|
26
26
|
import { type ExistingRow } from './plan';
|
|
27
|
-
import type { FieldMapping, MissingRecordPolicy, ReferenceCheck, RunFailure, RunIssue, RunPlan, RunProgress, WriteMode } from './types';
|
|
27
|
+
import type { AccountRule, FieldMapping, MissingRecordPolicy, ReferenceCheck, RunFailure, RunIssue, RunPlan, RunProgress, WriteMode } from './types';
|
|
28
28
|
import { type PreImage, type WriterDeps } from './writer';
|
|
29
29
|
/** The mapping being run, resolved from the database by the caller. */
|
|
30
30
|
export type MappingSpec = {
|
|
@@ -47,6 +47,8 @@ export type MappingSpec = {
|
|
|
47
47
|
* accounts must not also leave the key to them lying in a config table.
|
|
48
48
|
*/
|
|
49
49
|
passwordColumn?: string | null;
|
|
50
|
+
/** Creates a login alongside each new record, in the same transaction. */
|
|
51
|
+
accountRule?: AccountRule | null;
|
|
50
52
|
};
|
|
51
53
|
/** Everything a run needs, already read and decrypted. */
|
|
52
54
|
export type RunContext = {
|
|
@@ -67,6 +69,12 @@ export type RunContext = {
|
|
|
67
69
|
* handles a plaintext secret, and never persisted: it belongs to this run.
|
|
68
70
|
*/
|
|
69
71
|
createDefaults?: Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* The shared opening password, already hashed.
|
|
74
|
+
*
|
|
75
|
+
* Prepared by the caller so nothing in the engine handles a plaintext secret.
|
|
76
|
+
*/
|
|
77
|
+
accountPasswordHash?: string | null;
|
|
70
78
|
};
|
|
71
79
|
/** Persistence and lookups the runner needs. Injected so it can be tested. */
|
|
72
80
|
export type RunRepository = {
|
|
@@ -96,6 +104,8 @@ export type RunRepository = {
|
|
|
96
104
|
readExistingRows(targetEntity: string): Promise<ExistingRow[]>;
|
|
97
105
|
/** Distinct values of one column, for an `entity` parameter binding. */
|
|
98
106
|
readEntityColumn(entity: string, field: string): Promise<unknown[]>;
|
|
107
|
+
/** `value → id` for one column, so an existing account can be linked rather than doubled. */
|
|
108
|
+
readEntityIdsBy(entity: string, field: string): Promise<Record<string, string>>;
|
|
99
109
|
/** NOT NULL, no-default columns of the target. */
|
|
100
110
|
requiredFields(targetEntity: string): string[];
|
|
101
111
|
/** Single-column unique constraints of the target. */
|
|
@@ -128,6 +138,8 @@ export type RunOutcome = {
|
|
|
128
138
|
failure: RunFailure | null;
|
|
129
139
|
plan: RunPlan | null;
|
|
130
140
|
created: number;
|
|
141
|
+
/** Accounts created so this run's records have a login. */
|
|
142
|
+
accountsCreated: number;
|
|
131
143
|
/** Rows added to OTHER tables so this run's records could point at them. */
|
|
132
144
|
referencesCreated: number;
|
|
133
145
|
updated: number;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving a whole integration between installations.
|
|
3
|
+
*
|
|
4
|
+
* A connection is not one record. It is a connection, the calls it makes, the
|
|
5
|
+
* parameters those calls take from each other, and the mappings that turn the
|
|
6
|
+
* answers into rows — and rebuilding that by hand in a second environment is
|
|
7
|
+
* both hours of work and a fresh chance to get one field wrong in a way nobody
|
|
8
|
+
* notices until an import writes it.
|
|
9
|
+
*
|
|
10
|
+
* So it travels as one document.
|
|
11
|
+
*
|
|
12
|
+
* ## What deliberately does NOT travel
|
|
13
|
+
*
|
|
14
|
+
* Every secret. Tokens, client secrets, OAuth passwords: none of them are in the
|
|
15
|
+
* document, and the import asks for them again on the other side. A file that
|
|
16
|
+
* carries credentials is a file that gets mailed, pasted into a ticket and
|
|
17
|
+
* committed — and the whole point of storing them encrypted is undone by one
|
|
18
|
+
* convenient export.
|
|
19
|
+
*
|
|
20
|
+
* ## Why names, not identifiers
|
|
21
|
+
*
|
|
22
|
+
* An endpoint's parameters can be bound to another endpoint, and a mapping reads
|
|
23
|
+
* from one. Those are foreign keys, and a foreign key means nothing in a
|
|
24
|
+
* database that has never seen it. On the way out they become the endpoint's
|
|
25
|
+
* NAME; on the way in they are looked up again. So a document stays valid across
|
|
26
|
+
* installations, and a re-import onto an existing connection updates what is
|
|
27
|
+
* there rather than duplicating it.
|
|
28
|
+
*/
|
|
29
|
+
import type { FieldMapping, MissingRecordPolicy, ReferenceCheck, WriteMode } from './types';
|
|
30
|
+
/** Bumped only when an older document could be READ wrongly, not merely partially. */
|
|
31
|
+
export declare const TRANSFER_VERSION = 1;
|
|
32
|
+
export type TransferEndpoint = {
|
|
33
|
+
/** Unique within the document; how bindings and mappings refer to it. */
|
|
34
|
+
name: string;
|
|
35
|
+
method: string;
|
|
36
|
+
path: string;
|
|
37
|
+
description?: string | null;
|
|
38
|
+
queryParams?: Record<string, unknown> | null;
|
|
39
|
+
requestBody?: Record<string, unknown> | null;
|
|
40
|
+
responseRootPath?: string | null;
|
|
41
|
+
pagination?: Record<string, unknown> | null;
|
|
42
|
+
/** `endpointId` is replaced by `endpointName`; every other form is unchanged. */
|
|
43
|
+
paramBindings?: Record<string, unknown>[] | null;
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type TransferMapping = {
|
|
47
|
+
name: string;
|
|
48
|
+
/** The endpoint this mapping reads, by name. */
|
|
49
|
+
endpointName: string | null;
|
|
50
|
+
targetEntity: string;
|
|
51
|
+
fieldMappings: FieldMapping[];
|
|
52
|
+
referenceChecks?: ReferenceCheck[];
|
|
53
|
+
dedupSourceField?: string | null;
|
|
54
|
+
dedupTargetField?: string | null;
|
|
55
|
+
writeMode: WriteMode;
|
|
56
|
+
missingRecordPolicy: MissingRecordPolicy;
|
|
57
|
+
missingRecordColumn?: string | null;
|
|
58
|
+
missingRecordValue?: string | null;
|
|
59
|
+
passwordTargetColumn?: string | null;
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
};
|
|
62
|
+
export type TransferDocument = {
|
|
63
|
+
version: number;
|
|
64
|
+
/** Stamped by the caller; the engine has no clock of its own to trust. */
|
|
65
|
+
exportedAt?: string;
|
|
66
|
+
connection: {
|
|
67
|
+
name: string;
|
|
68
|
+
slug: string;
|
|
69
|
+
baseUrl: string;
|
|
70
|
+
description?: string | null;
|
|
71
|
+
authType: string;
|
|
72
|
+
authHeaderName?: string | null;
|
|
73
|
+
authQueryParam?: string | null;
|
|
74
|
+
extraHeaders?: Record<string, string> | null;
|
|
75
|
+
tokenUrl?: string | null;
|
|
76
|
+
oauthGrantType?: string | null;
|
|
77
|
+
oauthClientId?: string | null;
|
|
78
|
+
oauthUsername?: string | null;
|
|
79
|
+
oauthScope?: string | null;
|
|
80
|
+
oauthClientAuth?: string | null;
|
|
81
|
+
tlsVerify?: boolean;
|
|
82
|
+
timeoutMs?: number | null;
|
|
83
|
+
enabled?: boolean;
|
|
84
|
+
};
|
|
85
|
+
endpoints: TransferEndpoint[];
|
|
86
|
+
mappings: TransferMapping[];
|
|
87
|
+
/**
|
|
88
|
+
* Named so the person opening the file knows what it cannot do on its own.
|
|
89
|
+
* Read by nothing; it exists to be read by a human.
|
|
90
|
+
*/
|
|
91
|
+
secretsExcluded: true;
|
|
92
|
+
};
|
|
93
|
+
/** True when a row carries something that must not be exported. Used by tests. */
|
|
94
|
+
export declare function hasSecret(row: Record<string, unknown>): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Builds the document from rows as they are stored.
|
|
97
|
+
*
|
|
98
|
+
* Field by field rather than by spreading the row: a column added to the table
|
|
99
|
+
* later — including the next encrypted one — must not ride out to a file simply
|
|
100
|
+
* because nobody updated this function.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildTransferDocument(input: {
|
|
103
|
+
source: Record<string, unknown>;
|
|
104
|
+
endpoints: Record<string, unknown>[];
|
|
105
|
+
mappings: Record<string, unknown>[];
|
|
106
|
+
exportedAt?: string;
|
|
107
|
+
}): TransferDocument;
|
|
108
|
+
export type TransferProblem = {
|
|
109
|
+
path: string;
|
|
110
|
+
message: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Checks a document before anything is written.
|
|
114
|
+
*
|
|
115
|
+
* Refusing a bad file costs one message; accepting one costs a half-built
|
|
116
|
+
* connection that looks configured and imports nothing — or worse, imports into
|
|
117
|
+
* the wrong table.
|
|
118
|
+
*/
|
|
119
|
+
export declare function validateTransferDocument(value: unknown): {
|
|
120
|
+
ok: boolean;
|
|
121
|
+
document: TransferDocument | null;
|
|
122
|
+
problems: TransferProblem[];
|
|
123
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -11,7 +11,9 @@ export type MappingTransform = 'none' | 'trim' | 'upper' | 'lower' | 'toString'
|
|
|
11
11
|
/** Folds accents, lowercases, and removes what an address cannot contain. */
|
|
12
12
|
| 'emailSafe'
|
|
13
13
|
/** The last part of a path-like value: `A/B/C` → `C`. */
|
|
14
|
-
| 'pathLeaf'
|
|
14
|
+
| 'pathLeaf'
|
|
15
|
+
/** Wraps a value as a list, for a column that holds one: `100712` → `['100712']`. */
|
|
16
|
+
| 'toList';
|
|
15
17
|
/** One field of a mapping: external `source` path → internal `target` column. */
|
|
16
18
|
export type FieldMapping = {
|
|
17
19
|
/** Dot-path into the external record, e.g. `employeeNo` or `department.code`. */
|
|
@@ -133,7 +135,7 @@ export type RunStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancell
|
|
|
133
135
|
*/
|
|
134
136
|
export type RunIssue = {
|
|
135
137
|
sourceIndex: number;
|
|
136
|
-
kind: 'missing_required' | 'missing_dedup_key' | 'duplicate_in_batch' | 'duplicate_value' | 'invalid_value' | 'unmapped' | 'missing_reference';
|
|
138
|
+
kind: 'missing_required' | 'missing_dedup_key' | 'duplicate_in_batch' | 'duplicate_value' | 'invalid_value' | 'unmapped' | 'missing_reference' | 'account_unmatched';
|
|
137
139
|
message: string;
|
|
138
140
|
dedupKey?: string | null;
|
|
139
141
|
/**
|
|
@@ -189,6 +191,47 @@ export type ReferenceCheck = {
|
|
|
189
191
|
entityColumn: string;
|
|
190
192
|
onMissing: ReferenceOnMissing;
|
|
191
193
|
};
|
|
194
|
+
/**
|
|
195
|
+
* An account created alongside each imported record.
|
|
196
|
+
*
|
|
197
|
+
* A directory carries people, not logins. Importing eight hundred employees and
|
|
198
|
+
* then creating eight hundred accounts by hand — matching them up by e-mail, one
|
|
199
|
+
* at a time — is the job nobody does, so the import that was supposed to save the
|
|
200
|
+
* afternoon produces a table of people who cannot sign in.
|
|
201
|
+
*
|
|
202
|
+
* So the account is part of the same import and the same transaction: either the
|
|
203
|
+
* person and their login both exist, or neither does.
|
|
204
|
+
*
|
|
205
|
+
* The password is NOT here. It belongs to the run — asked for once, used for
|
|
206
|
+
* every account that run creates, and stored nowhere. A password on a mapping is
|
|
207
|
+
* a password that outlives the reason for it.
|
|
208
|
+
*/
|
|
209
|
+
export type AccountRule = {
|
|
210
|
+
/** Entity the account row goes into, e.g. `users`. */
|
|
211
|
+
entity: string;
|
|
212
|
+
/** Column on the TARGET entity that receives the account's id, e.g. `userId`. */
|
|
213
|
+
idColumn: string;
|
|
214
|
+
/** Column on the account entity that identifies it, e.g. `email`. */
|
|
215
|
+
matchColumn: string;
|
|
216
|
+
/** Column of the MAPPED record whose value is matched against it. */
|
|
217
|
+
matchFrom: string;
|
|
218
|
+
/** Column on the account entity that receives the run's shared password. */
|
|
219
|
+
passwordColumn: string;
|
|
220
|
+
/** Other columns to fill on a new account, taken from the mapped record. */
|
|
221
|
+
values?: {
|
|
222
|
+
column: string;
|
|
223
|
+
from: string;
|
|
224
|
+
}[];
|
|
225
|
+
};
|
|
226
|
+
/** An account a run would create so an imported record has a login. */
|
|
227
|
+
export type AccountInsert = {
|
|
228
|
+
entity: string;
|
|
229
|
+
/** What identifies it, so a row can be matched back to the record that needs it. */
|
|
230
|
+
matchColumn: string;
|
|
231
|
+
matchValue: string;
|
|
232
|
+
/** Everything to write, except the password the writer adds. */
|
|
233
|
+
data: Record<string, unknown>;
|
|
234
|
+
};
|
|
192
235
|
/** A value a run would add to a referenced table so a record can point at it. */
|
|
193
236
|
export type ReferenceInsert = {
|
|
194
237
|
entity: string;
|
|
@@ -251,6 +294,8 @@ export type RunPlan = {
|
|
|
251
294
|
* approving, not read about afterwards.
|
|
252
295
|
*/
|
|
253
296
|
referenceInserts: ReferenceInsert[];
|
|
297
|
+
/** Accounts this run would create so its records have a login. */
|
|
298
|
+
accountsToCreate: number;
|
|
254
299
|
/** A few mapped rows so an operator can eyeball the mapping before committing. */
|
|
255
300
|
sample: Record<string, unknown>[];
|
|
256
301
|
};
|
|
@@ -80,6 +80,28 @@ export type ApplyInput = {
|
|
|
80
80
|
ops: WriteOperations;
|
|
81
81
|
targetEntity: string;
|
|
82
82
|
userId: string;
|
|
83
|
+
/**
|
|
84
|
+
* How an account is created alongside each record, and where its id goes.
|
|
85
|
+
*
|
|
86
|
+
* The password arrives already hashed: nothing in the engine handles a
|
|
87
|
+
* plaintext secret, and it is never written anywhere but the account row.
|
|
88
|
+
*/
|
|
89
|
+
account?: {
|
|
90
|
+
idColumn: string;
|
|
91
|
+
/** Column on the ACCOUNT row that identifies it. */
|
|
92
|
+
matchColumn: string;
|
|
93
|
+
/**
|
|
94
|
+
* Column of the RECORD carrying the same value.
|
|
95
|
+
*
|
|
96
|
+
* Not the same thing as `matchColumn`, even when they share a name: one
|
|
97
|
+
* names a column on `users`, the other a column on `employees`, and reading
|
|
98
|
+
* the record with the account's column name is how a rename in either table
|
|
99
|
+
* would start handing people each other's logins.
|
|
100
|
+
*/
|
|
101
|
+
matchFrom: string;
|
|
102
|
+
passwordColumn: string;
|
|
103
|
+
passwordHash: string;
|
|
104
|
+
} | null;
|
|
83
105
|
/** Ownership predicates from the caller's claims. ANDed onto updates/deletes. */
|
|
84
106
|
scopeConditions?: SQL[];
|
|
85
107
|
/** Column and value written to rows the source no longer carries. */
|
|
@@ -91,6 +113,8 @@ export type ApplyInput = {
|
|
|
91
113
|
export type ApplyResult = {
|
|
92
114
|
ok: true;
|
|
93
115
|
created: number;
|
|
116
|
+
/** Accounts created so this run's records have a login. */
|
|
117
|
+
accountsCreated: number;
|
|
94
118
|
/** Rows added to OTHER tables so this run's records could point at them. */
|
|
95
119
|
referencesCreated: number;
|
|
96
120
|
updated: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.823",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
package/src/system.tables.json
CHANGED
|
@@ -5331,6 +5331,11 @@
|
|
|
5331
5331
|
"length": 100,
|
|
5332
5332
|
"comment": "Column that receives the shared opening password given at run time. The password itself is never stored."
|
|
5333
5333
|
},
|
|
5334
|
+
{
|
|
5335
|
+
"name": "account_rule",
|
|
5336
|
+
"type": "jsonb",
|
|
5337
|
+
"comment": "Creates a login alongside each imported record, in the same transaction, and links it. The password belongs to the run, never to this row."
|
|
5338
|
+
},
|
|
5334
5339
|
{ "name": "schedule_cron", "type": "varchar", "length": 120 },
|
|
5335
5340
|
{
|
|
5336
5341
|
"name": "schedule_timezone",
|
|
@@ -5411,6 +5416,13 @@
|
|
|
5411
5416
|
"default": 0,
|
|
5412
5417
|
"comment": "Rows this run added to OTHER tables so its records could point at them."
|
|
5413
5418
|
},
|
|
5419
|
+
{
|
|
5420
|
+
"name": "accounts_created_count",
|
|
5421
|
+
"type": "integer",
|
|
5422
|
+
"notNull": true,
|
|
5423
|
+
"default": 0,
|
|
5424
|
+
"comment": "Logins this run created so its records could be signed in to."
|
|
5425
|
+
},
|
|
5414
5426
|
{ "name": "updated_count", "type": "integer", "notNull": true, "default": 0 },
|
|
5415
5427
|
{ "name": "skipped_count", "type": "integer", "notNull": true, "default": 0 },
|
|
5416
5428
|
{ "name": "deleted_count", "type": "integer", "notNull": true, "default": 0 },
|