@nu-art/thunderstorm-backend 0.400.5
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/_entity/app-config/ModuleBE_AppConfigAPI.d.ts +9 -0
- package/_entity/app-config/ModuleBE_AppConfigAPI.js +20 -0
- package/_entity/app-config/ModuleBE_AppConfigDB.d.ts +27 -0
- package/_entity/app-config/ModuleBE_AppConfigDB.js +91 -0
- package/_entity/app-config/index.d.ts +2 -0
- package/_entity/app-config/index.js +2 -0
- package/_entity/app-config/module-pack.d.ts +2 -0
- package/_entity/app-config/module-pack.js +3 -0
- package/_entity/backup-doc/ModuleBE_BackupDocDB.d.ts +52 -0
- package/_entity/backup-doc/ModuleBE_BackupDocDB.js +350 -0
- package/_entity/backup-doc/ModuleBE_BackupScheduler.d.ts +7 -0
- package/_entity/backup-doc/ModuleBE_BackupScheduler.js +14 -0
- package/_entity/backup-doc/index.d.ts +3 -0
- package/_entity/backup-doc/index.js +3 -0
- package/_entity/backup-doc/module-pack.d.ts +2 -0
- package/_entity/backup-doc/module-pack.js +3 -0
- package/_entity/editable-test/ModuleBE_EditableTestDB.d.ts +8 -0
- package/_entity/editable-test/ModuleBE_EditableTestDB.js +8 -0
- package/_entity/editable-test/index.d.ts +1 -0
- package/_entity/editable-test/index.js +1 -0
- package/_entity/editable-test/module-pack.d.ts +1 -0
- package/_entity/editable-test/module-pack.js +3 -0
- package/_entity.d.ts +3 -0
- package/_entity.js +3 -0
- package/core/BaseStorm.d.ts +17 -0
- package/core/BaseStorm.js +77 -0
- package/core/Storm.d.ts +15 -0
- package/core/Storm.js +93 -0
- package/core/db-def.d.ts +10 -0
- package/core/db-def.js +11 -0
- package/core/default-storm.d.ts +3 -0
- package/core/default-storm.js +30 -0
- package/core/storm-modulepack.d.ts +3 -0
- package/core/storm-modulepack.js +20 -0
- package/core/typed-api.d.ts +7 -0
- package/core/typed-api.js +46 -0
- package/exceptions.d.ts +1 -0
- package/exceptions.js +21 -0
- package/index.d.ts +27 -0
- package/index.js +48 -0
- package/modules/CleanupScheduler.d.ts +14 -0
- package/modules/CleanupScheduler.js +50 -0
- package/modules/ModuleBE_APIs.d.ts +11 -0
- package/modules/ModuleBE_APIs.js +19 -0
- package/modules/ModuleBE_CSVParser.d.ts +9 -0
- package/modules/ModuleBE_CSVParser.js +50 -0
- package/modules/ModuleBE_ForceUpgrade.d.ts +21 -0
- package/modules/ModuleBE_ForceUpgrade.js +70 -0
- package/modules/ModuleBE_ServerInfo.d.ts +20 -0
- package/modules/ModuleBE_ServerInfo.js +76 -0
- package/modules/_imports.d.ts +6 -0
- package/modules/_imports.js +26 -0
- package/modules/_tdb/service-accounts.d.ts +19 -0
- package/modules/_tdb/service-accounts.js +2 -0
- package/modules/action-processor/Action_SetupProject.d.ts +9 -0
- package/modules/action-processor/Action_SetupProject.js +23 -0
- package/modules/action-processor/ModuleBE_ActionProcessor.d.ts +11 -0
- package/modules/action-processor/ModuleBE_ActionProcessor.js +67 -0
- package/modules/action-processor/types.d.ts +10 -0
- package/modules/action-processor/types.js +1 -0
- package/modules/archiving/ModuleBE_Archiving.d.ts +119 -0
- package/modules/archiving/ModuleBE_Archiving.js +236 -0
- package/modules/collection-actions/ModuleBE_CollectionActions.d.ts +12 -0
- package/modules/collection-actions/ModuleBE_CollectionActions.js +69 -0
- package/modules/collection-actions/dispatcher.d.ts +7 -0
- package/modules/collection-actions/dispatcher.js +2 -0
- package/modules/db-api-gen/ModuleBE_BaseApi.d.ts +16 -0
- package/modules/db-api-gen/ModuleBE_BaseApi.js +74 -0
- package/modules/db-api-gen/ModuleBE_BaseDB.d.ts +78 -0
- package/modules/db-api-gen/ModuleBE_BaseDB.js +298 -0
- package/modules/http/AxiosHttpModule.d.ts +25 -0
- package/modules/http/AxiosHttpModule.js +132 -0
- package/modules/http/types.d.ts +6 -0
- package/modules/http/types.js +1 -0
- package/modules/proxy/ModuleBE_RemoteProxy.d.ts +35 -0
- package/modules/proxy/ModuleBE_RemoteProxy.js +86 -0
- package/modules/proxy/RemoteProxyCaller.d.ts +19 -0
- package/modules/proxy/RemoteProxyCaller.js +82 -0
- package/modules/proxy/assert-secret-middleware.d.ts +2 -0
- package/modules/proxy/assert-secret-middleware.js +24 -0
- package/modules/server/HeaderKey.d.ts +8 -0
- package/modules/server/HeaderKey.js +41 -0
- package/modules/server/HttpServer.d.ts +41 -0
- package/modules/server/HttpServer.js +223 -0
- package/modules/server/consts.d.ts +13 -0
- package/modules/server/consts.js +9 -0
- package/modules/server/route-resolvers/RouteResolver_Dummy.d.ts +7 -0
- package/modules/server/route-resolvers/RouteResolver_Dummy.js +34 -0
- package/modules/server/route-resolvers/RouteResolver_ModulePath.d.ts +22 -0
- package/modules/server/route-resolvers/RouteResolver_ModulePath.js +84 -0
- package/modules/server/route-resolvers/index.d.ts +7 -0
- package/modules/server/route-resolvers/index.js +21 -0
- package/modules/server/server-api.d.ts +85 -0
- package/modules/server/server-api.js +362 -0
- package/modules/server/server-errors.d.ts +4 -0
- package/modules/server/server-errors.js +79 -0
- package/modules/sync-env/ModuleBE_SyncEnv.d.ts +36 -0
- package/modules/sync-env/ModuleBE_SyncEnv.js +212 -0
- package/modules/sync-manager/ModuleBE_SyncManager.d.ts +63 -0
- package/modules/sync-manager/ModuleBE_SyncManager.js +254 -0
- package/package.json +104 -0
- package/shared.d.ts +1 -0
- package/shared.js +21 -0
- package/test/StormTest.d.ts +23 -0
- package/test/StormTest.js +49 -0
- package/utils/file.d.ts +2 -0
- package/utils/file.js +29 -0
- package/utils/promisify-request.d.ts +3 -0
- package/utils/promisify-request.js +33 -0
- package/utils/types.d.ts +11 -0
- package/utils/types.js +21 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { ApiException, arrayToMap, BadImplementationException, Dispatcher, Minute, Module, MUSTNeverHappenException, RuntimeModules } from '@nu-art/ts-common';
|
|
2
|
+
import { ModuleBE_Firebase } from '@nu-art/firebase-backend';
|
|
3
|
+
import { addRoutes } from '../ModuleBE_APIs.js';
|
|
4
|
+
import { createBodyServerApi, createQueryServerApi } from '../../core/typed-api.js';
|
|
5
|
+
import { ApiDef_SyncEnv, HeaderKey_Authorization, HttpMethod } from '@nu-art/thunderstorm-shared';
|
|
6
|
+
import { AxiosHttpModule } from '../http/AxiosHttpModule.js';
|
|
7
|
+
import { MemKey_HttpRequest } from '../server/consts.js';
|
|
8
|
+
import { Storm } from '../../core/Storm.js';
|
|
9
|
+
import { ModuleBE_BackupDocDB } from '../../_entity/backup-doc/index.js';
|
|
10
|
+
import { Transform, Writable } from 'stream';
|
|
11
|
+
import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
|
|
12
|
+
const dispatch_OnSyncEnvCompleted = new Dispatcher('__onSyncEnvCompleted');
|
|
13
|
+
class ModuleBE_SyncEnv_Class extends Module {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.setDefaultConfig({ maxBatch: 500 });
|
|
17
|
+
}
|
|
18
|
+
init() {
|
|
19
|
+
super.init();
|
|
20
|
+
addRoutes([
|
|
21
|
+
createBodyServerApi(ApiDef_SyncEnv.vv1.syncToEnv, this.pushToEnv),
|
|
22
|
+
createBodyServerApi(ApiDef_SyncEnv.vv1.syncFromEnvBackup, this.syncFromEnvBackup),
|
|
23
|
+
createQueryServerApi(ApiDef_SyncEnv.vv1.getLatestBackup, this.getLatestBackupId),
|
|
24
|
+
createQueryServerApi(ApiDef_SyncEnv.vv1.createBackup, this.createBackup),
|
|
25
|
+
createQueryServerApi(ApiDef_SyncEnv.vv1.fetchBackupMetadata, this.fetchBackupMetadata),
|
|
26
|
+
createQueryServerApi(ApiDef_SyncEnv.vv1.syncFirebaseFromBackup, this.syncFirebaseFromBackup),
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
fetchBackupMetadata = async (queryParams) => {
|
|
30
|
+
const backupInfo = await this.getBackupInfo(queryParams);
|
|
31
|
+
if (!backupInfo)
|
|
32
|
+
throw new ApiException(404, 'backup file not found');
|
|
33
|
+
if (!backupInfo.metadata)
|
|
34
|
+
throw new ApiException(404, 'No metadata found on this backup');
|
|
35
|
+
return {
|
|
36
|
+
...backupInfo.metadata,
|
|
37
|
+
remoteCollectionNames: (RuntimeModules()
|
|
38
|
+
.filter((module) => !!module.dbDef?.dbKey)).map(_module => _module.dbDef.dbKey)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
async pushToEnv(body) {
|
|
42
|
+
const remoteUrls = {
|
|
43
|
+
dev: 'https://us-central1-shopify-manager-tool-dev.cloudfunctions.net/api',
|
|
44
|
+
prod: 'https://mng.be.petitfawn.com'
|
|
45
|
+
};
|
|
46
|
+
const url = remoteUrls[body.env];
|
|
47
|
+
const sessionId = MemKey_HttpRequest.get().headers[HeaderKey_Authorization];
|
|
48
|
+
const module = RuntimeModules().find((module) => module.dbModule?.dbDef?.dbKey === body.moduleName);
|
|
49
|
+
const upsertAll = module.apiDef.v1.upsertAll;
|
|
50
|
+
const response = await AxiosHttpModule
|
|
51
|
+
.createRequest({ ...upsertAll, fullUrl: url + '/' + upsertAll.path, timeout: 5 * Minute })
|
|
52
|
+
.setBody(body.items)
|
|
53
|
+
.setUrlParams(body.items)
|
|
54
|
+
.addHeader(HeaderKey_Authorization, sessionId)
|
|
55
|
+
.executeSync(true);
|
|
56
|
+
console.log(response);
|
|
57
|
+
}
|
|
58
|
+
createBackup = async () => {
|
|
59
|
+
return ModuleBE_BackupDocDB.initiateBackup(true);
|
|
60
|
+
};
|
|
61
|
+
getLatestBackupId = async () => {
|
|
62
|
+
const backups = await ModuleBE_BackupDocDB.collection.query.custom({ orderBy: [{ key: "__created", order: "desc" }], limit: 1 });
|
|
63
|
+
const latestBackup = backups[0];
|
|
64
|
+
if (!latestBackup)
|
|
65
|
+
throw HttpCodes._4XX.ENTITY_DOESNT_EXISTS("No backup found");
|
|
66
|
+
const latestBackupId = latestBackup?._id;
|
|
67
|
+
return { latestBackupId: latestBackupId };
|
|
68
|
+
};
|
|
69
|
+
syncFromEnvBackup = async (body) => {
|
|
70
|
+
if (!this.config.allowSyncEnv)
|
|
71
|
+
throw new MUSTNeverHappenException(`SyncEnv is disabled on this env- to sync into this env, add 'allowSyncEnv: true'.`);
|
|
72
|
+
//CleanSync means deleting collections before syncing them
|
|
73
|
+
if (!this.config.allowCleanSync && body.cleanSync)
|
|
74
|
+
throw new MUSTNeverHappenException(`CleanSync is disabled on this env- to CleanSync into this env, add 'allowCleanSync: true'.`);
|
|
75
|
+
if (Storm.getInstance().getEnvironment().toLowerCase() === 'prod' && body.env.toLowerCase() !== 'prod')
|
|
76
|
+
throw new MUSTNeverHappenException('MUST NEVER SYNC ENV THAT IS NOT PROD TO PROD!!');
|
|
77
|
+
if (this.config.allowedEnvsToSyncFrom && !this.config.allowedEnvsToSyncFrom.includes(body.env))
|
|
78
|
+
throw new MUSTNeverHappenException(`Env ${Storm.getInstance().getEnvironment()
|
|
79
|
+
.toLowerCase()} doesn't have env ${body.env} in it's allowedEnvsToSyncFrom list.`);
|
|
80
|
+
this.logInfoBold('Received API call Fetch From Env!');
|
|
81
|
+
this.logInfo(`Origin env: ${body.env}, backupId: ${body.backupId}`);
|
|
82
|
+
let startTime = undefined; // required for log
|
|
83
|
+
let endTime = undefined; // required for log
|
|
84
|
+
if (this.config.shouldBackupBeforeSync) {
|
|
85
|
+
this.logInfo(`---- Creating Backup... ----`);
|
|
86
|
+
startTime = performance.now(); // required for log
|
|
87
|
+
await this.createBackup();
|
|
88
|
+
endTime = performance.now(); // required for log
|
|
89
|
+
this.logInfo(`Backup took ${((endTime - startTime) / 1000).toFixed(3)} seconds`);
|
|
90
|
+
}
|
|
91
|
+
if (body.cleanSync) {
|
|
92
|
+
this.logInfo(`---- Cleaning Collections From DB... ----`);
|
|
93
|
+
//Delete all modules specified for syncing
|
|
94
|
+
const modulesToDelete = RuntimeModules().filter((module) => body.selectedModules.includes(module.dbDef?.dbKey));
|
|
95
|
+
for (const module of modulesToDelete) {
|
|
96
|
+
await module.collection.delete.yes.iam.sure.iwant.todelete.the.collection.delete();
|
|
97
|
+
this.logInfo(`---- Cleaned Collection ${module.dbDef.dbKey} ----`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//Prepare Syncing data
|
|
101
|
+
const backupInfo = await this.getBackupInfo(body);
|
|
102
|
+
const stream = await ModuleBE_BackupDocDB.createBackupReadStream(backupInfo);
|
|
103
|
+
const collectionFilter = new SyncCollectionFilter(body.selectedModules);
|
|
104
|
+
const collectionWriter = new CollectionBatchWriter(body.chunkSize);
|
|
105
|
+
this.logInfo(`---- Syncing Collections From Backup... ----`);
|
|
106
|
+
startTime = performance.now();
|
|
107
|
+
await new Promise((resolve, reject) => {
|
|
108
|
+
stream
|
|
109
|
+
.pipe(collectionFilter)
|
|
110
|
+
.pipe(collectionWriter)
|
|
111
|
+
.on('finish', () => resolve());
|
|
112
|
+
});
|
|
113
|
+
endTime = performance.now();
|
|
114
|
+
this.logInfo(`Syncing Collections took ${((endTime - startTime) / 1000).toFixed(3)} seconds`);
|
|
115
|
+
this.logInfo(`---- Syncing Other Modules... ----`);
|
|
116
|
+
await dispatch_OnSyncEnvCompleted.dispatchModuleAsync(body.env, this.config.urlMap[body.env], this.config.sessionMap[body.env]);
|
|
117
|
+
this.logInfo(`---- DONE Syncing Other Modules----`);
|
|
118
|
+
if (this.config.shouldBackupBeforeSync && endTime !== undefined && startTime !== undefined)
|
|
119
|
+
this.logInfo(`(Backup took ${((endTime - startTime) / 1000).toFixed(3)} seconds)`);
|
|
120
|
+
};
|
|
121
|
+
async getBackupInfo(queryParams) {
|
|
122
|
+
const { backupId, env } = queryParams;
|
|
123
|
+
if (!env)
|
|
124
|
+
throw new BadImplementationException(`Did not receive env in the fetch from env api call!`);
|
|
125
|
+
return ModuleBE_BackupDocDB.getBackupInfo(backupId, this.config.urlMap[env], this.config.sessionMap[env]);
|
|
126
|
+
}
|
|
127
|
+
syncFirebaseFromBackup = async (queryParams) => {
|
|
128
|
+
try {
|
|
129
|
+
this.logDebug('Getting the firebase backup file');
|
|
130
|
+
const firebaseSessionAdmin = ModuleBE_Firebase.createAdminSession();
|
|
131
|
+
const backupInfo = await this.getBackupInfo(queryParams);
|
|
132
|
+
const database = firebaseSessionAdmin.getDatabase();
|
|
133
|
+
this.logDebug('Reading the file from storage');
|
|
134
|
+
const signedUrlDef = {
|
|
135
|
+
method: HttpMethod.GET,
|
|
136
|
+
path: '',
|
|
137
|
+
fullUrl: backupInfo.firebaseSignedUrl
|
|
138
|
+
};
|
|
139
|
+
const firebaseFile = await AxiosHttpModule
|
|
140
|
+
.createRequest(signedUrlDef)
|
|
141
|
+
.executeSync();
|
|
142
|
+
this.logDebug('Setting the file in firebase database');
|
|
143
|
+
await database.set('/', firebaseFile);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
throw new ApiException(500, err);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export const ModuleBE_SyncEnv = new ModuleBE_SyncEnv_Class();
|
|
151
|
+
class SyncCollectionFilter extends Transform {
|
|
152
|
+
allowedDbKeys;
|
|
153
|
+
constructor(allowedDbKeys) {
|
|
154
|
+
super({ objectMode: true });
|
|
155
|
+
this.allowedDbKeys = allowedDbKeys;
|
|
156
|
+
}
|
|
157
|
+
_transform(chunk, encoding, callback) {
|
|
158
|
+
if (this.allowedDbKeys.includes(chunk.dbKey)) {
|
|
159
|
+
this.push(chunk);
|
|
160
|
+
}
|
|
161
|
+
callback();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
class CollectionBatchWriter extends Writable {
|
|
165
|
+
itemCount = 0;
|
|
166
|
+
paginationSize;
|
|
167
|
+
firestore;
|
|
168
|
+
batchWriter;
|
|
169
|
+
modules;
|
|
170
|
+
constructor(paginationSize) {
|
|
171
|
+
super({ objectMode: true });
|
|
172
|
+
this.paginationSize = paginationSize;
|
|
173
|
+
const firebaseSessionAdmin = ModuleBE_Firebase.createAdminSession();
|
|
174
|
+
this.firestore = firebaseSessionAdmin.getFirestoreV3().firestore;
|
|
175
|
+
this.batchWriter = this.firestore.batch();
|
|
176
|
+
this.modules = arrayToMap(RuntimeModules()
|
|
177
|
+
.filter((module) => !(!module || !module.dbDef)), module => module.dbDef.dbKey);
|
|
178
|
+
}
|
|
179
|
+
async _write(chunk, encoding, callback) {
|
|
180
|
+
try {
|
|
181
|
+
const module = this.modules[chunk.dbKey];
|
|
182
|
+
if (!module) {
|
|
183
|
+
ModuleBE_SyncEnv.logWarning(`Could not get module for chunk with dbKey ${chunk.dbKey}`);
|
|
184
|
+
callback();
|
|
185
|
+
}
|
|
186
|
+
const collectionName = module.dbDef.backend.name;
|
|
187
|
+
const docRef = this.firestore.doc(`${collectionName}/${chunk._id}`);
|
|
188
|
+
const data = JSON.parse(chunk.document);
|
|
189
|
+
this.batchWriter.set(docRef, data);
|
|
190
|
+
this.itemCount++;
|
|
191
|
+
if (this.itemCount === this.paginationSize) {
|
|
192
|
+
const prevBatchWriter = this.batchWriter;
|
|
193
|
+
this.batchWriter = this.firestore.batch();
|
|
194
|
+
this.itemCount = 0;
|
|
195
|
+
await prevBatchWriter.commit();
|
|
196
|
+
}
|
|
197
|
+
callback();
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async _final(callback) {
|
|
204
|
+
try {
|
|
205
|
+
await this.batchWriter.commit();
|
|
206
|
+
callback();
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
callback(err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { FirestoreQuery } from '@nu-art/firebase-shared';
|
|
2
|
+
import { DB_Object, Module, ResolvableContent, TypedMap, UniqueId } from '@nu-art/ts-common';
|
|
3
|
+
import { firestore } from 'firebase-admin';
|
|
4
|
+
import { ModuleBE_BaseDB } from '../db-api-gen/ModuleBE_BaseDB.js';
|
|
5
|
+
import { DeltaSyncModule, FullSyncModule, NoNeedToSyncModule, SyncDataFirebaseState } from '@nu-art/thunderstorm-shared/sync-manager/types';
|
|
6
|
+
import { DBProto_DeletedDoc } from '@nu-art/thunderstorm-shared';
|
|
7
|
+
import { OnSyncEnvCompleted } from '../sync-env/ModuleBE_SyncEnv.js';
|
|
8
|
+
import { OnModuleCleanupV2 } from '../../_entity.js';
|
|
9
|
+
import { FirestoreCollectionV3 } from '@nu-art/firebase-backend/firestore-v3/FirestoreCollectionV3';
|
|
10
|
+
import Transaction = firestore.Transaction;
|
|
11
|
+
type DeletedDBItem = DB_Object & {
|
|
12
|
+
__collectionName: string;
|
|
13
|
+
__docId: UniqueId;
|
|
14
|
+
};
|
|
15
|
+
type Config = {
|
|
16
|
+
retainDeletedCount: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* # ModuleBE_SyncManager
|
|
20
|
+
*
|
|
21
|
+
* ## <ins>Description:</ins>
|
|
22
|
+
* This module manages all the {@link BaseDB_Module} updates and deleted items in order to allow incremental sync of items with clients
|
|
23
|
+
*
|
|
24
|
+
* ## <ins>Config:</ins>
|
|
25
|
+
*
|
|
26
|
+
* ```json
|
|
27
|
+
* "ModuleBE_SyncManager" : {
|
|
28
|
+
* retainDeletedCount: 100
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class ModuleBE_SyncManager_Class extends Module<Config> implements OnModuleCleanupV2, OnSyncEnvCompleted {
|
|
33
|
+
collection: FirestoreCollectionV3<DBProto_DeletedDoc>;
|
|
34
|
+
private database;
|
|
35
|
+
private dbModules;
|
|
36
|
+
smartSyncApi: import("../_imports.js")._ServerBodyApi<import("@nu-art/thunderstorm-shared").BodyApi<{
|
|
37
|
+
modules: (NoNeedToSyncModule | DeltaSyncModule | FullSyncModule)[];
|
|
38
|
+
}, {
|
|
39
|
+
modules: import("@nu-art/thunderstorm-shared/sync-manager/types").SyncDbData[];
|
|
40
|
+
}>>;
|
|
41
|
+
private resolvableFirebaseBasePath;
|
|
42
|
+
constructor();
|
|
43
|
+
__onSyncEnvCompleted(env: string, baseUrl: string, requiredHeaders: TypedMap<string>): Promise<void>;
|
|
44
|
+
init(): void;
|
|
45
|
+
private calculateSmartSync;
|
|
46
|
+
getOrCreateSyncData: () => Promise<SyncDataFirebaseState>;
|
|
47
|
+
private prepareItemToDelete;
|
|
48
|
+
onItemsDeleted(collectionName: string, items: DB_Object[], uniqueKeys?: string[], transaction?: Transaction): Promise<void>;
|
|
49
|
+
queryDeleted(collectionName: string, query: FirestoreQuery<DB_Object>): Promise<DeletedDBItem[]>;
|
|
50
|
+
__onCleanupInvokedV2: () => Promise<void>;
|
|
51
|
+
getFullSyncData: () => Promise<SyncDataFirebaseState>;
|
|
52
|
+
setLastUpdated(collectionName: string, lastUpdated: number): Promise<void>;
|
|
53
|
+
setOldestDeleted(collectionName: string, oldestDeleted: number): Promise<void>;
|
|
54
|
+
setModuleFilter: (filter: (modules: (ModuleBE_BaseDB<any>)[]) => Promise<(ModuleBE_BaseDB<any>)[]>) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Set function that allows to set a custom resolver for the rtdb node path
|
|
57
|
+
* @param resolvablePath The resolver for the node path
|
|
58
|
+
*/
|
|
59
|
+
setResolvablePath: (resolvablePath: ResolvableContent<string>) => void;
|
|
60
|
+
private filterModules;
|
|
61
|
+
}
|
|
62
|
+
export declare const ModuleBE_SyncManager: ModuleBE_SyncManager_Class;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Database API Generator is a utility library for Thunderstorm.
|
|
3
|
+
*
|
|
4
|
+
* Given proper configurations it will dynamically generate APIs to your Firestore
|
|
5
|
+
* collections, will assert uniqueness and restrict deletion... and more
|
|
6
|
+
*
|
|
7
|
+
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
8
|
+
*
|
|
9
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
* you may not use this file except in compliance with the License.
|
|
11
|
+
* You may obtain a copy of the License at
|
|
12
|
+
*
|
|
13
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
*
|
|
15
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
* See the License for the specific language governing permissions and
|
|
19
|
+
* limitations under the License.
|
|
20
|
+
*/
|
|
21
|
+
import { _EmptyQuery } from '@nu-art/firebase-shared';
|
|
22
|
+
import { ModuleBE_Firebase } from '@nu-art/firebase-backend';
|
|
23
|
+
import { __stringify, arrayToMap, currentTimeMillis, dispatch_onApplicationException, exists, filterDuplicates, filterInstances, LogLevel, Module, resolveContent, RuntimeModules } from '@nu-art/ts-common';
|
|
24
|
+
import { createBodyServerApi } from '../../core/typed-api.js';
|
|
25
|
+
import { addRoutes } from '../ModuleBE_APIs.js';
|
|
26
|
+
import { SmartSync_DeltaSync, SmartSync_FullSync, SmartSync_UpToDateSync } from '@nu-art/thunderstorm-shared/sync-manager/types';
|
|
27
|
+
import { DBDef_DeletedDoc } from '@nu-art/thunderstorm-shared';
|
|
28
|
+
import { ApiDef_SyncManager } from '@nu-art/thunderstorm-shared/sync-manager/apis';
|
|
29
|
+
/**
|
|
30
|
+
* # ModuleBE_SyncManager
|
|
31
|
+
*
|
|
32
|
+
* ## <ins>Description:</ins>
|
|
33
|
+
* This module manages all the {@link BaseDB_Module} updates and deleted items in order to allow incremental sync of items with clients
|
|
34
|
+
*
|
|
35
|
+
* ## <ins>Config:</ins>
|
|
36
|
+
*
|
|
37
|
+
* ```json
|
|
38
|
+
* "ModuleBE_SyncManager" : {
|
|
39
|
+
* retainDeletedCount: 100
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class ModuleBE_SyncManager_Class extends Module {
|
|
44
|
+
collection;
|
|
45
|
+
database;
|
|
46
|
+
dbModules;
|
|
47
|
+
smartSyncApi;
|
|
48
|
+
resolvableFirebaseBasePath = `/state/${this.getName()}`;
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
this.setMinLevel(LogLevel.Debug);
|
|
52
|
+
this.smartSyncApi = createBodyServerApi(ApiDef_SyncManager.v1.smartSync, this.calculateSmartSync);
|
|
53
|
+
this.setDefaultConfig({ retainDeletedCount: 1000 });
|
|
54
|
+
}
|
|
55
|
+
async __onSyncEnvCompleted(env, baseUrl, requiredHeaders) {
|
|
56
|
+
await this.database.delete(resolveContent(this.resolvableFirebaseBasePath));
|
|
57
|
+
}
|
|
58
|
+
init() {
|
|
59
|
+
const firestore = ModuleBE_Firebase.createAdminSession().getFirestoreV3();
|
|
60
|
+
this.collection = firestore.getCollection(DBDef_DeletedDoc);
|
|
61
|
+
this.dbModules = RuntimeModules().filter(module => (module.ModuleBE_BaseDBV2));
|
|
62
|
+
this.database = ModuleBE_Firebase.createAdminSession().getDatabase();
|
|
63
|
+
addRoutes([this.smartSyncApi]);
|
|
64
|
+
}
|
|
65
|
+
calculateSmartSync = async (body) => {
|
|
66
|
+
const frontendCollectionNames = body.modules.map(item => item.dbKey);
|
|
67
|
+
this.logVerbose(`Modules wanted: ${__stringify(frontendCollectionNames)}`);
|
|
68
|
+
const permissibleModules = await this.filterModules(this.dbModules.filter(dbModule => frontendCollectionNames.includes(dbModule.dbDef.dbKey)));
|
|
69
|
+
const modulesAllowed = filterInstances(permissibleModules.map(_module => _module.dbDef.dbKey));
|
|
70
|
+
this.logVerbose(`Modules found: ${__stringify(modulesAllowed)}`);
|
|
71
|
+
this.logVerbose(`Modules not found: ${frontendCollectionNames.filter(collectionName => modulesAllowed.includes(collectionName))}`);
|
|
72
|
+
const dbNameToModuleMap = arrayToMap(permissibleModules, (item) => item.dbDef.dbKey);
|
|
73
|
+
const syncDataResponse = [];
|
|
74
|
+
const upToDateSyncData = await this.getOrCreateSyncData();
|
|
75
|
+
// For each module, create the response, which says what type of sync it needs: none, delta or full.
|
|
76
|
+
await Promise.all(body.modules.map(async (syncRequest) => {
|
|
77
|
+
const moduleToCheck = dbNameToModuleMap[syncRequest.dbKey];
|
|
78
|
+
if (!moduleToCheck)
|
|
79
|
+
return this.logError(`Calculating collections to sync, failing to find dbKey: ${syncRequest.dbKey}`);
|
|
80
|
+
const remoteSyncData = upToDateSyncData[syncRequest.dbKey] ?? { lastUpdated: 0, oldestDeleted: 0 };
|
|
81
|
+
// Local has no sync data, or it's too old - tell local to send a full sync request for this module
|
|
82
|
+
if (syncRequest.lastUpdated === 0 && remoteSyncData.lastUpdated > 0 || exists(remoteSyncData.oldestDeleted) && remoteSyncData.oldestDeleted > syncRequest.lastUpdated) {
|
|
83
|
+
// full sync
|
|
84
|
+
syncDataResponse.push({
|
|
85
|
+
dbKey: syncRequest.dbKey,
|
|
86
|
+
sync: SmartSync_FullSync,
|
|
87
|
+
lastUpdated: remoteSyncData.lastUpdated,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Same lastUpdated timestamp in local and remote, no need to sync
|
|
92
|
+
if (syncRequest.lastUpdated === remoteSyncData.lastUpdated) {
|
|
93
|
+
// no sync
|
|
94
|
+
syncDataResponse.push({
|
|
95
|
+
dbKey: syncRequest.dbKey,
|
|
96
|
+
sync: SmartSync_UpToDateSync,
|
|
97
|
+
lastUpdated: remoteSyncData.lastUpdated
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Different lastUpdated timestamp in local and remote - tell local to send a delta sync request for this module
|
|
102
|
+
if (syncRequest.lastUpdated !== remoteSyncData.lastUpdated) {
|
|
103
|
+
// delta sync
|
|
104
|
+
let toUpdate = [];
|
|
105
|
+
try {
|
|
106
|
+
toUpdate = await moduleToCheck.query.where({ __updated: { $gte: syncRequest.lastUpdated } });
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
this.logWarningBold(`Module assumed to be normal DB module: ${moduleToCheck.getName()}, collection:${moduleToCheck.dbDef.dbKey}`);
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
const itemsToReturn = {
|
|
113
|
+
toUpdate: toUpdate,
|
|
114
|
+
toDelete: await this.queryDeleted(syncRequest.dbKey, { where: { __updated: { $gte: syncRequest.lastUpdated } } })
|
|
115
|
+
};
|
|
116
|
+
syncDataResponse.push({
|
|
117
|
+
dbKey: syncRequest.dbKey,
|
|
118
|
+
sync: SmartSync_DeltaSync,
|
|
119
|
+
lastUpdated: remoteSyncData.lastUpdated,
|
|
120
|
+
items: itemsToReturn
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}));
|
|
124
|
+
return {
|
|
125
|
+
modules: syncDataResponse
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
getOrCreateSyncData = async () => {
|
|
129
|
+
this.logVerbose('Current node path', `${resolveContent(this.resolvableFirebaseBasePath)}/syncData`);
|
|
130
|
+
const syncDataRef = this.database.ref(`${resolveContent(this.resolvableFirebaseBasePath)}/syncData`);
|
|
131
|
+
const rtdbSyncData = await syncDataRef.get({});
|
|
132
|
+
const dbModuleDbKeys = filterInstances(this.dbModules.map(module => module.dbDef.dbKey));
|
|
133
|
+
this.logVerbose(`BE DB Modules: ${__stringify(dbModuleDbKeys)}`);
|
|
134
|
+
const missingModules = this.dbModules.filter(dbModule => {
|
|
135
|
+
const dbBE_SyncData = rtdbSyncData[dbModule.dbDef.dbKey];
|
|
136
|
+
if (!dbBE_SyncData)
|
|
137
|
+
return true;
|
|
138
|
+
// const dbFE_SyncData = body.modules.find(module => module.dbName === dbModule.getCollectionName());
|
|
139
|
+
// if (!dbFE_SyncData)
|
|
140
|
+
return false;
|
|
141
|
+
// return dbFE_SyncData.lastUpdated > dbBE_SyncData.lastUpdated;
|
|
142
|
+
});
|
|
143
|
+
if (missingModules.length) {
|
|
144
|
+
this.logWarning(`Syncing missing modules: `, missingModules.map(module => module.dbDef.dbKey).sort());
|
|
145
|
+
const query = { limit: 1, orderBy: [{ key: '__updated', order: 'desc' }] };
|
|
146
|
+
const newestItems = (await Promise.all(missingModules.map(async (missingModule) => {
|
|
147
|
+
try {
|
|
148
|
+
return (await missingModule.query.unManipulatedQuery(query))[0];
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
dispatch_onApplicationException.dispatchModule(e, this);
|
|
152
|
+
this.logError(e);
|
|
153
|
+
}
|
|
154
|
+
})));
|
|
155
|
+
newestItems.forEach((item, index) => rtdbSyncData[missingModules[index].dbDef.dbKey] = { lastUpdated: item?.__updated || 0 });
|
|
156
|
+
await syncDataRef.set(rtdbSyncData);
|
|
157
|
+
}
|
|
158
|
+
return rtdbSyncData;
|
|
159
|
+
};
|
|
160
|
+
prepareItemToDelete = (collectionName, item, uniqueKeys = ['_id']) => {
|
|
161
|
+
const { _id, __updated, __created, _v } = item;
|
|
162
|
+
const deletedItem = {
|
|
163
|
+
__docId: _id,
|
|
164
|
+
__updated,
|
|
165
|
+
__created,
|
|
166
|
+
_v,
|
|
167
|
+
__collectionName: collectionName
|
|
168
|
+
};
|
|
169
|
+
uniqueKeys.forEach(key => {
|
|
170
|
+
//Don't replace the _id, some items in the system have a calculated _id and can be deleted and created over and over.
|
|
171
|
+
if (key === '_id')
|
|
172
|
+
return;
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
deletedItem[key] = item[key] || '';
|
|
175
|
+
});
|
|
176
|
+
return deletedItem;
|
|
177
|
+
};
|
|
178
|
+
async onItemsDeleted(collectionName, items, uniqueKeys = ['_id'], transaction) {
|
|
179
|
+
const toInsert = items.map(item => this.prepareItemToDelete(collectionName, item, uniqueKeys));
|
|
180
|
+
const now = currentTimeMillis();
|
|
181
|
+
toInsert.forEach(item => item.__updated = now);
|
|
182
|
+
await this.collection.create.all(toInsert, transaction);
|
|
183
|
+
const deletedCountRef = this.database.ref(`${resolveContent(this.resolvableFirebaseBasePath)}/deletedCount`);
|
|
184
|
+
let deletedCount = await deletedCountRef.get(0);
|
|
185
|
+
deletedCount += items.length;
|
|
186
|
+
await deletedCountRef.set(deletedCount);
|
|
187
|
+
}
|
|
188
|
+
async queryDeleted(collectionName, query) {
|
|
189
|
+
const finalQuery = {
|
|
190
|
+
...query,
|
|
191
|
+
where: { ...query.where, __collectionName: collectionName }
|
|
192
|
+
};
|
|
193
|
+
const deletedItems = await this.collection.query.custom(finalQuery);
|
|
194
|
+
deletedItems.forEach(_item => _item._id = _item.__docId || _item._id);
|
|
195
|
+
return deletedItems;
|
|
196
|
+
}
|
|
197
|
+
__onCleanupInvokedV2 = async () => {
|
|
198
|
+
if (!this.config.retainDeletedCount)
|
|
199
|
+
return this.logWarning('Will not run cleanup of deleted values:\n No "retainDeletedCount" was specified in config..');
|
|
200
|
+
const deletedCountRef = this.database.ref(`${resolveContent(this.resolvableFirebaseBasePath)}/deletedCount`);
|
|
201
|
+
let deletedCount = await deletedCountRef.get();
|
|
202
|
+
if (deletedCount === undefined) {
|
|
203
|
+
deletedCount = (await this.collection.query.custom(_EmptyQuery)).length;
|
|
204
|
+
await deletedCountRef.set(deletedCount);
|
|
205
|
+
}
|
|
206
|
+
const toDeleteCount = deletedCount - this.config.retainDeletedCount;
|
|
207
|
+
if (toDeleteCount <= 0)
|
|
208
|
+
return;
|
|
209
|
+
this.logDebug('Docs to delete', deletedCount);
|
|
210
|
+
this.logDebug('Docs to retain', this.config.retainDeletedCount);
|
|
211
|
+
const deleted = await this.collection.delete.query({
|
|
212
|
+
limit: toDeleteCount,
|
|
213
|
+
orderBy: [{ key: '__updated', order: 'asc' }]
|
|
214
|
+
});
|
|
215
|
+
let newDeletedCount = deletedCount - deleted.length;
|
|
216
|
+
if (deleted.length !== toDeleteCount) {
|
|
217
|
+
this.logError(`Expected to delete ${toDeleteCount} but actually deleted ${deleted.length}`);
|
|
218
|
+
newDeletedCount = (await this.collection.query.custom(_EmptyQuery)).length;
|
|
219
|
+
}
|
|
220
|
+
await deletedCountRef.set(newDeletedCount);
|
|
221
|
+
const map = deleted.map(item => item.__collectionName);
|
|
222
|
+
const keys = filterDuplicates(map);
|
|
223
|
+
await Promise.all(keys.map(key => {
|
|
224
|
+
const newestDeletedItem = deleted.find(deletedItem => deletedItem.__collectionName === key);
|
|
225
|
+
this.logDebug(`setting oldest deleted timestamp ${key} = ${newestDeletedItem.__updated}`);
|
|
226
|
+
return this.setOldestDeleted(key, newestDeletedItem.__updated);
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
getFullSyncData = async () => {
|
|
230
|
+
const syncDataRef = this.database.ref(`${resolveContent(this.resolvableFirebaseBasePath)}/syncData`);
|
|
231
|
+
return (await syncDataRef.get({}));
|
|
232
|
+
};
|
|
233
|
+
async setLastUpdated(collectionName, lastUpdated) {
|
|
234
|
+
return this.database.patch(`${resolveContent(this.resolvableFirebaseBasePath)}/syncData/${collectionName}`, { lastUpdated });
|
|
235
|
+
}
|
|
236
|
+
async setOldestDeleted(collectionName, oldestDeleted) {
|
|
237
|
+
return this.database.patch(`${resolveContent(this.resolvableFirebaseBasePath)}/deletedCount/${collectionName}`, { oldestDeleted });
|
|
238
|
+
}
|
|
239
|
+
setModuleFilter = (filter) => {
|
|
240
|
+
const previousFilter = this.filterModules;
|
|
241
|
+
this.filterModules = async (modules) => filter(await previousFilter(modules));
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Set function that allows to set a custom resolver for the rtdb node path
|
|
245
|
+
* @param resolvablePath The resolver for the node path
|
|
246
|
+
*/
|
|
247
|
+
setResolvablePath = (resolvablePath) => {
|
|
248
|
+
this.resolvableFirebaseBasePath = resolvablePath;
|
|
249
|
+
};
|
|
250
|
+
filterModules = async (modules) => {
|
|
251
|
+
return modules;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export const ModuleBE_SyncManager = new ModuleBE_SyncManager_Class();
|
package/package.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nu-art/thunderstorm-backend",
|
|
3
|
+
"version": "0.400.5",
|
|
4
|
+
"description": "Thunderstorm Backend",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"TacB0sS",
|
|
7
|
+
"express",
|
|
8
|
+
"infra",
|
|
9
|
+
"nu-art",
|
|
10
|
+
"thunderstorm",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/nu-art-js/thunderstorm",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/nu-art-js/thunderstorm/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+ssh://git@github.com:nu-art-js/thunderstorm.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": "TacB0sS",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc"
|
|
25
|
+
},
|
|
26
|
+
"contributors": [
|
|
27
|
+
{
|
|
28
|
+
"name": "TacB0sS"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Cipher",
|
|
32
|
+
"url": "https://www.linkedin.com/in/itay-leybovich-470b87229/"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"directory": "dist",
|
|
37
|
+
"linkDirectory": true
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@nu-art/thunderstorm-shared": "0.400.5",
|
|
41
|
+
"@nu-art/firebase-backend": "0.400.5",
|
|
42
|
+
"@nu-art/firebase-shared": "0.400.5",
|
|
43
|
+
"@nu-art/google-services-backend": "0.400.5",
|
|
44
|
+
"@nu-art/ts-common": "0.400.5",
|
|
45
|
+
"@nu-art/ts-styles": "0.400.5",
|
|
46
|
+
"abort-controller": "^3.0.0",
|
|
47
|
+
"axios": "^1.13.1",
|
|
48
|
+
"body-parser": "^1.19.0",
|
|
49
|
+
"browserify-zlib": "^0.2.0",
|
|
50
|
+
"buffer": "^6.0.3",
|
|
51
|
+
"compression": "^1.7.4",
|
|
52
|
+
"cors": "^2.8.5",
|
|
53
|
+
"crypto-browserify": "^3.12.0",
|
|
54
|
+
"csstype": "^3.0.0",
|
|
55
|
+
"express": "^4.18.2",
|
|
56
|
+
"firebase": "^11.9.0",
|
|
57
|
+
"firebase-admin": "13.4.0",
|
|
58
|
+
"firebase-functions": "6.3.2",
|
|
59
|
+
"history": "^4.9.0",
|
|
60
|
+
"moment": "^2.29.4",
|
|
61
|
+
"pako": "^2.1.0",
|
|
62
|
+
"papaparse": "^5.4.1",
|
|
63
|
+
"qs": "^6.6.0",
|
|
64
|
+
"react": "^18.0.0",
|
|
65
|
+
"react-dom": "^18.0.0",
|
|
66
|
+
"react-router-dom": "^6.9.0",
|
|
67
|
+
"react-virtualized-auto-sizer": "^1.0.12",
|
|
68
|
+
"react-window": "^1.8.8",
|
|
69
|
+
"request": "^2.88.0",
|
|
70
|
+
"stream-browserify": "^3.0.0",
|
|
71
|
+
"util": "^0.12.4"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/papaparse": "^5.3.14",
|
|
75
|
+
"@types/pako": "^2.0.0",
|
|
76
|
+
"@types/cors": "^2.8.17",
|
|
77
|
+
"@types/react": "^18.0.0",
|
|
78
|
+
"@types/react-dom": "^18.0.0",
|
|
79
|
+
"@types/react-router": "^5.1.20",
|
|
80
|
+
"@types/react-router-dom": "^5.3.3",
|
|
81
|
+
"@types/compression": "^1.7.0",
|
|
82
|
+
"@types/express": "^4.17.17",
|
|
83
|
+
"@types/history": "^4.7.2",
|
|
84
|
+
"@types/request": "^2.48.1",
|
|
85
|
+
"@types/chrome": "^0.0.202",
|
|
86
|
+
"@types/react-window": "^1.8.5",
|
|
87
|
+
"@types/react-virtualized-auto-sizer": "^1.0.1"
|
|
88
|
+
},
|
|
89
|
+
"unitConfig": {
|
|
90
|
+
"type": "typescript-lib"
|
|
91
|
+
},
|
|
92
|
+
"type": "module",
|
|
93
|
+
"exports": {
|
|
94
|
+
".": {
|
|
95
|
+
"types": "./index.d.ts",
|
|
96
|
+
"import": "./index.js"
|
|
97
|
+
},
|
|
98
|
+
"./*": {
|
|
99
|
+
"types": "./*.d.ts",
|
|
100
|
+
"import": "./*.js"
|
|
101
|
+
},
|
|
102
|
+
"./styles": "./styles.scss"
|
|
103
|
+
}
|
|
104
|
+
}
|
package/shared.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@nu-art/thunderstorm-shared';
|
package/shared.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Thunderstorm is a full web app framework!
|
|
3
|
+
*
|
|
4
|
+
* Typescript & Express backend infrastructure that natively runs on firebase function
|
|
5
|
+
* Typescript & React frontend infrastructure
|
|
6
|
+
*
|
|
7
|
+
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
8
|
+
*
|
|
9
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
* you may not use this file except in compliance with the License.
|
|
11
|
+
* You may obtain a copy of the License at
|
|
12
|
+
*
|
|
13
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
*
|
|
15
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
* See the License for the specific language governing permissions and
|
|
19
|
+
* limitations under the License.
|
|
20
|
+
*/
|
|
21
|
+
export * from '@nu-art/thunderstorm-shared';
|