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,518 @@
1
+ import { cache } from "socket-function/src/caching";
2
+ import { nextId, recursiveFreeze } from "socket-function/src/misc";
3
+ import { canHaveChildren } from "socket-function/src/types";
4
+ import { isDefined } from "../misc";
5
+
6
+ function example() {
7
+ return {
8
+ value: t.number,
9
+ valueUnion: t.number.string,
10
+ valueWithWeirdDefault: t.number(1),
11
+ obj: t.atomic({ key: "", value: 0 }),
12
+ objWithUnion: t.atomic<{ key: string; value: number | string; }>({ key: "", value: "" }),
13
+ users: t.lookup({
14
+ name: t.string
15
+ }),
16
+ userNoGC: t.lookupNoGC({
17
+ name: t.string
18
+ }),
19
+ // atomicOnlyOnWrite makes new reads faster, without breaking old non-atomic data
20
+ // (but it means you can't do truthy checks on the value).
21
+ metadata: t.atomicOnlyOnWrite<{ a: number }>()
22
+ };
23
+ }
24
+
25
+ type TypeDefType<T = never, Optional = false> = {
26
+
27
+ /** A passthrough to strip the type wrapper for qreact.state,
28
+ * or for piecing together schemas.
29
+ */
30
+ state<StateT>(typeDef: StateT): TypeDefToT<StateT>;
31
+
32
+ // NOTE: BigInt isn't supported well by cbor-x (their documentation says it is stored in 64-bit precision,
33
+ // although maybe that is out of date?), so we won't encourage it's use here.
34
+ // NOTE: Symbol isn't supported by cbor-x, so we definitely won't encourage it's use here.
35
+ // NOTE: Buffer will probably work fine, but... it is better to not use it directly, as it encourages
36
+ // using large buffers, which... while they might work, are not handled efficiently by our framework.
37
+ // It is better to offload large Buffers to cloud storage and pass around a hash instead.
38
+
39
+ /** Atomic values are read/written atomically, as the value OR undefined (if not value is set). This allows
40
+ * objects to be used more naturally, without having to wrap them in atomic() calls.
41
+ */
42
+ atomic<NewT>(defaultValue?: T | NewT): TypeDefType<T | NewT>;
43
+ type<NewT>(defaultValue?: T | NewT): TypeDefType<T | NewT>;
44
+
45
+ // NOTE: Defaults aren't that useful, as the type will still require the values on write.
46
+ // Defaults are mostly for new fields, so that readers of old types don't have to deal with undefined.
47
+ // - And we can't make the fields optional for writers, are this would make readers have to handle
48
+ // the field being undefined, and as there are usually more readers than writers, this is even worse.
49
+ // - Of course, you can always write `undefined as any` to the field on creation, which won't write to
50
+ // it, and will force the default to be used. It's not great, but... it would work, and at least
51
+ // if the field is removed entirely you will get a type error (but if the field's default is removed,
52
+ // you won't get a type error for the place that writes undefined).
53
+ number: TypeDefType<T | number> & ((defaultValue: T | number) => TypeDefType<T | number>);
54
+ string: TypeDefType<T | string> & ((defaultValue: T | string) => TypeDefType<T | string>);
55
+ boolean: TypeDefType<T | boolean> & ((defaultValue: T | boolean) => TypeDefType<T | boolean>);
56
+ undefined: TypeDefType<T | undefined> & ((defaultValue: T | undefined) => TypeDefType<T | undefined>);
57
+ null: TypeDefType<T | null> & ((defaultValue: T | null) => TypeDefType<T | null>);
58
+
59
+ // NOTE: By definition, optional values don't have defaults
60
+ numberOptional: TypeDefType<T | number, true>;
61
+ stringOptional: TypeDefType<T | string, true>;
62
+ booleanOptional: TypeDefType<T | boolean, true>;
63
+ undefinedOptional: TypeDefType<T | undefined, true>;
64
+ nullOptional: TypeDefType<T | null, true>;
65
+
66
+ /** Atomic and optional */
67
+ optional<NewT>(defaultValue?: T | NewT): TypeDefType<T | NewT, true>;
68
+
69
+ /** An object (so not atomic), but optional (so you can delete it).
70
+ * NOTE: We automatically create an AliveChecker for optionalObjects when creating a schema. If you don't
71
+ * want this, use optionalObjectNoGC.
72
+ */
73
+ optionalObject<ObjectT>(object: ObjectT): TypeDefType<TypeDefToT<ObjectT>, true>;
74
+
75
+ /** An object (so not atomic), but optional (so you can delete it), without automatic GCing of deleted objects. */
76
+ optionalObjectNoGC<ObjectT>(object: ObjectT): TypeDefType<TypeDefToT<ObjectT>, true>;
77
+
78
+ /** "optionalObject" should usually be used, OR, directly use objects, ex, { a: { value: t.string } }.
79
+ * However there are some cases when this is useful.
80
+ * - An object (so not atomic)
81
+ */
82
+ object<ObjectT>(object: ObjectT): TypeDefType<TypeDefToT<ObjectT>, true>;
83
+
84
+ /** IMPORTANT! This allows anyone who passes the allowedToWrite check (as well as normal read permissions
85
+ * check), is allow to write arbitrary to ANY values at or under `undoRegion[key]`
86
+ * - USED on lookups, as this is what we want to do 99% of the time
87
+ * - Makes this an object (so it requires a value otherwise it is undefined). Mostly to simplify
88
+ * our implementation, but also... if the user can write any values to it, and so the extra
89
+ * safety is a good idea (maybe...)
90
+ */
91
+ undoRegion(
92
+ allowedToWrite: (key: string, path: string[]) => boolean | undefined,
93
+ ): TypeDefType<T>;
94
+ /** Same as "undoRegion", but applies to the entire object. For example if you want a user to be
95
+ * able to undo a specific object in their state, but nothing else.
96
+ */
97
+ undoRegionObject(
98
+ allowedToWrite: (key: string, path: string[]) => boolean | undefined,
99
+ ): TypeDefType<T>;
100
+
101
+ /** NOTE: GC Delay is extremely useful if deleted keys are stored in another lookup
102
+ * with the ability for the user to "see deleted", and then undo deletions.
103
+ * If the user adds back the key to the lookup (via undeleteFromLookup),
104
+ * the value will no longer be GCed.
105
+ */
106
+ gcDelay(gcDelay: number): TypeDefType<T>;
107
+
108
+ /** ONLY atomic on write, not on read. Useful for backwards compatibility,
109
+ * when we made a type atomic for efficiency, and still have old data that isn't atomic.
110
+ * (New writes will be atomic, but reads won't be, so you have to use atomic() on all
111
+ * the resulting fields, or check a specific field, etc).
112
+ */
113
+ atomicOnlyOnWrite<NewT>(defaultValue?: T | NewT): TypeDefType<T | NewT>;
114
+ /** Lookups behave as expected with respect to delete, even if the entries are not atomic.
115
+ * As in, `delete lookup["a"]` will mean `lookup["a"] === undefined`, even
116
+ * if there are nested non-atomic values in the lookup.
117
+ *
118
+ * NOTE: There is no array. We don't support synchronized arrays. Use an object instead,
119
+ * even using numbers as keys if you want (they'll be casted to strings, but at least
120
+ * it will add some type safety).
121
+ * - Arrays can't work because shifting elements would require updating all keys, which
122
+ * is inefficient.
123
+ * - You can use Object.keys(), and sorting, with unique keys, to simulate an array.
124
+ * - But, this can be slow. If the array is short, just make the entire array atomic instead.
125
+ *
126
+ * NOTE: We automatically create an AliveChecker for lookups when creating a schema. If you don't
127
+ * want this, use lookupNoGC.
128
+ */
129
+ lookup<NewT>(
130
+ schema: NewT,
131
+ ): TypeDefType<{ [key: string]: TypeDefToT<NewT> }>;
132
+ lookupNumber<NewT>(
133
+ schema: NewT,
134
+ ): TypeDefType<{ [key: number]: TypeDefToT<NewT> }>;
135
+ lookupEither<NewT>(
136
+ schema: NewT,
137
+ ): TypeDefType<{ [key: string | number]: TypeDefToT<NewT> }>;
138
+
139
+ /** Hints to the GC system not to collect the nested orphaned values. Required if the key
140
+ * is stored somewhere else, giving accessor access to the value.
141
+ * - ALSO, skips the special `delete` behavior (as otherwise you couldn't access
142
+ * the value), so you have to check if the object is set yourself
143
+ * (and the "in" check won't work, as you deleted the value, so... you likely
144
+ * need to check a special field, or, just assume it exists?)
145
+ */
146
+ lookupNoGC<NewT>(
147
+ schema: NewT
148
+ ): TypeDefType<{ [key: string | number]: TypeDefToT<NewT> }>;
149
+ lookupNumberNoGC<NewT>(
150
+ schema: NewT
151
+ ): TypeDefType<{ [key: number]: TypeDefToT<NewT> }>;
152
+ lookupEitherNoGC<NewT>(
153
+ schema: NewT
154
+ ): TypeDefType<{ [key: string | number]: TypeDefToT<NewT> }>;
155
+
156
+
157
+
158
+ // TODO: Support .union, which just spreads the inputs together, making an object which
159
+ // has both values (but the type which still be a union).
160
+
161
+ zzz_finalT: T;
162
+ zzz_optional: Optional;
163
+ };
164
+
165
+ const typeDefProxyRoot = new Proxy(() => { }, {
166
+ get(target, prop) {
167
+ if (typeof prop !== "string") return undefined;
168
+ if (prop === "state") {
169
+ return (value: unknown) => value;
170
+ }
171
+ let defaultValue: TypeDefValues = {
172
+ path: [],
173
+ pathWithCalls: [],
174
+ };
175
+ defaultValue.path.push(prop);
176
+ defaultValue.pathWithCalls.push({ key: prop, callParams: [] });
177
+ return typeDefProxy(defaultValue);
178
+ },
179
+ });
180
+ export const t = typeDefProxyRoot as any as TypeDefType;
181
+
182
+ export type Schema2 = TypeDefObj;
183
+ export type Schema2T<T> = TypeDefToT<T>;
184
+
185
+ export type SchemaPath = (string | WildCard)[];
186
+
187
+ const WildCard = Symbol("WildCard");
188
+ type WildCard = typeof WildCard;
189
+ export class Schema2Fncs {
190
+ public static WildCard = WildCard;
191
+ public static isAtomic(schema: Schema2, type: "read" | "write", path: string[]): {
192
+ defaultValue?: unknown;
193
+ } | undefined {
194
+ let internalType = typeDefCached(schema);
195
+ let def = getDefMatch(internalType.defs, path);
196
+ if (def && !def.isNotAtomic) {
197
+ if (type === "read" && def.atomicOnlyOnWrite) {
198
+ return undefined;
199
+ }
200
+ return { defaultValue: def.defaultValue, };
201
+ }
202
+ return undefined;
203
+ }
204
+ public static isObject(schema: Schema2, path: string[]): boolean {
205
+ let internalType = typeDefCached(schema);
206
+ let def = getDefMatch(internalType.defs, path);
207
+ return !!def?.isObject;
208
+ }
209
+ public static getKeys(schema: Schema2, path: string[]): string[] | undefined {
210
+ let internalType = typeDefCached(schema);
211
+ let childValues = internalType.defs.filter(def => isDefKeyMatch(def.path, path));
212
+ if (childValues.length > 0) {
213
+ let keys = childValues.map(x => x.path[path.length]);
214
+ // If any are a wildcard, it means we have a lookup, so they need to
215
+ // synchronize the keys (they aren't constant).
216
+ if (keys.some(x => x === WildCard)) return undefined;
217
+ return Array.from(new Set(keys as string[]));
218
+ }
219
+
220
+ return undefined;
221
+ }
222
+ public static getPaths(schema: Schema2): SchemaPath[] {
223
+ return typeDefCached(schema).paths;
224
+ }
225
+ public static getGCObjects(schema: Schema2): {
226
+ path: SchemaPath;
227
+ gcDelay?: number;
228
+ }[] {
229
+ return typeDefCached(schema).gcObjects;
230
+ }
231
+
232
+ public static getAllUndoChecks(schema: Schema2): {
233
+ path: SchemaPath;
234
+ undoChecks: ((key: string, path: string[]) => boolean | undefined)[];
235
+ }[] {
236
+ let defs = typeDefCached(schema).defs;
237
+ return defs.map(def => {
238
+ if (!def.undoChecks) return undefined;
239
+ return {
240
+ path: def.path,
241
+ undoChecks: def.undoChecks,
242
+ };
243
+ }).filter(isDefined);
244
+ }
245
+ }
246
+
247
+
248
+
249
+ // #region Implementation
250
+
251
+ type EraseEmptyObject<T> = T extends Record<string, never> ? unknown : T;
252
+
253
+ /*
254
+ Super annoying type code:
255
+ Objects with zzz_finalT are treated as leaf nodes and resolved to their final type.
256
+ Objects with zzz_optional: true are made optional in the resulting type.
257
+ Other objects are recursively resolved.
258
+ */
259
+ type TypeDefToT<T> = T extends { zzz_finalT: infer X } ? X : (
260
+ //{ [K in keyof T]: TypeDefToT<T[K]> }
261
+ EraseEmptyObject<{ [K in keyof T as T[K] extends { zzz_optional: true } ? never : K]: TypeDefToT<T[K]> }>
262
+ & EraseEmptyObject<{ [K in keyof T as T[K] extends { zzz_optional: true } ? K : never]?: TypeDefToT<T[K]> }>
263
+ );
264
+
265
+ type TypeDefValues = {
266
+ path: string[];
267
+ pathWithCalls: { key: string; callParams: unknown[] }[];
268
+ };
269
+ const getProxyTypeDefs = Symbol("getProxyTypeDefs");
270
+ function typeDefProxy(typeDef: TypeDefValues) {
271
+ const proxy = new Proxy(() => { }, {
272
+ get(target, prop): any {
273
+ if (prop === getProxyTypeDefs) return typeDef;
274
+ if (typeof prop !== "string") return undefined;
275
+ typeDef.path.push(prop);
276
+ typeDef.pathWithCalls.push({ key: prop, callParams: [] });
277
+ return proxy;
278
+ },
279
+ apply(target, thisArg, args): any {
280
+ typeDef.pathWithCalls[typeDef.pathWithCalls.length - 1].callParams = args;
281
+ return proxy;
282
+ }
283
+ });
284
+ return proxy;
285
+ }
286
+
287
+
288
+ type InternalTypeDef = {
289
+ // NOTE: We don't store primitives, because, 1, we don't need to, and 2, because
290
+ // they could be added with the .type<number>() call, at which point we can't track them.
291
+ path: SchemaPath;
292
+ atomicOnlyOnWrite?: boolean;
293
+
294
+ isObject?: boolean;
295
+ isObjectGC?: boolean;
296
+
297
+ isNotAtomic?: boolean;
298
+
299
+ gcDelay?: number;
300
+
301
+ defaultValue?: unknown;
302
+
303
+ undoChecks?: ((key: string, path: string[]) => boolean | undefined)[];
304
+ };
305
+
306
+
307
+ type TypeDefObj = TypeDefType<any> | {
308
+ [key: string]: TypeDefObj;
309
+ }
310
+ function typeDefTypeToInternalType(
311
+ typeDef: TypeDefValues,
312
+ path: SchemaPath = [],
313
+ initialValues?: Partial<InternalTypeDef>
314
+ ): InternalTypeDef[] {
315
+ let rootResult: InternalTypeDef = { path, ...initialValues };
316
+ // NOTE: By default functions cause atomic behavior ("atomic", "type", etc), and don't need special handler
317
+ // Most of the overloads are for purely type behavior, and not runtime behavior (or just
318
+ // for nice aliases, so it is more clear what the functions do).
319
+ if (typeDef.path.includes("atomicOnlyOnWrite")) {
320
+ rootResult.atomicOnlyOnWrite = true;
321
+ }
322
+ let isLookup = false;
323
+ let isLookupGC = false;
324
+ if (typeDef.path.includes("lookup") || typeDef.path.includes("lookupNumber") || typeDef.path.includes("lookupEither")) {
325
+ isLookup = true;
326
+ isLookupGC = true;
327
+ }
328
+ if (typeDef.path.includes("lookupNoGC") || typeDef.path.includes("lookupNumberNoGC") || typeDef.path.includes("lookupEitherNoGC")) {
329
+ isLookup = true;
330
+ isLookupGC = false;
331
+ }
332
+
333
+ let isObject = false;
334
+ let isObjectGC = false;
335
+ if (typeDef.path.includes("optionalObject")) {
336
+ isObject = true;
337
+ isObjectGC = true;
338
+ }
339
+ if (typeDef.path.includes("optionalObjectNoGC") || typeDef.path.includes("object")) {
340
+ isObject = true;
341
+ isObjectGC = false;
342
+ }
343
+
344
+ let undoRegions = typeDef.pathWithCalls.filter(x => x.key === "undoRegionObject");
345
+ for (let undoRegion of undoRegions) {
346
+ let allowedToWrite = undoRegion.callParams[0] as (key: string, path: string[]) => boolean | undefined;
347
+ rootResult.undoChecks = rootResult.undoChecks || [];
348
+ rootResult.undoChecks.push(allowedToWrite);
349
+ }
350
+
351
+ let results: InternalTypeDef[] = [rootResult];
352
+ if (isLookup || isObject) {
353
+ rootResult.isNotAtomic = true;
354
+
355
+ let callArgs: unknown;
356
+ for (let part of typeDef.pathWithCalls) {
357
+ if (
358
+ part.key.startsWith("lookup")
359
+ || part.key === "optionalObject" || part.key === "optionalObjectNoGC" || part.key === "object"
360
+ ) {
361
+ callArgs = part.callParams[0];
362
+ }
363
+ }
364
+
365
+ let nestedValues: Partial<InternalTypeDef> = {
366
+ };
367
+ nestedValues.isObject = isObject;
368
+ // NOTE: Gets reset if the nestedValues are used with a primitive
369
+ nestedValues.isNotAtomic = true;
370
+ nestedValues.isObjectGC = isObjectGC || isLookupGC;
371
+ nestedValues.gcDelay = Number(typeDef.pathWithCalls.find(x => x.key === "gcDelay")?.callParams?.[0]) || undefined;
372
+
373
+ if (isLookup) {
374
+ let undoRegions = typeDef.pathWithCalls.filter(x => x.key === "undoRegion");
375
+ for (let undoRegion of undoRegions) {
376
+ let allowedToWrite = undoRegion.callParams[0] as (key: string, path: string[]) => boolean | undefined;
377
+ nestedValues.undoChecks = nestedValues.undoChecks || [];
378
+ nestedValues.undoChecks.push(allowedToWrite);
379
+ }
380
+ }
381
+
382
+ // Lookups are basically objects, except they end with a wildcard
383
+ let nestedPath = isLookup ? path.concat(WildCard) : path;
384
+
385
+ let directTypeDef = getTypeDefValues(callArgs as any);
386
+ results.push(...typeDefTypeToInternalType(
387
+ directTypeDef || { path: [], pathWithCalls: [] },
388
+ nestedPath,
389
+ nestedValues
390
+ ));
391
+ if (!directTypeDef) {
392
+ results.push(...typeDefToInternalType(callArgs as any, nestedPath));
393
+ }
394
+ } else {
395
+ if (typeDef.path.length > 0) {
396
+ rootResult.isNotAtomic = false;
397
+ }
398
+ let params = typeDef.pathWithCalls;
399
+ let baseDefault: unknown = undefined;
400
+ if (typeDef.path.includes("number")) {
401
+ baseDefault = 0;
402
+ } else if (typeDef.path.includes("string")) {
403
+ baseDefault = "";
404
+ } else if (typeDef.path.includes("boolean")) {
405
+ baseDefault = false;
406
+ }
407
+ rootResult.defaultValue = recursiveFreeze(params[params.length - 1]?.callParams?.[0] ?? baseDefault);
408
+ }
409
+ return results;
410
+ }
411
+ function getTypeDefValues(typeDef: TypeDefObj) {
412
+ let typeDefType: TypeDefValues | undefined = (typeof typeDef === "function" && typeDef[getProxyTypeDefs] || undefined);
413
+ return typeDefType;
414
+ }
415
+ function typeDefToInternalType(typeDef: TypeDefObj, path: SchemaPath = []): InternalTypeDef[] {
416
+ let typeDefType = getTypeDefValues(typeDef);
417
+ if (typeDefType) {
418
+ return typeDefTypeToInternalType(typeDefType, path);
419
+ }
420
+ if (!canHaveChildren(typeDef)) return [];
421
+ let results: InternalTypeDef[][] = [];
422
+ for (let [key, value] of Object.entries(typeDef)) {
423
+ results.push(typeDefToInternalType(value, path.concat(key)));
424
+ }
425
+ return results.flat();
426
+ }
427
+ const typeDefCached = cache((typeDef: TypeDefObj) => {
428
+ let defs = typeDefToInternalType(typeDef);
429
+ let gcObjects = defs.filter(def => def.isObjectGC);
430
+ // NOTE: If the lookup only has primitives, then it doesn't need to be GCed
431
+ gcObjects = gcObjects.filter(def => {
432
+ let nonPrimitivePath = def.path.concat(WildCard);
433
+ return defs.some(x => isMatch(nonPrimitivePath, x.path));
434
+ });
435
+ return {
436
+ defs,
437
+ paths: defs.map(x => x.path),
438
+ gcObjects: gcObjects.map(x => ({ path: x.path, gcDelay: x.gcDelay })),
439
+ };
440
+ });
441
+ function isMatch(defPath: SchemaPath, path: SchemaPath) {
442
+ if (path.length !== defPath.length) return false;
443
+ for (let i = 0; i < path.length; i++) {
444
+ if (defPath[i] === WildCard) continue;
445
+ if (path[i] === WildCard) continue;
446
+ if (path[i] === defPath[i]) continue;
447
+ return false;
448
+ }
449
+ return true;
450
+ }
451
+ function getDefMatch(defs: InternalTypeDef[], path: string[]) {
452
+ for (let def of defs) {
453
+ if (isMatch(def.path, path)) return def;
454
+ }
455
+ return undefined;
456
+ }
457
+ function isDefKeyMatch(defPath: SchemaPath, path: string[]) {
458
+ if (path.length + 1 > defPath.length) return false;
459
+ for (let i = 0; i < path.length; i++) {
460
+ if (defPath[i] === WildCard) continue;
461
+ if (path[i] === defPath[i]) continue;
462
+ return false;
463
+ }
464
+ return true;
465
+ }
466
+ function isDefParentOrEqualMatch(defPath: SchemaPath, path: string[]) {
467
+ if (defPath.length > path.length) return false;
468
+ for (let i = 0; i < defPath.length; i++) {
469
+ if (defPath[i] === WildCard) continue;
470
+ if (path[i] === defPath[i]) continue;
471
+ return false;
472
+ }
473
+ return true;
474
+ }
475
+
476
+ // let x = t.lookup({
477
+ // name: t.string,
478
+ // num: t.number,
479
+ // defaultedNum: t.number(1),
480
+ // obj: t.atomic<{ key: string; value: number }>(),
481
+ // objWriteOnly: t.atomicOnlyOnWrite<{ key: string; value: number }>(),
482
+ // justObj: {
483
+ // nestedLookup: t.lookupNoGC({
484
+ // k: t.number,
485
+ // str: t.string,
486
+ // and: t.boolean,
487
+ // more: t.atomic<{ key: string; value: number }>(),
488
+ // directLookup2: t.lookup(t.number),
489
+ // }),
490
+ // directLookup: t.lookup(t.number),
491
+ // gcedLookup: t.lookup({
492
+ // key: t.string,
493
+ // value: t.number,
494
+ // }),
495
+ // }
496
+ // });
497
+ // console.log(typeDefCached(x));
498
+
499
+ // console.log("name", Schema2Fncs.isAtomic(x, "read", ["x", "name"]));
500
+ // console.log("k", Schema2Fncs.isAtomic(x, "read", ["x", "justObj", "nestedLookup", "afsd", "k"]));
501
+ // console.log("j", Schema2Fncs.isAtomic(x, "read", ["x", "justObj", "nestedLookup", "afd", "j"]));
502
+ // console.log("objWriteOnly read", Schema2Fncs.isAtomic(x, "read", ["x", "objWriteOnly"]));
503
+ // console.log("objWriteOnly write", Schema2Fncs.isAtomic(x, "write", ["x", "objWriteOnly"]));
504
+ // console.log("lookup", Schema2Fncs.isAtomic(x, "read", ["x"]));
505
+
506
+ //console.log("isLookup lookup", Schema2Fncs.isLookup(x, []));
507
+ //console.log("isLookup lookup.obj", Schema2Fncs.isLookup(x, ["x", "obj"]));
508
+ //console.log("isLookup lookup.justObj.directLookup", Schema2Fncs.isLookup(x, ["x", "justObj", "directLookup"]));
509
+
510
+ // console.log("getKeys root", Schema2Fncs.getKeys(x, []));
511
+ // console.log("getKeys lookup", Schema2Fncs.getKeys(x, ["x"]));
512
+ // console.log("getKeys lookup.justObj.nestedLookup", Schema2Fncs.getKeys(x, ["x", "justObj", "nestedLookup", "x"]));
513
+ // console.log("getKeys lookup.justObj.directLookup", Schema2Fncs.getKeys(x, ["x", "justObj", "directLookup", "x"]));
514
+ // console.log("getKeys lookup.justObj.directLookup", Schema2Fncs.getKeys(x, ["x", "justObj", "directLookup"]));
515
+
516
+ // console.log("getGCLookups", Schema2Fncs.getGCLookups(x));
517
+
518
+ // #endregion
@@ -0,0 +1,129 @@
1
+ import { getOwnMachineId } from "../-a-auth/certs";
2
+ import { atomicObjectWrite, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
3
+ import { pathValueCommitter } from "../0-path-value-core/PathValueController";
4
+ import { getNextTime } from "../0-path-value-core/pathValueCore";
5
+ import { FunctionSpec, functionSchema, CallSpec } from "./PathFunctionRunner";
6
+ import { logErrors } from "../errors";
7
+ import { FunctionMetadata } from "./syncSchema";
8
+ import debugbreak from "debugbreak";
9
+ import cborx from "cbor-x";
10
+ import { lazy } from "socket-function/src/caching";
11
+ import { secureRandom } from "../misc/random";
12
+ import { onCallPredict } from "../4-querysub/QuerysubController";
13
+ const cborxInstance = lazy(() => new cborx.Encoder({ structuredClone: true }));
14
+
15
+ export async function deployFunction(spec: FunctionSpec) {
16
+ const { DomainName, ModuleId, FunctionId } = spec;
17
+ await proxyWatcher.commitFunction({
18
+ canWrite: true,
19
+ watchFunction: function writeTest() {
20
+ functionSchema()[DomainName].PathFunctionRunner[ModuleId].Sources[FunctionId] = atomicObjectWrite(spec);
21
+ },
22
+ });
23
+ }
24
+ export async function undeployFunction(spec: FunctionSpec) {
25
+ const { DomainName, ModuleId, FunctionId } = spec;
26
+ await proxyWatcher.commitFunction({
27
+ canWrite: true,
28
+ watchFunction: function writeTest() {
29
+ functionSchema()[DomainName].PathFunctionRunner[ModuleId].Sources[FunctionId] = undefined;
30
+ },
31
+ });
32
+ }
33
+
34
+ export const writeCall = {
35
+ value: async (callSpec: CallSpec, metadata: FunctionMetadata) => {
36
+ let domainName = callSpec.DomainName;
37
+ let moduleId = callSpec.ModuleId;
38
+ let callId = callSpec.CallId;
39
+ proxyWatcher.writeOnly({
40
+ canWrite: true,
41
+ eventWrite: true,
42
+ doNotStoreWritesAsPredictions: true,
43
+ // NOTE: We write the call in the present, it is callSpec.runAtTime that is in the past. No one cares
44
+ // when the call was added, only when it wants to run at.
45
+ watchFunction: function writeCall() {
46
+ functionSchema()[domainName].PathFunctionRunner[moduleId].Calls[callId] = atomicObjectWrite(callSpec);
47
+ },
48
+ });
49
+ await pathValueCommitter.waitForValuesToCommit();
50
+ }
51
+ };
52
+
53
+ export async function runCall(call: CallSpec, metadata: FunctionMetadata) {
54
+ return await writeCall.value(call, metadata);
55
+ }
56
+
57
+ interface CallInterceptor<T> {
58
+ onCall: (call: CallSpec, metadata: FunctionMetadata) => void;
59
+ code: () => T;
60
+ }
61
+
62
+ let curInterceptor: CallInterceptor<unknown> | undefined = undefined;
63
+ export function interceptCalls<T>(
64
+ interceptor: CallInterceptor<T>
65
+ ) {
66
+ let prev = curInterceptor;
67
+ curInterceptor = interceptor;
68
+ try {
69
+ return interceptor.code();
70
+ } finally {
71
+ curInterceptor = prev;
72
+ }
73
+ }
74
+
75
+ export function writeFunctionCall(config: {
76
+ domainName: string;
77
+ moduleId: string;
78
+ functionId: string;
79
+ args: unknown[];
80
+ metadata: FunctionMetadata,
81
+ }) {
82
+ let { domainName, moduleId, functionId, args, metadata } = config;
83
+
84
+ let now = Date.now();
85
+ // IMPORTANT! CallId MUST be secure, otherwise other users can guess it, and read our calls
86
+ // (which could contain secret data).
87
+ // secureRandom() is pretty good... combined with date it should be hard enough to guess
88
+ // - date adds about 10 bits of randomness (an attacker might be able to guess within a second
89
+ // the time a specific call was made)
90
+ // - secureRandom adds at more 64 bits of randomess, but in reality it is probably less
91
+ // - ALSO, if they don't guess the call within the event remove window... it will be be deleted,
92
+ // which gives them about a minute to guess it...
93
+ let callId = now + "_" + secureRandom();
94
+ let argsEncoded = encodeArgs(args);
95
+ let callSpec: CallSpec = {
96
+ argsEncoded,
97
+ ModuleId: moduleId,
98
+ CallId: callId,
99
+ DomainName: domainName,
100
+ FunctionId: functionId,
101
+ runAtTime: getNextTime(),
102
+ callerMachineId: getOwnMachineId(),
103
+ // Will be updated by Querysub to be correct (unless we are directly writing, then...
104
+ // this is fine).
105
+ callerIP: "127.0.0.1",
106
+ };
107
+
108
+ if (curInterceptor) {
109
+ curInterceptor.onCall(callSpec, metadata);
110
+ } else {
111
+ logErrors(writeCall.value(callSpec, metadata));
112
+ }
113
+
114
+ return callSpec;
115
+ }
116
+
117
+ export function parseArgs(call: CallSpec) {
118
+ return cborxInstance().decode(Buffer.from(call.argsEncoded, "base64"));
119
+ }
120
+
121
+ export function encodeArgs(args: unknown[]) {
122
+ // NOTE: This will throw if we try to encode a function. This is probably okay.
123
+ // - We use this to prevent accidentally encoding VirtualDOM to the database / objects
124
+ // with functions.
125
+ // - If this becomes an issue, we might catch the error, and strip functions if we
126
+ // have found them.
127
+ // - Or... is there are a way to tell cborx to strip the functions?
128
+ return cborxInstance().encode(args).toString("base64");
129
+ }