@friggframework/core 2.0.0--canary.590.094a71b.0 → 2.0.0--canary.590.ffb7d1b.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/integrations/EXTENSIONS.md +45 -8
- package/integrations/integration-base.js +114 -31
- package/modules/repositories/module-repository-documentdb.js +15 -0
- package/modules/repositories/module-repository-interface.js +16 -0
- package/modules/repositories/module-repository-mongo.js +28 -0
- package/modules/repositories/module-repository-postgres.js +28 -0
- package/modules/repositories/module-repository.js +24 -0
- package/package.json +5 -5
|
@@ -104,10 +104,15 @@ module.exports = {
|
|
|
104
104
|
// verify signature, look up integration by portalId, queue
|
|
105
105
|
await verifyHubSpotSignature(req);
|
|
106
106
|
for (const evt of req.body) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
107
|
+
// Core helper is generic; the extension wraps it in
|
|
108
|
+
// HubSpot vocabulary for its own readability:
|
|
109
|
+
// findIntegrationByPortalId(portalId) =
|
|
110
|
+
// findIntegrationByEntityExternalId(portalId, 'hubspot')
|
|
111
|
+
const integrationId =
|
|
112
|
+
await this.findIntegrationByEntityExternalId(
|
|
113
|
+
evt.portalId,
|
|
114
|
+
'hubspot'
|
|
115
|
+
);
|
|
111
116
|
if (!integrationId) continue;
|
|
112
117
|
await this.queueWebhook({
|
|
113
118
|
integrationId,
|
|
@@ -155,15 +160,47 @@ At `initialize()`, the framework validates each binding:
|
|
|
155
160
|
|
|
156
161
|
Validation failures throw at boot with a message identifying the integration, binding, and field.
|
|
157
162
|
|
|
158
|
-
## Reverse-lookup
|
|
163
|
+
## Reverse-lookup helpers
|
|
159
164
|
|
|
160
|
-
For app-level webhooks (HubSpot, Slack, etc.) where one URL serves many accounts, extension default handlers
|
|
165
|
+
For app-level webhooks (HubSpot, Slack, Asana, Microsoft Teams, etc.) where one URL serves many accounts, extension default handlers need to resolve the inbound external ID (HubSpot `portalId`, Slack `team_id`, Asana `workspace_id`, Teams `tenant_id`) to a Frigg integration record. Two inherited helpers:
|
|
161
166
|
|
|
162
167
|
```javascript
|
|
163
|
-
|
|
168
|
+
// Throws on ambiguous resolution. Use when one externalId is expected
|
|
169
|
+
// to map to exactly one integration.
|
|
170
|
+
const integrationId = await this.findIntegrationByEntityExternalId(
|
|
171
|
+
externalId,
|
|
172
|
+
'hubspot' // optional moduleName
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Returns array. Use when one externalId may legitimately fan out to
|
|
176
|
+
// multiple integrations (e.g. one upstream account broadcasting to
|
|
177
|
+
// several Frigg integration records).
|
|
178
|
+
const integrationIds = await this.listIntegrationsByEntityExternalId(
|
|
179
|
+
externalId,
|
|
180
|
+
'hubspot'
|
|
181
|
+
);
|
|
164
182
|
```
|
|
165
183
|
|
|
166
|
-
|
|
184
|
+
`findIntegrationByEntityExternalId` throws if:
|
|
185
|
+
- the (externalId, moduleName) tuple matches more than one Entity row, OR
|
|
186
|
+
- the matched entity is owned by more than one Integration record
|
|
187
|
+
|
|
188
|
+
A silent first-match at either layer is a cross-tenant routing risk; the helper refuses to pick. The second argument (moduleName) disambiguates when multiple modules in the same app could carry colliding external IDs — pass it whenever an api-module knows its own moduleName.
|
|
189
|
+
|
|
190
|
+
### Where platform-named wrappers belong
|
|
191
|
+
|
|
192
|
+
The core helpers are intentionally platform-neutral. Platform-vocabulary wrappers (`findIntegrationByPortalId`, `findIntegrationByTeamId`, `findIntegrationByWorkspaceId`, etc.) belong **inside the api-module's own extension**, not in core:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
// inside @friggframework/api-module-hubspot/extensions/webhooks
|
|
196
|
+
async function findIntegrationByPortalId(portalId) {
|
|
197
|
+
// thin wrapper — reads as self-documenting HubSpot code,
|
|
198
|
+
// delegates to the platform-neutral core primitive
|
|
199
|
+
return this.findIntegrationByEntityExternalId(portalId, 'hubspot');
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This keeps core platform-neutral and reusable while keeping the api-module code self-documenting for the platform's developers. The same rule applies to any helper that can be named in a single platform's vocabulary.
|
|
167
204
|
|
|
168
205
|
## See also
|
|
169
206
|
|
|
@@ -434,6 +434,19 @@ class IntegrationBase {
|
|
|
434
434
|
// Default: no-op, integrations override this
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Queue a webhook for asynchronous worker dispatch.
|
|
439
|
+
*
|
|
440
|
+
* The dispatch event defaults to `ON_WEBHOOK` for backward compatibility
|
|
441
|
+
* with the `Definition.webhooks: true` path. Extensions (and any caller
|
|
442
|
+
* that needs the worker to invoke a specific bound handler) can override
|
|
443
|
+
* by passing `event` in the payload — it's stripped from the payload and
|
|
444
|
+
* used as the SQS message's dispatch event.
|
|
445
|
+
*
|
|
446
|
+
* @param {Object} data - Webhook payload. May include `event` to override
|
|
447
|
+
* the default `ON_WEBHOOK` dispatch event. All other fields are passed
|
|
448
|
+
* through to the worker as the `data` field of the SQS message.
|
|
449
|
+
*/
|
|
437
450
|
async queueWebhook(data) {
|
|
438
451
|
const { QueuerUtil } = require('../queues');
|
|
439
452
|
|
|
@@ -446,10 +459,12 @@ class IntegrationBase {
|
|
|
446
459
|
throw new Error(`Queue URL not found for ${queueName}`);
|
|
447
460
|
}
|
|
448
461
|
|
|
462
|
+
const { event: dispatchEvent, ...payload } = data || {};
|
|
463
|
+
|
|
449
464
|
return QueuerUtil.send(
|
|
450
465
|
{
|
|
451
|
-
event: 'ON_WEBHOOK',
|
|
452
|
-
data,
|
|
466
|
+
event: dispatchEvent || 'ON_WEBHOOK',
|
|
467
|
+
data: payload,
|
|
453
468
|
},
|
|
454
469
|
queueUrl
|
|
455
470
|
);
|
|
@@ -600,66 +615,134 @@ class IntegrationBase {
|
|
|
600
615
|
}
|
|
601
616
|
|
|
602
617
|
/**
|
|
603
|
-
* Reverse-lookup helper: find
|
|
618
|
+
* Reverse-lookup helper: find a single integration ID by the externalId of
|
|
619
|
+
* one of its module entities.
|
|
620
|
+
*
|
|
621
|
+
* Used by extension default handlers (HubSpot's `portalId`, Slack's `team_id`,
|
|
622
|
+
* Asana's `workspace_id`, etc.) to resolve an inbound app-level event to the
|
|
623
|
+
* specific per-account integration record that should handle it.
|
|
604
624
|
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* entity carrying that external ID.
|
|
625
|
+
* The helper is intentionally platform-neutral. Platform-vocabulary wrappers
|
|
626
|
+
* (e.g. `findIntegrationByPortalId(portalId)`) belong in the api-module's
|
|
627
|
+
* own extension, where they read as self-documenting for that platform's
|
|
628
|
+
* developers and where naming collisions across providers are impossible.
|
|
610
629
|
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
* legitimately need to fan out to multiple integrations should use the
|
|
615
|
-
* lower-level repository call directly.
|
|
630
|
+
* **Refuses to pick** on ambiguous resolution at either layer:
|
|
631
|
+
* - more than one matching Entity row for the (externalId, moduleName) tuple
|
|
632
|
+
* - more than one Integration owning the matched entity
|
|
616
633
|
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
* @
|
|
620
|
-
*
|
|
634
|
+
* A silent first-match here is a cross-tenant routing risk. Callers that
|
|
635
|
+
* legitimately expect a one-to-many relationship must use
|
|
636
|
+
* {@link listIntegrationsByEntityExternalId} instead.
|
|
637
|
+
*
|
|
638
|
+
* @param {string} externalId - Provider's stable identifier for the account/portal/workspace.
|
|
639
|
+
* @param {string} [moduleName] - Disambiguates when multiple modules in the same app could carry colliding externalIds.
|
|
640
|
+
* @returns {Promise<string|null>} The integration ID, or null if no entity or no owning integration matches.
|
|
641
|
+
* @throws {Error} If the (externalId, moduleName) tuple matches multiple entities, or if the matched entity is owned by multiple integrations.
|
|
621
642
|
*/
|
|
622
|
-
async
|
|
643
|
+
async findIntegrationByEntityExternalId(externalId, moduleName) {
|
|
623
644
|
if (!externalId) return null;
|
|
624
|
-
const {
|
|
625
|
-
createModuleRepository,
|
|
626
|
-
} = require('../modules/repositories/module-repository-factory');
|
|
627
|
-
const moduleRepository = createModuleRepository();
|
|
628
645
|
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (
|
|
646
|
+
const entities = await this._findEntitiesByExternalId(
|
|
647
|
+
externalId,
|
|
648
|
+
moduleName
|
|
649
|
+
);
|
|
650
|
+
if (entities.length === 0) {
|
|
634
651
|
console.log(
|
|
635
|
-
`[Frigg]
|
|
652
|
+
`[Frigg] findIntegrationByEntityExternalId: no entity for externalId=${externalId}${
|
|
636
653
|
moduleName ? ` moduleName=${moduleName}` : ''
|
|
637
654
|
}`
|
|
638
655
|
);
|
|
639
656
|
return null;
|
|
640
657
|
}
|
|
658
|
+
if (entities.length > 1) {
|
|
659
|
+
const ids = entities.map((e) => e.id).join(', ');
|
|
660
|
+
throw new Error(
|
|
661
|
+
`findIntegrationByEntityExternalId: ambiguous resolution — externalId=${externalId}` +
|
|
662
|
+
`${moduleName ? ` moduleName=${moduleName}` : ''} matches ${entities.length} entities [${ids}]. ` +
|
|
663
|
+
`Refusing to pick one to avoid cross-tenant routing. ` +
|
|
664
|
+
`Pass a moduleName, or use listIntegrationsByEntityExternalId if multiple integrations are expected.`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
641
667
|
|
|
668
|
+
const entity = entities[0];
|
|
642
669
|
const integrations =
|
|
643
670
|
await this.integrationRepository.findIntegrationsByEntityId(
|
|
644
671
|
entity.id
|
|
645
672
|
);
|
|
646
673
|
if (!integrations || integrations.length === 0) {
|
|
647
674
|
console.log(
|
|
648
|
-
`[Frigg]
|
|
675
|
+
`[Frigg] findIntegrationByEntityExternalId: entity ${entity.id} has no owning integrations (orphan)`
|
|
649
676
|
);
|
|
650
677
|
return null;
|
|
651
678
|
}
|
|
652
679
|
if (integrations.length > 1) {
|
|
653
680
|
const ids = integrations.map((i) => i.id).join(', ');
|
|
654
681
|
throw new Error(
|
|
655
|
-
`
|
|
682
|
+
`findIntegrationByEntityExternalId: ambiguous resolution — externalId=${externalId}` +
|
|
656
683
|
`${moduleName ? ` moduleName=${moduleName}` : ''} maps to ${integrations.length} integrations [${ids}]. ` +
|
|
657
|
-
`Refusing to pick one to avoid cross-tenant routing
|
|
684
|
+
`Refusing to pick one to avoid cross-tenant routing. ` +
|
|
685
|
+
`Use listIntegrationsByEntityExternalId if a one-to-many fan-out is intended.`
|
|
658
686
|
);
|
|
659
687
|
}
|
|
660
688
|
return integrations[0].id;
|
|
661
689
|
}
|
|
662
690
|
|
|
691
|
+
/**
|
|
692
|
+
* List all integration IDs whose module entities match an externalId.
|
|
693
|
+
*
|
|
694
|
+
* Use this instead of {@link findIntegrationByEntityExternalId} when a single
|
|
695
|
+
* externalId is *expected* to map to multiple integrations (e.g. an
|
|
696
|
+
* intentional fan-out: one upstream account broadcasting to several Frigg
|
|
697
|
+
* integration records, possibly across tenants).
|
|
698
|
+
*
|
|
699
|
+
* Does not throw on ambiguous resolution — that's the whole point.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} externalId - Provider's stable identifier for the account/portal/workspace.
|
|
702
|
+
* @param {string} [moduleName] - Disambiguates when multiple modules in the same app could carry colliding externalIds.
|
|
703
|
+
* @returns {Promise<Array<string>>} Array of integration IDs (empty if no entity or no owning integrations).
|
|
704
|
+
*/
|
|
705
|
+
async listIntegrationsByEntityExternalId(externalId, moduleName) {
|
|
706
|
+
if (!externalId) return [];
|
|
707
|
+
|
|
708
|
+
const entities = await this._findEntitiesByExternalId(
|
|
709
|
+
externalId,
|
|
710
|
+
moduleName
|
|
711
|
+
);
|
|
712
|
+
if (entities.length === 0) return [];
|
|
713
|
+
|
|
714
|
+
const integrationIds = new Set();
|
|
715
|
+
for (const entity of entities) {
|
|
716
|
+
const owners =
|
|
717
|
+
await this.integrationRepository.findIntegrationsByEntityId(
|
|
718
|
+
entity.id
|
|
719
|
+
);
|
|
720
|
+
for (const integration of owners || []) {
|
|
721
|
+
integrationIds.add(integration.id);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return Array.from(integrationIds);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Internal: load entities matching an externalId (+ optional moduleName).
|
|
729
|
+
* Pulled out so the single-result and list-result helpers share lookup logic.
|
|
730
|
+
*
|
|
731
|
+
* @private
|
|
732
|
+
*/
|
|
733
|
+
async _findEntitiesByExternalId(externalId, moduleName) {
|
|
734
|
+
const {
|
|
735
|
+
createModuleRepository,
|
|
736
|
+
} = require('../modules/repositories/module-repository-factory');
|
|
737
|
+
const moduleRepository = createModuleRepository();
|
|
738
|
+
|
|
739
|
+
const filter = { externalId: String(externalId) };
|
|
740
|
+
if (moduleName) filter.moduleName = moduleName;
|
|
741
|
+
|
|
742
|
+
const entities = await moduleRepository.findEntities(filter);
|
|
743
|
+
return entities || [];
|
|
744
|
+
}
|
|
745
|
+
|
|
663
746
|
async initialize() {
|
|
664
747
|
try {
|
|
665
748
|
const additionalUserActions = await this.loadDynamicUserActions();
|
|
@@ -100,6 +100,21 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
100
100
|
return this._mapEntity(doc, credential);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
async findEntities(filter) {
|
|
104
|
+
const query = this._buildFilter(filter);
|
|
105
|
+
const docs = await findMany(this.prisma, 'Entity', query);
|
|
106
|
+
if (!docs || docs.length === 0) return [];
|
|
107
|
+
const credentialMap = await this._fetchCredentialsBulk(
|
|
108
|
+
docs.map((doc) => doc.credentialId)
|
|
109
|
+
);
|
|
110
|
+
return docs.map((doc) =>
|
|
111
|
+
this._mapEntity(
|
|
112
|
+
doc,
|
|
113
|
+
credentialMap.get(fromObjectId(doc.credentialId)) || null
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
async createEntity(entityData) {
|
|
104
119
|
const {
|
|
105
120
|
user,
|
|
@@ -91,6 +91,22 @@ class ModuleRepositoryInterface {
|
|
|
91
91
|
throw new Error('Method findEntity must be implemented by subclass');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Find all entities matching a filter.
|
|
96
|
+
*
|
|
97
|
+
* Symmetric with findEntity but returns the full match set. Use this when
|
|
98
|
+
* a single externalId may correspond to more than one Entity row
|
|
99
|
+
* (e.g. shared upstream account across tenants) and the caller needs to
|
|
100
|
+
* detect/handle the multi-match case explicitly.
|
|
101
|
+
*
|
|
102
|
+
* @param {Object} filter - Filter criteria
|
|
103
|
+
* @returns {Promise<Array>} Array of entity objects (empty if no match)
|
|
104
|
+
* @abstract
|
|
105
|
+
*/
|
|
106
|
+
async findEntities(filter) {
|
|
107
|
+
throw new Error('Method findEntities must be implemented by subclass');
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
/**
|
|
95
111
|
* Create a new entity
|
|
96
112
|
*
|
|
@@ -234,6 +234,34 @@ class ModuleRepositoryMongo extends ModuleRepositoryInterface {
|
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Find all entities matching a filter.
|
|
239
|
+
*
|
|
240
|
+
* @param {Object} filter - Filter criteria
|
|
241
|
+
* @returns {Promise<Array>} Array of entity objects with string IDs (empty if no match)
|
|
242
|
+
*/
|
|
243
|
+
async findEntities(filter) {
|
|
244
|
+
const where = this._convertFilterToWhere(filter);
|
|
245
|
+
const entities = await this.prisma.entity.findMany({
|
|
246
|
+
where,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const credentialIds = entities
|
|
250
|
+
.map((e) => e.credentialId)
|
|
251
|
+
.filter(Boolean);
|
|
252
|
+
const credentialMap = await this._fetchCredentialsBulk(credentialIds);
|
|
253
|
+
|
|
254
|
+
return entities.map((e) => ({
|
|
255
|
+
id: e.id,
|
|
256
|
+
credential: credentialMap.get(e.credentialId) || null,
|
|
257
|
+
userId: e.userId,
|
|
258
|
+
name: e.name,
|
|
259
|
+
externalId: e.externalId,
|
|
260
|
+
moduleName: e.moduleName,
|
|
261
|
+
...(e.data || {}),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
237
265
|
/**
|
|
238
266
|
* Create a new entity
|
|
239
267
|
* Replaces: Entity.create(entityData)
|
|
@@ -275,6 +275,34 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
|
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Find all entities matching a filter.
|
|
280
|
+
*
|
|
281
|
+
* @param {Object} filter - Filter criteria
|
|
282
|
+
* @returns {Promise<Array>} Array of entity objects with string IDs (empty if no match)
|
|
283
|
+
*/
|
|
284
|
+
async findEntities(filter) {
|
|
285
|
+
const where = this._convertFilterToWhere(filter);
|
|
286
|
+
const entities = await this.prisma.entity.findMany({
|
|
287
|
+
where,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const credentialIds = entities
|
|
291
|
+
.map((e) => e.credentialId)
|
|
292
|
+
.filter(Boolean);
|
|
293
|
+
const credentialMap = await this._fetchCredentialsBulk(credentialIds);
|
|
294
|
+
|
|
295
|
+
return entities.map((e) => ({
|
|
296
|
+
id: e.id.toString(),
|
|
297
|
+
credential: credentialMap.get(e.credentialId) || null,
|
|
298
|
+
userId: e.userId?.toString(),
|
|
299
|
+
name: e.name,
|
|
300
|
+
externalId: e.externalId,
|
|
301
|
+
moduleName: e.moduleName,
|
|
302
|
+
...(e.data || {}),
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
|
|
278
306
|
/**
|
|
279
307
|
* Create a new entity
|
|
280
308
|
* Replaces: Entity.create(entityData)
|
|
@@ -172,6 +172,30 @@ class ModuleRepository extends ModuleRepositoryInterface {
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Find all entities matching a filter.
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} filter - Filter criteria
|
|
179
|
+
* @returns {Promise<Array>} Array of entity objects (empty if no match)
|
|
180
|
+
*/
|
|
181
|
+
async findEntities(filter) {
|
|
182
|
+
const where = this._convertFilterToWhere(filter);
|
|
183
|
+
const entities = await this.prisma.entity.findMany({
|
|
184
|
+
where,
|
|
185
|
+
include: { credential: true },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return entities.map((e) => ({
|
|
189
|
+
id: e.id,
|
|
190
|
+
credential: e.credential,
|
|
191
|
+
userId: e.userId,
|
|
192
|
+
name: e.name,
|
|
193
|
+
externalId: e.externalId,
|
|
194
|
+
moduleName: e.moduleName,
|
|
195
|
+
...(e.data || {}),
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
175
199
|
/**
|
|
176
200
|
* Create a new entity
|
|
177
201
|
* Replaces: Entity.create(entityData)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.590.
|
|
4
|
+
"version": "2.0.0--canary.590.ffb7d1b.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0--canary.590.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0--canary.590.
|
|
43
|
-
"@friggframework/test": "2.0.0--canary.590.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0--canary.590.ffb7d1b.0",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0--canary.590.ffb7d1b.0",
|
|
43
|
+
"@friggframework/test": "2.0.0--canary.590.ffb7d1b.0",
|
|
44
44
|
"@prisma/client": "^6.17.0",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "ffb7d1bc294080aa9e9a3611dd30e21a4b800542"
|
|
84
84
|
}
|