@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.
@@ -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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"}