@salesforce/lds-runtime-mobile 1.112.1 → 1.113.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/durableStore/NimbusSqliteStore/tables/LdsDraftIdMapDataTable.d.ts +1 -1
- package/dist/durableStore/NimbusSqliteStore/tables/LdsDraftsDataTable.d.ts +1 -1
- package/dist/main.js +113 -27
- package/dist/mocks.d.ts +1 -0
- package/dist/priming/SqlitePrimingStore.d.ts +10 -0
- package/package.json +15 -15
- package/sfdc/durableStore/NimbusSqliteStore/tables/LdsDraftIdMapDataTable.d.ts +1 -1
- package/sfdc/durableStore/NimbusSqliteStore/tables/LdsDraftsDataTable.d.ts +1 -1
- package/sfdc/main.js +113 -27
- package/sfdc/mocks.d.ts +1 -0
- package/sfdc/priming/SqlitePrimingStore.d.ts +10 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SqliteStorePlugin } from '
|
|
1
|
+
import type { SqliteStorePlugin } from '@salesforce/nimbus-plugin-lds';
|
|
2
2
|
import { AbstractKeyValueDataTable } from './AbstractKeyValueDataTable';
|
|
3
3
|
export declare class LdsDraftIdMapDataTable extends AbstractKeyValueDataTable {
|
|
4
4
|
constructor(plugin: SqliteStorePlugin);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SqliteStorePlugin } from '
|
|
1
|
+
import type { SqliteStorePlugin } from '@salesforce/nimbus-plugin-lds';
|
|
2
2
|
import { AbstractKeyValueDataTable } from './AbstractKeyValueDataTable';
|
|
3
3
|
export declare class LdsDraftsDataTable extends AbstractKeyValueDataTable {
|
|
4
4
|
constructor(plugin: SqliteStorePlugin);
|
package/dist/main.js
CHANGED
|
@@ -15003,8 +15003,14 @@ class PrimingSession extends EventEmitter {
|
|
|
15003
15003
|
}
|
|
15004
15004
|
// pop any pending work off the queue
|
|
15005
15005
|
const batches = typedBatches.splice(0, typedBatches.length);
|
|
15006
|
+
const before = Date.now();
|
|
15006
15007
|
// fetch the metadata for the batches
|
|
15007
|
-
const { availableBatches, unavailableTypes, unavailableIds } = await this.fetchMetadata(batches);
|
|
15008
|
+
const { availableBatches, unavailableTypes, unavailableIds, availableTypes } = await this.fetchMetadata(batches);
|
|
15009
|
+
this.emit('metadata-fetched', {
|
|
15010
|
+
duration: Date.now() - before,
|
|
15011
|
+
availableTypes,
|
|
15012
|
+
unavailableTypes,
|
|
15013
|
+
});
|
|
15008
15014
|
if (unavailableIds.length > 0) {
|
|
15009
15015
|
this.emit('error', {
|
|
15010
15016
|
ids: unavailableIds,
|
|
@@ -15023,14 +15029,21 @@ class PrimingSession extends EventEmitter {
|
|
|
15023
15029
|
// parallelizes batches of priming work
|
|
15024
15030
|
enqueueBatches(batches) {
|
|
15025
15031
|
for (const batch of batches) {
|
|
15032
|
+
const queuedTime = Date.now();
|
|
15026
15033
|
this.networkWorkerPool.push({
|
|
15027
15034
|
workFn: (abortController) => {
|
|
15035
|
+
const workTime = Date.now();
|
|
15036
|
+
this.emit('batch-starting', { queuedTime: workTime - queuedTime });
|
|
15028
15037
|
return this.recordLoader
|
|
15029
15038
|
.fetchRecordData(batch, abortController)
|
|
15030
15039
|
.then(async (result) => {
|
|
15031
15040
|
if (abortController.signal.aborted) {
|
|
15032
15041
|
return;
|
|
15033
15042
|
}
|
|
15043
|
+
this.emit('batch-fetched', {
|
|
15044
|
+
ids: batch.ids,
|
|
15045
|
+
duration: Date.now() - workTime,
|
|
15046
|
+
});
|
|
15034
15047
|
if (result.ok === false) {
|
|
15035
15048
|
const { error } = result;
|
|
15036
15049
|
const primingError = error === 'network-error' ? 'service-unavailable' : 'unknown';
|
|
@@ -15050,13 +15063,31 @@ class PrimingSession extends EventEmitter {
|
|
|
15050
15063
|
});
|
|
15051
15064
|
}
|
|
15052
15065
|
const { records } = result;
|
|
15066
|
+
const beforeWrite = Date.now();
|
|
15053
15067
|
// dispatch the write but DO NOT wait on it to unblock the network pool
|
|
15054
15068
|
this.recordIngestor
|
|
15055
15069
|
.insertRecords(records)
|
|
15056
|
-
.then(({ written, conflicted }) => {
|
|
15070
|
+
.then(({ written, conflicted, errors }) => {
|
|
15071
|
+
this.emit('batch-written', {
|
|
15072
|
+
written,
|
|
15073
|
+
conflicted,
|
|
15074
|
+
errors: errors
|
|
15075
|
+
.map((e) => e.ids)
|
|
15076
|
+
.reduce((a, b) => a.concat(b), []),
|
|
15077
|
+
duration: Date.now() - beforeWrite,
|
|
15078
|
+
});
|
|
15057
15079
|
if (abortController.signal.aborted) {
|
|
15058
15080
|
return;
|
|
15059
15081
|
}
|
|
15082
|
+
if (errors.length > 0) {
|
|
15083
|
+
errors.forEach(({ ids, message }) => {
|
|
15084
|
+
this.emit('error', {
|
|
15085
|
+
ids,
|
|
15086
|
+
code: 'unknown',
|
|
15087
|
+
message: message,
|
|
15088
|
+
});
|
|
15089
|
+
});
|
|
15090
|
+
}
|
|
15060
15091
|
// now that the records are persisted, emit the primed event
|
|
15061
15092
|
if (written.length > 0) {
|
|
15062
15093
|
this.emit('primed', Array.from(written));
|
|
@@ -15089,6 +15120,7 @@ class PrimingSession extends EventEmitter {
|
|
|
15089
15120
|
}, new Set()));
|
|
15090
15121
|
const objectInfoMap = await this.objectInfoLoader.getObjectInfos(apiNames);
|
|
15091
15122
|
const unavailableTypes = apiNames.filter((x) => !objectInfoMap[x]);
|
|
15123
|
+
const availableTypes = apiNames.filter((x) => objectInfoMap[x]);
|
|
15092
15124
|
const unavilableBatches = batches.filter((x) => unavailableTypes.includes(x.type));
|
|
15093
15125
|
const availableBatches = batches.filter((x) => !unavailableTypes.includes(x.type));
|
|
15094
15126
|
const unavailableIds = unavilableBatches.reduce((acc, x) => {
|
|
@@ -15096,9 +15128,11 @@ class PrimingSession extends EventEmitter {
|
|
|
15096
15128
|
return acc;
|
|
15097
15129
|
}, []);
|
|
15098
15130
|
return {
|
|
15131
|
+
apiNames,
|
|
15099
15132
|
availableBatches,
|
|
15100
15133
|
unavilableBatches,
|
|
15101
15134
|
unavailableTypes,
|
|
15135
|
+
availableTypes,
|
|
15102
15136
|
unavailableIds,
|
|
15103
15137
|
};
|
|
15104
15138
|
}
|
|
@@ -15258,45 +15292,30 @@ class RecordIngestor {
|
|
|
15258
15292
|
*/
|
|
15259
15293
|
async insertRecords(syntheticRecords) {
|
|
15260
15294
|
if (syntheticRecords.length === 0) {
|
|
15261
|
-
return { written: [], conflicted: [] };
|
|
15295
|
+
return { written: [], conflicted: [], errors: [] };
|
|
15262
15296
|
}
|
|
15263
15297
|
const luvio = this.getLuvio();
|
|
15264
15298
|
const ingestionTimestamp = Date.now();
|
|
15265
15299
|
const ttlOverride = await luvio.storeGetTTLOverride(UiApiNamespace, RecordRepresentationType);
|
|
15266
|
-
const metadata =
|
|
15300
|
+
const metadata = {
|
|
15267
15301
|
ingestionTimestamp,
|
|
15268
15302
|
expirationTimestamp: (ttlOverride !== null && ttlOverride !== void 0 ? ttlOverride : RecordRepresentationTTL) + ingestionTimestamp,
|
|
15269
15303
|
namespace: UiApiNamespace,
|
|
15270
15304
|
representationName: RecordRepresentationType,
|
|
15271
15305
|
metadataVersion: DURABLE_METADATA_VERSION,
|
|
15272
15306
|
version: RecordRepresentationVersion,
|
|
15273
|
-
}
|
|
15274
|
-
|
|
15275
|
-
const written = [];
|
|
15276
|
-
const rows = syntheticRecords.map((record) => {
|
|
15277
|
-
idsToPrime.add(record.id);
|
|
15278
|
-
return `('${keyBuilderRecord(luvio, { recordId: record.id })}', '${JSON.stringify(record)}', '${metadata}')`;
|
|
15279
|
-
});
|
|
15280
|
-
const dml = `INSERT or IGNORE INTO lds_data (key, data, metadata) VALUES ${rows.join(',\n')} returning key;`;
|
|
15281
|
-
const result = await this.store.query(dml, []);
|
|
15282
|
-
for (const row of result.rows) {
|
|
15283
|
-
const id = extractRecordIdFromStoreKey(row[0]);
|
|
15284
|
-
if (id) {
|
|
15285
|
-
written.push(id);
|
|
15286
|
-
idsToPrime.delete(id);
|
|
15287
|
-
}
|
|
15288
|
-
}
|
|
15289
|
-
return { written: Array.from(written), conflicted: Array.from(idsToPrime) };
|
|
15307
|
+
};
|
|
15308
|
+
return this.store.writeRecords(syntheticRecords.map((record) => ({ record, metadata })));
|
|
15290
15309
|
}
|
|
15291
15310
|
}
|
|
15292
15311
|
|
|
15293
15312
|
function instrumentPrimingSession(session) {
|
|
15294
15313
|
reportPrimingSessionCreated();
|
|
15295
|
-
session.on('error', (
|
|
15296
|
-
reportPrimingError(
|
|
15314
|
+
session.on('error', ({ code, ids }) => {
|
|
15315
|
+
reportPrimingError(code, ids.length);
|
|
15297
15316
|
});
|
|
15298
|
-
session.on('primed', (
|
|
15299
|
-
reportPrimingSuccess(
|
|
15317
|
+
session.on('primed', ({ length }) => {
|
|
15318
|
+
reportPrimingSuccess(length);
|
|
15300
15319
|
});
|
|
15301
15320
|
return session;
|
|
15302
15321
|
}
|
|
@@ -15349,11 +15368,78 @@ class NimbusPrimingNetworkAdapter {
|
|
|
15349
15368
|
}
|
|
15350
15369
|
}
|
|
15351
15370
|
|
|
15371
|
+
// ref: https://gnome.pages.gitlab.gnome.org/tracker/docs/developer/limits.html?gi-language=c
|
|
15372
|
+
const SQLITE_MAX_VARIABLE_NUMBER = 999;
|
|
15373
|
+
const PARAMS_PER_RECORD = 3;
|
|
15374
|
+
// We need to batch the records to avoid hitting the SQLITE_MAX_VARIABLE_NUMBER limit. Each record has 3 parameters
|
|
15375
|
+
const BATCH_SIZE = Math.floor(SQLITE_MAX_VARIABLE_NUMBER / PARAMS_PER_RECORD);
|
|
15376
|
+
class SqlitePrimingStore {
|
|
15377
|
+
constructor(getLuvio, store) {
|
|
15378
|
+
this.getLuvio = getLuvio;
|
|
15379
|
+
this.store = store;
|
|
15380
|
+
}
|
|
15381
|
+
async writeRecords(records) {
|
|
15382
|
+
const batches = batchArray(records);
|
|
15383
|
+
const writeResult = { written: [], conflicted: [], errors: [] };
|
|
15384
|
+
return (await Promise.all(batches.map((batch) => this.writeBatch(batch)))).reduce((acc, curr) => {
|
|
15385
|
+
acc.written.push(...curr.written);
|
|
15386
|
+
acc.conflicted.push(...curr.conflicted);
|
|
15387
|
+
acc.errors.push(...curr.errors);
|
|
15388
|
+
return acc;
|
|
15389
|
+
}, writeResult);
|
|
15390
|
+
}
|
|
15391
|
+
async writeBatch(records) {
|
|
15392
|
+
const idsToPrime = new Set();
|
|
15393
|
+
const written = [];
|
|
15394
|
+
const statement = `INSERT or IGNORE INTO lds_data (key, data, metadata) VALUES ${records
|
|
15395
|
+
.map((_) => `(?,?,?)`)
|
|
15396
|
+
.join(',')} returning key;`;
|
|
15397
|
+
const params = [];
|
|
15398
|
+
records.forEach(({ record, metadata }) => {
|
|
15399
|
+
idsToPrime.add(record.id);
|
|
15400
|
+
const key = keyBuilderRecord(this.getLuvio(), { recordId: record.id });
|
|
15401
|
+
params.push(key);
|
|
15402
|
+
params.push(JSON.stringify(record));
|
|
15403
|
+
params.push(JSON.stringify(metadata));
|
|
15404
|
+
});
|
|
15405
|
+
try {
|
|
15406
|
+
const result = await this.store.query(statement, params);
|
|
15407
|
+
for (const row of result.rows) {
|
|
15408
|
+
const id = extractRecordIdFromStoreKey(row[0]);
|
|
15409
|
+
if (id) {
|
|
15410
|
+
written.push(id);
|
|
15411
|
+
idsToPrime.delete(id);
|
|
15412
|
+
}
|
|
15413
|
+
}
|
|
15414
|
+
return { written, conflicted: Array.from(idsToPrime), errors: [] };
|
|
15415
|
+
}
|
|
15416
|
+
catch (e) {
|
|
15417
|
+
return {
|
|
15418
|
+
written: [],
|
|
15419
|
+
conflicted: [],
|
|
15420
|
+
errors: [{ ids: Array.from(idsToPrime), message: e }],
|
|
15421
|
+
};
|
|
15422
|
+
}
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
function batchArray(arr, batchSize = BATCH_SIZE) {
|
|
15426
|
+
const batches = [];
|
|
15427
|
+
// If the array length is less than or equal to the batch size, return the array as a single batch
|
|
15428
|
+
if (arr.length <= batchSize) {
|
|
15429
|
+
return [arr];
|
|
15430
|
+
}
|
|
15431
|
+
// Split the array into batches of size batchSize
|
|
15432
|
+
for (let i = 0; i < arr.length; i += batchSize) {
|
|
15433
|
+
batches.push(arr.slice(i, i + batchSize));
|
|
15434
|
+
}
|
|
15435
|
+
return batches;
|
|
15436
|
+
}
|
|
15437
|
+
|
|
15352
15438
|
function primingSessionFactory(config) {
|
|
15353
15439
|
const { store, objectInfoService, getLuvio } = config;
|
|
15354
15440
|
const networkAdapter = new NimbusPrimingNetworkAdapter();
|
|
15355
15441
|
const recordLoader = new RecordLoaderGraphQL(networkAdapter);
|
|
15356
|
-
const recordIngestor = new RecordIngestor(store, getLuvio);
|
|
15442
|
+
const recordIngestor = new RecordIngestor(new SqlitePrimingStore(getLuvio, store), getLuvio);
|
|
15357
15443
|
const session = new PrimingSession({
|
|
15358
15444
|
recordLoader,
|
|
15359
15445
|
recordIngestor,
|
|
@@ -15499,4 +15585,4 @@ register({
|
|
|
15499
15585
|
});
|
|
15500
15586
|
|
|
15501
15587
|
export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
|
|
15502
|
-
// version: 1.
|
|
15588
|
+
// version: 1.113.0-a8af103c9
|
package/dist/mocks.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MockNimbusBinaryStorePlugin, mockNimbusBinaryStorePlugin, } from './__tests__/MockNimbusBinaryStorePlugin.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PrimingStore, RecordWithMetadata, WriteResult } from '@salesforce/lds-priming';
|
|
2
|
+
import type { Luvio } from '@luvio/engine';
|
|
3
|
+
import type { SqliteStore } from '@salesforce/lds-store-sql';
|
|
4
|
+
export declare class SqlitePrimingStore implements PrimingStore {
|
|
5
|
+
private readonly getLuvio;
|
|
6
|
+
private readonly store;
|
|
7
|
+
constructor(getLuvio: () => Luvio, store: SqliteStore);
|
|
8
|
+
writeRecords(records: RecordWithMetadata[]): Promise<WriteResult>;
|
|
9
|
+
private writeBatch;
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-runtime-mobile",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.113.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS runtime for mobile/hybrid environments.",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-mobile"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@salesforce/lds-adapters-uiapi": "^1.
|
|
36
|
-
"@salesforce/lds-bindings": "^1.
|
|
37
|
-
"@salesforce/lds-instrumentation": "^1.
|
|
38
|
-
"@salesforce/lds-priming": "^1.
|
|
35
|
+
"@salesforce/lds-adapters-uiapi": "^1.113.0",
|
|
36
|
+
"@salesforce/lds-bindings": "^1.113.0",
|
|
37
|
+
"@salesforce/lds-instrumentation": "^1.113.0",
|
|
38
|
+
"@salesforce/lds-priming": "^1.113.0",
|
|
39
39
|
"@salesforce/user": "0.0.12",
|
|
40
40
|
"o11y": "244.0.0"
|
|
41
41
|
},
|
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
"@luvio/engine": "0.137.1",
|
|
44
44
|
"@luvio/environments": "0.137.1",
|
|
45
45
|
"@luvio/graphql-parser": "0.137.1",
|
|
46
|
-
"@salesforce/lds-adapters-graphql": "^1.
|
|
47
|
-
"@salesforce/lds-drafts": "^1.
|
|
48
|
-
"@salesforce/lds-drafts-adapters-uiapi": "^1.
|
|
49
|
-
"@salesforce/lds-graphql-eval": "^1.
|
|
50
|
-
"@salesforce/lds-network-adapter": "^1.
|
|
51
|
-
"@salesforce/lds-network-nimbus": "^1.
|
|
52
|
-
"@salesforce/lds-store-binary": "^1.
|
|
53
|
-
"@salesforce/lds-store-sql": "^1.
|
|
54
|
-
"@salesforce/lds-utils-adapters": "^1.
|
|
55
|
-
"@salesforce/nimbus-plugin-lds": "^1.
|
|
46
|
+
"@salesforce/lds-adapters-graphql": "^1.113.0",
|
|
47
|
+
"@salesforce/lds-drafts": "^1.113.0",
|
|
48
|
+
"@salesforce/lds-drafts-adapters-uiapi": "^1.113.0",
|
|
49
|
+
"@salesforce/lds-graphql-eval": "^1.113.0",
|
|
50
|
+
"@salesforce/lds-network-adapter": "^1.113.0",
|
|
51
|
+
"@salesforce/lds-network-nimbus": "^1.113.0",
|
|
52
|
+
"@salesforce/lds-store-binary": "^1.113.0",
|
|
53
|
+
"@salesforce/lds-store-sql": "^1.113.0",
|
|
54
|
+
"@salesforce/lds-utils-adapters": "^1.113.0",
|
|
55
|
+
"@salesforce/nimbus-plugin-lds": "^1.113.0",
|
|
56
56
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
57
57
|
"wait-for-expect": "^3.0.2"
|
|
58
58
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SqliteStorePlugin } from '
|
|
1
|
+
import type { SqliteStorePlugin } from '@salesforce/nimbus-plugin-lds';
|
|
2
2
|
import { AbstractKeyValueDataTable } from './AbstractKeyValueDataTable';
|
|
3
3
|
export declare class LdsDraftIdMapDataTable extends AbstractKeyValueDataTable {
|
|
4
4
|
constructor(plugin: SqliteStorePlugin);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SqliteStorePlugin } from '
|
|
1
|
+
import type { SqliteStorePlugin } from '@salesforce/nimbus-plugin-lds';
|
|
2
2
|
import { AbstractKeyValueDataTable } from './AbstractKeyValueDataTable';
|
|
3
3
|
export declare class LdsDraftsDataTable extends AbstractKeyValueDataTable {
|
|
4
4
|
constructor(plugin: SqliteStorePlugin);
|
package/sfdc/main.js
CHANGED
|
@@ -15003,8 +15003,14 @@ class PrimingSession extends EventEmitter {
|
|
|
15003
15003
|
}
|
|
15004
15004
|
// pop any pending work off the queue
|
|
15005
15005
|
const batches = typedBatches.splice(0, typedBatches.length);
|
|
15006
|
+
const before = Date.now();
|
|
15006
15007
|
// fetch the metadata for the batches
|
|
15007
|
-
const { availableBatches, unavailableTypes, unavailableIds } = await this.fetchMetadata(batches);
|
|
15008
|
+
const { availableBatches, unavailableTypes, unavailableIds, availableTypes } = await this.fetchMetadata(batches);
|
|
15009
|
+
this.emit('metadata-fetched', {
|
|
15010
|
+
duration: Date.now() - before,
|
|
15011
|
+
availableTypes,
|
|
15012
|
+
unavailableTypes,
|
|
15013
|
+
});
|
|
15008
15014
|
if (unavailableIds.length > 0) {
|
|
15009
15015
|
this.emit('error', {
|
|
15010
15016
|
ids: unavailableIds,
|
|
@@ -15023,14 +15029,21 @@ class PrimingSession extends EventEmitter {
|
|
|
15023
15029
|
// parallelizes batches of priming work
|
|
15024
15030
|
enqueueBatches(batches) {
|
|
15025
15031
|
for (const batch of batches) {
|
|
15032
|
+
const queuedTime = Date.now();
|
|
15026
15033
|
this.networkWorkerPool.push({
|
|
15027
15034
|
workFn: (abortController) => {
|
|
15035
|
+
const workTime = Date.now();
|
|
15036
|
+
this.emit('batch-starting', { queuedTime: workTime - queuedTime });
|
|
15028
15037
|
return this.recordLoader
|
|
15029
15038
|
.fetchRecordData(batch, abortController)
|
|
15030
15039
|
.then(async (result) => {
|
|
15031
15040
|
if (abortController.signal.aborted) {
|
|
15032
15041
|
return;
|
|
15033
15042
|
}
|
|
15043
|
+
this.emit('batch-fetched', {
|
|
15044
|
+
ids: batch.ids,
|
|
15045
|
+
duration: Date.now() - workTime,
|
|
15046
|
+
});
|
|
15034
15047
|
if (result.ok === false) {
|
|
15035
15048
|
const { error } = result;
|
|
15036
15049
|
const primingError = error === 'network-error' ? 'service-unavailable' : 'unknown';
|
|
@@ -15050,13 +15063,31 @@ class PrimingSession extends EventEmitter {
|
|
|
15050
15063
|
});
|
|
15051
15064
|
}
|
|
15052
15065
|
const { records } = result;
|
|
15066
|
+
const beforeWrite = Date.now();
|
|
15053
15067
|
// dispatch the write but DO NOT wait on it to unblock the network pool
|
|
15054
15068
|
this.recordIngestor
|
|
15055
15069
|
.insertRecords(records)
|
|
15056
|
-
.then(({ written, conflicted }) => {
|
|
15070
|
+
.then(({ written, conflicted, errors }) => {
|
|
15071
|
+
this.emit('batch-written', {
|
|
15072
|
+
written,
|
|
15073
|
+
conflicted,
|
|
15074
|
+
errors: errors
|
|
15075
|
+
.map((e) => e.ids)
|
|
15076
|
+
.reduce((a, b) => a.concat(b), []),
|
|
15077
|
+
duration: Date.now() - beforeWrite,
|
|
15078
|
+
});
|
|
15057
15079
|
if (abortController.signal.aborted) {
|
|
15058
15080
|
return;
|
|
15059
15081
|
}
|
|
15082
|
+
if (errors.length > 0) {
|
|
15083
|
+
errors.forEach(({ ids, message }) => {
|
|
15084
|
+
this.emit('error', {
|
|
15085
|
+
ids,
|
|
15086
|
+
code: 'unknown',
|
|
15087
|
+
message: message,
|
|
15088
|
+
});
|
|
15089
|
+
});
|
|
15090
|
+
}
|
|
15060
15091
|
// now that the records are persisted, emit the primed event
|
|
15061
15092
|
if (written.length > 0) {
|
|
15062
15093
|
this.emit('primed', Array.from(written));
|
|
@@ -15089,6 +15120,7 @@ class PrimingSession extends EventEmitter {
|
|
|
15089
15120
|
}, new Set()));
|
|
15090
15121
|
const objectInfoMap = await this.objectInfoLoader.getObjectInfos(apiNames);
|
|
15091
15122
|
const unavailableTypes = apiNames.filter((x) => !objectInfoMap[x]);
|
|
15123
|
+
const availableTypes = apiNames.filter((x) => objectInfoMap[x]);
|
|
15092
15124
|
const unavilableBatches = batches.filter((x) => unavailableTypes.includes(x.type));
|
|
15093
15125
|
const availableBatches = batches.filter((x) => !unavailableTypes.includes(x.type));
|
|
15094
15126
|
const unavailableIds = unavilableBatches.reduce((acc, x) => {
|
|
@@ -15096,9 +15128,11 @@ class PrimingSession extends EventEmitter {
|
|
|
15096
15128
|
return acc;
|
|
15097
15129
|
}, []);
|
|
15098
15130
|
return {
|
|
15131
|
+
apiNames,
|
|
15099
15132
|
availableBatches,
|
|
15100
15133
|
unavilableBatches,
|
|
15101
15134
|
unavailableTypes,
|
|
15135
|
+
availableTypes,
|
|
15102
15136
|
unavailableIds,
|
|
15103
15137
|
};
|
|
15104
15138
|
}
|
|
@@ -15258,45 +15292,30 @@ class RecordIngestor {
|
|
|
15258
15292
|
*/
|
|
15259
15293
|
async insertRecords(syntheticRecords) {
|
|
15260
15294
|
if (syntheticRecords.length === 0) {
|
|
15261
|
-
return { written: [], conflicted: [] };
|
|
15295
|
+
return { written: [], conflicted: [], errors: [] };
|
|
15262
15296
|
}
|
|
15263
15297
|
const luvio = this.getLuvio();
|
|
15264
15298
|
const ingestionTimestamp = Date.now();
|
|
15265
15299
|
const ttlOverride = await luvio.storeGetTTLOverride(UiApiNamespace, RecordRepresentationType);
|
|
15266
|
-
const metadata =
|
|
15300
|
+
const metadata = {
|
|
15267
15301
|
ingestionTimestamp,
|
|
15268
15302
|
expirationTimestamp: (ttlOverride !== null && ttlOverride !== void 0 ? ttlOverride : RecordRepresentationTTL) + ingestionTimestamp,
|
|
15269
15303
|
namespace: UiApiNamespace,
|
|
15270
15304
|
representationName: RecordRepresentationType,
|
|
15271
15305
|
metadataVersion: DURABLE_METADATA_VERSION,
|
|
15272
15306
|
version: RecordRepresentationVersion,
|
|
15273
|
-
}
|
|
15274
|
-
|
|
15275
|
-
const written = [];
|
|
15276
|
-
const rows = syntheticRecords.map((record) => {
|
|
15277
|
-
idsToPrime.add(record.id);
|
|
15278
|
-
return `('${keyBuilderRecord(luvio, { recordId: record.id })}', '${JSON.stringify(record)}', '${metadata}')`;
|
|
15279
|
-
});
|
|
15280
|
-
const dml = `INSERT or IGNORE INTO lds_data (key, data, metadata) VALUES ${rows.join(',\n')} returning key;`;
|
|
15281
|
-
const result = await this.store.query(dml, []);
|
|
15282
|
-
for (const row of result.rows) {
|
|
15283
|
-
const id = extractRecordIdFromStoreKey(row[0]);
|
|
15284
|
-
if (id) {
|
|
15285
|
-
written.push(id);
|
|
15286
|
-
idsToPrime.delete(id);
|
|
15287
|
-
}
|
|
15288
|
-
}
|
|
15289
|
-
return { written: Array.from(written), conflicted: Array.from(idsToPrime) };
|
|
15307
|
+
};
|
|
15308
|
+
return this.store.writeRecords(syntheticRecords.map((record) => ({ record, metadata })));
|
|
15290
15309
|
}
|
|
15291
15310
|
}
|
|
15292
15311
|
|
|
15293
15312
|
function instrumentPrimingSession(session) {
|
|
15294
15313
|
reportPrimingSessionCreated();
|
|
15295
|
-
session.on('error', (
|
|
15296
|
-
reportPrimingError(
|
|
15314
|
+
session.on('error', ({ code, ids }) => {
|
|
15315
|
+
reportPrimingError(code, ids.length);
|
|
15297
15316
|
});
|
|
15298
|
-
session.on('primed', (
|
|
15299
|
-
reportPrimingSuccess(
|
|
15317
|
+
session.on('primed', ({ length }) => {
|
|
15318
|
+
reportPrimingSuccess(length);
|
|
15300
15319
|
});
|
|
15301
15320
|
return session;
|
|
15302
15321
|
}
|
|
@@ -15349,11 +15368,78 @@ class NimbusPrimingNetworkAdapter {
|
|
|
15349
15368
|
}
|
|
15350
15369
|
}
|
|
15351
15370
|
|
|
15371
|
+
// ref: https://gnome.pages.gitlab.gnome.org/tracker/docs/developer/limits.html?gi-language=c
|
|
15372
|
+
const SQLITE_MAX_VARIABLE_NUMBER = 999;
|
|
15373
|
+
const PARAMS_PER_RECORD = 3;
|
|
15374
|
+
// We need to batch the records to avoid hitting the SQLITE_MAX_VARIABLE_NUMBER limit. Each record has 3 parameters
|
|
15375
|
+
const BATCH_SIZE = Math.floor(SQLITE_MAX_VARIABLE_NUMBER / PARAMS_PER_RECORD);
|
|
15376
|
+
class SqlitePrimingStore {
|
|
15377
|
+
constructor(getLuvio, store) {
|
|
15378
|
+
this.getLuvio = getLuvio;
|
|
15379
|
+
this.store = store;
|
|
15380
|
+
}
|
|
15381
|
+
async writeRecords(records) {
|
|
15382
|
+
const batches = batchArray(records);
|
|
15383
|
+
const writeResult = { written: [], conflicted: [], errors: [] };
|
|
15384
|
+
return (await Promise.all(batches.map((batch) => this.writeBatch(batch)))).reduce((acc, curr) => {
|
|
15385
|
+
acc.written.push(...curr.written);
|
|
15386
|
+
acc.conflicted.push(...curr.conflicted);
|
|
15387
|
+
acc.errors.push(...curr.errors);
|
|
15388
|
+
return acc;
|
|
15389
|
+
}, writeResult);
|
|
15390
|
+
}
|
|
15391
|
+
async writeBatch(records) {
|
|
15392
|
+
const idsToPrime = new Set();
|
|
15393
|
+
const written = [];
|
|
15394
|
+
const statement = `INSERT or IGNORE INTO lds_data (key, data, metadata) VALUES ${records
|
|
15395
|
+
.map((_) => `(?,?,?)`)
|
|
15396
|
+
.join(',')} returning key;`;
|
|
15397
|
+
const params = [];
|
|
15398
|
+
records.forEach(({ record, metadata }) => {
|
|
15399
|
+
idsToPrime.add(record.id);
|
|
15400
|
+
const key = keyBuilderRecord(this.getLuvio(), { recordId: record.id });
|
|
15401
|
+
params.push(key);
|
|
15402
|
+
params.push(JSON.stringify(record));
|
|
15403
|
+
params.push(JSON.stringify(metadata));
|
|
15404
|
+
});
|
|
15405
|
+
try {
|
|
15406
|
+
const result = await this.store.query(statement, params);
|
|
15407
|
+
for (const row of result.rows) {
|
|
15408
|
+
const id = extractRecordIdFromStoreKey(row[0]);
|
|
15409
|
+
if (id) {
|
|
15410
|
+
written.push(id);
|
|
15411
|
+
idsToPrime.delete(id);
|
|
15412
|
+
}
|
|
15413
|
+
}
|
|
15414
|
+
return { written, conflicted: Array.from(idsToPrime), errors: [] };
|
|
15415
|
+
}
|
|
15416
|
+
catch (e) {
|
|
15417
|
+
return {
|
|
15418
|
+
written: [],
|
|
15419
|
+
conflicted: [],
|
|
15420
|
+
errors: [{ ids: Array.from(idsToPrime), message: e }],
|
|
15421
|
+
};
|
|
15422
|
+
}
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
function batchArray(arr, batchSize = BATCH_SIZE) {
|
|
15426
|
+
const batches = [];
|
|
15427
|
+
// If the array length is less than or equal to the batch size, return the array as a single batch
|
|
15428
|
+
if (arr.length <= batchSize) {
|
|
15429
|
+
return [arr];
|
|
15430
|
+
}
|
|
15431
|
+
// Split the array into batches of size batchSize
|
|
15432
|
+
for (let i = 0; i < arr.length; i += batchSize) {
|
|
15433
|
+
batches.push(arr.slice(i, i + batchSize));
|
|
15434
|
+
}
|
|
15435
|
+
return batches;
|
|
15436
|
+
}
|
|
15437
|
+
|
|
15352
15438
|
function primingSessionFactory(config) {
|
|
15353
15439
|
const { store, objectInfoService, getLuvio } = config;
|
|
15354
15440
|
const networkAdapter = new NimbusPrimingNetworkAdapter();
|
|
15355
15441
|
const recordLoader = new RecordLoaderGraphQL(networkAdapter);
|
|
15356
|
-
const recordIngestor = new RecordIngestor(store, getLuvio);
|
|
15442
|
+
const recordIngestor = new RecordIngestor(new SqlitePrimingStore(getLuvio, store), getLuvio);
|
|
15357
15443
|
const session = new PrimingSession({
|
|
15358
15444
|
recordLoader,
|
|
15359
15445
|
recordIngestor,
|
|
@@ -15499,4 +15585,4 @@ register({
|
|
|
15499
15585
|
});
|
|
15500
15586
|
|
|
15501
15587
|
export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
|
|
15502
|
-
// version: 1.
|
|
15588
|
+
// version: 1.113.0-a8af103c9
|
package/sfdc/mocks.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MockNimbusBinaryStorePlugin, mockNimbusBinaryStorePlugin, } from './__tests__/MockNimbusBinaryStorePlugin.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PrimingStore, RecordWithMetadata, WriteResult } from '@salesforce/lds-priming';
|
|
2
|
+
import type { Luvio } from '@luvio/engine';
|
|
3
|
+
import type { SqliteStore } from '@salesforce/lds-store-sql';
|
|
4
|
+
export declare class SqlitePrimingStore implements PrimingStore {
|
|
5
|
+
private readonly getLuvio;
|
|
6
|
+
private readonly store;
|
|
7
|
+
constructor(getLuvio: () => Luvio, store: SqliteStore);
|
|
8
|
+
writeRecords(records: RecordWithMetadata[]): Promise<WriteResult>;
|
|
9
|
+
private writeBatch;
|
|
10
|
+
}
|