@typetorch/framework 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -9
- package/out/analytics/SCHEMA.md +6 -4
- package/out/analytics/engine.luau +1 -1
- package/out/analytics/schema.d.ts +4 -8
- package/out/analytics/schema.luau +1 -7
- package/out/analytics/server.d.ts +1 -1
- package/out/analytics/server.luau +39 -88
- package/out/analytics/settings.d.ts +3 -3
- package/out/analytics/settings.luau +3 -3
- package/out/detached.d.ts +26 -0
- package/out/detached.luau +95 -0
- package/out/devtools/admin-server.luau +30 -52
- package/out/devtools/client.luau +13 -4
- package/out/devtools/health.d.ts +24 -3
- package/out/devtools/health.luau +200 -3
- package/out/devtools/roll-call.d.ts +11 -6
- package/out/devtools/roll-call.luau +11 -6
- package/out/index.d.ts +8 -1
- package/out/init.luau +6 -0
- package/out/kernel.d.ts +186 -2
- package/out/kernel.luau +26 -1
- package/out/messaging.d.ts +74 -0
- package/out/messaging.luau +479 -0
- package/out/module.d.ts +9 -0
- package/out/module.luau +2 -0
- package/out/net/compat.d.ts +147 -0
- package/out/net/compat.luau +440 -0
- package/out/net/index.d.ts +11 -2
- package/out/net/init.luau +26 -3
- package/out/net/limits.d.ts +11 -0
- package/out/net/limits.luau +25 -0
- package/out/net/runtime.d.ts +15 -2
- package/out/net/runtime.luau +76 -9
- package/out/net/types.d.ts +20 -1
- package/out/runtime/dependency.d.ts +79 -0
- package/out/runtime/dependency.luau +240 -0
- package/out/runtime/player-state.d.ts +30 -0
- package/out/runtime/player-state.luau +183 -0
- package/out/runtime/relay.d.ts +15 -0
- package/out/runtime/relay.luau +58 -0
- package/out/runtime/start.luau +119 -40
- package/out/servers.d.ts +70 -0
- package/out/servers.luau +364 -0
- package/out/settings.d.ts +49 -0
- package/out/settings.luau +236 -0
- package/out/typetorch.d.ts +70 -1
- package/out/typetorch.luau +82 -3
- package/out/version.d.ts +1 -1
- package/out/version.luau +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,16 +57,25 @@ swap. It needs the TypeTorch kernel in the place (`@typetorch/kernel`).
|
|
|
57
57
|
|
|
58
58
|
### Coming from Flamework
|
|
59
59
|
|
|
60
|
-
Game code only changes imports: `@Service`, `@Controller`, constructor injection, the lifecycle
|
|
61
|
-
`createNetwork` are the same.
|
|
60
|
+
Game code only changes imports: `@Service`, `@Controller`, constructor injection, `Dependency<T>()`, the lifecycle
|
|
61
|
+
interfaces and `createNetwork` are the same.
|
|
62
62
|
|
|
63
63
|
| Flamework | TypeTorch |
|
|
64
64
|
|---|---|
|
|
65
65
|
| `rbxts-transformer-flamework` plugin, `node_modules/@flamework` type root | `@typetorch/transformer` plugin, `node_modules/@typetorch` type root |
|
|
66
66
|
| `import { Modding, Reflect } from "@flamework/core"` | `import { Modding, Reflect } from "@typetorch/framework"` |
|
|
67
|
+
| `import { Dependency } from "@flamework/core"` | `import { Dependency } from "@typetorch/framework"` (from onInit/onStart on, not in constructors) |
|
|
68
|
+
| `import type` + `Dependency<T>()` to break a cycle | the same, or `Lazy<T>` (below) |
|
|
69
|
+
| `ClientEvents.x.predict(...)` | `network.client.x.emit(...)` |
|
|
70
|
+
| `invokeWithTimeout(timeout, ...)` | `invokeWithTimeout(seconds, ...)` (seconds, 0.5 to 120) |
|
|
71
|
+
| `Observers.observeCharacter` / `observeLocalCharacter` (@rbxts/observers) | still fine: put the stop function in the trove, `this.trove.add(observeCharacter(...))` |
|
|
67
72
|
| `import { t } from "@rbxts/t"` (still fine) | also `import { t } from "@typetorch/framework"` |
|
|
68
73
|
| `@metadata flamework:parameters` keys, `"flamework:parameters"` metadata | `@metadata typetorch:parameters`, `"typetorch:parameters"` |
|
|
69
74
|
| `flamework.build`, `include/flamework`, the `@flamework` Rojo mapping | gone; delete them |
|
|
75
|
+
| `Networking.createEvent` / `createFunction` and their call sites | `createFlameworkCompat` (below) keeps every call site; `createNetwork` is the native API |
|
|
76
|
+
|
|
77
|
+
`typetorch migrate --from flamework` (CLI) does most of this, and the docs' `guides/from-flamework.md` has the whole
|
|
78
|
+
mapping.
|
|
70
79
|
|
|
71
80
|
## Usage
|
|
72
81
|
|
|
@@ -99,6 +108,19 @@ export class CoinService extends Module implements OnStart {
|
|
|
99
108
|
- **Modules:** `@Service()` (server) and `@Controller()` (client) classes extending `Module`. Constructor parameters
|
|
100
109
|
are other modules, injected by type. Lifecycle: `OnInit` (sequential, dependencies first), `OnStart` (spawned),
|
|
101
110
|
`OnStop` (reverse order), `OnTick`, `OnPhysics`, `OnRender`, `OnPlayerAdded` (replays players already in the server).
|
|
111
|
+
- **Modules outside the constructor:** `Dependency<T>()` (or `TypeTorch.module<T>()`) returns the running module of
|
|
112
|
+
type T from anywhere: a method, a plain class a module built, a command handler. `TypeTorch.tryModule<T>()` returns
|
|
113
|
+
undefined instead of throwing. They work once every module is constructed (from onInit on); in a constructor, a
|
|
114
|
+
field initializer or module top-level code they throw `"ShopService isn't constructed yet: ..."`. Only this realm's
|
|
115
|
+
modules, and per generation: after a swap they return the new generation's instance. An `import type` of the class is
|
|
116
|
+
enough (no require cycle).
|
|
117
|
+
- **Cycles:** two modules that need each other take one side as `Lazy<T>`, resolved on first use:
|
|
118
|
+
`constructor(private readonly team: Lazy<TeamService>)` (needs `@typetorch/transformer` 0.2.1+) or a field,
|
|
119
|
+
`private readonly team = Lazy<TeamService>()` (any transformer). `this.team.get()` works from onInit on (in a
|
|
120
|
+
constructor it throws), and the start order ignores lazy edges. A cycle error names the cycle and suggests this.
|
|
121
|
+
- **Per-player state:** `this.ctx.playerState("cooldowns.v1", (player) => init)` (or `TypeTorch.playerState`) returns
|
|
122
|
+
`{ get, set, has, delete }` keyed by UserId. It survives swaps (persist), and a player's entry goes when they really
|
|
123
|
+
leave, never on a swap. The dev menu shows it under Modules > State > persist, `__playerState`.
|
|
102
124
|
- **Bad deploys roll back (kernel 0.3.2):** a server `onStart` that throws is reported to the kernel, which rolls the
|
|
103
125
|
server back to its last known good artifact when it happens within 30 s of start (so do 3 errors from the new
|
|
104
126
|
code's scripts in that time). On a real shutdown every module's `onStop` runs too (the kernel's BindToClose).
|
|
@@ -106,6 +128,18 @@ export class CoinService extends Module implements OnStart {
|
|
|
106
128
|
behind. `TypeTorch.persist(key, init)` (or `this.ctx.persist`) keeps plain data across swaps.
|
|
107
129
|
- **Network:** `createNetwork<C2S, S2C>()` with nested namespaces. The server checks rate limits, shape limits and the
|
|
108
130
|
generated type guard on every client message. `setNetworkLimits({ "chat.say": { maxString: 200 } })` tunes a leaf.
|
|
131
|
+
Requests wait 15 s for the answer: `invokeWithTimeout(5, ...args)` sets it per call, and a leaf's `timeout`
|
|
132
|
+
(`setNetworkLimits({ "vc.spawn": { timeout: 30 } })`, read by the client, so set it in shared code) per leaf; both in
|
|
133
|
+
seconds, 0.5 to 120. `network.client.x.emit(...args)` runs this client's own `on` handlers for a server -> client
|
|
134
|
+
leaf right away, with no traffic (Flamework's `predict`); `network.server.x.emit(player, ...args)` does the same on
|
|
135
|
+
the server, for tests. A request handler may return a Promise: the answer waits for it.
|
|
136
|
+
- **Flamework networking names:** `createFlameworkCompat<C2SEvents, S2CEvents, C2SFunctions, S2CFunctions>()` returns
|
|
137
|
+
`{ ServerEvents, ClientEvents, ServerFunctions, ClientFunctions, GlobalEvents, GlobalFunctions }` with
|
|
138
|
+
`@flamework/networking` 1.x's methods (`connect` returning a connection, `fire(player | players)`, `broadcast`,
|
|
139
|
+
`except`, `predict`, `setCallback`, `invoke`, `invokeWithTimeout`, calling a leaf directly) on this same network:
|
|
140
|
+
same guards, limits and swap rules, so a Flamework game's call sites compile unchanged. Listeners run on their own
|
|
141
|
+
thread each, like Flamework's, and belong to the running generation (no trove needed). Not carried over: server ->
|
|
142
|
+
client requests, middleware, `Networking.Unreliable`. `typetorch build` notes a game still using it.
|
|
109
143
|
Across a swap (0.3.0): what the server sends a player waits until that player's client runs the new generation
|
|
110
144
|
(reliable messages are queued, unreliable ones dropped), and on kernel 0.3.2 a request the server can no longer
|
|
111
145
|
answer fails at once ("The game is updating, try again.") instead of timing out.
|
|
@@ -182,6 +216,13 @@ this.trove.add(TypeTorch.onUpdatePending((update) => (hint.Visible = !update.can
|
|
|
182
216
|
|
|
183
217
|
// State that survives swaps (plain data only)
|
|
184
218
|
const scores = TypeTorch.persist("scores.v1", () => new Map<number, number>());
|
|
219
|
+
// Per player, removed when the player really leaves (same as this.ctx.playerState)
|
|
220
|
+
const cooldowns = TypeTorch.playerState("cooldowns.v1", () => ({ lastUse: 0 }));
|
|
221
|
+
cooldowns.get(player).lastUse = os.clock();
|
|
222
|
+
|
|
223
|
+
// The running module of a type (same as Dependency<T>()), from onInit/onStart on
|
|
224
|
+
TypeTorch.module<ShopService>().open(player);
|
|
225
|
+
TypeTorch.tryModule<ShopService>()?.open(player); // undefined instead of an error
|
|
185
226
|
|
|
186
227
|
// Dev and roles (server: the kernel decides; client: only the local player, cosmetic)
|
|
187
228
|
if (TypeTorch.isOwner(player)) showOwnerPanel(player);
|
|
@@ -198,6 +239,23 @@ TypeTorch.onLog((entry) => errors.push(entry)); // don't print from inside it
|
|
|
198
239
|
|
|
199
240
|
// Hot assets (below): same as hotAsset(...)
|
|
200
241
|
const shop = TypeTorch.asset("ui/shop");
|
|
242
|
+
|
|
243
|
+
// Live values, server only (kernel 0.3.8): the signed settings record's `game` field (`typetorch settings set game.<key> <json>`).
|
|
244
|
+
const price = TypeTorch.liveConfig("shop.price", { default: 50, parse: (raw) => (typeIs(raw, "number") ? raw : 50) });
|
|
245
|
+
price.get();
|
|
246
|
+
this.trove.add(price.onChanged((value) => this.reprice(value)));
|
|
247
|
+
|
|
248
|
+
// Cross-server, server only. Kernel 0.3.8: game topics over ONE kernel-held MessagingService topic; listeners hear
|
|
249
|
+
// prod servers and this branch by default (dev branches never reach prod); publish queues and retries, max 1 KiB.
|
|
250
|
+
this.trove.add(TypeTorch.messaging.subscribe<Ban>("1guard", (ban, meta) => applyBan(ban, meta.jobId)));
|
|
251
|
+
TypeTorch.messaging.publish("1guard", { kind: "ban", userId }, { to: "prod" });
|
|
252
|
+
|
|
253
|
+
// The universe's live servers (the roll call; cached per server, yields up to ~3 s when stale) and this server's public fields
|
|
254
|
+
TypeTorch.setServerInfo({ region: "eu", vc: true }); // JSON, at most 400 bytes, kept across swaps
|
|
255
|
+
const list = TypeTorch.servers(); // [{ jobId, placeVersion, players, maxPlayers, serverType, branch, channel, uptime, here, info }]
|
|
256
|
+
|
|
257
|
+
// A library call a swap must not cut off halfway (kernel 0.3.8): runs on a kernel thread; write what must survive into persist.
|
|
258
|
+
TypeTorch.runDetached(() => store.StartSessionAsync(`Player_${userId}`)).then((profile) => attach(player, profile));
|
|
201
259
|
```
|
|
202
260
|
|
|
203
261
|
- **`startInfo.reason`:** `boot`, `deploy`, `rollback`, `branch`, `pin`, `reload`, `server_rollback`,
|
|
@@ -214,6 +272,25 @@ const shop = TypeTorch.asset("ui/shop");
|
|
|
214
272
|
reason and timings, the next artifact in `onSwapOut`, `onUpdatePending`, server-side `onPlayerDevChanged` and
|
|
215
273
|
`requestReload`. On older kernels `startInfo` still knows boot vs swap, the previous artifact and branch (the
|
|
216
274
|
framework records them), `onBranchChanged` still fires, and the reason of a swap is `unknown`.
|
|
275
|
+
- **`messaging`** (kernel 0.3.8): `subscribe(topic, (data, meta) => ...)` returns a disconnect; `meta` = `{ channel,
|
|
276
|
+
branch, jobId, serverType, placeVersion, sentAt, self, replayed? }`; `{ from: "all" | "prod" | "branch" }` changes
|
|
277
|
+
who it hears. `publish(topic, data, { to })` throws on a bad topic, non-JSON data or a message over 1 KiB (with its
|
|
278
|
+
size), returns false when dropped at once (queue full). Studio loops back; the cloud test publishes nothing; older
|
|
279
|
+
kernels throw "needs kernel 0.3.8". `servers()` works on every kernel (0.3.8 holds the roll call topic, so a swap
|
|
280
|
+
doesn't subscribe again).
|
|
281
|
+
- **`liveConfig(key, { default, parse? })`** (kernel 0.3.8, plans/20): reads `game[key]` of the signed settings record the
|
|
282
|
+
kernel verified (signed by both prod keys, so game code can't forge it); `get()` and `onChanged(fn)` (only on real
|
|
283
|
+
changes; generation-scoped). The default without a record or key, when `parse` throws (warned per value), and on older
|
|
284
|
+
kernels (warned once: "needs kernel 0.3.8"; no ConfigService fallback). `TypeTorch.settings()` is the whole copy
|
|
285
|
+
(`seq`, `at`, `game`, `analytics`, `fleet`, ...): server only, it holds tokens.
|
|
286
|
+
- **`runDetached(fn)`** (kernel 0.3.8, server only): runs `fn` on a thread the kernel owns, so a deploy's hard stop
|
|
287
|
+
(which kills this generation's threads mid-call) can't cut it off: for ProfileStore / ProfileService loads, saves and
|
|
288
|
+
releases. Returns a Promise that settles while this generation runs; after a swap the result is dropped, so a job
|
|
289
|
+
whose result must survive writes it into `persist` itself. The job keeps this generation's closures alive until it
|
|
290
|
+
ends. Errors reject it and go to the kernel's log with the generation's name (never the health window); at most 256
|
|
291
|
+
run at once per server (then it throws); one past 60 s is logged and flagged in Server > Status. Older kernels
|
|
292
|
+
throw "needs kernel 0.3.8; use the DataHost job queue (Player data guide)": the other option, which works on every
|
|
293
|
+
kernel. `features.runDetached` tells which one this server has.
|
|
217
294
|
- **Edit mode** (UI Labs stories, no kernel): `running` is false, identity has defaults, `persist` keeps a local table,
|
|
218
295
|
events never fire, and the server-only reads throw.
|
|
219
296
|
|
|
@@ -227,6 +304,8 @@ pieces that keep it safe across swaps:
|
|
|
227
304
|
- **Session handles live in `persist`**, keyed by `UserId`. `onPlayerAdded` replays everyone after a swap, so it
|
|
228
305
|
re-attaches to the open session instead of loading again.
|
|
229
306
|
- **Release only on `Players.PlayerRemoving`**, never in `onStop` or `onSwapOut`.
|
|
307
|
+
- **Library calls can't be cut off by a swap:** run them as DataHost jobs (any kernel) or with
|
|
308
|
+
`TypeTorch.runDetached` (kernel 0.3.8).
|
|
230
309
|
- **Split store names by channel** (`TypeTorch.channel === "prod" ? "PlayerData" : "PlayerData_dev"`).
|
|
231
310
|
|
|
232
311
|
Full example with ProfileStore: [Player data guide](https://github.com/typetorch/docs/blob/main/guides/player-data.md).
|
|
@@ -287,7 +366,7 @@ self-hosted DuckDB analytics server). Nothing runs until an engine is created: n
|
|
|
287
366
|
```ts
|
|
288
367
|
import { AnalyticsEngine } from "@typetorch/framework";
|
|
289
368
|
|
|
290
|
-
// A server module (onInit):
|
|
369
|
+
// A server module (onInit): the sink comes from the signed settings' `analytics` (kernel 0.3.8).
|
|
291
370
|
const analytics = new AnalyticsEngine();
|
|
292
371
|
analytics.track(player, "quest_done", { quest: "tutorial" }); // custom
|
|
293
372
|
analytics.step(player, "onboarding", 3, "opened_shop"); // funnels
|
|
@@ -317,13 +396,13 @@ const variant = analytics.experiment("onboarding", ["short", "long"]); // same a
|
|
|
317
396
|
`TTZone`, named by a `Name` attribute or the instance name), `screens` (ScreenGuis in PlayerGui, GuiObjects tagged
|
|
318
397
|
`TTScreen`), `recording` (the first-ever session in detail), `fleet` (kernel 0.3.2 heartbeats and deploy reports),
|
|
319
398
|
`identity` (one `{ pid, uid }` row per session to the dev's own server, below). `settings` (server only) replaces the
|
|
320
|
-
|
|
399
|
+
signed settings' `analytics`, for tests, Studio and kernels before 0.3.8.
|
|
321
400
|
- **Every row** carries the time (server clock), a random player id (never the UserId), the session, the server
|
|
322
401
|
(JobId, type, place), the artifact (id, seq, branch, channel), the device, new vs returning, the player's state
|
|
323
402
|
(`zone:Lobby|screen:Shop|activity:round`) and experiment variants. The exact rows: `src/analytics/SCHEMA.md`.
|
|
324
403
|
- **Experiments:** `experiment(...)` is deterministic per player and name (they keep their variant in every session),
|
|
325
404
|
may yield briefly the first time (until the player's id loads), and stamps the variant on the player's later events.
|
|
326
|
-
The settings
|
|
405
|
+
The settings turn one off (`active: false`: everyone gets the first variant), sets `weights` or forces a
|
|
327
406
|
`variant`, live.
|
|
328
407
|
- **First-ever session:** for new players (a share of them, `recordShare`), the client records character and camera
|
|
329
408
|
about 10 times a second, every input (never while a TextBox or the chat has focus; text boxes only say which box was
|
|
@@ -333,7 +412,7 @@ const variant = analytics.experiment("onboarding", ["short", "long"]); // same a
|
|
|
333
412
|
row carries a UserId.
|
|
334
413
|
- **Identities** (option `identity`, default on): once the pid is known, one row `{ pid, uid, t }` (the UserId, nothing
|
|
335
414
|
else) per session goes to the dev's own server only, never into the events: duckdb games in the batch body, Basin
|
|
336
|
-
games to the fleet API (`
|
|
415
|
+
games to the fleet API (the settings' `fleet` url + `/v1/identity`, or the `identity` / `identityToken` settings). The
|
|
337
416
|
analytics server keeps pid -> UserId in a deletable table (support lookups, Right to Erasure).
|
|
338
417
|
- **Player ids:** DataStore `TypeTorchAnalytics`, key `p/<UserId>` -> `{ pid, first, last }`: one read per join, a
|
|
339
418
|
write on the first join and at leave. Deleting the key (Right to Erasure) leaves that player's rows anonymous.
|
|
@@ -352,9 +431,10 @@ const variant = analytics.experiment("onboarding", ["short", "long"]); // same a
|
|
|
352
431
|
- **Friends in the server** (the join's `friends`): one `Players:GetFriendsAsync` per join (at most 10 pages, none
|
|
353
432
|
when the player is alone) intersected with the players there, not a web call per player.
|
|
354
433
|
|
|
355
|
-
**Settings** (server only, never sent to clients): the
|
|
356
|
-
|
|
357
|
-
|
|
434
|
+
**Settings** (server only, never sent to clients): the signed settings record's `analytics` field (kernel 0.3.8,
|
|
435
|
+
plans/20), written with `typetorch settings set analytics -` (JSON on stdin; `@typetorch/analytics`' `writeSettings()`
|
|
436
|
+
and `bun run local` call it). Every new copy applies live within seconds (the CLI pings servers). Before kernel 0.3.8
|
|
437
|
+
there are no settings unless the game passes `new AnalyticsEngine({ settings })`:
|
|
358
438
|
|
|
359
439
|
```json
|
|
360
440
|
{ "backend": "basin", "events": "https://<stream-id>.ingest.cloudflare.com",
|
|
@@ -392,6 +472,14 @@ bun run build # rbxtsc --type package -> out/
|
|
|
392
472
|
package.json.
|
|
393
473
|
`scripts/test-layout.luau` checks the dev menu's pane layout core (`devtools/layout.ts`: splits and their limits,
|
|
394
474
|
closing, pop out and dock, singleton pages, ratios, focus, and cleaning a remembered layout).
|
|
475
|
+
`scripts/test-modules.luau` runs the real `startServer`/`startClient` of `out/` (with RuntimeLib, Promise, trove and
|
|
476
|
+
t from node_modules) over several generations that share a persist store: `Dependency<T>()`, `TypeTorch.module` /
|
|
477
|
+
`tryModule` and their errors, `Lazy<T>` (parameters and fields, cycles, start order), network `emit` and request
|
|
478
|
+
timeouts and `playerState` (swaps, leaves).
|
|
479
|
+
`scripts/test-flamework-compat.luau` (same harness) drives `createFlameworkCompat` through each generation's
|
|
480
|
+
dispatcher: connect (one thread per call, guards, Disconnect, troves), fire / except / broadcast / the call
|
|
481
|
+
shorthand, setCallback with values and Promises, predict, a swap, and the client side (invoke, invokeWithTimeout,
|
|
482
|
+
`createClient({ defaultTimeout })`).
|
|
395
483
|
`scripts/test-analytics.luau` checks the analytics engine's pure parts (experiment assignment, settings, the queue
|
|
396
484
|
and HTTP budget, tt-rec-1, the sink request bodies); `test-analytics-server.luau` and `test-analytics-client.luau`
|
|
397
485
|
run the compiled server and client cores against mocked services (sessions, intake, retries, a swap with a request
|
package/out/analytics/SCHEMA.md
CHANGED
|
@@ -128,8 +128,10 @@ start, i.e. the row's `t`). Time is the running sum of `dt`.
|
|
|
128
128
|
|
|
129
129
|
## Sink settings
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
`
|
|
131
|
+
The signed settings record's `analytics` field (kernel 0.3.8, plans/20; server-only; before 0.3.8 the ConfigService key
|
|
132
|
+
`TypeTorchAnalytics`), a JSON object, written with `typetorch settings set analytics -` (JSON on stdin; the analytics
|
|
133
|
+
repo's `bun run local` does it). Every new signed copy applies at once, so dials change live (the CLI pings servers).
|
|
134
|
+
`new AnalyticsEngine({ settings })` replaces it (tests, Studio, kernels before 0.3.8).
|
|
133
135
|
|
|
134
136
|
```json
|
|
135
137
|
{
|
|
@@ -154,8 +156,8 @@ ConfigService key `TypeTorchAnalytics` (server-only), a JSON object (or a JSON s
|
|
|
154
156
|
| `recordShare` | 1 | 0..1, share of new players recorded (stable per pid) |
|
|
155
157
|
| `techEvery` | 60 | 15..3600 seconds between tech samples |
|
|
156
158
|
| `experiments` | none | Per experiment: `active: false` (everyone gets the first variant, not stamped), `weights` (per variant, in the game's order), `variant` (force one) |
|
|
157
|
-
| `identity` | `
|
|
158
|
-
| `identityToken` | the `
|
|
159
|
+
| `identity` | the settings' `fleet` url + `/v1/identity` | basin: where identity rows go (below); none and no `fleet` in the settings: not sent. duckdb: unused |
|
|
160
|
+
| `identityToken` | the settings' `fleet` token | basin: the token for `identity`. Never logged |
|
|
159
161
|
|
|
160
162
|
Unknown fields are ignored. Settings errors are warned once (never with the token).
|
|
161
163
|
|
|
@@ -10,7 +10,7 @@ local ServerAnalytics = TS.import(script, script.Parent, "server").ServerAnalyti
|
|
|
10
10
|
* client. Nothing runs until one is created. Every engine created in a generation shares one core, which stops with
|
|
11
11
|
* the generation; its unsent rows survive a hot swap in `persist`.
|
|
12
12
|
*
|
|
13
|
-
* The server sends rows to the sink in the settings (
|
|
13
|
+
* The server sends rows to the sink in the settings (the signed settings' `analytics`, kernel 0.3.8; or `options.settings`).
|
|
14
14
|
* The client sends everything through the server, never to the internet. See the README "Analytics" section and
|
|
15
15
|
* src/analytics/SCHEMA.md.
|
|
16
16
|
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Row format version (column `v`). */
|
|
7
7
|
export declare const SCHEMA_VERSION = 1;
|
|
8
|
-
/** The ConfigService key (server-only) that holds the sink settings. */
|
|
9
|
-
export declare const SETTINGS_KEY = "TypeTorchAnalytics";
|
|
10
|
-
/** The kernel's fleet API settings `{ url, token }` (server-only ConfigService key); identities can go there. */
|
|
11
|
-
export declare const FLEET_SETTINGS_KEY = "TypeTorchFleet";
|
|
12
8
|
/** The first-session recording codec (column `codec`). */
|
|
13
9
|
export declare const RECORDING_CODEC = "tt-rec-1";
|
|
14
10
|
/** Most bytes of the `props` JSON text. Larger props become `{"_trunc":<bytes>}`. */
|
|
@@ -91,7 +87,7 @@ export interface ExperimentOverride {
|
|
|
91
87
|
/** Everyone gets this variant (must be one of the game's variants). */
|
|
92
88
|
variant?: string;
|
|
93
89
|
}
|
|
94
|
-
/** The sink settings:
|
|
90
|
+
/** The sink settings: the signed settings' `analytics` (kernel 0.3.8, server-only), or `new AnalyticsEngine({ settings })`. */
|
|
95
91
|
export interface AnalyticsSettings {
|
|
96
92
|
backend: "basin" | "duckdb";
|
|
97
93
|
/** basin: the events stream's ingest URL; duckdb: the analytics server's ingest URL. */
|
|
@@ -109,12 +105,12 @@ export interface AnalyticsSettings {
|
|
|
109
105
|
/** Live experiment overrides by experiment name. */
|
|
110
106
|
experiments?: Record<string, ExperimentOverride>;
|
|
111
107
|
/**
|
|
112
|
-
* basin: where identity rows go (the fleet API's `POST /v1/identity` URL). Default: the `
|
|
108
|
+
* basin: where identity rows go (the fleet API's `POST /v1/identity` URL). Default: the settings' `fleet` url
|
|
113
109
|
* + /v1/identity when the server can read it; without either, identities aren't sent. duckdb: unused (they go in
|
|
114
110
|
* the batch body).
|
|
115
111
|
*/
|
|
116
112
|
identity?: string;
|
|
117
|
-
/** basin: the token for `identity` (default: the `
|
|
113
|
+
/** basin: the token for `identity` (default: the settings' `fleet` token). Never logged. */
|
|
118
114
|
identityToken?: string;
|
|
119
115
|
}
|
|
120
116
|
/** AnalyticsSettings with every default filled in. */
|
|
@@ -147,7 +143,7 @@ export interface AnalyticsPurchase {
|
|
|
147
143
|
}
|
|
148
144
|
/** What the automatic collectors do. Everything is on by default. */
|
|
149
145
|
export interface AnalyticsOptions {
|
|
150
|
-
/** Server only: the sink settings. When set, the
|
|
146
|
+
/** Server only: the sink settings. When set, the signed settings aren't read (tests, Studio, kernels before 0.3.8). */
|
|
151
147
|
settings?: AnalyticsSettings;
|
|
152
148
|
/** Server: joins, leaves, device, join source, first-ever vs returning. Default true. */
|
|
153
149
|
sessions?: boolean;
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
]]
|
|
9
9
|
--* Row format version (column `v`).
|
|
10
10
|
local SCHEMA_VERSION = 1
|
|
11
|
-
--* The ConfigService key (server-only) that holds the sink settings.
|
|
12
|
-
local SETTINGS_KEY = "TypeTorchAnalytics"
|
|
13
|
-
--* The kernel's fleet API settings `{ url, token }` (server-only ConfigService key); identities can go there.
|
|
14
|
-
local FLEET_SETTINGS_KEY = "TypeTorchFleet"
|
|
15
11
|
--* The first-session recording codec (column `codec`).
|
|
16
12
|
local RECORDING_CODEC = "tt-rec-1"
|
|
17
13
|
--* Most bytes of the `props` JSON text. Larger props become `{"_trunc":<bytes>}`.
|
|
@@ -26,7 +22,7 @@ local PROPS_MAX_BYTES = 4096
|
|
|
26
22
|
|
|
27
23
|
]]
|
|
28
24
|
--* Live overrides of one per-player experiment (settings key `experiments`).
|
|
29
|
-
--* The sink settings:
|
|
25
|
+
--* The sink settings: the signed settings' `analytics` (kernel 0.3.8, server-only), or `new AnalyticsEngine({ settings })`.
|
|
30
26
|
--* AnalyticsSettings with every default filled in.
|
|
31
27
|
--* Event properties: a flat JSON object (nested tables work but are harder to query).
|
|
32
28
|
--* What the automatic collectors do. Everything is on by default.
|
|
@@ -35,8 +31,6 @@ local PROPS_MAX_BYTES = 4096
|
|
|
35
31
|
--* Counters of the server engine (`AnalyticsEngine.stats()`).
|
|
36
32
|
return {
|
|
37
33
|
SCHEMA_VERSION = SCHEMA_VERSION,
|
|
38
|
-
SETTINGS_KEY = SETTINGS_KEY,
|
|
39
|
-
FLEET_SETTINGS_KEY = FLEET_SETTINGS_KEY,
|
|
40
34
|
RECORDING_CODEC = RECORDING_CODEC,
|
|
41
35
|
PROPS_MAX_BYTES = PROPS_MAX_BYTES,
|
|
42
36
|
}
|
|
@@ -63,7 +63,7 @@ export declare class ServerAnalytics {
|
|
|
63
63
|
private settingsErrors;
|
|
64
64
|
/** The first settings read finished (found or not), so experiment overrides are as known as they get. */
|
|
65
65
|
private settingsTried;
|
|
66
|
-
/** The `
|
|
66
|
+
/** The settings' `fleet` value (Basin games send identities to the fleet API). */
|
|
67
67
|
private fleetSettings?;
|
|
68
68
|
private nextFlushAt;
|
|
69
69
|
private backoffUntil;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
4
4
|
local CollectionService = _services.CollectionService
|
|
5
|
-
local ConfigService = _services.ConfigService
|
|
6
5
|
local DataStoreService = _services.DataStoreService
|
|
7
6
|
local HttpService = _services.HttpService
|
|
8
7
|
local LocalizationService = _services.LocalizationService
|
|
@@ -12,6 +11,10 @@ local Stats = _services.Stats
|
|
|
12
11
|
local Workspace = _services.Workspace
|
|
13
12
|
local maybeServerDispatcher = TS.import(script, script.Parent.Parent, "net", "runtime").maybeServerDispatcher
|
|
14
13
|
local observePlayers = TS.import(script, script.Parent.Parent, "players").observePlayers
|
|
14
|
+
local _settings = TS.import(script, script.Parent.Parent, "settings")
|
|
15
|
+
local currentSettings = _settings.currentSettings
|
|
16
|
+
local onSettings = _settings.onSettings
|
|
17
|
+
local settingsSupported = _settings.settingsSupported
|
|
15
18
|
local _typetorch = TS.import(script, script.Parent.Parent, "typetorch")
|
|
16
19
|
local closeHooksSupported = _typetorch.closeHooksSupported
|
|
17
20
|
local onGenerationClose = _typetorch.onGenerationClose
|
|
@@ -39,15 +42,13 @@ local newQueueState = _queue.newQueueState
|
|
|
39
42
|
local RowQueue = _queue.RowQueue
|
|
40
43
|
local takeToken = _queue.takeToken
|
|
41
44
|
local _schema = TS.import(script, script.Parent, "schema")
|
|
42
|
-
local FLEET_SETTINGS_KEY = _schema.FLEET_SETTINGS_KEY
|
|
43
45
|
local RECORDING_CODEC = _schema.RECORDING_CODEC
|
|
44
46
|
local SCHEMA_VERSION = _schema.SCHEMA_VERSION
|
|
45
|
-
local
|
|
46
|
-
local
|
|
47
|
-
local
|
|
48
|
-
local
|
|
49
|
-
local
|
|
50
|
-
local DEFAULT_TECH_EVERY = _settings.DEFAULT_TECH_EVERY
|
|
47
|
+
local _settings_1 = TS.import(script, script.Parent, "settings")
|
|
48
|
+
local identityTarget = _settings_1.identityTarget
|
|
49
|
+
local parseSettings = _settings_1.parseSettings
|
|
50
|
+
local recordsSupported = _settings_1.recordsSupported
|
|
51
|
+
local DEFAULT_TECH_EVERY = _settings_1.DEFAULT_TECH_EVERY
|
|
51
52
|
local _sinks = TS.import(script, script.Parent, "sinks")
|
|
52
53
|
local buildRequests = _sinks.buildRequests
|
|
53
54
|
local estimateEventBytes = _sinks.estimateEventBytes
|
|
@@ -104,7 +105,6 @@ local RECORDING_MAX_CHUNKS = 80
|
|
|
104
105
|
local RECORDING_MAX_SECONDS = 900
|
|
105
106
|
--* The join event waits this long for the client's device info.
|
|
106
107
|
local DEVICE_WAIT = 8
|
|
107
|
-
local SETTINGS_REFRESH = 180
|
|
108
108
|
local ZONE_INTERVAL = 0.5
|
|
109
109
|
local ZONE_TAG = "TTZone"
|
|
110
110
|
--* One player's session. Plain data (persist), keyed by tostring(UserId).
|
|
@@ -484,7 +484,7 @@ do
|
|
|
484
484
|
end
|
|
485
485
|
function ServerAnalytics:track(player, kind, name, props)
|
|
486
486
|
if not isEventName(name) then
|
|
487
|
-
warn("[src/analytics/server.ts:
|
|
487
|
+
warn("[src/analytics/server.ts:455]", `[analytics] {kind} event name must be 1-64 characters: {string.sub(tostring(name), 1, 80)}`)
|
|
488
488
|
return nil
|
|
489
489
|
end
|
|
490
490
|
local session = if player then self:sessionOf(player) else nil
|
|
@@ -592,7 +592,7 @@ do
|
|
|
592
592
|
end
|
|
593
593
|
local control = _condition
|
|
594
594
|
if not isExperimentName(name) or not isVariantList(variants) then
|
|
595
|
-
warn("[src/analytics/server.ts:
|
|
595
|
+
warn("[src/analytics/server.ts:506]", `[analytics] experiment("{string.sub(tostring(name), 1, 64)}"): bad name or variants (1-16 distinct names)`)
|
|
596
596
|
return control
|
|
597
597
|
end
|
|
598
598
|
local session = self:sessionOf(player)
|
|
@@ -657,7 +657,7 @@ do
|
|
|
657
657
|
function ServerAnalytics:applySettings(parsed)
|
|
658
658
|
local errorsText = table.concat(parsed.errors, "; ")
|
|
659
659
|
if errorsText ~= table.concat(self.settingsErrors, "; ") and errorsText ~= "" then
|
|
660
|
-
warn("[src/analytics/server.ts:
|
|
660
|
+
warn("[src/analytics/server.ts:541]", `[analytics] settings.analytics: {errorsText}`)
|
|
661
661
|
end
|
|
662
662
|
self.settingsErrors = parsed.errors
|
|
663
663
|
local hadSettings = self.settings ~= nil
|
|
@@ -682,91 +682,42 @@ do
|
|
|
682
682
|
self.settingsTried = true
|
|
683
683
|
return nil
|
|
684
684
|
end
|
|
685
|
+
-- Kernel 0.3.8 (plans/20): the signed settings record's `analytics` (the sink) and `fleet` (identities on Basin),
|
|
686
|
+
-- live through the kernel's change hook. Older kernels have no settings (no ConfigService fallback): pass
|
|
687
|
+
-- `new AnalyticsEngine({ settings })`.
|
|
688
|
+
if not settingsSupported() then
|
|
689
|
+
if self.kernel ~= nil and self.kernel.test ~= true then
|
|
690
|
+
warn("[src/analytics/server.ts:568]", `[analytics] no sink settings: they come from the signed settings record (kernel 0.3.8+, "typetorch settings set analytics -"); this server runs kernel {self.kernel.kernelVersion}. Or pass new AnalyticsEngine(\{ settings \})`)
|
|
691
|
+
end
|
|
692
|
+
self.settingsTried = true
|
|
693
|
+
return nil
|
|
694
|
+
end
|
|
685
695
|
local lastRaw
|
|
686
|
-
local
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return nil
|
|
696
|
+
local apply = function(settings)
|
|
697
|
+
local _result = settings
|
|
698
|
+
if _result ~= nil then
|
|
699
|
+
_result = _result.fleet
|
|
691
700
|
end
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
self.fleetSettings = fleetValue
|
|
701
|
+
self.fleetSettings = _result
|
|
702
|
+
local _value = settings
|
|
703
|
+
if _value ~= nil then
|
|
704
|
+
_value = _value.analytics
|
|
697
705
|
end
|
|
698
|
-
local
|
|
699
|
-
return snapshot:GetValue(SETTINGS_KEY)
|
|
700
|
-
end)
|
|
701
|
-
if not ok then
|
|
702
|
-
return nil
|
|
703
|
-
end
|
|
704
|
-
missing = value == nil
|
|
706
|
+
local value = _value
|
|
705
707
|
-- Compare without decoding twice; the text never reaches a log.
|
|
706
708
|
local encodedOk, raw = pcall(function()
|
|
707
|
-
return if
|
|
709
|
+
return if value == nil then "" else HttpService:JSONEncode(value)
|
|
708
710
|
end)
|
|
709
|
-
local text = if
|
|
711
|
+
local text = if encodedOk then raw else tostring(os.clock())
|
|
710
712
|
if text == lastRaw then
|
|
711
713
|
return nil
|
|
712
714
|
end
|
|
713
715
|
lastRaw = text
|
|
714
716
|
self:applySettings(parseSettings(value, decode))
|
|
715
717
|
end
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
local attempt = 1
|
|
720
|
-
local _shouldIncrement = false
|
|
721
|
-
while true do
|
|
722
|
-
if _shouldIncrement then
|
|
723
|
-
attempt += 1
|
|
724
|
-
else
|
|
725
|
-
_shouldIncrement = true
|
|
726
|
-
end
|
|
727
|
-
if not (attempt <= 6 and not snapshot) then
|
|
728
|
-
break
|
|
729
|
-
end
|
|
730
|
-
local ok, result = pcall(function()
|
|
731
|
-
return ConfigService:GetConfigAsync()
|
|
732
|
-
end)
|
|
733
|
-
if ok then
|
|
734
|
-
snapshot = result
|
|
735
|
-
else
|
|
736
|
-
if attempt == 1 then
|
|
737
|
-
warn("[src/analytics/server.ts:590]", `[analytics] ConfigService unavailable: {string.sub(tostring(result), 1, 120)}`)
|
|
738
|
-
end
|
|
739
|
-
task.wait(delay)
|
|
740
|
-
delay = math.min(delay * 2, 30)
|
|
741
|
-
end
|
|
742
|
-
end
|
|
743
|
-
end
|
|
744
|
-
if not snapshot then
|
|
745
|
-
self.settingsTried = true
|
|
746
|
-
return nil
|
|
747
|
-
end
|
|
748
|
-
local current = snapshot
|
|
749
|
-
self.trove:connect(current.UpdateAvailable, function()
|
|
750
|
-
pcall(function()
|
|
751
|
-
return current:Refresh()
|
|
752
|
-
end)
|
|
753
|
-
read(true)
|
|
754
|
-
end)
|
|
755
|
-
pcall(function()
|
|
756
|
-
return self.trove:connect(current:GetValueChangedSignal(SETTINGS_KEY), function()
|
|
757
|
-
return read(true)
|
|
758
|
-
end)
|
|
759
|
-
end)
|
|
760
|
-
read(true)
|
|
761
|
-
self.settingsTried = true
|
|
762
|
-
while true do
|
|
763
|
-
task.wait(SETTINGS_REFRESH)
|
|
764
|
-
pcall(function()
|
|
765
|
-
return current:Refresh()
|
|
766
|
-
end)
|
|
767
|
-
read(false)
|
|
768
|
-
end
|
|
769
|
-
end))
|
|
718
|
+
apply(currentSettings())
|
|
719
|
+
self.settingsTried = true
|
|
720
|
+
self.trove:add(onSettings(apply))
|
|
770
721
|
end
|
|
771
722
|
function ServerAnalytics:dataStore()
|
|
772
723
|
local ok, store = pcall(function()
|
|
@@ -1481,7 +1432,7 @@ do
|
|
|
1481
1432
|
end
|
|
1482
1433
|
end)
|
|
1483
1434
|
if not ok then
|
|
1484
|
-
warn("[src/analytics/server.ts:
|
|
1435
|
+
warn("[src/analytics/server.ts:1033]", `[analytics] client message failed: {string.sub(tostring(err), 1, 200)}`)
|
|
1485
1436
|
end
|
|
1486
1437
|
end)
|
|
1487
1438
|
self.trove:add(function()
|
|
@@ -1761,7 +1712,7 @@ do
|
|
|
1761
1712
|
return nil
|
|
1762
1713
|
end
|
|
1763
1714
|
self.lastWarnAt = now
|
|
1764
|
-
warn("[src/analytics/server.ts:
|
|
1715
|
+
warn("[src/analytics/server.ts:1222]", `[analytics] {message}`)
|
|
1765
1716
|
end
|
|
1766
1717
|
function ServerAnalytics:post(request)
|
|
1767
1718
|
local ok, response = pcall(function()
|
|
@@ -1883,7 +1834,7 @@ do
|
|
|
1883
1834
|
end
|
|
1884
1835
|
local reason = _result
|
|
1885
1836
|
local fix = if status == 530 then ": the tunnel is down, run bun run local on the dev PC" else ""
|
|
1886
|
-
self:warn(if outcome == "config" then `{what} got HTTP {status}: check the URL and token in
|
|
1837
|
+
self:warn(if outcome == "config" then `{what} got HTTP {status}: check the URL and token in settings.analytics (typetorch settings set analytics -); retrying in {math.floor(wait)} s` else `{what} failed ({reason}{fix}); retrying in {math.floor(wait)} s`)
|
|
1887
1838
|
end
|
|
1888
1839
|
end
|
|
1889
1840
|
-- Recordings without a request (basin without a recordings URL) were never taken; nothing else is left.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The sink settings (
|
|
2
|
+
* The sink settings (the signed settings' `analytics`, kernel 0.3.8, or `new AnalyticsEngine({ settings })`): parsing with
|
|
3
3
|
* defaults and clamps. Errors never repeat the token.
|
|
4
4
|
*
|
|
5
5
|
* Pure: no imports and no services, so it also runs offline under Lune (scripts/test-analytics.luau). The JSON
|
|
@@ -18,14 +18,14 @@ export interface ParsedSettings {
|
|
|
18
18
|
* no settings and no errors. Unknown fields are ignored.
|
|
19
19
|
*/
|
|
20
20
|
export declare function parseSettings(raw: unknown, decode: (text: string) => unknown): ParsedSettings;
|
|
21
|
-
/** Where identity rows go on a Basin game: the fleet API (`
|
|
21
|
+
/** Where identity rows go on a Basin game: the fleet API (the settings' `fleet` = { url, token }) at /v1/identity. */
|
|
22
22
|
export interface IdentityTarget {
|
|
23
23
|
url: string;
|
|
24
24
|
token?: string;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* The identity target for these settings: duckdb none (identities ride in the batch body); basin the `identity` URL,
|
|
28
|
-
* else the fleet API's /v1/identity from the `
|
|
28
|
+
* else the fleet API's /v1/identity from the settings' `fleet` value (a table or JSON `{ url, token }`), else none.
|
|
29
29
|
*/
|
|
30
30
|
export declare function identityTarget(settings: ResolvedSettings, fleetRaw: unknown, decode: (text: string) => unknown): IdentityTarget | undefined;
|
|
31
31
|
/** Whether recordings can be sent at all: duckdb always (one body), basin only with a recordings stream URL. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
--[[
|
|
3
3
|
*
|
|
4
|
-
* The sink settings (
|
|
4
|
+
* The sink settings (the signed settings' `analytics`, kernel 0.3.8, or `new AnalyticsEngine({ settings })`): parsing with
|
|
5
5
|
* defaults and clamps. Errors never repeat the token.
|
|
6
6
|
*
|
|
7
7
|
* Pure: no imports and no services, so it also runs offline under Lune (scripts/test-analytics.luau). The JSON
|
|
@@ -215,11 +215,11 @@ local function parseSettings(raw, decode)
|
|
|
215
215
|
errors = errors,
|
|
216
216
|
}
|
|
217
217
|
end
|
|
218
|
-
--* Where identity rows go on a Basin game: the fleet API (`
|
|
218
|
+
--* Where identity rows go on a Basin game: the fleet API (the settings' `fleet` = { url, token }) at /v1/identity.
|
|
219
219
|
--[[
|
|
220
220
|
*
|
|
221
221
|
* The identity target for these settings: duckdb none (identities ride in the batch body); basin the `identity` URL,
|
|
222
|
-
* else the fleet API's /v1/identity from the `
|
|
222
|
+
* else the fleet API's /v1/identity from the settings' `fleet` value (a table or JSON `{ url, token }`), else none.
|
|
223
223
|
|
|
224
224
|
]]
|
|
225
225
|
local function identityTarget(settings, fleetRaw, decode)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Trove } from "@rbxts/trove";
|
|
2
|
+
/**
|
|
3
|
+
* `TypeTorch.runDetached(fn)` (server only, kernel 0.3.8): runs `fn` on a thread the KERNEL owns, so a deploy's swap
|
|
4
|
+
* (this generation's stop, and its hard stop, which kills the generation's own threads mid-call) never cuts it off.
|
|
5
|
+
* Made for library calls that must not stop halfway: a ProfileStore / ProfileService load, save or release cut off
|
|
6
|
+
* by a hard stop jams that player's profile on this server (docs: Player data, "Why the jobs").
|
|
7
|
+
*
|
|
8
|
+
* TypeTorch.runDetached(() => store.StartSessionAsync(key)).then((profile) => ...);
|
|
9
|
+
*
|
|
10
|
+
* - The Promise settles on this generation's thread while it runs. A result that arrives after this generation
|
|
11
|
+
* stopped is dropped: a job whose result must survive a swap writes it into `persist` itself.
|
|
12
|
+
* - A job keeps this generation's closures (and everything they reference) alive until it ends: keep jobs short.
|
|
13
|
+
* - Errors reject the Promise and go to the kernel's log with the generation's name; they never count toward the
|
|
14
|
+
* build's health window. At most 256 jobs run at once per server (then it throws); one past 60 s is logged.
|
|
15
|
+
* - Older kernels: throws "needs kernel 0.3.8; use the DataHost job queue (Player data guide)". Clients: throws.
|
|
16
|
+
*/
|
|
17
|
+
/** The first kernel with `api:runDetached`. */
|
|
18
|
+
export declare const DETACHED_KERNEL = "0.3.8";
|
|
19
|
+
/** Called by bindTypeTorch. */
|
|
20
|
+
export declare function bindDetached(realm: "server" | "client", kernel: unknown, trove: Trove, kernelVersion: string): void;
|
|
21
|
+
/** Called by unbindTypeTorch. */
|
|
22
|
+
export declare function unbindDetached(): void;
|
|
23
|
+
/** `runDetached` reaches a kernel thread (a server on kernel 0.3.8+). */
|
|
24
|
+
export declare function detachedSupported(): boolean;
|
|
25
|
+
/** `TypeTorch.runDetached`. */
|
|
26
|
+
export declare function runDetached<T>(fn: () => T): Promise<T>;
|