@memberjunction/integration-engine-base 6.2.0-edge.4 → 6.2.0-edge.6
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/CompanyIntegrationCatalog.d.ts +173 -0
- package/dist/CompanyIntegrationCatalog.d.ts.map +1 -0
- package/dist/CompanyIntegrationCatalog.js +185 -0
- package/dist/CompanyIntegrationCatalog.js.map +1 -0
- package/dist/IntegrationEngineBase.d.ts +224 -5
- package/dist/IntegrationEngineBase.d.ts.map +1 -1
- package/dist/IntegrationEngineBase.js +480 -16
- package/dist/IntegrationEngineBase.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { BaseEntity } from '@memberjunction/core';
|
|
2
|
+
import type { MJIntegrationObjectEntity, MJIntegrationObjectFieldEntity } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* The per-connection catalog: read-side types, column contract, and projection.
|
|
5
|
+
*
|
|
6
|
+
* Every connector and every engine read site names an IntegrationObject's properties directly —
|
|
7
|
+
* `io.Name` appears 241 times across the connectors repository, `io.Fields` 93, `io.APIPath` 52.
|
|
8
|
+
* A row loaded for an entity with no registered subclass is a plain `BaseEntity`: it carries its
|
|
9
|
+
* values in `Fields` and exposes them through `Get()`, and every one of those property reads is
|
|
10
|
+
* `undefined`. So the cache cannot hand connectors the entity rows themselves.
|
|
11
|
+
*
|
|
12
|
+
* It hands them PROJECTIONS instead — plain objects carrying exactly the property surface the read
|
|
13
|
+
* sites use. That is not a new idea here: `IntegrationEngineBase.SeedForTesting` already seeds the
|
|
14
|
+
* shared catalog with "plain objects shaped like the entity (property reads only)" for the same
|
|
15
|
+
* reason, and the replay tiers exercise metadata-driven connectors through them.
|
|
16
|
+
*
|
|
17
|
+
* Writes are the other half and do NOT come through here — they go through the store in
|
|
18
|
+
* `@memberjunction/integration-engine`, which holds real entity rows and uses `Get()`/`Set()`
|
|
19
|
+
* exclusively. Assigning `row.Name = x` on a subclass-less `BaseEntity` silently creates an own
|
|
20
|
+
* property, `Save()` sends nothing, and the write is lost with no error anywhere.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ENTITY_COMPANY_INTEGRATION_OBJECTS = "MJ: Company Integration Objects";
|
|
23
|
+
export declare const ENTITY_COMPANY_INTEGRATION_OBJECT_FIELDS = "MJ: Company Integration Object Fields";
|
|
24
|
+
/**
|
|
25
|
+
* Columns the object projection reads. The first 43 are IntegrationObject's own, copied verbatim so
|
|
26
|
+
* a per-connection row answers every question a shared row answers; the rest are what per-connection
|
|
27
|
+
* rows add. `IntegrationID` is deliberately among the first group and denormalised onto the table:
|
|
28
|
+
* connectors read it off the object, and a join per read would be the wrong trade.
|
|
29
|
+
*/
|
|
30
|
+
export declare const CATALOG_OBJECT_COLUMNS: readonly ["ID", "IntegrationID", "Name", "DisplayName", "Description", "Category", "APIPath", "ResponseDataKey", "DefaultPageSize", "SupportsPagination", "PaginationType", "SupportsIncrementalSync", "SupportsWrite", "DefaultQueryParams", "Configuration", "Sequence", "Status", "WriteAPIPath", "WriteMethod", "DeleteMethod", "IsCustom", "CreateAPIPath", "CreateMethod", "CreateBodyShape", "CreateBodyKey", "CreateIDLocation", "UpdateAPIPath", "UpdateMethod", "UpdateBodyShape", "UpdateBodyKey", "UpdateIDLocation", "DeleteAPIPath", "DeleteIDLocation", "IncrementalWatermarkField", "MetadataSource", "SupportsCreate", "SupportsUpdate", "SupportsDelete", "SyncStrategy", "ContentHashApplicable", "StableOrderingKey", "CompanyIntegrationID", "IntegrationObjectID", "Provenance", "ProvenanceDetail", "IsSelected", "SelectedAt", "FirstSeenAt", "LastSeenAt", "LastSampledAt"];
|
|
31
|
+
/**
|
|
32
|
+
* Columns the field projection reads. These are all REAL columns on the per-connection field
|
|
33
|
+
* table — the legacy names `IntegrationObjectID` / `RelatedIntegrationObjectID` are NOT here and
|
|
34
|
+
* must not be: they are derived in `ProjectCatalogFields` (see FIELD_READ_ALIASES).
|
|
35
|
+
*
|
|
36
|
+
* They are derived in TypeScript rather than aliased in the base view because the view is
|
|
37
|
+
* CodeGen-generated: a hand-added alias column would be silently dropped the next time CodeGen
|
|
38
|
+
* regenerates it, and the loss would only surface as a schema-mismatch throw much later.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CATALOG_FIELD_COLUMNS: readonly ["ID", "Name", "DisplayName", "Description", "Category", "Type", "Length", "Precision", "Scale", "AllowsNull", "DefaultValue", "IsPrimaryKey", "IsUniqueKey", "IsReadOnly", "IsRequired", "RelatedIntegrationObjectFieldName", "Sequence", "Configuration", "Status", "IsCustom", "MetadataSource", "CompanyIntegrationObjectID", "RelatedCompanyIntegrationObjectID", "IntegrationObjectFieldID", "Provenance", "ProvenanceDetail", "IsSelected", "SelectedAt", "FirstSeenAt", "LastSeenAt", "LastSampledAt", "ObservedMaxLength"];
|
|
41
|
+
/**
|
|
42
|
+
* The three columns of a per-connection field row that the dependency walk reads.
|
|
43
|
+
*
|
|
44
|
+
* The walk is the one consumer that needs every field of a connection that points at another
|
|
45
|
+
* object, and it needs nothing else of them, so those are loaded for the whole connection as plain
|
|
46
|
+
* rows — never entities — while full field rows are loaded one object at a time. Conflating the two
|
|
47
|
+
* is what forced the whole catalog to be resident.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CATALOG_EDGE_COLUMNS: readonly ["ID", "CompanyIntegrationObjectID", "RelatedCompanyIntegrationObjectID"];
|
|
50
|
+
/** One dependency edge: a field of one per-connection object pointing at another of the same connection. */
|
|
51
|
+
export interface CatalogDependencyEdge {
|
|
52
|
+
ID: string;
|
|
53
|
+
CompanyIntegrationObjectID: string;
|
|
54
|
+
RelatedCompanyIntegrationObjectID: string | null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The per-connection catalog of the scope in force, as its owner hands it to the engine base
|
|
58
|
+
* through `IntegrationEngineBase.CatalogDataResolver`.
|
|
59
|
+
*
|
|
60
|
+
* Raw rows rather than projections, so projection and memoisation have one home (the engine base)
|
|
61
|
+
* and a scope owner cannot hand over a projection that skipped the column check.
|
|
62
|
+
*/
|
|
63
|
+
export interface CatalogScopeData {
|
|
64
|
+
/** Every object row of the connection in scope, loaded when the scope was entered. */
|
|
65
|
+
Objects: BaseEntity[];
|
|
66
|
+
/**
|
|
67
|
+
* Full field rows of the objects warm right now, keyed by lowercased object id. Field rows are
|
|
68
|
+
* fetched one object at a time into a bounded cache, so an object absent from this map is COLD,
|
|
69
|
+
* which is a different thing from an object with no fields.
|
|
70
|
+
*/
|
|
71
|
+
FieldsByObjectID: ReadonlyMap<string, BaseEntity[]>;
|
|
72
|
+
/**
|
|
73
|
+
* The connection's dependency edges: one per field that points at another object. Required
|
|
74
|
+
* whenever a dependency order is asked for: a missing edge set does not fail, it silently
|
|
75
|
+
* reorders a sync, children first.
|
|
76
|
+
*/
|
|
77
|
+
Edges?: readonly CatalogDependencyEdge[];
|
|
78
|
+
/**
|
|
79
|
+
* True when the scope's owner warms every object before reading it, so a read of a cold object
|
|
80
|
+
* is a missed warm site and throws. False for owners that have no warm sites yet, whose cold
|
|
81
|
+
* reads answer empty exactly as they did before the catalog stopped being resident.
|
|
82
|
+
*/
|
|
83
|
+
RequireWarmFields: boolean;
|
|
84
|
+
}
|
|
85
|
+
export declare const DECLARED_OWNED_OBJECT_COLUMNS: readonly string[];
|
|
86
|
+
export declare const DECLARED_OWNED_FIELD_COLUMNS: readonly string[];
|
|
87
|
+
/** Where a per-connection row's shape came from. */
|
|
88
|
+
export type CatalogProvenance = 'Declared' | 'Endpoint' | 'Sampled';
|
|
89
|
+
/**
|
|
90
|
+
* A per-connection object as read sites see it: every IntegrationObject property, plus the
|
|
91
|
+
* per-connection additions. Structurally assignable to `MJIntegrationObjectEntity` for the property
|
|
92
|
+
* reads connectors perform, which is what makes the swap invisible to them.
|
|
93
|
+
*/
|
|
94
|
+
export type CompanyIntegrationObjectRow = Pick<MJIntegrationObjectEntity, 'ID' | 'IntegrationID' | 'Name' | 'DisplayName' | 'Description' | 'Category' | 'APIPath' | 'ResponseDataKey' | 'DefaultPageSize' | 'SupportsPagination' | 'PaginationType' | 'SupportsIncrementalSync' | 'SupportsWrite' | 'DefaultQueryParams' | 'Configuration' | 'Sequence' | 'Status' | 'WriteAPIPath' | 'WriteMethod' | 'DeleteMethod' | 'IsCustom' | 'CreateAPIPath' | 'CreateMethod' | 'CreateBodyShape' | 'CreateBodyKey' | 'CreateIDLocation' | 'UpdateAPIPath' | 'UpdateMethod' | 'UpdateBodyShape' | 'UpdateBodyKey' | 'UpdateIDLocation' | 'DeleteAPIPath' | 'DeleteIDLocation' | 'IncrementalWatermarkField' | 'MetadataSource' | 'SupportsCreate' | 'SupportsUpdate' | 'SupportsDelete' | 'SyncStrategy' | 'ContentHashApplicable' | 'StableOrderingKey'> & {
|
|
95
|
+
/** The connection this row belongs to. The axis the whole design exists for. */
|
|
96
|
+
CompanyIntegrationID: string;
|
|
97
|
+
/** The declared row this was matched to, or null when the object exists only for this connection. */
|
|
98
|
+
IntegrationObjectID: string | null;
|
|
99
|
+
Provenance: CatalogProvenance;
|
|
100
|
+
/** Per-attribute merge log the persist already computes and currently discards. */
|
|
101
|
+
ProvenanceDetail: string | null;
|
|
102
|
+
/**
|
|
103
|
+
* Catalog membership and sync selection are separate axes. Conflating them is what made a
|
|
104
|
+
* newly-discovered object arrive `Disabled` and therefore vanish from schema introspection, key
|
|
105
|
+
* inference and migration — the customer saw it in the picker, selected it, and got nothing.
|
|
106
|
+
*/
|
|
107
|
+
IsSelected: boolean;
|
|
108
|
+
SelectedAt: Date | null;
|
|
109
|
+
FirstSeenAt: Date | null;
|
|
110
|
+
LastSeenAt: Date | null;
|
|
111
|
+
LastSampledAt: Date | null;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* A per-connection field as read sites see it. `IntegrationObjectID` and
|
|
115
|
+
* `RelatedIntegrationObjectID` carry per-connection ids through the view aliases.
|
|
116
|
+
*/
|
|
117
|
+
export type CompanyIntegrationObjectFieldRow = Pick<MJIntegrationObjectFieldEntity, 'ID' | 'IntegrationObjectID' | 'Name' | 'DisplayName' | 'Description' | 'Category' | 'Type' | 'Length' | 'Precision' | 'Scale' | 'AllowsNull' | 'DefaultValue' | 'IsPrimaryKey' | 'IsUniqueKey' | 'IsReadOnly' | 'IsRequired' | 'RelatedIntegrationObjectID' | 'RelatedIntegrationObjectFieldName' | 'Sequence' | 'Configuration' | 'Status' | 'IsCustom' | 'MetadataSource'> & {
|
|
118
|
+
CompanyIntegrationObjectID: string;
|
|
119
|
+
RelatedCompanyIntegrationObjectID: string | null;
|
|
120
|
+
IntegrationObjectFieldID: string | null;
|
|
121
|
+
Provenance: CatalogProvenance;
|
|
122
|
+
ProvenanceDetail: string | null;
|
|
123
|
+
IsSelected: boolean;
|
|
124
|
+
SelectedAt: Date | null;
|
|
125
|
+
FirstSeenAt: Date | null;
|
|
126
|
+
LastSeenAt: Date | null;
|
|
127
|
+
LastSampledAt: Date | null;
|
|
128
|
+
/**
|
|
129
|
+
* The raw sampled maximum, kept apart from `Length`, which carries the padded width the DDL
|
|
130
|
+
* uses. Without both, a width complaint cannot be told from a padding complaint.
|
|
131
|
+
*/
|
|
132
|
+
ObservedMaxLength: number | null;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Thrown when the loaded entity is missing columns this code requires — the migration is absent or
|
|
136
|
+
* an older version of it ran. Failing here names the problem; letting it through would produce rows
|
|
137
|
+
* whose per-connection columns are all `undefined`, which reads downstream as "no selection, no
|
|
138
|
+
* provenance, never seen" and is indistinguishable from a legitimately empty catalog.
|
|
139
|
+
*/
|
|
140
|
+
export declare class CompanyIntegrationCatalogSchemaMismatch extends Error {
|
|
141
|
+
readonly Code = "PER_CONNECTION_CATALOG_SCHEMA_MISMATCH";
|
|
142
|
+
constructor(entityName: string, missing: string[]);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Verify a loaded row carries every column we read. Called once per array load — the cost is one
|
|
146
|
+
* pass over one row's field list, and the alternative is a silent wrong answer.
|
|
147
|
+
*/
|
|
148
|
+
export declare function AssertCatalogColumns(entityName: string, row: BaseEntity, required: readonly string[]): void;
|
|
149
|
+
/**
|
|
150
|
+
* Project loaded object rows for the read path. Validates the column set against the FIRST row
|
|
151
|
+
* only: every row in the array is the same entity, so one is proof, and an empty array proves
|
|
152
|
+
* nothing and needs no check — a connection with no catalog rows is the ordinary pre-discovery
|
|
153
|
+
* state, not a schema problem.
|
|
154
|
+
*/
|
|
155
|
+
export declare function ProjectCatalogObjects(rows: BaseEntity[]): CompanyIntegrationObjectRow[];
|
|
156
|
+
/** Project loaded field rows for the read path. Same validation rule as the object projection. */
|
|
157
|
+
export declare function ProjectCatalogFields(rows: BaseEntity[]): CompanyIntegrationObjectFieldRow[];
|
|
158
|
+
/**
|
|
159
|
+
* Present a projection to code typed against the shared catalog entities.
|
|
160
|
+
*
|
|
161
|
+
* The two casts below are the ONLY place this narrowing happens, deliberately, so the reasoning
|
|
162
|
+
* sits in one auditable spot. A projection carries every property the read sites touch and none of
|
|
163
|
+
* `BaseEntity`'s behaviour — no `Save()`, no `Fields`, no dirty tracking. That is safe here and
|
|
164
|
+
* nowhere else, because reads are all this cache serves: the per-connection write path holds real
|
|
165
|
+
* entity rows and never receives one of these.
|
|
166
|
+
*
|
|
167
|
+
* Calling `.Save()` on the result would throw at runtime rather than corrupt anything, which is the
|
|
168
|
+
* failure mode to want if this contract is ever broken.
|
|
169
|
+
*/
|
|
170
|
+
export declare function AsReadOnlyObject(row: CompanyIntegrationObjectRow): MJIntegrationObjectEntity;
|
|
171
|
+
/** See {@link AsReadOnlyObject}. */
|
|
172
|
+
export declare function AsReadOnlyField(row: CompanyIntegrationObjectFieldRow): MJIntegrationObjectFieldEntity;
|
|
173
|
+
//# sourceMappingURL=CompanyIntegrationCatalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompanyIntegrationCatalog.d.ts","sourceRoot":"","sources":["../src/CompanyIntegrationCatalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-connection catalog: read-side types, column contract, and projection.
|
|
3
|
+
*
|
|
4
|
+
* Every connector and every engine read site names an IntegrationObject's properties directly —
|
|
5
|
+
* `io.Name` appears 241 times across the connectors repository, `io.Fields` 93, `io.APIPath` 52.
|
|
6
|
+
* A row loaded for an entity with no registered subclass is a plain `BaseEntity`: it carries its
|
|
7
|
+
* values in `Fields` and exposes them through `Get()`, and every one of those property reads is
|
|
8
|
+
* `undefined`. So the cache cannot hand connectors the entity rows themselves.
|
|
9
|
+
*
|
|
10
|
+
* It hands them PROJECTIONS instead — plain objects carrying exactly the property surface the read
|
|
11
|
+
* sites use. That is not a new idea here: `IntegrationEngineBase.SeedForTesting` already seeds the
|
|
12
|
+
* shared catalog with "plain objects shaped like the entity (property reads only)" for the same
|
|
13
|
+
* reason, and the replay tiers exercise metadata-driven connectors through them.
|
|
14
|
+
*
|
|
15
|
+
* Writes are the other half and do NOT come through here — they go through the store in
|
|
16
|
+
* `@memberjunction/integration-engine`, which holds real entity rows and uses `Get()`/`Set()`
|
|
17
|
+
* exclusively. Assigning `row.Name = x` on a subclass-less `BaseEntity` silently creates an own
|
|
18
|
+
* property, `Save()` sends nothing, and the write is lost with no error anywhere.
|
|
19
|
+
*/
|
|
20
|
+
export const ENTITY_COMPANY_INTEGRATION_OBJECTS = 'MJ: Company Integration Objects';
|
|
21
|
+
export const ENTITY_COMPANY_INTEGRATION_OBJECT_FIELDS = 'MJ: Company Integration Object Fields';
|
|
22
|
+
/**
|
|
23
|
+
* Columns the object projection reads. The first 43 are IntegrationObject's own, copied verbatim so
|
|
24
|
+
* a per-connection row answers every question a shared row answers; the rest are what per-connection
|
|
25
|
+
* rows add. `IntegrationID` is deliberately among the first group and denormalised onto the table:
|
|
26
|
+
* connectors read it off the object, and a join per read would be the wrong trade.
|
|
27
|
+
*/
|
|
28
|
+
export const CATALOG_OBJECT_COLUMNS = [
|
|
29
|
+
'ID', 'IntegrationID', 'Name', 'DisplayName', 'Description', 'Category', 'APIPath',
|
|
30
|
+
'ResponseDataKey', 'DefaultPageSize', 'SupportsPagination', 'PaginationType',
|
|
31
|
+
'SupportsIncrementalSync', 'SupportsWrite', 'DefaultQueryParams', 'Configuration', 'Sequence',
|
|
32
|
+
'Status', 'WriteAPIPath', 'WriteMethod', 'DeleteMethod', 'IsCustom', 'CreateAPIPath',
|
|
33
|
+
'CreateMethod', 'CreateBodyShape', 'CreateBodyKey', 'CreateIDLocation', 'UpdateAPIPath',
|
|
34
|
+
'UpdateMethod', 'UpdateBodyShape', 'UpdateBodyKey', 'UpdateIDLocation', 'DeleteAPIPath',
|
|
35
|
+
'DeleteIDLocation', 'IncrementalWatermarkField', 'MetadataSource', 'SupportsCreate',
|
|
36
|
+
'SupportsUpdate', 'SupportsDelete', 'SyncStrategy', 'ContentHashApplicable', 'StableOrderingKey',
|
|
37
|
+
'CompanyIntegrationID', 'IntegrationObjectID', 'Provenance', 'ProvenanceDetail', 'IsSelected',
|
|
38
|
+
'SelectedAt', 'FirstSeenAt', 'LastSeenAt', 'LastSampledAt',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Columns the field projection reads. These are all REAL columns on the per-connection field
|
|
42
|
+
* table — the legacy names `IntegrationObjectID` / `RelatedIntegrationObjectID` are NOT here and
|
|
43
|
+
* must not be: they are derived in `ProjectCatalogFields` (see FIELD_READ_ALIASES).
|
|
44
|
+
*
|
|
45
|
+
* They are derived in TypeScript rather than aliased in the base view because the view is
|
|
46
|
+
* CodeGen-generated: a hand-added alias column would be silently dropped the next time CodeGen
|
|
47
|
+
* regenerates it, and the loss would only surface as a schema-mismatch throw much later.
|
|
48
|
+
*/
|
|
49
|
+
export const CATALOG_FIELD_COLUMNS = [
|
|
50
|
+
'ID', 'Name', 'DisplayName', 'Description', 'Category', 'Type', 'Length', 'Precision', 'Scale',
|
|
51
|
+
'AllowsNull', 'DefaultValue', 'IsPrimaryKey', 'IsUniqueKey', 'IsReadOnly', 'IsRequired',
|
|
52
|
+
'RelatedIntegrationObjectFieldName', 'Sequence', 'Configuration', 'Status', 'IsCustom',
|
|
53
|
+
'MetadataSource', 'CompanyIntegrationObjectID', 'RelatedCompanyIntegrationObjectID',
|
|
54
|
+
'IntegrationObjectFieldID', 'Provenance', 'ProvenanceDetail', 'IsSelected', 'SelectedAt',
|
|
55
|
+
'FirstSeenAt', 'LastSeenAt', 'LastSampledAt', 'ObservedMaxLength',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* The three columns of a per-connection field row that the dependency walk reads.
|
|
59
|
+
*
|
|
60
|
+
* The walk is the one consumer that needs every field of a connection that points at another
|
|
61
|
+
* object, and it needs nothing else of them, so those are loaded for the whole connection as plain
|
|
62
|
+
* rows — never entities — while full field rows are loaded one object at a time. Conflating the two
|
|
63
|
+
* is what forced the whole catalog to be resident.
|
|
64
|
+
*/
|
|
65
|
+
export const CATALOG_EDGE_COLUMNS = ['ID', 'CompanyIntegrationObjectID', 'RelatedCompanyIntegrationObjectID'];
|
|
66
|
+
/**
|
|
67
|
+
* Legacy read names mapped onto their per-connection columns.
|
|
68
|
+
*
|
|
69
|
+
* A connector resolving a dependency edge reads `f.RelatedIntegrationObjectID` and matches it
|
|
70
|
+
* against its sibling objects (e.g. Rhythm, PathLMS). Deriving these keeps that code working
|
|
71
|
+
* unchanged while the value it gets is the per-connection id.
|
|
72
|
+
*/
|
|
73
|
+
const FIELD_READ_ALIASES = [
|
|
74
|
+
['IntegrationObjectID', 'CompanyIntegrationObjectID'],
|
|
75
|
+
['RelatedIntegrationObjectID', 'RelatedCompanyIntegrationObjectID'],
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* Columns whose value the CONNECTOR declares (metadata/ folder), as opposed to columns the
|
|
79
|
+
* connection owns or discovery decides.
|
|
80
|
+
*
|
|
81
|
+
* plan.md requires that a schema refresh treat IO/IOF as the source of truth and REPLACE what is
|
|
82
|
+
* in CIO/CIOF rather than layering on it: "it will use IO/IOF as the SOT before then replacing
|
|
83
|
+
* what is in the respective CIO/CIOF, it will not do the changes on top of CIO/CIOF since it is
|
|
84
|
+
* stale metadata". Without that, an open-app upgrade that changes a declared APIPath or page size
|
|
85
|
+
* would never reach a connection that had already discovered once — the stale per-connection value
|
|
86
|
+
* would win every subsequent overlay.
|
|
87
|
+
*
|
|
88
|
+
* Derived by SUBTRACTION so it cannot drift when a column is added: anything not explicitly
|
|
89
|
+
* identity, per-connection state, or discovery-owned lifecycle is connector-declared.
|
|
90
|
+
* Description / DisplayName / IncrementalWatermarkField are deliberately IN this set — they are
|
|
91
|
+
* rebased from the declaration and then discovery overlays them if it reports a value, which is
|
|
92
|
+
* exactly the documented precedence.
|
|
93
|
+
*/
|
|
94
|
+
const NOT_DECLARED_OWNED = new Set([
|
|
95
|
+
// identity and linkage
|
|
96
|
+
'ID', 'IntegrationID', 'Name',
|
|
97
|
+
'CompanyIntegrationID', 'IntegrationObjectID',
|
|
98
|
+
'CompanyIntegrationObjectID', 'RelatedCompanyIntegrationObjectID', 'IntegrationObjectFieldID',
|
|
99
|
+
// per-connection state
|
|
100
|
+
'Provenance', 'ProvenanceDetail', 'IsSelected', 'SelectedAt',
|
|
101
|
+
'FirstSeenAt', 'LastSeenAt', 'LastSampledAt', 'ObservedMaxLength',
|
|
102
|
+
// decided by discovery, not declared
|
|
103
|
+
'Status', 'IsCustom', 'MetadataSource',
|
|
104
|
+
]);
|
|
105
|
+
export const DECLARED_OWNED_OBJECT_COLUMNS = CATALOG_OBJECT_COLUMNS.filter(c => !NOT_DECLARED_OWNED.has(c));
|
|
106
|
+
export const DECLARED_OWNED_FIELD_COLUMNS = CATALOG_FIELD_COLUMNS.filter(c => !NOT_DECLARED_OWNED.has(c));
|
|
107
|
+
/**
|
|
108
|
+
* Thrown when the loaded entity is missing columns this code requires — the migration is absent or
|
|
109
|
+
* an older version of it ran. Failing here names the problem; letting it through would produce rows
|
|
110
|
+
* whose per-connection columns are all `undefined`, which reads downstream as "no selection, no
|
|
111
|
+
* provenance, never seen" and is indistinguishable from a legitimately empty catalog.
|
|
112
|
+
*/
|
|
113
|
+
export class CompanyIntegrationCatalogSchemaMismatch extends Error {
|
|
114
|
+
constructor(entityName, missing) {
|
|
115
|
+
super(`${entityName} is missing ${missing.length} required column(s): ${missing.join(', ')}. `
|
|
116
|
+
+ `The per-connection catalog migration has not been applied to this database, or the `
|
|
117
|
+
+ `applied version predates these columns.`);
|
|
118
|
+
this.Code = 'PER_CONNECTION_CATALOG_SCHEMA_MISMATCH';
|
|
119
|
+
this.name = 'CompanyIntegrationCatalogSchemaMismatch';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Names present on a loaded row, whether the value is set or not. */
|
|
123
|
+
function fieldNamesOf(row) {
|
|
124
|
+
return new Set((row.Fields ?? []).map(f => f.Name));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Verify a loaded row carries every column we read. Called once per array load — the cost is one
|
|
128
|
+
* pass over one row's field list, and the alternative is a silent wrong answer.
|
|
129
|
+
*/
|
|
130
|
+
export function AssertCatalogColumns(entityName, row, required) {
|
|
131
|
+
const present = fieldNamesOf(row);
|
|
132
|
+
const missing = required.filter(c => !present.has(c));
|
|
133
|
+
if (missing.length > 0)
|
|
134
|
+
throw new CompanyIntegrationCatalogSchemaMismatch(entityName, missing);
|
|
135
|
+
}
|
|
136
|
+
function project(row, columns) {
|
|
137
|
+
const out = {};
|
|
138
|
+
for (const c of columns)
|
|
139
|
+
out[c] = row.Get(c);
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Project loaded object rows for the read path. Validates the column set against the FIRST row
|
|
144
|
+
* only: every row in the array is the same entity, so one is proof, and an empty array proves
|
|
145
|
+
* nothing and needs no check — a connection with no catalog rows is the ordinary pre-discovery
|
|
146
|
+
* state, not a schema problem.
|
|
147
|
+
*/
|
|
148
|
+
export function ProjectCatalogObjects(rows) {
|
|
149
|
+
if (rows.length > 0) {
|
|
150
|
+
AssertCatalogColumns(ENTITY_COMPANY_INTEGRATION_OBJECTS, rows[0], CATALOG_OBJECT_COLUMNS);
|
|
151
|
+
}
|
|
152
|
+
return rows.map(r => project(r, CATALOG_OBJECT_COLUMNS));
|
|
153
|
+
}
|
|
154
|
+
/** Project loaded field rows for the read path. Same validation rule as the object projection. */
|
|
155
|
+
export function ProjectCatalogFields(rows) {
|
|
156
|
+
if (rows.length > 0) {
|
|
157
|
+
AssertCatalogColumns(ENTITY_COMPANY_INTEGRATION_OBJECT_FIELDS, rows[0], CATALOG_FIELD_COLUMNS);
|
|
158
|
+
}
|
|
159
|
+
return rows.map(r => {
|
|
160
|
+
const projected = project(r, CATALOG_FIELD_COLUMNS);
|
|
161
|
+
for (const [alias, source] of FIELD_READ_ALIASES)
|
|
162
|
+
projected[alias] = projected[source];
|
|
163
|
+
return projected;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Present a projection to code typed against the shared catalog entities.
|
|
168
|
+
*
|
|
169
|
+
* The two casts below are the ONLY place this narrowing happens, deliberately, so the reasoning
|
|
170
|
+
* sits in one auditable spot. A projection carries every property the read sites touch and none of
|
|
171
|
+
* `BaseEntity`'s behaviour — no `Save()`, no `Fields`, no dirty tracking. That is safe here and
|
|
172
|
+
* nowhere else, because reads are all this cache serves: the per-connection write path holds real
|
|
173
|
+
* entity rows and never receives one of these.
|
|
174
|
+
*
|
|
175
|
+
* Calling `.Save()` on the result would throw at runtime rather than corrupt anything, which is the
|
|
176
|
+
* failure mode to want if this contract is ever broken.
|
|
177
|
+
*/
|
|
178
|
+
export function AsReadOnlyObject(row) {
|
|
179
|
+
return row;
|
|
180
|
+
}
|
|
181
|
+
/** See {@link AsReadOnlyObject}. */
|
|
182
|
+
export function AsReadOnlyField(row) {
|
|
183
|
+
return row;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=CompanyIntegrationCatalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompanyIntegrationCatalog.js","sourceRoot":"","sources":["../src/CompanyIntegrationCatalog.ts"],"names":[],"mappings":"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"}
|