querysub 0.2.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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,67 @@
1
+ // import { disableMeasurements } from "socket-function/src/profiling/measure";
2
+ // disableMeasurements();
3
+
4
+ import "../inject";
5
+ import yargs from "yargs";
6
+ let yargObj = yargs(process.argv)
7
+ .option("shardcenter", { type: "number", default: 0.5, desc: "Center of sharding fraction from 0 to 1" })
8
+ .option("shardsize", { type: "number", default: 1, desc: "Size of sharding fraction from 0 to 1 (ex, shardsize = 0.35 and shardcenter = 0.5 results in sharding from 0.15 to 0.85)" })
9
+ .option("secondarysize", { type: "number", default: 0, desc: "Also shards this size HOWEVER, any values in this range (which are not matched in shardsize) are delayed." })
10
+ .argv
11
+ ;
12
+
13
+ import { logErrors } from "../errors";
14
+ import { PathFunctionRunner } from "./PathFunctionRunner";
15
+ import { authorityStorage, debugCoreMode, MAX_CHANGE_AGE, quietCoreMode } from "../0-path-value-core/pathValueCore";
16
+ import { SocketFunction } from "socket-function/SocketFunction";
17
+ import { getThreadKeyCert } from "../-a-auth/certs";
18
+ import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
19
+ import { listenOnDebugger } from "../diagnostics/listenOnDebugger";
20
+ import { PathValueProxyWatcher } from "../2-proxy/PathValueProxyWatcher";
21
+ import { PermissionsCheck } from "../4-querysub/permissions";
22
+ import { Querysub } from "../4-querysub/QuerysubController";
23
+ import { timeInMinute } from "socket-function/src/misc";
24
+ import { getDomain, isPublic } from "../config";
25
+ import { publishMachineARecords } from "../-e-certs/EdgeCertController";
26
+ import { green } from "socket-function/src/formatting/logColors";
27
+
28
+ async function main() {
29
+
30
+ listenOnDebugger("FunctionRunner");
31
+ Error.stackTraceLimit = 20;
32
+
33
+ //ActionsHistory.LOG_ACTION_HISTORY = "runner";
34
+
35
+ // ClientWatcher.DEBUG_READS = true;
36
+ ClientWatcher.DEBUG_WRITES = true;
37
+ // ClientWatcher.DEBUG_TRIGGERS = "heavy";
38
+ // authorityStorage.DEBUG_UNWATCH = true;
39
+
40
+ PathFunctionRunner.DEBUG_CALLS = true;
41
+ // debugCoreMode();
42
+
43
+ let keyPair = await getThreadKeyCert();
44
+ await SocketFunction.mount({ port: 0, ...keyPair, public: isPublic() });
45
+ await publishMachineARecords();
46
+
47
+ // Use a fairly high stick time (the default is 10s), because having wait to sync data is very slow,
48
+ // and the function runner SHOULD have more memory than the clients, and much faster network speeds
49
+ // (it should be on the same local network as the path value authorities).
50
+ ClientWatcher.WATCH_STICK_TIME = timeInMinute * 5;
51
+
52
+ let shardStart = yargObj.shardcenter - yargObj.shardsize / 2;
53
+ let shardEnd = yargObj.shardcenter + yargObj.shardsize / 2;
54
+ let secondaryStart = yargObj.shardcenter - yargObj.secondarysize / 2;
55
+ let secondaryEnd = yargObj.shardcenter + yargObj.secondarysize / 2;
56
+
57
+ console.log(green(`Sharding from ${shardStart} to ${shardEnd}. Fallback sharding (dead function recovery) from ${secondaryStart} to ${secondaryEnd}`));
58
+
59
+ new PathFunctionRunner({
60
+ domainName: getDomain(),
61
+ shardRange: { startFraction: shardStart, endFraction: shardEnd },
62
+ secondaryShardRange: yargObj.secondarysize ? { startFraction: secondaryStart, endFraction: secondaryEnd } : undefined,
63
+ // TODO: Maybe abstract this out even more, so anything can plug in permissions checks?
64
+ PermissionsChecker: PermissionsCheck,
65
+ });
66
+ }
67
+ logErrors(main());
@@ -0,0 +1,10 @@
1
+ let promises: Promise<void>[] = [];
2
+ /** Blocks the deploy until this promise finishes. Only check before the deploy starts
3
+ * (essentially is just a way to register async startup code).
4
+ */
5
+ export function registerDeployBlocker(promise: Promise<void>) {
6
+ promises.push(promise);
7
+ }
8
+ export async function deployBlock() {
9
+ await Promise.all(promises);
10
+ }
@@ -0,0 +1,7 @@
1
+ let isDeployValue = false;
2
+ export function setIsDeploy() {
3
+ isDeployValue = true;
4
+ }
5
+ export function isDeploy() {
6
+ return isDeployValue;
7
+ }
@@ -0,0 +1,160 @@
1
+ import "../inject";
2
+
3
+ import { errorToUndefined } from "../errors";
4
+ import { functionSchema, FunctionSpec } from "./PathFunctionRunner";
5
+ import path from "path";
6
+ import { getExportPath, getSchemaObject, PERMISSIONS_FUNCTION_ID } from "./syncSchema";
7
+ import { getModuleRelativePath } from "./syncSchema";
8
+ import * as child_process from "child_process";
9
+ import { proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
10
+ import { green, red } from "socket-function/src/formatting/logColors";
11
+ import { isDefined } from "../misc";
12
+ import fs from "fs";
13
+ import { SocketFunction } from "socket-function/SocketFunction";
14
+ import { getThreadKeyCert } from "../-a-auth/certs";
15
+ import { deployFunction, undeployFunction } from "./PathFunctionHelpers";
16
+ import { pathValueCommitter } from "../0-path-value-core/PathValueController";
17
+ import { getPathStr2 } from "../path";
18
+ import { cacheJSONArgsEqual } from "socket-function/src/caching";
19
+ import { setIsDeploy } from "./deployCheck";
20
+ import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
21
+ import debugbreak from "debugbreak";
22
+
23
+ import yargs from "yargs";
24
+ import { isPublic } from "../config";
25
+ import { deployBlock } from "./deployBlock";
26
+ let yargObj = yargs(process.argv)
27
+ .option("domain", { type: "string", required: true, desc: "Domain to deploy to" })
28
+ .option("nogit", { type: "boolean", default: false, desc: `Disable git operations. Useful for debugging, when using --local` })
29
+ .argv
30
+ ;
31
+
32
+ setIsDeploy();
33
+
34
+ export async function deployMain() {
35
+ //SocketFunction.logMessages = true;
36
+ //quietCoreMode();
37
+ //ClientWatcher.DEBUG_READS = true;
38
+ //ClientWatcher.DEBUG_WRITES = true;
39
+ await deployBlock();
40
+
41
+ // Mount, so we can write directly to the database
42
+ await SocketFunction.mount({ port: 0, ...await getThreadKeyCert(), public: isPublic() });
43
+
44
+ const domainName = yargObj.domain;
45
+
46
+ let folderRoot = path.resolve(".").replaceAll("\\", "/");
47
+ const deployPath = path.resolve("./deploy.ts");
48
+ require(deployPath);
49
+
50
+ const srcRoot = path.resolve(__dirname + "/../").replaceAll("\\", "/");
51
+
52
+ let currentFunctions: FunctionSpec[] = [];
53
+ function debugFunction(func: FunctionSpec) {
54
+ return `${func.DomainName}:${func.FilePath}:${func.FunctionId}`;
55
+ }
56
+
57
+ let gitDir = folderRoot;
58
+ if (!fs.existsSync(gitDir + "/.git")) {
59
+ throw new Error(`No .git folder found at ${JSON.stringify(gitDir + "/.git")}`);
60
+ }
61
+ let gitURL = getGitURL(gitDir);
62
+ if (!gitURL) {
63
+ throw new Error(`Git repo has no remote url, at ${JSON.stringify(gitDir + "/.git")}`);
64
+ }
65
+ let gitRef = getGitRef(gitDir);
66
+
67
+ let usedModules = new Map<string, string>();
68
+
69
+ for (let module of Object.values(require.cache)) {
70
+ if (!module) continue;
71
+
72
+ // NOTE: We don't want dependencies to deploy schemas. This would result in using an
73
+ // API also deploying it.
74
+ // - Also, this add more security, by preventing 3rd party libraries from adding apis
75
+ // (maybe even just innocent, for diagnostics), which might have vulnerabilities.
76
+ let isAllowedToDeploy = (
77
+ !module.filename.includes("node_modules")
78
+ && module.filename.startsWith(folderRoot)
79
+ );
80
+
81
+ // We DO allow shard to deploy, so we can deploy diagnostic functions, such as
82
+ // for the managment interfaces. It only works if the user opts in (by calling
83
+ // registerManagementPages2), so it is actually quite safe to do this.
84
+ if (module.filename.startsWith(srcRoot)) {
85
+ isAllowedToDeploy = true;
86
+ }
87
+
88
+ if (!isAllowedToDeploy) continue;
89
+ let schema = getSchemaObject(module);
90
+
91
+ if (schema) {
92
+ let filePath = getModuleRelativePath(module);
93
+
94
+ let moduleId = getPathStr2(domainName, filePath);
95
+ let otherUsed = usedModules.get(moduleId);
96
+ if (otherUsed) {
97
+ throw new Error(`Overlapping module ids for ${domainName}.${filePath}, at ${otherUsed} and ${filePath}`);
98
+ }
99
+ usedModules.set(moduleId, filePath);
100
+
101
+ const functionIds = Object.keys(schema.rawFunctions);
102
+ functionIds.push(PERMISSIONS_FUNCTION_ID);
103
+ for (let functionId of functionIds) {
104
+ const exportPathStr = getExportPath(functionId);
105
+ currentFunctions.push({
106
+ DomainName: domainName,
107
+ ModuleId: schema.moduleId,
108
+ FilePath: filePath,
109
+ FunctionId: functionId,
110
+ exportPathStr,
111
+ gitURL,
112
+ gitRef,
113
+ });
114
+ }
115
+ }
116
+ }
117
+
118
+ let previousFunctions = await proxyWatcher.commitFunction({
119
+ debugName: "readPreviousFunctions",
120
+ watchFunction(): FunctionSpec[] {
121
+ let base = functionSchema()[domainName].PathFunctionRunner;
122
+ return Object.values(base).flatMap(x => Object.values(x.Sources)).filter(isDefined);
123
+ },
124
+ });
125
+
126
+ let previousHashes = new Set(previousFunctions.map(x => JSON.stringify(x)));
127
+ let currentHashes = new Set(currentFunctions.map(x => JSON.stringify(x)));
128
+ // BUG: This is incorrect, and breaks when you accidentally reuse a modelId
129
+ for (let func of currentFunctions) {
130
+ if (previousHashes.has(JSON.stringify(func))) {
131
+ console.log(`Unchanged ${debugFunction(func)}`);
132
+ }
133
+ }
134
+
135
+ for (let previous of previousFunctions) {
136
+ let hash = JSON.stringify(previous);
137
+ if (currentHashes.has(hash)) continue;
138
+ console.log(`Removing ${red(debugFunction(previous))}`);
139
+ await undeployFunction(previous);
140
+ }
141
+
142
+ console.log();
143
+ for (let func of currentFunctions) {
144
+ if (previousHashes.has(JSON.stringify(func))) continue;
145
+ console.log(`Deploying ${green(debugFunction(func))}`);
146
+ await deployFunction(func);
147
+ }
148
+
149
+ await pathValueCommitter.waitForValuesToCommit();
150
+ }
151
+ function getGitURL(gitDir: string) {
152
+ if (yargObj.nogit) return "git@github.com:nogit/nogit.git";
153
+ return child_process.execSync(`git remote get-url origin`, { cwd: gitDir }).toString().trim();
154
+ }
155
+ function getGitRef(gitDir: string) {
156
+ if (yargObj.nogit) return "nogit";
157
+ return child_process.execSync(`git rev-parse HEAD`, { cwd: gitDir }).toString().trim();
158
+ }
159
+
160
+ deployMain().catch(e => console.error(e)).finally(() => process.exit());
@@ -0,0 +1,282 @@
1
+ import { getSubFolder } from "../fs";
2
+ import { FunctionSpec } from "./PathFunctionRunner";
3
+ import child_process from "child_process";
4
+ import fs from "fs";
5
+ import { blue, magenta, red } from "socket-function/src/formatting/logColors";
6
+ import debugbreak from "debugbreak";
7
+ import { cache, lazy } from "socket-function/src/caching";
8
+ import { batchFunction, runInfinitePoll } from "socket-function/src/batching";
9
+ import { logErrors } from "../errors";
10
+ import crypto from "crypto";
11
+ import { MaybePromise } from "socket-function/src/types";
12
+ import yargs from "yargs";
13
+ import { SyncWatcher } from "../2-proxy/PathValueProxyWatcher";
14
+ import { MAX_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
15
+ import { isNode, isNodeTrue } from "socket-function/src/misc";
16
+ import { getPathStr2, getPathStr3 } from "../path";
17
+ import { consistentHash } from "../misc/hash";
18
+ import { setExternalHotReloading } from "socket-function/hot/HotReloadController";
19
+ import { isPublic } from "../config";
20
+
21
+ // Get localPathRemappings using yargs, so it is easy to configure in multiple entry points
22
+ let yargObj = isNodeTrue() && yargs(process.argv)
23
+ .option("local", { type: "boolean", desc: `If true, uses the local directory instead of the remote git repo. Also hotreloads from disk. Determines the repo to replace through the package.json "repository" property.` })
24
+ .argv || {}
25
+ ;
26
+
27
+ let watchers = new Set<SyncWatcher>();
28
+ let ensureDisposingDeadWatchers = lazy(() => {
29
+ runInfinitePoll(MAX_CHANGE_AGE, function disposeDeadWatchers() {
30
+ for (let watcher of watchers) {
31
+ if (watcher.disposed) {
32
+ watchers.delete(watcher);
33
+ }
34
+ }
35
+ });
36
+ });
37
+ export function watchModuleHotreloads(watcher: SyncWatcher) {
38
+ watchers.add(watcher);
39
+ ensureDisposingDeadWatchers();
40
+ }
41
+
42
+ const getLocalPathRemapping = lazy((): { [gitUrl: string]: string } => {
43
+ let localPaths: string[] = [];
44
+ if (yargObj.local) {
45
+ localPaths.push(process.cwd().replaceAll(/\\/g, "/") + "/");
46
+ }
47
+ if (!localPaths.length) return {};
48
+ let mapping: { [gitUrl: string]: string } = Object.create(null);
49
+ for (let path of localPaths) {
50
+ if (!path.endsWith("/")) {
51
+ path += "/";
52
+ }
53
+ let packageJSON = fs.readFileSync(path + "package.json", "utf8");
54
+ let packageJSONObj = JSON.parse(packageJSON);
55
+ let repo = packageJSONObj.repository as { type: string, url: string } | undefined;
56
+ if (!repo) throw new Error(`No "repository" property in package.json at ${path}`);
57
+ if (repo.type !== "git") {
58
+ throw new Error(`Repository type ${JSON.stringify(repo.type)} is not supported yet. Only "git" is presently supported.`);
59
+ }
60
+ mapping[repo.url] = path;
61
+ // Also, use the git@ version
62
+ // Replace "https://*/" with "git@*:"
63
+ let gitAtUrl = repo.url.replace(/^https:\/\/([^\/]+)\//, "git@$1:");
64
+ mapping[gitAtUrl] = path;
65
+ }
66
+ return mapping;
67
+ });
68
+
69
+ // If we have so many different functions that we can't cache them all... then we are going
70
+ // to have a problem! Also... this cache is only the function, not the require.cache, which
71
+ // isn't being cleared anyways, so THIS isn't really where we would leak.
72
+ export function getModuleFromSpec(spec: FunctionSpec): MaybePromise<NodeJS.Module> {
73
+ return getModuleFromConfig(spec);
74
+ }
75
+ let moduleCache = new Map<string, {
76
+ result: MaybePromise<NodeJS.Module>;
77
+ error: Error | undefined;
78
+ }>();
79
+ function getSpecKey(spec: FunctionSpec) {
80
+ // Only include these specific keys AND order them, so the hash hits no matter how this function is called
81
+ spec = {
82
+ FilePath: spec.FilePath,
83
+ gitURL: spec.gitURL,
84
+ gitRef: spec.gitRef,
85
+ DomainName: spec.DomainName,
86
+ exportPathStr: spec.exportPathStr,
87
+ FunctionId: spec.FunctionId,
88
+ ModuleId: spec.ModuleId,
89
+ };
90
+ return JSON.stringify(spec);
91
+ }
92
+ export function getModuleFromConfig(spec: FunctionSpec): MaybePromise<NodeJS.Module> {
93
+ let key = getSpecKey(spec);
94
+ let value = moduleCache.get(key);
95
+ if (!value) {
96
+ let promise = getModuleFromSpecBase(spec);
97
+ value = { result: promise, error: undefined };
98
+ moduleCache.set(key, value);
99
+ promise.then(
100
+ result => {
101
+ moduleCache.set(key, { result, error: undefined });
102
+ },
103
+ error => {
104
+ moduleCache.set(key, { result: null as any, error });
105
+ }
106
+ );
107
+ }
108
+ if (value.error) throw value.error;
109
+ return value.result;
110
+ }
111
+
112
+ let gitURLRefMappings = new Map<string, string>();
113
+ export function setGitURLMapping(config: {
114
+ spec: FunctionSpec;
115
+ resolvedPath: string;
116
+ }) {
117
+ gitURLRefMappings.set(getSpecKey(config.spec), config.resolvedPath);
118
+ }
119
+
120
+ /** spec => path that we can use with require */
121
+ let moduleResolver = async (spec: FunctionSpec) => {
122
+ //todonext;
123
+ // I think the cloning isn't working?
124
+ debugbreak(2);
125
+ debugger;
126
+
127
+ let gitURL = spec.gitURL;
128
+ let urlForPath = gitURL;
129
+
130
+ if (urlForPath.startsWith("http")) {
131
+ // Switch to a git url... incase the repo is private? Or something?
132
+ // TODO: We should only do this selectively, as I think we can't sync this
133
+ // way without owning the repo, or... something?
134
+
135
+ let url = new URL(urlForPath);
136
+ gitURL = "git@" + url.host + ":" + url.pathname.slice(1);
137
+ urlForPath = gitURL;
138
+ }
139
+ urlForPath = urlForPath.replaceAll("@", "/");
140
+ urlForPath = urlForPath.replaceAll(":", "/");
141
+ if (urlForPath.endsWith(".git")) {
142
+ urlForPath = urlForPath.slice(0, -".git".length);
143
+ }
144
+ let repoPath = getSubFolder("synced_repos") + urlForPath + "/" + spec.gitRef;
145
+ if (!fs.existsSync(repoPath)) {
146
+ await executeCommand("git", ["clone", gitURL, repoPath]);
147
+ await executeCommand("git", ["reset", "--hard", spec.gitRef], { cwd: repoPath });
148
+ }
149
+ if (!fs.existsSync(repoPath + "node_modules")) {
150
+ // NOTE: `--ignore-scripts` to make installing faster, as presently a slow install step will cause functions
151
+ // after a new deploy to timeout for a bit.
152
+ await executeCommand("yarn", ["install", "--ignore-scripts"], { cwd: repoPath });
153
+ }
154
+ return repoPath;
155
+ };
156
+
157
+
158
+ async function getModuleFromSpecBase(
159
+ spec: FunctionSpec
160
+ ): Promise<NodeJS.Module> {
161
+ let hotReloadPackagePath = "";
162
+ let path = gitURLRefMappings.get(getSpecKey(spec));
163
+ if (!path) {
164
+ // Sync the git repo, `yarn install --ignore-scripts`, require the path, get the export, and then return that function.
165
+ let packagePath = getLocalPathRemapping()[spec.gitURL];
166
+ hotReloadPackagePath = packagePath;
167
+ if (!packagePath) {
168
+ packagePath = await moduleResolver(spec);
169
+ }
170
+
171
+ if (!packagePath.endsWith("/")) {
172
+ packagePath += "/";
173
+ }
174
+
175
+ let specFilePath = spec.FilePath;
176
+ if (specFilePath.startsWith("/")) {
177
+ specFilePath = specFilePath.slice(1);
178
+ }
179
+ path = packagePath + specFilePath;
180
+ }
181
+ console.log(blue(`require(${JSON.stringify(path)})`));
182
+ try {
183
+ // NOTE: The true tells require to not warn about the async loading
184
+ await (require as any)(path, true);
185
+ } catch (e: any) {
186
+ throw new Error(`Error when loading function for ${JSON.stringify(path)}\n${e.stack}`);
187
+ }
188
+ let moduleId = require.resolve(path) || path;
189
+ let module = require.cache[moduleId];
190
+ if (!module) {
191
+ debugbreak(2);
192
+ debugger;
193
+ throw new Error(`Module not found: ${moduleId}`);
194
+ }
195
+
196
+ //todonext
197
+ // Expose a function that goes from NodeJS.Module to FunctionSpec,
198
+ // via a lookup we control?
199
+ // - Hmm... we COULD change the module. This is nice, as it is more discoverable.
200
+ // But... eh... I don't know. Just a lookup is safer...
201
+
202
+ if (hotReloadPackagePath) {
203
+ hotReloadUnderPath(hotReloadPackagePath);
204
+ }
205
+
206
+ return module;
207
+ }
208
+
209
+ // Hot reload at or under the path
210
+ const hotReloadUnderPath = cache((path: string) => {
211
+ console.log(magenta(`Hot reloading under path: ${path}`));
212
+ path = path.replace(/\\/g, "/");
213
+ for (let module of Object.values(require.cache)) {
214
+ if (!module) continue;
215
+ let modulePath = module.filename.replace(/\\/g, "/");
216
+ if (modulePath.startsWith(path)) {
217
+ hotReloadModule(module);
218
+ }
219
+ }
220
+ });
221
+ const hotReloadModule = cache((module: NodeJS.Module) => {
222
+ fs.watch(module.filename, () => {
223
+ logErrors(hotreloadIfChanged(module));
224
+ });
225
+ });
226
+ const hotreloadIfChanged = batchFunction({ delay: 100, name: "hotreloadIfChanged" }, async (modules: NodeJS.Module[]) => {
227
+ let changedModules = new Set<NodeJS.Module>();
228
+ for (let module of modules) {
229
+ let newContents = await fs.promises.readFile(module.filename, "utf8");
230
+ let newSHA256 = crypto.createHash("sha256").update(newContents).digest("hex");
231
+ if (newSHA256 !== module.sourceSHA256) {
232
+ changedModules.add(module);
233
+ }
234
+ }
235
+ console.log(magenta(`Hot reloading changed modules:`));
236
+ for (let module of changedModules) {
237
+ if (module.updateContents) {
238
+ let justContents = module.hotreload === false || isNode() && module.noserverhotreload;
239
+ console.log(magenta(` ${module.filename} ${justContents && "(no re-evaluate)" || ""}`));
240
+ setExternalHotReloading(true);
241
+ try {
242
+ try {
243
+ module.updateContents();
244
+ } catch (e) {
245
+ console.error(red(`Error when loading module contents ${module.filename}`));
246
+ console.error(e);
247
+ }
248
+ if (!justContents) {
249
+ module.loaded = false;
250
+ try {
251
+ module.load(module.id);
252
+ } catch (e) {
253
+ module.load(module.id);
254
+ console.error(red(`Error when hot reloading ${module.filename}`));
255
+ console.error(e);
256
+ }
257
+ }
258
+ } finally {
259
+ setExternalHotReloading(false);
260
+ }
261
+ } else {
262
+ console.log(red(` (skipping due to missing updateContents functions) ${module.filename}`));
263
+ }
264
+ }
265
+
266
+ for (let watcher of watchers) {
267
+ if (watcher.disposed) {
268
+ watcher.dispose();
269
+ } else {
270
+ watcher.explicitlyTrigger();
271
+ }
272
+ }
273
+ });
274
+
275
+ async function executeCommand(command: string, args: string[], options?: {
276
+ cwd?: string;
277
+ }) {
278
+ child_process.execSync(`${command} ${args.join(" ")}`, {
279
+ stdio: "inherit",
280
+ cwd: options?.cwd
281
+ });
282
+ }