querysub 0.152.0 → 0.154.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.
Files changed (65) hide show
  1. package/package.json +6 -6
  2. package/src/-b-authorities/cloudflareHelpers.ts +11 -2
  3. package/src/3-path-functions/PathFunctionRunner.ts +168 -97
  4. package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
  5. package/src/3-path-functions/pathFunctionLoader.ts +11 -6
  6. package/src/3-path-functions/syncSchema.ts +10 -1
  7. package/src/4-deploy/edgeBootstrap.ts +10 -1
  8. package/src/4-querysub/Querysub.ts +77 -3
  9. package/src/4-querysub/QuerysubController.ts +22 -2
  10. package/src/4-querysub/permissions.ts +33 -2
  11. package/src/4-querysub/querysubPrediction.ts +52 -18
  12. package/src/archiveapps/archiveGCEntry.tsx +38 -0
  13. package/src/archiveapps/archiveJoinEntry.ts +121 -0
  14. package/src/archiveapps/archiveMergeEntry.tsx +47 -0
  15. package/src/archiveapps/compressTest.tsx +59 -0
  16. package/src/archiveapps/lockTest.ts +127 -0
  17. package/src/config.ts +5 -0
  18. package/src/diagnostics/managementPages.tsx +55 -0
  19. package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
  20. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
  21. package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
  22. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
  23. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
  24. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
  25. package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
  26. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
  27. package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
  28. package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
  29. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
  30. package/src/email/postmark.tsx +40 -0
  31. package/src/email/sendgrid.tsx +44 -0
  32. package/src/functional/UndoWatch.tsx +133 -0
  33. package/src/functional/diff.ts +858 -0
  34. package/src/functional/promiseCache.ts +67 -0
  35. package/src/functional/random.ts +9 -0
  36. package/src/functional/runCommand.ts +42 -0
  37. package/src/functional/runOnce.ts +7 -0
  38. package/src/functional/stats.ts +61 -0
  39. package/src/functional/throttleRerender.tsx +80 -0
  40. package/src/library-components/AspectSizedComponent.tsx +88 -0
  41. package/src/library-components/Histogram.tsx +338 -0
  42. package/src/library-components/InlinePopup.tsx +67 -0
  43. package/src/library-components/Notifications.tsx +153 -0
  44. package/src/library-components/RenderIfVisible.tsx +80 -0
  45. package/src/library-components/SimpleNotification.tsx +133 -0
  46. package/src/library-components/TabbedUI.tsx +39 -0
  47. package/src/library-components/animateAnyElement.tsx +65 -0
  48. package/src/library-components/errorNotifications.tsx +81 -0
  49. package/src/library-components/placeholder.ts +18 -0
  50. package/src/misc/format2.ts +48 -0
  51. package/src/misc.ts +33 -0
  52. package/src/misc2.ts +5 -0
  53. package/src/server.ts +2 -1
  54. package/src/storage/diskCache.ts +227 -0
  55. package/src/storage/diskCache2.ts +122 -0
  56. package/src/storage/fileSystemPointer.ts +72 -0
  57. package/src/user-implementation/LoginPage.tsx +78 -0
  58. package/src/user-implementation/RequireAuditPage.tsx +219 -0
  59. package/src/user-implementation/SecurityPage.tsx +212 -0
  60. package/src/user-implementation/UserPage.tsx +320 -0
  61. package/src/user-implementation/addSuperUser.ts +21 -0
  62. package/src/user-implementation/canSeeSource.ts +41 -0
  63. package/src/user-implementation/loginEmail.tsx +159 -0
  64. package/src/user-implementation/setEmailKey.ts +20 -0
  65. package/src/user-implementation/userData.ts +974 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.152.0",
3
+ "version": "0.154.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -24,7 +24,7 @@
24
24
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
25
25
  "pako": "^2.1.0",
26
26
  "preact": "^10.11.3",
27
- "socket-function": "^0.82.0",
27
+ "socket-function": "^0.87.0",
28
28
  "terser": "^5.31.0",
29
29
  "typesafecss": "^0.6.3",
30
30
  "yaml": "^2.5.0",
