@memberjunction/geo-core 0.0.1 → 5.26.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/dist/GeoCodeSyncService.d.ts +120 -0
- package/dist/GeoCodeSyncService.d.ts.map +1 -0
- package/dist/GeoCodeSyncService.js +435 -0
- package/dist/GeoCodeSyncService.js.map +1 -0
- package/dist/hash.d.ts +12 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +19 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +30 -7
- package/README.md +0 -45
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
2
|
+
import { BaseEntity, EntityInfo, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { MJRecordGeoCodeEntity } from '@memberjunction/core-entities';
|
|
4
|
+
import { GeoFieldMapping, GeocodeResult } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Singleton service that manages the geocoding lifecycle for entity records.
|
|
7
|
+
* Called by CodeGen-generated AfterSave hooks on geo-enabled entities.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* - Computes source field hashes for change detection
|
|
11
|
+
* - Checks existing RecordGeoCode rows for staleness
|
|
12
|
+
* - Dispatches geocoding (delegates to registered geocoding providers)
|
|
13
|
+
* - Upserts RecordGeoCode rows with results or error status
|
|
14
|
+
*
|
|
15
|
+
* All geocoding is fire-and-forget — errors are captured in RecordGeoCode
|
|
16
|
+
* and retried by the scheduled geocoding job. Never throws.
|
|
17
|
+
*/
|
|
18
|
+
export declare class GeoCodeSyncService extends BaseSingleton<GeoCodeSyncService> {
|
|
19
|
+
constructor();
|
|
20
|
+
static get Instance(): GeoCodeSyncService;
|
|
21
|
+
/**
|
|
22
|
+
* Check if any geo field mappings have changed and dispatch geocoding if needed.
|
|
23
|
+
* Called from the GenericDatabaseProvider OnSaveCompleted hook.
|
|
24
|
+
*
|
|
25
|
+
* This method never throws — all errors are captured in RecordGeoCode rows.
|
|
26
|
+
*
|
|
27
|
+
* @param entity - The entity instance that was just saved
|
|
28
|
+
* @param contextUser - The user context for data operations
|
|
29
|
+
* @param mappings - Field-to-location mappings (if not provided, derived from EntityField.ExtendedType metadata)
|
|
30
|
+
* @returns The geocode result from the first successfully geocoded mapping, or null
|
|
31
|
+
* if no geocoding was performed (e.g., hash unchanged) or all attempts failed.
|
|
32
|
+
* The caller can use this to patch virtual lat/lng fields on the entity's
|
|
33
|
+
* SP result before finalizeSave() loads it into the entity object.
|
|
34
|
+
*/
|
|
35
|
+
SyncIfChanged(entity: BaseEntity, contextUser: UserInfo, mappings?: GeoFieldMapping[]): Promise<GeocodeResult | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Derive geo field mappings from EntityField.ExtendedType metadata.
|
|
38
|
+
* Finds all fields with Geo* ExtendedType values and groups them into
|
|
39
|
+
* a Primary location mapping.
|
|
40
|
+
*
|
|
41
|
+
* @param entityInfo - The entity metadata to inspect
|
|
42
|
+
* @returns Array of geo field mappings (empty if no geo fields found)
|
|
43
|
+
*/
|
|
44
|
+
static BuildMappingsFromMetadata(entityInfo: EntityInfo): GeoFieldMapping[];
|
|
45
|
+
/**
|
|
46
|
+
* Check if an entity has any geo fields defined in its metadata.
|
|
47
|
+
* @param entityInfo - The entity metadata to check
|
|
48
|
+
* @returns true if the entity has at least one field with a Geo* ExtendedType
|
|
49
|
+
*/
|
|
50
|
+
static HasGeoFields(entityInfo: EntityInfo): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Process a single field mapping for a single entity record.
|
|
53
|
+
* @returns The geocode result if geocoding was performed successfully, or null if
|
|
54
|
+
* no geocoding was needed (hash unchanged) or the attempt failed.
|
|
55
|
+
*/
|
|
56
|
+
protected ProcessMapping(entity: BaseEntity, mapping: GeoFieldMapping, contextUser: UserInfo): Promise<GeocodeResult | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Find an existing RecordGeoCode row for a given entity/record/location type.
|
|
59
|
+
*/
|
|
60
|
+
protected FindExistingGeoCode(entityID: string, recordID: string, locationType: string, contextUser: UserInfo): Promise<MJRecordGeoCodeEntity | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Create a new RecordGeoCode row.
|
|
63
|
+
*/
|
|
64
|
+
protected CreateGeoCodeRow(entityID: string, recordID: string, locationType: string, contextUser: UserInfo): Promise<MJRecordGeoCodeEntity | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Update a RecordGeoCode row with successful geocoding results.
|
|
67
|
+
*/
|
|
68
|
+
protected UpdateSuccess(row: MJRecordGeoCodeEntity, result: GeocodeResult, hash: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Mark a RecordGeoCode row as not geocodable — the address can't be resolved
|
|
71
|
+
* to coordinates (e.g., "Conference Room B", "TBD"). Won't be retried by the
|
|
72
|
+
* bulk job. If the source record's address fields change, the hash mismatch
|
|
73
|
+
* in SyncIfChanged will trigger a fresh attempt.
|
|
74
|
+
*/
|
|
75
|
+
protected UpdateNotGeocodable(row: MJRecordGeoCodeEntity, reason: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Update a RecordGeoCode row with failure information (transient error, eligible for retry).
|
|
78
|
+
*/
|
|
79
|
+
protected UpdateFailure(row: MJRecordGeoCodeEntity, errorMessage: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Perform the actual geocoding using a priority-based strategy:
|
|
82
|
+
*
|
|
83
|
+
* 1. Native lat/lng fields → copy directly (GeocodingSource = 'native')
|
|
84
|
+
* 2. Address-level fields → external geocoding API (GeocodingSource = 'google')
|
|
85
|
+
* 3. Country/state only → reference table centroid lookup (GeocodingSource = 'reference_data')
|
|
86
|
+
*/
|
|
87
|
+
protected Geocode(entity: BaseEntity, _mapping: GeoFieldMapping): Promise<GeocodeResult | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Extract geo field values organized by their ExtendedType role.
|
|
90
|
+
*/
|
|
91
|
+
private extractGeoFieldValues;
|
|
92
|
+
/**
|
|
93
|
+
* Build an address string suitable for the Google Geocoding API from geo field values.
|
|
94
|
+
* Orders components logically: Address, City, StateProvince, PostalCode, Country.
|
|
95
|
+
*/
|
|
96
|
+
private buildAddressString;
|
|
97
|
+
/**
|
|
98
|
+
* Call the Google Geocoding API to convert an address string to coordinates.
|
|
99
|
+
*/
|
|
100
|
+
private geocodeViaGoogle;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve CountryID and StateProvinceID from entity geo field values
|
|
103
|
+
* against the reference Country/StateProvince tables.
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* Resolve CountryID and StateProvinceID from entity geo field values
|
|
107
|
+
* using the in-memory GeoDataEngine (O(1) lookups, no DB queries).
|
|
108
|
+
*/
|
|
109
|
+
private resolveReferenceIDs;
|
|
110
|
+
/**
|
|
111
|
+
* Fallback geocoding using reference table centroids from GeoDataEngine.
|
|
112
|
+
* All lookups are O(1) in-memory — no DB queries.
|
|
113
|
+
*/
|
|
114
|
+
private geocodeViaReferenceData;
|
|
115
|
+
/**
|
|
116
|
+
* Get the Google Geocoding API key from config or environment.
|
|
117
|
+
*/
|
|
118
|
+
private getGoogleApiKey;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=GeoCodeSyncService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoCodeSyncService.d.ts","sourceRoot":"","sources":["../src/GeoCodeSyncService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAmB,UAAU,EAAqB,QAAQ,EAAuB,MAAM,sBAAsB,CAAC;AACjI,OAAO,EAAE,qBAAqB,EAAiB,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAmC,MAAM,SAAS,CAAC;AAuB1F;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,SAAQ,aAAa,CAAC,kBAAkB,CAAC;;IAKrE,WAAkB,QAAQ,IAAI,kBAAkB,CAE/C;IAED;;;;;;;;;;;;;OAaG;IACU,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAclI;;;;;;;OAOG;WACW,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,eAAe,EAAE;IAgBlF;;;;OAIG;WACW,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO;IAI3D;;;;OAIG;cACa,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAkElI;;OAEG;cACa,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAcxC;;OAEG;cACa,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAiBxC;;OAEG;cACa,aAAa,CACzB,GAAG,EAAE,qBAAqB,EAC1B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;;;OAKG;cACa,mBAAmB,CAAC,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9F;;OAEG;cACa,aAAa,CAAC,GAAG,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9F;;;;;;OAMG;cACa,OAAO,CACnB,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,eAAe,GAC1B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA4ChC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAa7B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;YACW,gBAAgB;IAwC9B;;;OAGG;IACH;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IA6C/B;;OAEG;IACH,OAAO,CAAC,eAAe;CAc1B"}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
2
|
+
import { Metadata, RunView, LogError, LogStatus } from '@memberjunction/core';
|
|
3
|
+
import { GeoDataEngine } from '@memberjunction/core-entities';
|
|
4
|
+
import { ComputeGeoSourceHash } from './hash.js';
|
|
5
|
+
/**
|
|
6
|
+
* Singleton service that manages the geocoding lifecycle for entity records.
|
|
7
|
+
* Called by CodeGen-generated AfterSave hooks on geo-enabled entities.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* - Computes source field hashes for change detection
|
|
11
|
+
* - Checks existing RecordGeoCode rows for staleness
|
|
12
|
+
* - Dispatches geocoding (delegates to registered geocoding providers)
|
|
13
|
+
* - Upserts RecordGeoCode rows with results or error status
|
|
14
|
+
*
|
|
15
|
+
* All geocoding is fire-and-forget — errors are captured in RecordGeoCode
|
|
16
|
+
* and retried by the scheduled geocoding job. Never throws.
|
|
17
|
+
*/
|
|
18
|
+
export class GeoCodeSyncService extends BaseSingleton {
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
}
|
|
22
|
+
static get Instance() {
|
|
23
|
+
return GeoCodeSyncService.getInstance();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if any geo field mappings have changed and dispatch geocoding if needed.
|
|
27
|
+
* Called from the GenericDatabaseProvider OnSaveCompleted hook.
|
|
28
|
+
*
|
|
29
|
+
* This method never throws — all errors are captured in RecordGeoCode rows.
|
|
30
|
+
*
|
|
31
|
+
* @param entity - The entity instance that was just saved
|
|
32
|
+
* @param contextUser - The user context for data operations
|
|
33
|
+
* @param mappings - Field-to-location mappings (if not provided, derived from EntityField.ExtendedType metadata)
|
|
34
|
+
* @returns The geocode result from the first successfully geocoded mapping, or null
|
|
35
|
+
* if no geocoding was performed (e.g., hash unchanged) or all attempts failed.
|
|
36
|
+
* The caller can use this to patch virtual lat/lng fields on the entity's
|
|
37
|
+
* SP result before finalizeSave() loads it into the entity object.
|
|
38
|
+
*/
|
|
39
|
+
async SyncIfChanged(entity, contextUser, mappings) {
|
|
40
|
+
const resolvedMappings = mappings ?? GeoCodeSyncService.BuildMappingsFromMetadata(entity.EntityInfo);
|
|
41
|
+
for (const mapping of resolvedMappings) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await this.ProcessMapping(entity, mapping, contextUser);
|
|
44
|
+
if (result)
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
49
|
+
LogError(`GeoCodeSyncService: Error processing ${mapping.LocationType} for ${entity.EntityInfo.Name} ${entity.PrimaryKey.ToString()}: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Derive geo field mappings from EntityField.ExtendedType metadata.
|
|
56
|
+
* Finds all fields with Geo* ExtendedType values and groups them into
|
|
57
|
+
* a Primary location mapping.
|
|
58
|
+
*
|
|
59
|
+
* @param entityInfo - The entity metadata to inspect
|
|
60
|
+
* @returns Array of geo field mappings (empty if no geo fields found)
|
|
61
|
+
*/
|
|
62
|
+
static BuildMappingsFromMetadata(entityInfo) {
|
|
63
|
+
const geoExtTypes = new Set([
|
|
64
|
+
'Geo', 'GeoAddress', 'GeoCity', 'GeoStateProvince',
|
|
65
|
+
'GeoCountry', 'GeoPostalCode', 'GeoLatitude', 'GeoLongitude'
|
|
66
|
+
]);
|
|
67
|
+
const geoFields = entityInfo.Fields.filter((f) => f.ExtendedType != null && geoExtTypes.has(f.ExtendedType));
|
|
68
|
+
if (geoFields.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
return [{
|
|
71
|
+
LocationType: 'Primary',
|
|
72
|
+
Fields: geoFields.map((f) => f.Name)
|
|
73
|
+
}];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if an entity has any geo fields defined in its metadata.
|
|
77
|
+
* @param entityInfo - The entity metadata to check
|
|
78
|
+
* @returns true if the entity has at least one field with a Geo* ExtendedType
|
|
79
|
+
*/
|
|
80
|
+
static HasGeoFields(entityInfo) {
|
|
81
|
+
return entityInfo.SupportsGeoCoding && GeoCodeSyncService.BuildMappingsFromMetadata(entityInfo).length > 0;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Process a single field mapping for a single entity record.
|
|
85
|
+
* @returns The geocode result if geocoding was performed successfully, or null if
|
|
86
|
+
* no geocoding was needed (hash unchanged) or the attempt failed.
|
|
87
|
+
*/
|
|
88
|
+
async ProcessMapping(entity, mapping, contextUser) {
|
|
89
|
+
const hash = ComputeGeoSourceHash(entity, mapping.Fields);
|
|
90
|
+
// Build RecordID matching the format used in the view's LEFT JOIN to vwRecordGeoCodes:
|
|
91
|
+
// - Single PK: bare value as string (e.g., "38CB433E-F36B-1410-84B4-00BD01F02867")
|
|
92
|
+
// - Composite PK: values joined with "||" (e.g., "val1||val2")
|
|
93
|
+
// This must match sql_codegen.ts generateBaseViewJoins() geo JOIN format.
|
|
94
|
+
const pkPairs = entity.PrimaryKey.KeyValuePairs;
|
|
95
|
+
const recordId = pkPairs.length === 1
|
|
96
|
+
? String(pkPairs[0].Value)
|
|
97
|
+
: pkPairs.map(pk => String(pk.Value)).join('||');
|
|
98
|
+
const existing = await this.FindExistingGeoCode(entity.EntityInfo.ID, recordId, mapping.LocationType, contextUser);
|
|
99
|
+
if (existing && existing.SourceFieldHash === hash && existing.Status === 'success') {
|
|
100
|
+
return null; // No change, already geocoded successfully
|
|
101
|
+
}
|
|
102
|
+
// Upsert a pending row
|
|
103
|
+
const row = existing ?? await this.CreateGeoCodeRow(entity.EntityInfo.ID, recordId, mapping.LocationType, contextUser);
|
|
104
|
+
if (!row) {
|
|
105
|
+
LogError(`GeoCodeSyncService: Failed to create/find RecordGeoCode row`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
row.SourceFieldHash = hash;
|
|
109
|
+
row.Status = 'pending';
|
|
110
|
+
row.RetryCount = 0; // Reset retries — fresh attempt for new/changed address
|
|
111
|
+
row.GeocodedAt = new Date();
|
|
112
|
+
const pendingSaved = await row.Save();
|
|
113
|
+
if (!pendingSaved) {
|
|
114
|
+
LogError(`GeoCodeSyncService: Failed to save pending RecordGeoCode: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
// Attempt geocoding
|
|
118
|
+
try {
|
|
119
|
+
const result = await this.Geocode(entity, mapping);
|
|
120
|
+
if (result) {
|
|
121
|
+
await this.UpdateSuccess(row, result, hash);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// No result means the address can't be geocoded (e.g., "Conference Room B").
|
|
126
|
+
// Mark as not_geocodable so the retry job skips it. If the user later edits
|
|
127
|
+
// the address, the hash will change and SyncIfChanged will re-attempt.
|
|
128
|
+
await this.UpdateNotGeocodable(row, 'Geocoding returned no result — address may not be a valid location');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
// Exception = transient API error — mark as failed for retry
|
|
133
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
134
|
+
await this.UpdateFailure(row, message);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Find an existing RecordGeoCode row for a given entity/record/location type.
|
|
140
|
+
*/
|
|
141
|
+
async FindExistingGeoCode(entityID, recordID, locationType, contextUser) {
|
|
142
|
+
const rv = new RunView();
|
|
143
|
+
const result = await rv.RunView({
|
|
144
|
+
EntityName: 'MJ: Record Geo Codes',
|
|
145
|
+
ExtraFilter: `EntityID='${entityID}' AND RecordID='${recordID}' AND LocationType='${locationType}'`,
|
|
146
|
+
ResultType: 'entity_object',
|
|
147
|
+
MaxRows: 1
|
|
148
|
+
}, contextUser);
|
|
149
|
+
if (result.Success && result.Results.length > 0) {
|
|
150
|
+
return result.Results[0];
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a new RecordGeoCode row.
|
|
156
|
+
*/
|
|
157
|
+
async CreateGeoCodeRow(entityID, recordID, locationType, contextUser) {
|
|
158
|
+
const md = new Metadata();
|
|
159
|
+
const row = await md.GetEntityObject('MJ: Record Geo Codes', contextUser);
|
|
160
|
+
row.NewRecord();
|
|
161
|
+
row.EntityID = entityID;
|
|
162
|
+
row.RecordID = recordID;
|
|
163
|
+
row.LocationType = locationType;
|
|
164
|
+
row.Status = 'pending';
|
|
165
|
+
row.RetryCount = 0;
|
|
166
|
+
const saved = await row.Save();
|
|
167
|
+
if (!saved) {
|
|
168
|
+
LogError(`GeoCodeSyncService: Failed to create RecordGeoCode row: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return row;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update a RecordGeoCode row with successful geocoding results.
|
|
175
|
+
*/
|
|
176
|
+
async UpdateSuccess(row, result, hash) {
|
|
177
|
+
row.Latitude = result.Latitude;
|
|
178
|
+
row.Longitude = result.Longitude;
|
|
179
|
+
row.Precision = result.Precision;
|
|
180
|
+
row.CountryID = result.CountryID ?? null;
|
|
181
|
+
row.StateProvinceID = result.StateProvinceID ?? null;
|
|
182
|
+
row.Status = 'success';
|
|
183
|
+
row.GeocodingSource = result.Source;
|
|
184
|
+
row.SourceFieldHash = hash;
|
|
185
|
+
row.GeocodedAt = new Date();
|
|
186
|
+
row.ErrorMessage = '';
|
|
187
|
+
const saved = await row.Save();
|
|
188
|
+
if (!saved) {
|
|
189
|
+
LogError(`GeoCodeSyncService: Failed to save successful geocode: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Mark a RecordGeoCode row as not geocodable — the address can't be resolved
|
|
194
|
+
* to coordinates (e.g., "Conference Room B", "TBD"). Won't be retried by the
|
|
195
|
+
* bulk job. If the source record's address fields change, the hash mismatch
|
|
196
|
+
* in SyncIfChanged will trigger a fresh attempt.
|
|
197
|
+
*/
|
|
198
|
+
async UpdateNotGeocodable(row, reason) {
|
|
199
|
+
row.Status = 'failed';
|
|
200
|
+
row.ErrorMessage = reason;
|
|
201
|
+
row.RetryCount = 9999; // Permanently skip retries — hash change will reset this
|
|
202
|
+
row.GeocodedAt = new Date();
|
|
203
|
+
const saved = await row.Save();
|
|
204
|
+
if (!saved) {
|
|
205
|
+
LogError(`GeoCodeSyncService: Failed to save not_geocodable status: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Update a RecordGeoCode row with failure information (transient error, eligible for retry).
|
|
210
|
+
*/
|
|
211
|
+
async UpdateFailure(row, errorMessage) {
|
|
212
|
+
row.Status = 'failed';
|
|
213
|
+
row.ErrorMessage = errorMessage;
|
|
214
|
+
row.RetryCount = (row.RetryCount ?? 0) + 1;
|
|
215
|
+
row.GeocodedAt = new Date();
|
|
216
|
+
const saved = await row.Save();
|
|
217
|
+
if (!saved) {
|
|
218
|
+
LogError(`GeoCodeSyncService: Failed to save failure status: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Perform the actual geocoding using a priority-based strategy:
|
|
223
|
+
*
|
|
224
|
+
* 1. Native lat/lng fields → copy directly (GeocodingSource = 'native')
|
|
225
|
+
* 2. Address-level fields → external geocoding API (GeocodingSource = 'google')
|
|
226
|
+
* 3. Country/state only → reference table centroid lookup (GeocodingSource = 'reference_data')
|
|
227
|
+
*/
|
|
228
|
+
async Geocode(entity, _mapping) {
|
|
229
|
+
// Strategy 1: Check for native (non-virtual) lat/lng fields.
|
|
230
|
+
// Virtual fields like __mj_Latitude/__mj_Longitude come from the RecordGeoCode JOIN
|
|
231
|
+
// and would create circular logic — reading old geocoded values instead of re-geocoding.
|
|
232
|
+
const latField = entity.EntityInfo.Fields.find(f => f.ExtendedType === 'GeoLatitude' && !f.IsVirtual);
|
|
233
|
+
const lngField = entity.EntityInfo.Fields.find(f => f.ExtendedType === 'GeoLongitude' && !f.IsVirtual);
|
|
234
|
+
if (latField && lngField) {
|
|
235
|
+
const latVal = entity.Get(latField.Name);
|
|
236
|
+
const lngVal = entity.Get(lngField.Name);
|
|
237
|
+
const lat = Number(latVal);
|
|
238
|
+
const lng = Number(lngVal);
|
|
239
|
+
if (latVal != null && lngVal != null && !isNaN(lat) && !isNaN(lng)) {
|
|
240
|
+
return {
|
|
241
|
+
Latitude: lat,
|
|
242
|
+
Longitude: lng,
|
|
243
|
+
Precision: 'exact',
|
|
244
|
+
CountryID: null,
|
|
245
|
+
StateProvinceID: null,
|
|
246
|
+
Source: 'native'
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Collect address field values organized by their geo role
|
|
251
|
+
const geoValues = this.extractGeoFieldValues(entity);
|
|
252
|
+
// Strategy 2: Google Geocoding API (if API key is available)
|
|
253
|
+
const apiKey = this.getGoogleApiKey();
|
|
254
|
+
if (apiKey) {
|
|
255
|
+
const addressString = this.buildAddressString(geoValues);
|
|
256
|
+
if (addressString) {
|
|
257
|
+
const googleResult = await this.geocodeViaGoogle(addressString, apiKey);
|
|
258
|
+
if (googleResult) {
|
|
259
|
+
// Resolve country/state IDs from Google's response
|
|
260
|
+
this.resolveReferenceIDs(googleResult, entity);
|
|
261
|
+
return googleResult;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Strategy 3: Reference data centroid lookup (country/state → approximate lat/lng)
|
|
266
|
+
return this.geocodeViaReferenceData(geoValues);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Extract geo field values organized by their ExtendedType role.
|
|
270
|
+
*/
|
|
271
|
+
extractGeoFieldValues(entity) {
|
|
272
|
+
const values = {};
|
|
273
|
+
for (const field of entity.EntityInfo.Fields) {
|
|
274
|
+
if (field.ExtendedType && field.ExtendedType.startsWith('Geo')) {
|
|
275
|
+
const val = entity.Get(field.Name);
|
|
276
|
+
if (val != null && String(val).trim() !== '') {
|
|
277
|
+
values[field.ExtendedType] = String(val).trim();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return values;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Build an address string suitable for the Google Geocoding API from geo field values.
|
|
285
|
+
* Orders components logically: Address, City, StateProvince, PostalCode, Country.
|
|
286
|
+
*/
|
|
287
|
+
buildAddressString(geoValues) {
|
|
288
|
+
const parts = [];
|
|
289
|
+
if (geoValues['GeoAddress'])
|
|
290
|
+
parts.push(geoValues['GeoAddress']);
|
|
291
|
+
if (geoValues['GeoCity'])
|
|
292
|
+
parts.push(geoValues['GeoCity']);
|
|
293
|
+
if (geoValues['GeoStateProvince'])
|
|
294
|
+
parts.push(geoValues['GeoStateProvince']);
|
|
295
|
+
if (geoValues['GeoPostalCode'])
|
|
296
|
+
parts.push(geoValues['GeoPostalCode']);
|
|
297
|
+
if (geoValues['GeoCountry'])
|
|
298
|
+
parts.push(geoValues['GeoCountry']);
|
|
299
|
+
if (geoValues['Geo'])
|
|
300
|
+
parts.push(geoValues['Geo']); // generic location field
|
|
301
|
+
return parts.length > 0 ? parts.join(', ') : null;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Call the Google Geocoding API to convert an address string to coordinates.
|
|
305
|
+
*/
|
|
306
|
+
async geocodeViaGoogle(address, apiKey) {
|
|
307
|
+
try {
|
|
308
|
+
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${apiKey}`;
|
|
309
|
+
const response = await fetch(url);
|
|
310
|
+
if (!response.ok) {
|
|
311
|
+
LogError(`GeoCodeSyncService: Google API HTTP error: ${response.status}`);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
const data = await response.json();
|
|
315
|
+
if (data.status !== 'OK' || !data.results?.length) {
|
|
316
|
+
LogStatus(`GeoCodeSyncService: Google returned status "${data.status}" for "${address}"`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const result = data.results[0];
|
|
320
|
+
const locationType = result.geometry.location_type;
|
|
321
|
+
// Map Google's location_type to our precision enum
|
|
322
|
+
let precision = 'city';
|
|
323
|
+
if (locationType === 'ROOFTOP')
|
|
324
|
+
precision = 'exact';
|
|
325
|
+
else if (locationType === 'RANGE_INTERPOLATED')
|
|
326
|
+
precision = 'exact';
|
|
327
|
+
else if (locationType === 'GEOMETRIC_CENTER')
|
|
328
|
+
precision = 'city';
|
|
329
|
+
else if (locationType === 'APPROXIMATE')
|
|
330
|
+
precision = 'state_province';
|
|
331
|
+
return {
|
|
332
|
+
Latitude: result.geometry.location.lat,
|
|
333
|
+
Longitude: result.geometry.location.lng,
|
|
334
|
+
Precision: precision,
|
|
335
|
+
CountryID: null, // resolved in resolveReferenceIDs
|
|
336
|
+
StateProvinceID: null, // resolved in resolveReferenceIDs
|
|
337
|
+
Source: 'google'
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
342
|
+
LogError(`GeoCodeSyncService: Google geocoding error: ${msg}`);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Resolve CountryID and StateProvinceID from entity geo field values
|
|
348
|
+
* against the reference Country/StateProvince tables.
|
|
349
|
+
*/
|
|
350
|
+
/**
|
|
351
|
+
* Resolve CountryID and StateProvinceID from entity geo field values
|
|
352
|
+
* using the in-memory GeoDataEngine (O(1) lookups, no DB queries).
|
|
353
|
+
*/
|
|
354
|
+
resolveReferenceIDs(result, entity) {
|
|
355
|
+
const geoValues = this.extractGeoFieldValues(entity);
|
|
356
|
+
const geo = GeoDataEngine.Instance;
|
|
357
|
+
const countryVal = geoValues['GeoCountry'];
|
|
358
|
+
if (countryVal) {
|
|
359
|
+
const country = geo.ResolveCountry(countryVal);
|
|
360
|
+
if (country) {
|
|
361
|
+
result.CountryID = country.ID;
|
|
362
|
+
const stateVal = geoValues['GeoStateProvince'];
|
|
363
|
+
if (stateVal) {
|
|
364
|
+
const state = geo.ResolveState(country.ID, stateVal);
|
|
365
|
+
if (state) {
|
|
366
|
+
result.StateProvinceID = state.ID;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Fallback geocoding using reference table centroids from GeoDataEngine.
|
|
374
|
+
* All lookups are O(1) in-memory — no DB queries.
|
|
375
|
+
*/
|
|
376
|
+
geocodeViaReferenceData(geoValues) {
|
|
377
|
+
const geo = GeoDataEngine.Instance;
|
|
378
|
+
const countryVal = geoValues['GeoCountry'];
|
|
379
|
+
const stateVal = geoValues['GeoStateProvince'];
|
|
380
|
+
if (!countryVal && !stateVal) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
// Try state-level first (more precise)
|
|
384
|
+
if (countryVal && stateVal) {
|
|
385
|
+
const country = geo.ResolveCountry(countryVal);
|
|
386
|
+
if (country) {
|
|
387
|
+
const state = geo.ResolveState(country.ID, stateVal);
|
|
388
|
+
if (state && state.Latitude != null && state.Longitude != null) {
|
|
389
|
+
return {
|
|
390
|
+
Latitude: state.Latitude,
|
|
391
|
+
Longitude: state.Longitude,
|
|
392
|
+
Precision: 'state_province',
|
|
393
|
+
CountryID: country.ID,
|
|
394
|
+
StateProvinceID: state.ID,
|
|
395
|
+
Source: 'reference_data'
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Fall back to country-level centroid
|
|
401
|
+
if (countryVal) {
|
|
402
|
+
const country = geo.ResolveCountry(countryVal);
|
|
403
|
+
if (country && country.Latitude != null && country.Longitude != null) {
|
|
404
|
+
return {
|
|
405
|
+
Latitude: country.Latitude,
|
|
406
|
+
Longitude: country.Longitude,
|
|
407
|
+
Precision: 'country',
|
|
408
|
+
CountryID: country.ID,
|
|
409
|
+
StateProvinceID: null,
|
|
410
|
+
Source: 'reference_data'
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get the Google Geocoding API key from config or environment.
|
|
418
|
+
*/
|
|
419
|
+
getGoogleApiKey() {
|
|
420
|
+
// Check environment variables
|
|
421
|
+
const envKey = process.env.GOOGLE_GEOCODING_API_KEY || process.env.GOOGLE_MAPS_API_KEY;
|
|
422
|
+
if (envKey)
|
|
423
|
+
return envKey;
|
|
424
|
+
// Try mj.config.cjs via cosmiconfig pattern
|
|
425
|
+
try {
|
|
426
|
+
// Dynamic import to avoid hard dependency on cosmiconfig
|
|
427
|
+
const configVal = globalThis['__mj_config_apiIntegrations'];
|
|
428
|
+
return configVal?.google?.geocoding?.apiKey ?? null;
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=GeoCodeSyncService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoCodeSyncService.js","sourceRoot":"","sources":["../src/GeoCodeSyncService.ts"],"names":[],"mappings":"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"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
2
|
+
/**
|
|
3
|
+
* Computes a SHA-256 hash of the geo-relevant field values for change detection.
|
|
4
|
+
* When source fields change on entity save, the hash won't match the stored
|
|
5
|
+
* SourceFieldHash in RecordGeoCode, triggering re-geocoding.
|
|
6
|
+
*
|
|
7
|
+
* @param entity - The entity instance to extract field values from
|
|
8
|
+
* @param fieldNames - The field names that contribute to geocoding
|
|
9
|
+
* @returns SHA-256 hex digest of the concatenated field values
|
|
10
|
+
*/
|
|
11
|
+
export declare function ComputeGeoSourceHash(entity: BaseEntity, fieldNames: string[]): string;
|
|
12
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAOrF"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Computes a SHA-256 hash of the geo-relevant field values for change detection.
|
|
4
|
+
* When source fields change on entity save, the hash won't match the stored
|
|
5
|
+
* SourceFieldHash in RecordGeoCode, triggering re-geocoding.
|
|
6
|
+
*
|
|
7
|
+
* @param entity - The entity instance to extract field values from
|
|
8
|
+
* @param fieldNames - The field names that contribute to geocoding
|
|
9
|
+
* @returns SHA-256 hex digest of the concatenated field values
|
|
10
|
+
*/
|
|
11
|
+
export function ComputeGeoSourceHash(entity, fieldNames) {
|
|
12
|
+
const values = fieldNames.map(f => {
|
|
13
|
+
const val = entity.Get(f);
|
|
14
|
+
return val != null ? String(val).trim() : '';
|
|
15
|
+
});
|
|
16
|
+
const input = values.join('|');
|
|
17
|
+
return createHash('sha256').update(input, 'utf8').digest('hex');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkB,EAAE,UAAoB;IACzE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QAC9B,MAAM,GAAG,GAAY,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,sBAAsB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping from entity fields to a geocodable location.
|
|
3
|
+
* CodeGen generates these per-entity based on LLM analysis of field names.
|
|
4
|
+
*/
|
|
5
|
+
export interface GeoFieldMapping {
|
|
6
|
+
/** Discriminator for the location. Currently only 'Primary' is generated by CodeGen. */
|
|
7
|
+
LocationType: 'Primary';
|
|
8
|
+
/** Entity field names that contribute to this location's address. */
|
|
9
|
+
Fields: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Precision level of a geocoding result.
|
|
13
|
+
*/
|
|
14
|
+
export type GeocodePrecision = 'exact' | 'postal_code' | 'city' | 'county' | 'state_province' | 'country';
|
|
15
|
+
/**
|
|
16
|
+
* Status of a geocoding attempt.
|
|
17
|
+
*/
|
|
18
|
+
export type GeocodeStatus = 'success' | 'failed' | 'pending';
|
|
19
|
+
/**
|
|
20
|
+
* Source that produced the geocode.
|
|
21
|
+
*/
|
|
22
|
+
export type GeocodingSource = 'google' | 'reference_data' | 'manual' | 'ip_geolocation' | 'native' | 'reverse';
|
|
23
|
+
/**
|
|
24
|
+
* Result from a geocoding operation.
|
|
25
|
+
*/
|
|
26
|
+
export interface GeocodeResult {
|
|
27
|
+
Latitude: number;
|
|
28
|
+
Longitude: number;
|
|
29
|
+
Precision: GeocodePrecision;
|
|
30
|
+
CountryID: string | null;
|
|
31
|
+
StateProvinceID: string | null;
|
|
32
|
+
Source: GeocodingSource;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Distance unit for proximity queries.
|
|
36
|
+
*/
|
|
37
|
+
export type DistanceUnit = 'mi' | 'km';
|
|
38
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,wFAAwF;IACxF,YAAY,EAAE,SAAS,CAAC;IACxB,qEAAqE;IACrE,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAE1G;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/G;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,eAAe,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/geo-core",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "5.26.0",
|
|
5
|
+
"description": "MemberJunction: Core geo-spatial types, interfaces, and GeoCodeSyncService for geocoding lifecycle management",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && tsc-alias -f",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage"
|
|
16
|
+
},
|
|
17
|
+
"author": "MemberJunction.com",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@memberjunction/global": "5.26.0",
|
|
21
|
+
"@memberjunction/core": "5.26.0",
|
|
22
|
+
"@memberjunction/core-entities": "5.26.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.10.11",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.0.18"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
32
|
+
}
|
|
10
33
|
}
|
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @memberjunction/geo-core
|
|
2
|
-
|
|
3
|
-
## ⚠️ IMPORTANT NOTICE ⚠️
|
|
4
|
-
|
|
5
|
-
**This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
|
|
6
|
-
|
|
7
|
-
This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
This package exists to:
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@memberjunction/geo-core`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
15
|
-
|
|
16
|
-
## What is OIDC Trusted Publishing?
|
|
17
|
-
|
|
18
|
-
OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
|
|
19
|
-
|
|
20
|
-
## Setup Instructions
|
|
21
|
-
|
|
22
|
-
To properly configure OIDC trusted publishing for this package:
|
|
23
|
-
|
|
24
|
-
1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
28
|
-
|
|
29
|
-
## DO NOT USE THIS PACKAGE
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|