@@ -36,12 +36,12 @@
36
36
  "scripts": {
37
37
  "server": "yarn typenode ./src/server.ts",
38
38
  "serversharded": "yarn server --authority ./pathremain.json & yarn server --authority ./patha.json & yarn server --authority ./pathb.json & yarn server --authority ./pathc.json & yarn server --authority ./pathd.json",
39
- "function": "yarn typenode ./src/3-path-functions/PathFunctionRunnerMain.ts --function --local D:/repos/shard/",
40
- "deploy": "yarn typenode ./src/4-deploy/deployMain.ts --domain querysub.com --local D:/repos/shard/",
39
+ "function": "yarn typenode ./src/3-path-functions/PathFunctionRunnerMain.ts --function --local",
40
+ "deploy": "yarn typenode ./src/4-deploy/deployMain.ts --domain querysub.com --local",
41
41
  "type": "yarn tsc --noEmit",
42
42
  "depend": "yarn --silent depcruise src --include-only \"^src\" --config --output-type dot | dot -T svg > dependency-graph.svg",
43
- "test": "yarn typenode ./src/test/test.tsx --local D:/repos/shard/",
44
- "test2": "yarn typenode ./src/4-dom/qreactTest.tsx --local D:/repos/shard/"
43
+ "test": "yarn typenode ./src/test/test.tsx --local",
44
+ "test2": "yarn typenode ./src/4-dom/qreactTest.tsx --local"
45
45
  },
46
46
  "bin": {
47
47
  "querysub-deploy": "./bin/deploy.js",
@@ -1,12 +1,21 @@
1
1
  import { httpsRequest } from "socket-function/src/https";
2
2
  import { getArchives } from "../-a-archives/archives";
3
3
  import { lazy } from "socket-function/src/caching";
4
-
4
+ import { getStorageDir, getSubFolder } from "../fs";
5
+ import fs from "fs";
5
6
  export const keys = getArchives("keys");
6
7
 
7
8
  export const getCloudflareCreds = lazy(async (): Promise<{ key: string; email: string }> => {
8
9
  let credsJSON = await keys.get("cloudflare.json");
9
- if (!credsJSON) throw new Error(`b2:/keys/cloudflare.json is missing. It should contain { "key": "your-key", "email": "your-email" }`);
10
+ if (!credsJSON) {
11
+ let localPath = getStorageDir() + "cloudflare.json";
12
+ if (fs.existsSync(localPath)) {
13
+ credsJSON = fs.readFileSync(localPath);
14
+ await keys.set("cloudflare.json", credsJSON);
15
+ } else {
16
+ throw new Error(`b2:/keys/cloudflare.json is missing. It should contain { "key": "your-key", "email": "your-email" }`);
17
+ }
18
+ }
10
19
  return JSON.parse(credsJSON.toString());
11
20
  });
12
21
 
@@ -1,7 +1,7 @@
1
1
  import { delay, runInfinitePoll } from "socket-function/src/batching";
2
2
  import { cache } from "socket-function/src/caching";
3
3
  import { blue, magenta, red, yellow } from "socket-function/src/formatting/logColors";
4
- import { sha256HashBuffer, timeInHour } from "socket-function/src/misc";
4
+ import { sha256HashBuffer, timeInHour, timeInSecond } from "socket-function/src/misc";
5
5
  import { measureFnc } from "socket-function/src/profiling/measure";
6
6
  import { getLowUint32, getShortNumber } from "../bits";
7
7
  import { registerDynamicResource } from "../diagnostics/trackResources";
@@ -15,14 +15,15 @@ import { __getRoutingHash, authorityStorage, compareTime, debugTime, epochTime,
15
15
  import { getModuleFromSpec, watchModuleHotreloads } from "./pathFunctionLoader";
16
16
  import debugbreak from "debugbreak";
17
17
  import { parseArgs } from "./PathFunctionHelpers";
18
- import { PERMISSIONS_FUNCTION_ID, getExportPath } from "./syncSchema";
18
+ import { PERMISSIONS_FUNCTION_ID, getAllDevelopmentModulesIds, getDevelopmentModule, getExportPath, getModuleRelativePath, getSchemaObject } from "./syncSchema";
19
19
  import { formatTime } from "socket-function/src/formatting/format";
20
20
  import { getControllerNodeIdList, set_debug_getFunctionRunnerShards } from "../-g-core-values/NodeCapabilities";
21
21
  import { diskLog } from "../diagnostics/logs/diskLogger";
22
22
  import { FilterSelector, Filterable, doesMatch } from "../misc/filterable";
23
23
  import { SocketFunction } from "socket-function/SocketFunction";
24
24
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
25
- import { isLocal } from "../config";
25
+ import { getDomain, isLocal } from "../config";
26
+ import { getGitRef, getGitURL } from "../4-deploy/git";
26
27
 
27
28
  export const functionSchema = rawSchema<{
28
29
  [domainName: string]: {
@@ -220,7 +221,6 @@ export class PathFunctionRunner {
220
221
  Querysub.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Results, fullFraction);
221
222
  });
222
223
 
223
- let callsToCall = new Map<string, { call: CallSpec; functionSpec: FunctionSpec }>();
224
224
  let runningCalls = new Set<string>();
225
225
 
226
226
  let callIdsToUnwatch = new Set<{ filePath: string; callId: string; }>();
@@ -258,89 +258,106 @@ export class PathFunctionRunner {
258
258
  watcher.pendingWatches.paths.delete(getProxyPath(() => data()[filePath].Results[callId]));
259
259
  }
260
260
  callIdsToUnwatch.clear();
261
- for (let [filePath, moduleData] of Object.entries(data())) {
262
- watchModuleCalls(filePath);
261
+
262
+ let moduleIds = Object.keys(data());
263
+ if (isLocal()) {
264
+ moduleIds.push(...getAllDevelopmentModulesIds());
265
+ moduleIds = Array.from(new Set(moduleIds));
263
266
  }
264
267
 
265
268
  let triggerPaths = new Set<string>();
266
- for (let [filePath, moduleData] of Object.entries(data())) {
267
- let callPath = getProxyPath(() => data()[filePath].Calls);
269
+ for (let moduleId of moduleIds) {
270
+ watchModuleCalls(moduleId);
271
+
272
+ let callPath = getProxyPath(() => data()[moduleId].Calls);
268
273
  triggerPaths.add(callPath);
269
274
 
270
275
  // ALSO, Result changes (result rejections), can trigger a run
271
- let resultPath = getProxyPath(() => data()[filePath].Results);
276
+ let resultPath = getProxyPath(() => data()[moduleId].Results);
272
277
  triggerPaths.add(resultPath);
273
278
  }
274
279
 
275
- let callPathLength = 0;
276
- if (triggerPaths.size > 0) {
277
- callPathLength = getPathDepth(Array.from(triggerPaths)[0]);
278
- }
280
+ let callPathLength = (
281
+ 1 // domain
282
+ + 1 // PathFunctionRunner
283
+ + 1 // module
284
+ + 1 // Calls
285
+ + 1 // callId
286
+ );
279
287
 
280
288
  let newCalls: { call: CallSpec; functionSpec: FunctionSpec }[] = [];
281
289
 
282
290
  if (watcher.triggeredByChanges) {
283
291
  for (let path of watcher.triggeredByChanges.paths) {
284
- let callPathEquivalent = trimPathStrToDepth(path, callPathLength);
285
- if (triggerPaths.has(callPathEquivalent)) {
286
- const callId = getPathIndex(path, callPathLength);
287
- if (!callId) continue;
288
- let moduleId = getPathIndex(path, callPathLength - 2);
289
- if (!moduleId) continue;
290
-
291
- callsToCall.delete(callId);
292
-
293
- let moduleData = data()[moduleId];
294
-
295
- let result = atomicObjectRead(moduleData.Results[callId]);
296
- if (result) continue;
297
-
298
- let callData = atomicObjectRead(moduleData.Calls[callId]);
299
- if (!callData) continue;
300
-
301
- if (callData.DomainName !== domainName) continue;
302
- if (callData.ModuleId !== moduleId) continue;
303
- if (callData.CallId !== callId) continue;
304
-
305
- let functionSpec = atomicObjectRead(moduleData.Sources[callData.FunctionId]);
306
- if (!functionSpec) {
307
- if (isSynced(moduleData.Sources[callData.FunctionId])) {
308
- console.warn(yellow(`Cannot call ${callData.DomainName}.${callData.ModuleId}.Sources.${callData.FunctionId} because it is not deployed`));
309
- }
292
+ let callsOrResult = getPathIndex(path, 3);
293
+ if (callsOrResult !== "Calls" && callsOrResult !== "Results") continue;
294
+ if (getPathIndex(path, 1) !== "PathFunctionRunner") continue;
295
+ if (getPathIndex(path, 0) !== domainName) continue;
296
+
297
+ const callId = getPathIndex(path, 4);
298
+ if (!callId) continue;
299
+ let moduleId = getPathIndex(path, 2);
300
+ if (!moduleId) continue;
301
+ if (!moduleIds.includes(moduleId)) continue;
302
+
303
+ let moduleData = data()[moduleId];
304
+
305
+ let result = atomicObjectRead(moduleData.Results[callId]);
306
+ if (result) continue;
307
+
308
+ let callData = atomicObjectRead(moduleData.Calls[callId]);
309
+ if (!callData) continue;
310
+
311
+ // Ignore invalid data
312
+ if (callData.DomainName !== domainName) continue;
313
+ if (callData.ModuleId !== moduleId) continue;
314
+ if (callData.CallId !== callId) continue;
315
+
316
+ const functionId = callData.FunctionId;
317
+ let functionSpec = atomicObjectRead(moduleData.Sources[functionId]);
318
+ if (!functionSpec) {
319
+ if (!isSynced(moduleData.Sources[callData.FunctionId])) {
310
320
  continue;
311
321
  }
312
-
313
- // If we haven't synced the result, we can't know if it is ready or not
314
- // (we'll run anyways once it is synced). Most other parts are immutable,
315
- // but the result is by definition not, so... it is one of the values we check!
316
- if (!isSynced(moduleData.Results[callId])) continue;
317
-
318
- if (runningCalls.has(callId)) continue;
319
- runningCalls.add(callId);
320
-
321
- let limitCount = queueLimitCounts.get(callId) || 0;
322
- limitCount++;
323
- queueLimitCounts.set(callId, limitCount);
324
- // NOTE: Calls are event writes, so... they should just clean themselves up, after we ignore them for long enough.
325
- if (limitCount >= MAX_QUEUE_COUNT) {
326
- // Only error the first time, as we don't need need that many errors
327
- if (limitCount === MAX_QUEUE_COUNT) {
328
- console.error(`Queue limit reached (${limitCount}) for ${getDebugName(callData, functionSpec, true)}`);
329
- }
322
+ if (isLocal()) {
323
+ functionSpec = getDevFunctionSpecFromCall(callData);
324
+ if (!functionSpec) continue;
325
+ } else {
326
+ console.warn(yellow(`Cannot call ${callData.DomainName}.${callData.ModuleId}.Sources.${callData.FunctionId} because it is not deployed`));
330
327
  continue;
331
328
  }
329
+ }
332
330
 
333
- if (PathFunctionRunner.DEBUG_CALLS) {
334
- console.log(`QUEUING ${getDebugName(callData, functionSpec, true)}`);
335
- let resultsPath = getProxyPath(() => moduleData.Results[callId]);
336
- let history = authorityStorage.getValuePlusHistory(resultsPath);
337
- console.log(` History: ${history.length}`);
338
- for (let { valid, time, canGCValue } of history) {
339
- console.log(` ${valid ? "✅" : "❌"} ${debugTime(time)} ${canGCValue ? "value is undefined" : ""}`);
340
- }
331
+ // If we haven't synced the result, we can't know if it is ready or not
332
+ // (we'll run anyways once it is synced). Most other parts are immutable,
333
+ // but the result is by definition not, so... it is one of the values we check!
334
+ if (!isSynced(moduleData.Results[callId])) continue;
335
+
336
+ if (runningCalls.has(callId)) continue;
337
+ runningCalls.add(callId);
338
+
339
+ let limitCount = queueLimitCounts.get(callId) || 0;
340
+ limitCount++;
341
+ queueLimitCounts.set(callId, limitCount);
342
+ // NOTE: Calls are event writes, so... they should just clean themselves up, after we ignore them for long enough.
343
+ if (limitCount >= MAX_QUEUE_COUNT) {
344
+ // Only error the first time, as we don't need need that many errors
345
+ if (limitCount === MAX_QUEUE_COUNT) {
346
+ console.error(`Queue limit reached (${limitCount}) for ${getDebugName(callData, functionSpec, true)}`);
347
+ }
348
+ continue;
349
+ }
350
+
351
+ if (PathFunctionRunner.DEBUG_CALLS) {
352
+ console.log(`QUEUING ${getDebugName(callData, functionSpec, true)}`);
353
+ let resultsPath = getProxyPath(() => moduleData.Results[callId]);
354
+ let history = authorityStorage.getValuePlusHistory(resultsPath);
355
+ console.log(` History: ${history.length}`);
356
+ for (let { valid, time, canGCValue } of history) {
357
+ console.log(` ${valid ? "✅" : "❌"} ${debugTime(time)} ${canGCValue ? "value is undefined" : ""}`);
341
358
  }
342
- newCalls.push({ call: callData, functionSpec });
343
359
  }
360
+ newCalls.push({ call: callData, functionSpec });
344
361
  }
345
362
  }
346
363
 
@@ -368,6 +385,27 @@ export class PathFunctionRunner {
368
385
  });
369
386
  registerDynamicResource("paths|PathFunctionRunner.watches", () => watcher.lastWatches.paths.size);
370
387
  registerDynamicResource("paths|PathFunctionRunner.outstandingCalls", () => outstandingCalls);
388
+
389
+ // If local, periodically check all modules
390
+ // NOTE: `yarn deploy` commits the moduleIds to the database, but calls do not, as this would require either an extra
391
+ // read, or an extra write. So this is somewhat a hack, but also fairly safe.
392
+ if (isLocal()) {
393
+ let prevModuleIds = new Set<string>();
394
+ runInfinitePoll(timeInSecond * 15, () => {
395
+ let moduleIds = getAllDevelopmentModulesIds();
396
+ let changed = false;
397
+ for (let moduleId of moduleIds) {
398
+ if (!prevModuleIds.has(moduleId)) {
399
+ changed = true;
400
+ break;
401
+ }
402
+ }
403
+ prevModuleIds = new Set(moduleIds);
404
+ if (changed) {
405
+ watcher.explicitlyTrigger();
406
+ }
407
+ });
408
+ }
371
409
  }
372
410
 
373
411
 
@@ -520,43 +558,48 @@ export class PathFunctionRunner {
520
558
  let syncedModule = skipPermissions(() =>
521
559
  functionSchema()[callPath.DomainName].PathFunctionRunner[callPath.ModuleId]
522
560
  );
523
- let syncedSpec = skipPermissions(() =>
524
- atomicObjectRead(syncedModule.Sources[callPath.FunctionId])
525
- );
526
- if (!syncedSpec) {
527
- throw new Error(`Function spec not found for ${getDebugName(callPath, functionSpec, true)}`);
528
- }
529
561
 
530
- // (We also need to depend on the RIGHT function spec).
531
- if (
532
- syncedSpec && (
533
- syncedSpec.DomainName !== functionSpec.DomainName
534
- || syncedSpec.ModuleId !== functionSpec.ModuleId
535
- || syncedSpec.FilePath !== functionSpec.FilePath
536
- || syncedSpec.FunctionId !== functionSpec.FunctionId
537
- || syncedSpec.exportPathStr !== functionSpec.exportPathStr
538
- || syncedSpec.FilePath !== functionSpec.FilePath
539
- || syncedSpec.gitRef !== functionSpec.gitRef
540
- || syncedSpec.gitURL !== functionSpec.gitURL
541
- )
542
- ) {
543
- isFunctionSpecOutdated = true;
544
- functionSpec = {
545
- DomainName: syncedSpec.DomainName,
546
- ModuleId: syncedSpec.ModuleId,
547
- FilePath: syncedSpec.FilePath,
548
- FunctionId: syncedSpec.FunctionId,
549
- exportPathStr: syncedSpec.exportPathStr,
550
- gitRef: syncedSpec.gitRef,
551
- gitURL: syncedSpec.gitURL,
552
- };
553
- return;
562
+ // Make sure we are running the latest function (by checking it here, we also lock the values,
563
+ // so if it is updated we atomically rerun / reject all calls which were racing with us).
564
+ if (!isLocal()) {
565
+ let syncedSpec = skipPermissions(() =>
566
+ atomicObjectRead(syncedModule.Sources[callPath.FunctionId])
567
+ );
568
+ if (!syncedSpec) {
569
+ throw new Error(`Function spec not found for ${getDebugName(callPath, functionSpec, true)}`);
570
+ }
571
+
572
+ // (We also need to depend on the RIGHT function spec).
573
+ if (
574
+ syncedSpec && (
575
+ syncedSpec.DomainName !== functionSpec.DomainName
576
+ || syncedSpec.ModuleId !== functionSpec.ModuleId
577
+ || syncedSpec.FilePath !== functionSpec.FilePath
578
+ || syncedSpec.FunctionId !== functionSpec.FunctionId
579
+ || syncedSpec.exportPathStr !== functionSpec.exportPathStr
580
+ || syncedSpec.FilePath !== functionSpec.FilePath
581
+ || syncedSpec.gitRef !== functionSpec.gitRef
582
+ || syncedSpec.gitURL !== functionSpec.gitURL
583
+ )
584
+ ) {
585
+ isFunctionSpecOutdated = true;
586
+ functionSpec = {
587
+ DomainName: syncedSpec.DomainName,
588
+ ModuleId: syncedSpec.ModuleId,
589
+ FilePath: syncedSpec.FilePath,
590
+ FunctionId: syncedSpec.FunctionId,
591
+ exportPathStr: syncedSpec.exportPathStr,
592
+ gitRef: syncedSpec.gitRef,
593
+ gitURL: syncedSpec.gitURL,
594
+ };
595
+ return;
596
+ }
554
597
  }
555
598
 
556
599
  let evalTimeStart = Date.now();
557
600
  try {
558
601
  let args = parseArgs(callPath);
559
- overrideCurrentCall({ spec: callPath, fnc: syncedSpec, }, () => {
602
+ overrideCurrentCall({ spec: callPath, fnc: functionSpec, }, () => {
560
603
  baseFunction(...args);
561
604
  });
562
605
  } finally {
@@ -671,6 +714,34 @@ export async function preloadFunctions(specs: FunctionSpec[]) {
671
714
  }));
672
715
  }
673
716
 
717
+ export function getDevFunctionSpecFromCall(call: {
718
+ DomainName: string;
719
+ ModuleId: string;
720
+ FunctionId: string;
721
+ }): FunctionSpec | undefined {
722
+ let domainName = call.DomainName;
723
+ let moduleId = call.ModuleId;
724
+ let functionId = call.FunctionId;
725
+
726
+ let devModule = getDevelopmentModule(moduleId);
727
+ if (!devModule) {
728
+ console.warn(yellow(`Cannot call ${domainName}.${moduleId}.Sources.${functionId} because the module is not deployed and not referenced in deploy.ts`));
729
+ return undefined;
730
+ }
731
+ let filePath = getModuleRelativePath(devModule);
732
+ let gitURL = getGitURL();
733
+ let gitRef = getGitRef();
734
+ return {
735
+ DomainName: domainName,
736
+ ModuleId: moduleId,
737
+ FunctionId: functionId,
738
+ exportPathStr: getExportPath(functionId),
739
+ FilePath: filePath,
740
+ gitURL,
741
+ gitRef,
742
+ };
743
+ }
744
+
674
745
  class FunctionPreloaderBase {
675
746
  async preloadFunctions(specs: FunctionSpec[]) {
676
747
  if (isLocal()) return;
@@ -22,13 +22,13 @@ import { PathValueProxyWatcher } from "../2-proxy/PathValueProxyWatcher";
22
22
  import { PermissionsCheck } from "../4-querysub/permissions";
23
23
  import { Querysub } from "../4-querysub/QuerysubController";
24
24
  import { timeInMinute } from "socket-function/src/misc";
25
- import { getDomain, isPublic } from "../config";
25
+ import { devDebugbreak, getDomain, isLocal, isPublic } from "../config";
26
26
  import { publishMachineARecords } from "../-e-certs/EdgeCertController";
27
27
  import { green } from "socket-function/src/formatting/logColors";
28
28
  import { parseFilterSelector } from "../misc/filterable";
29
+ import path from "path";
29
30
 
30
31
  async function main() {
31
-
32
32
  listenOnDebugger("FunctionRunner");
33
33
  Error.stackTraceLimit = 20;
34
34
 
@@ -68,5 +68,11 @@ async function main() {
68
68
  PermissionsChecker: PermissionsCheck,
69
69
  filterSelector,
70
70
  });
71
+
72
+ if (isLocal()) {
73
+ // We have to import deploy, otherwise local moduleIds won't be known about
74
+ let deployPath = path.resolve("./deploy.ts");
75
+ await import(deployPath);
76
+ }
71
77
  }
72
78
  logErrors(main());
@@ -63,14 +63,15 @@ const getLocalPathRemapping = lazy((): { [gitUrl: string]: string } => {
63
63
  let packageJSONObj = JSON.parse(packageJSON);
64
64
  let repo = packageJSONObj.repository as { type: string, url: string } | undefined;
65
65
  if (!repo) throw new Error(`No "repository" property in package.json at ${path}`);
66
- if (repo.type !== "git") {
67
- throw new Error(`Repository type ${JSON.stringify(repo.type)} is not supported yet. Only "git" is presently supported.`);
66
+ if (repo.type !== "git" && repo.type !== "git+https" && repo.type !== "https") {
67
+ throw new Error(`Repository type ${JSON.stringify(repo.type)} is not supported yet. Only "git", "git+https", and "https" are presently supported.`);
68
68
  }
69
69
  mapping[repo.url] = path;
70
- // Also, use the git@ version
71
- // Replace "https://*/" with "git@*:"
72
- let gitAtUrl = repo.url.replace(/^https:\/\/([^\/]+)\//, "git@$1:");
73
- mapping[gitAtUrl] = path;
70
+ // https://github.com/sliftist/qs-cyoa.git
71
+ // git@github.com:sliftist/qs-cyoa.git
72
+ mapping[repo.url.replace(/^https:\/\/([^\/]+)\//, "git@$1:")] = path;
73
+ // Go from git to https as well, as it might be https
74
+ mapping[repo.url.replace(/^git@([^:]+):(.+)$/, "https://$1/$2")] = path;
74
75
  }
75
76
  return mapping;
76
77
  });
@@ -131,6 +132,10 @@ let moduleResolver = async (spec: {
131
132
  gitURL: string;
132
133
  gitRef: string;
133
134
  }) => {
135
+ if (isLocal()) {
136
+ // Probably a bug. The local path should have matched.
137
+ devDebugbreak();
138
+ }
134
139
  let gitURL = spec.gitURL;
135
140
  let urlForPath = gitURL;
136
141
 
@@ -5,7 +5,7 @@ import { Args } from "socket-function/src/types";
5
5
  import { appendToPathStr, getPathFromStr, getPathStr, rootPathStr } from "../path";
6
6
  import { writeFunctionCall } from "./PathFunctionHelpers";
7
7
  import { CallSpec, functionSchema } from "./PathFunctionRunner";
8
- import { getDomain } from "../config";
8
+ import { getDomain, isLocal } from "../config";
9
9
  import { isHotReloading } from "socket-function/hot/HotReloadController";
10
10
  import { Schema2, Schema2Fncs, Schema2T, SchemaPath } from "../2-proxy/schema2";
11
11
  import { PathValueProxyWatcher, atomic, proxyWatcher, registerSchema } from "../2-proxy/PathValueProxyWatcher";
@@ -399,9 +399,18 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
399
399
  // NOTE: We clobber the previous value, as we might just be hotreloading, which
400
400
  // could mean the exports value has our previous value.
401
401
  module.exports[SCHEMA_EXPORT_KEY] = result;
402
+ developmentModules.set(moduleId, module);
402
403
  return result;
403
404
  };
404
405
  }
406
+ let developmentModules = new Map<string, NodeJS.Module>();
407
+
408
+ export function getDevelopmentModule(moduleId: string): NodeJS.Module | undefined {
409
+ return developmentModules.get(moduleId);
410
+ }
411
+ export function getAllDevelopmentModulesIds(): string[] {
412
+ return Array.from(developmentModules.keys());
413
+ }
405
414
 
406
415
  function isChildPath(spec: SchemaPath, path: string[]): boolean {
407
416
  if (spec.length > path.length) return false;
@@ -35,7 +35,14 @@ export async function getEdgeBootstrapScript(config: {
35
35
  }): Promise<string> {
36
36
  return await measureBlock(async function getEdgeBootstrapScript() {
37
37
  let cachedConfig = await getCachedConfig(config.edgeNodeConfigURL);
38
- return `(${edgeNodeFunction.toString()})(${JSON.stringify({ ...config, cachedConfig })});`;
38
+ let allowedArgs = ["--local"];
39
+ // TODO: Be smarter about this (getting values and not just flags)
40
+ let argv = process.argv.filter(x => allowedArgs.includes(x));
41
+ return `(${edgeNodeFunction.toString()})(${JSON.stringify({
42
+ ...config,
43
+ cachedConfig,
44
+ argv,
45
+ })});`;
39
46
  });
40
47
  }
41
48
 
@@ -64,7 +71,9 @@ declare global {
64
71
  async function edgeNodeFunction(config: {
65
72
  edgeNodeConfigURL: string;
66
73
  cachedConfig: EdgeNodesIndex | undefined;
74
+ argv: string[];
67
75
  }) {
76
+ process.argv = config.argv;
68
77
  // IMPORTANT! Everything in this function is embedded, so this function can't use any external imports
69
78
  // (except for type imports, of course).
70
79
 
@@ -3,7 +3,7 @@
3
3
  // a file prevents it from transforming it, so we'll miss a lot of files doing it this late).
4
4
  import "../inject";
5
5
 
6
- import { isNode, timeInDay, timeInHour, timeInMinute } from "socket-function/src/misc";
6
+ import { isNode, isNodeTrue, timeInDay, timeInHour, timeInMinute } from "socket-function/src/misc";
7
7
 
8
8
  import { SocketFunction } from "socket-function/SocketFunction";
9
9
  import { isHotReloading, onHotReload, watchFilesAndTriggerHotReloading } from "socket-function/hot/HotReloadController";
@@ -17,7 +17,7 @@ import { clientWatcher, ClientWatcher } from "../1-path-client/pathValueClientWa
17
17
  import { SyncWatcher, proxyWatcher, specialObjectWriteValue, isSynced, PathValueProxyWatcher, atomic, doAtomicWrites, noAtomicSchema, undeleteFromLookup, registerSchemaPrefix } from "../2-proxy/PathValueProxyWatcher";
18
18
  import { isInProxyDatabase, rawSchema } from "../2-proxy/pathDatabaseProxyBase";
19
19
  import { isValueProxy2, getProxyPath } from "../2-proxy/pathValueProxy";
20
- import { getCurrentCallAllowUndefined, getCurrentCall, CallSpec } from "../3-path-functions/PathFunctionRunner";
20
+ import { getCurrentCallAllowUndefined, getCurrentCall, CallSpec, PathFunctionRunner } from "../3-path-functions/PathFunctionRunner";
21
21
  import { listenOnDebugger } from "../diagnostics/listenOnDebugger";
22
22
  import { logErrors } from "../errors";
23
23
  import { getLastPathPart, getPathIndexAssert, getPathStr2, hack_setPackedPathSuffix } from "../path";
@@ -47,9 +47,45 @@ import { hookErrors } from "../diagnostics/errorLogs/hookErrors";
47
47
  import { Schema2, Schema2T, t } from "../2-proxy/schema2";
48
48
  import { CALL_PERMISSIONS_KEY } from "./permissionsShared";
49
49
  import { isDynamicModule } from "../3-path-functions/pathFunctionLoader";
50
+ import yargs from "yargs";
50
51
 
51
52
  export { t };
52
53
 
54
+ let yargObj = isNodeTrue() && yargs(process.argv)
55
+ .option("verboseall", { type: "boolean", desc: "Log all everything (except network)" })
56
+ .option("verbose", { type: "boolean", desc: "Log all writes and reads" })
57
+ .option("verbosewrites", { type: "boolean", desc: "Log all writes" })
58
+ .option("verbosereads", { type: "boolean", desc: "Log all reads" })
59
+ .option("verbosecalls", { type: "boolean", desc: "Log all calls" })
60
+ .option("verbosenetwork", { type: "boolean", desc: "Log all network activity" })
61
+ .argv || {}
62
+ ;
63
+ setImmediate(() => {
64
+ if (yargObj.verbose) {
65
+ ClientWatcher.DEBUG_WRITES = true;
66
+ ClientWatcher.DEBUG_READS = true;
67
+ }
68
+ if (yargObj.verbosewrites) {
69
+ ClientWatcher.DEBUG_WRITES = true;
70
+ }
71
+ if (yargObj.verbosereads) {
72
+ ClientWatcher.DEBUG_READS = true;
73
+ }
74
+ if (yargObj.verbosecalls) {
75
+ PathFunctionRunner.DEBUG_CALLS = true;
76
+ Querysub.DEBUG_CALLS = true;
77
+ }
78
+ if (yargObj.verboseall) {
79
+ ClientWatcher.DEBUG_WRITES = true;
80
+ ClientWatcher.DEBUG_READS = true;
81
+ PathFunctionRunner.DEBUG_CALLS = true;
82
+ Querysub.DEBUG_CALLS = true;
83
+ }
84
+ if (yargObj.verbosenetwork) {
85
+ SocketFunction.logMessages = true;
86
+ }
87
+ });
88
+
53
89
  if (!registerGetCompressNetwork) {
54
90
  devDebugbreak();
55
91
  }
@@ -302,6 +338,38 @@ export class Querysub {
302
338
  proxyWatcher.getTriggeredWatcher().onInnerDisposed.push(callback);
303
339
  }
304
340
 
341
+ /** A more powerful version of omCommitFinished, which even waits for call predictions (or tries to).
342
+ * - Also see afterPredictionsSynced, which runs the callback in a write.
343
+ * NOTE: IDEALLY, you would just call a series of synced functions. They will be run in order,
344
+ * with all the synced data, and just work. However... if you are running very expensive
345
+ * asynchronous operations, you might need to use an async workflow.
346
+ * "afterPredictions" helps in async workflows, by calling your callback after all local function
347
+ * predictions finish. This allows you to (somewhat) read back their writes. It isn't perfect,
348
+ * but it is safer then Promise.resolve().then(callback), or any type of setTimeout. Usually
349
+ * the callback will be fast, except for the first time, when code will needed by loaded to
350
+ * run the predictive functions.
351
+ * NOTE: Only waits for calls BEFORE the callback is called. This is useful, but also means
352
+ * you can't call this at the start of your function (as nothing will have been called yet).
353
+ * NOTE: This can also be used to prevent
354
+ */
355
+ public static afterPredictions(callback: () => Promise<void>) {
356
+ let calls = proxyWatcher.getTriggeredWatcher().pendingCalls.map(x => x.call);
357
+ Querysub.onCommitFinished(async () => {
358
+ await Promise.all(calls.map(x => Querysub.onCallPredict(x)));
359
+ logErrors(callback());
360
+ });
361
+ }
362
+
363
+ /** Solely for use to prevent local writes from occuring before predictions. We MIGHT make this a framework thing,
364
+ * or just not bother with it (as after a prediction is run once the code will be loaded allowing it to always
365
+ * run before the next frame).
366
+ */
367
+ public static afterPredictionsSynced(callback: () => void) {
368
+ Querysub.afterPredictions(async () => {
369
+ Querysub.serviceWriteDetached(callback);
370
+ });
371
+ }
372
+
305
373
  public static onCallPredict = (x: CallSpec | undefined) => onCallPredict(x);
306
374
  /** NOTE: USUALLY you don't have to wait for predictions, as functions will run in order anyways.
307
375
  * BUT, if you run a synced function in the UI, then somewhere unrelated try to read values
@@ -889,6 +957,11 @@ setImmediate(() => {
889
957
  hookErrors();
890
958
  });
891
959
 
960
+ setImmediate(async () => {
961
+ // Import, so it registers addStatPeriodic
962
+ await import("../5-diagnostics/nodeMetadata");
963
+ });
964
+
892
965
  registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
893
966
  registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
894
967
 
@@ -897,5 +970,6 @@ registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
897
970
  import "../diagnostics/watchdog";
898
971
  import "../diagnostics/trackResources";
899
972
  import "../diagnostics/benchmark";
900
- import { getEdgeNodeConfigURL, registerEdgeNode } from "../4-deploy/edgeNodes"; import { getEdgeBootstrapScript } from "../4-deploy/edgeBootstrap";
973
+ import { getEdgeNodeConfigURL, registerEdgeNode } from "../4-deploy/edgeNodes";
974
+ import { getEdgeBootstrapScript } from "../4-deploy/edgeBootstrap";
901
975