@typetorch/framework 0.5.2 → 0.5.3
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/out/analytics/SCHEMA.md +1 -0
- package/out/analytics/server.luau +22 -6
- package/out/assets/manifest.d.ts +29 -0
- package/out/assets/manifest.luau +92 -0
- package/out/assets/sync.luau +33 -8
- package/out/devtools/assets-ui.luau +14 -1
- package/out/devtools/health.d.ts +1 -1
- package/out/devtools/health.luau +1 -1
- package/out/version.d.ts +1 -1
- package/out/version.luau +1 -1
- package/package.json +53 -53
package/out/analytics/SCHEMA.md
CHANGED
|
@@ -65,6 +65,7 @@ First-ever-session detail, one row per packed chunk.
|
|
|
65
65
|
| tech | `load` (src client) | `secs`: from the client starting to the engine starting (first client generation only) |
|
|
66
66
|
| tech | `error` | Server or client (src). `msg` (first 300 chars, player names replaced by `<player>`), `n` (times in the window). At most 10 per tech tick (server), 5 per 30 s and 100 per session (client) |
|
|
67
67
|
| tech | `start` | First generation of a server that runs the engine: `gen`, `kernel`, `load?` |
|
|
68
|
+
| tech | `assets` | Each generation whose artifact has hot assets (or an unusable manifest), at its start: `gen`, `boot` (the server's first generation), `manifest` (`ok`, `invalid`, `unread`), `place?` (`same`: the manifest names this place version; `vouched`: the place's backup vouches for it; `differs`: keys without a matching baked copy load from their uploads; `unknown`), `mpv?` (the manifest's place version), `pv?` (game.PlaceVersion), `keys`, `kept`, `baked`, `loaded`, `failed`, `loading` (keys per AssetSync source), `missing` (keys with nothing live), `late` (loaded after the start-up wait), `timedOut`, `ms?`, `first?` (the first failure or manifest error) |
|
|
68
69
|
| tech | `swap` / `rollback` | A generation started by a hot swap (`rollback` for rollback reasons), 5 s after it started: `reason`, `from?` (previous artifact id), `gen`, `load?`, `stop?`, `swap?` (seconds), `players` |
|
|
69
70
|
| tech | `swap_out` | Just before a generation stops: `reason`, `next?` (next artifact id), `players` |
|
|
70
71
|
| zone | `enter` | `zone`, `from` (previous zone or `""`) |
|
|
@@ -19,6 +19,8 @@ local _typetorch = TS.import(script, script.Parent.Parent, "typetorch")
|
|
|
19
19
|
local closeHooksSupported = _typetorch.closeHooksSupported
|
|
20
20
|
local onGenerationClose = _typetorch.onGenerationClose
|
|
21
21
|
local TypeTorch = _typetorch.TypeTorch
|
|
22
|
+
local assetCounts = TS.import(script, script.Parent.Parent, "assets", "manifest").assetCounts
|
|
23
|
+
local assetReport = TS.import(script, script.Parent.Parent, "assets", "sync").assetReport
|
|
22
24
|
local toBase64 = TS.import(script, script.Parent, "codec").toBase64
|
|
23
25
|
local _props = TS.import(script, script.Parent, "props")
|
|
24
26
|
local encodeProps = _props.encodeProps
|
|
@@ -495,7 +497,7 @@ do
|
|
|
495
497
|
end
|
|
496
498
|
function ServerAnalytics:track(player, kind, name, props)
|
|
497
499
|
if not isEventName(name) then
|
|
498
|
-
warn("[src/analytics/server.ts:
|
|
500
|
+
warn("[src/analytics/server.ts:473]", `[analytics] {kind} event name must be 1-64 characters: {string.sub(tostring(name), 1, 80)}`)
|
|
499
501
|
return nil
|
|
500
502
|
end
|
|
501
503
|
local session = if player then self:sessionOf(player) else nil
|
|
@@ -603,7 +605,7 @@ do
|
|
|
603
605
|
end
|
|
604
606
|
local control = _condition
|
|
605
607
|
if not isExperimentName(name) or not isVariantList(variants) then
|
|
606
|
-
warn("[src/analytics/server.ts:
|
|
608
|
+
warn("[src/analytics/server.ts:525]", `[analytics] experiment("{string.sub(tostring(name), 1, 64)}"): bad name or variants (1-16 distinct names)`)
|
|
607
609
|
return control
|
|
608
610
|
end
|
|
609
611
|
local session = self:sessionOf(player)
|
|
@@ -668,7 +670,7 @@ do
|
|
|
668
670
|
function ServerAnalytics:applySettings(parsed)
|
|
669
671
|
local errorsText = table.concat(parsed.errors, "; ")
|
|
670
672
|
if errorsText ~= table.concat(self.settingsErrors, "; ") and errorsText ~= "" then
|
|
671
|
-
warn("[src/analytics/server.ts:
|
|
673
|
+
warn("[src/analytics/server.ts:560]", `[analytics] the sink settings (settings.backend or settings.analytics): {errorsText}`)
|
|
672
674
|
end
|
|
673
675
|
self.settingsErrors = parsed.errors
|
|
674
676
|
local hadSettings = self.settings ~= nil
|
|
@@ -698,7 +700,7 @@ do
|
|
|
698
700
|
-- `new AnalyticsEngine({ settings })`.
|
|
699
701
|
if not settingsSupported() then
|
|
700
702
|
if self.kernel ~= nil and self.kernel.test ~= true then
|
|
701
|
-
warn("[src/analytics/server.ts:
|
|
703
|
+
warn("[src/analytics/server.ts:587]", `[analytics] no sink settings: they come from the signed settings record (kernel 0.3.8+, "typetorch backend setup"); this server runs kernel {self.kernel.kernelVersion}. Or pass new AnalyticsEngine(\{ settings \})`)
|
|
702
704
|
end
|
|
703
705
|
self.settingsTried = true
|
|
704
706
|
return nil
|
|
@@ -1227,6 +1229,20 @@ do
|
|
|
1227
1229
|
load = start.loadSeconds,
|
|
1228
1230
|
}))
|
|
1229
1231
|
end
|
|
1232
|
+
-- Hot assets: how this generation's AssetSync went (it ran before the modules loaded), so "every new server
|
|
1233
|
+
-- loads its hot assets from uploads" shows in the events table (2026-10-13 incident).
|
|
1234
|
+
local assets = assetReport()
|
|
1235
|
+
if assets.manifest ~= "none" or #assets.errors > 0 then
|
|
1236
|
+
local _self = self
|
|
1237
|
+
local _object = {
|
|
1238
|
+
gen = generation,
|
|
1239
|
+
boot = start.kind ~= "swap",
|
|
1240
|
+
}
|
|
1241
|
+
for _k, _v in assetCounts(assets) do
|
|
1242
|
+
_object[_k] = _v
|
|
1243
|
+
end
|
|
1244
|
+
_self:emit(nil, "tech", "assets", encodeProps(_object))
|
|
1245
|
+
end
|
|
1230
1246
|
end
|
|
1231
1247
|
self.trove:add(TypeTorch:onSwapOut(function(info)
|
|
1232
1248
|
local _self = self
|
|
@@ -1500,7 +1516,7 @@ do
|
|
|
1500
1516
|
end
|
|
1501
1517
|
end)
|
|
1502
1518
|
if not ok then
|
|
1503
|
-
warn("[src/analytics/server.ts:
|
|
1519
|
+
warn("[src/analytics/server.ts:1086]", `[analytics] client message failed: {string.sub(tostring(err), 1, 200)}`)
|
|
1504
1520
|
end
|
|
1505
1521
|
end)
|
|
1506
1522
|
self.trove:add(function()
|
|
@@ -1783,7 +1799,7 @@ do
|
|
|
1783
1799
|
self.lastWarnAt = now
|
|
1784
1800
|
local more = if self.suppressed > 0 then ` (+{self.suppressed} more in the last minute)` else ""
|
|
1785
1801
|
self.suppressed = 0
|
|
1786
|
-
warn("[src/analytics/server.ts:
|
|
1802
|
+
warn("[src/analytics/server.ts:1281]", `[analytics] {message}{more}`)
|
|
1787
1803
|
return true
|
|
1788
1804
|
end
|
|
1789
1805
|
function ServerAnalytics:noteSuccess()
|
package/out/assets/manifest.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ export declare const ATTR_VERSION = "TypeTorchAssetVersion";
|
|
|
23
23
|
* a template are tagged too: only an instance whose Parent is still this path counts as the live one.
|
|
24
24
|
*/
|
|
25
25
|
export declare const ATTR_PATH = "TypeTorchAssetPath";
|
|
26
|
+
/**
|
|
27
|
+
* The place's backup build (ServerStorage.TypeTorchBackup) vouches for the place's hot-asset copies after the CLI's
|
|
28
|
+
* backup refresh published the place (cli commands/backup.ts, kernelpatch-task step 4b): `HotAssetsPlaceVersion` is the
|
|
29
|
+
* lockfile placeVersion the copies match, `HotAssetsSavedAs` the place version that save became.
|
|
30
|
+
*/
|
|
31
|
+
export declare const BACKUP_NAME = "TypeTorchBackup";
|
|
32
|
+
export declare const ATTR_BACKUP_PLACE_VERSION = "HotAssetsPlaceVersion";
|
|
33
|
+
export declare const ATTR_BACKUP_SAVED_AS = "HotAssetsSavedAs";
|
|
26
34
|
export declare const MAX_KEY_LENGTH = 64;
|
|
27
35
|
export type AssetRealm = "replicated" | "server";
|
|
28
36
|
/** One manifest entry (one key), validated. */
|
|
@@ -95,6 +103,20 @@ export interface AssetDecision {
|
|
|
95
103
|
*/
|
|
96
104
|
fallbackBaked?: number;
|
|
97
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Whether this server's place holds exactly the copies the manifest was exported from:
|
|
108
|
+
* - same: the manifest names this place version;
|
|
109
|
+
* - vouched: it names another one, but the place's backup says this version is a save of that one that changed nothing
|
|
110
|
+
* outside the kernel slots (`backup` = its HotAssetsPlaceVersion / HotAssetsSavedAs);
|
|
111
|
+
* - differs: neither (new servers load every key whose baked copy has no matching hash);
|
|
112
|
+
* - unknown: the manifest names no place version.
|
|
113
|
+
*/
|
|
114
|
+
export type PlaceMatch = "same" | "vouched" | "differs" | "unknown";
|
|
115
|
+
export interface BackupVouch {
|
|
116
|
+
placeVersion?: number;
|
|
117
|
+
savedAs?: number;
|
|
118
|
+
}
|
|
119
|
+
export declare function placeMatchesManifest(manifestVersion: number | undefined, placeVersion: number, backup?: BackupVouch): PlaceMatch;
|
|
98
120
|
/**
|
|
99
121
|
* What AssetSync does for one key:
|
|
100
122
|
* 1. keep the tagged copy whose TypeTorchAssetVersion is the manifest's `ver` (the newest one if several);
|
|
@@ -128,6 +150,11 @@ export interface AssetSyncReport {
|
|
|
128
150
|
manifest: "none" | "ok" | "invalid" | "unread";
|
|
129
151
|
/** Where the manifest was found: "mount", "Server" (the payload's Server folder) or "payload" (re-read). */
|
|
130
152
|
from?: string;
|
|
153
|
+
/** placeMatchesManifest: whether the place's copies are the manifest's ("differs": the upload path). */
|
|
154
|
+
place?: PlaceMatch;
|
|
155
|
+
/** The manifest's placeVersion and this server's game.PlaceVersion. */
|
|
156
|
+
manifestPlaceVersion?: number;
|
|
157
|
+
placeVersion?: number;
|
|
131
158
|
errors: string[];
|
|
132
159
|
/** AssetSync is still holding the generation's start. */
|
|
133
160
|
running: boolean;
|
|
@@ -138,6 +165,8 @@ export interface AssetSyncReport {
|
|
|
138
165
|
/** Tagged keys this manifest doesn't name: left as they are. */
|
|
139
166
|
unmanaged: string[];
|
|
140
167
|
}
|
|
168
|
+
/** Keys per source, and the rest of what the `tech` / `assets` analytics row carries (SCHEMA.md). */
|
|
169
|
+
export declare function assetCounts(report: AssetSyncReport): Record<string, unknown>;
|
|
141
170
|
/** What Server > Status needs (devtools ServerFacts.assets). */
|
|
142
171
|
export interface AssetFacts {
|
|
143
172
|
failed: number;
|
package/out/assets/manifest.luau
CHANGED
|
@@ -28,6 +28,16 @@ local ATTR_VERSION = "TypeTorchAssetVersion"
|
|
|
28
28
|
|
|
29
29
|
]]
|
|
30
30
|
local ATTR_PATH = "TypeTorchAssetPath"
|
|
31
|
+
--[[
|
|
32
|
+
*
|
|
33
|
+
* The place's backup build (ServerStorage.TypeTorchBackup) vouches for the place's hot-asset copies after the CLI's
|
|
34
|
+
* backup refresh published the place (cli commands/backup.ts, kernelpatch-task step 4b): `HotAssetsPlaceVersion` is the
|
|
35
|
+
* lockfile placeVersion the copies match, `HotAssetsSavedAs` the place version that save became.
|
|
36
|
+
|
|
37
|
+
]]
|
|
38
|
+
local BACKUP_NAME = "TypeTorchBackup"
|
|
39
|
+
local ATTR_BACKUP_PLACE_VERSION = "HotAssetsPlaceVersion"
|
|
40
|
+
local ATTR_BACKUP_SAVED_AS = "HotAssetsSavedAs"
|
|
31
41
|
local MAX_KEY_LENGTH = 64
|
|
32
42
|
local MAX_ASSETS = 1000
|
|
33
43
|
local MAX_ERRORS = 20
|
|
@@ -255,6 +265,28 @@ end
|
|
|
255
265
|
-- The decision --------------------------------------------------------------------------------------------------------
|
|
256
266
|
--* A live (tagged) copy of the key: its TypeTorchAssetVersion. Only copies whose Parent is their own path count.
|
|
257
267
|
--* An untagged copy of the key under the entry's path (the place's baked copy): its TypeTorchAssetHash.
|
|
268
|
+
--[[
|
|
269
|
+
*
|
|
270
|
+
* Whether this server's place holds exactly the copies the manifest was exported from:
|
|
271
|
+
* - same: the manifest names this place version;
|
|
272
|
+
* - vouched: it names another one, but the place's backup says this version is a save of that one that changed nothing
|
|
273
|
+
* outside the kernel slots (`backup` = its HotAssetsPlaceVersion / HotAssetsSavedAs);
|
|
274
|
+
* - differs: neither (new servers load every key whose baked copy has no matching hash);
|
|
275
|
+
* - unknown: the manifest names no place version.
|
|
276
|
+
|
|
277
|
+
]]
|
|
278
|
+
local function placeMatchesManifest(manifestVersion, placeVersion, backup)
|
|
279
|
+
if manifestVersion == nil then
|
|
280
|
+
return "unknown"
|
|
281
|
+
end
|
|
282
|
+
if manifestVersion == placeVersion then
|
|
283
|
+
return "same"
|
|
284
|
+
end
|
|
285
|
+
if backup ~= nil and backup.placeVersion == manifestVersion and backup.savedAs == placeVersion then
|
|
286
|
+
return "vouched"
|
|
287
|
+
end
|
|
288
|
+
return "differs"
|
|
289
|
+
end
|
|
258
290
|
local function indexes(count, except)
|
|
259
291
|
local list = {}
|
|
260
292
|
do
|
|
@@ -328,6 +360,61 @@ local function decideAsset(entry, tagged, baked, placeMatches)
|
|
|
328
360
|
end
|
|
329
361
|
-- Status (devtools op "assets", Server > Status) ------------------------------------------------------------------------
|
|
330
362
|
--* kept: already live with the manifest version; baked: the place's copy adopted; loaded; failed (see `error`).
|
|
363
|
+
--* Keys per source, and the rest of what the `tech` / `assets` analytics row carries (SCHEMA.md).
|
|
364
|
+
local function assetCounts(report)
|
|
365
|
+
local counts = {
|
|
366
|
+
kept = 0,
|
|
367
|
+
baked = 0,
|
|
368
|
+
loaded = 0,
|
|
369
|
+
failed = 0,
|
|
370
|
+
loading = 0,
|
|
371
|
+
}
|
|
372
|
+
local missing = 0
|
|
373
|
+
local late = 0
|
|
374
|
+
local first
|
|
375
|
+
for _, status in report.entries do
|
|
376
|
+
local _condition = counts[status.source]
|
|
377
|
+
if _condition == nil then
|
|
378
|
+
_condition = 0
|
|
379
|
+
end
|
|
380
|
+
counts[status.source] = _condition + 1
|
|
381
|
+
if not status.live then
|
|
382
|
+
missing += 1
|
|
383
|
+
end
|
|
384
|
+
if status.late == true then
|
|
385
|
+
late += 1
|
|
386
|
+
end
|
|
387
|
+
if status.source == "failed" and first == nil then
|
|
388
|
+
local _exp = status.key
|
|
389
|
+
local _condition_1 = status.error
|
|
390
|
+
if _condition_1 == nil then
|
|
391
|
+
_condition_1 = "failed"
|
|
392
|
+
end
|
|
393
|
+
first = `{_exp}: {_condition_1}`
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
local _object = {
|
|
397
|
+
manifest = report.manifest,
|
|
398
|
+
place = report.place,
|
|
399
|
+
mpv = report.manifestPlaceVersion,
|
|
400
|
+
pv = report.placeVersion,
|
|
401
|
+
keys = #report.entries,
|
|
402
|
+
}
|
|
403
|
+
for _k, _v in counts do
|
|
404
|
+
_object[_k] = _v
|
|
405
|
+
end
|
|
406
|
+
_object.missing = missing
|
|
407
|
+
_object.late = late
|
|
408
|
+
_object.timedOut = report.timedOut == true
|
|
409
|
+
_object.ms = report.ms
|
|
410
|
+
local _left = "first"
|
|
411
|
+
local _condition = first
|
|
412
|
+
if _condition == nil then
|
|
413
|
+
_condition = report.errors[1]
|
|
414
|
+
end
|
|
415
|
+
_object[_left] = _condition
|
|
416
|
+
return _object
|
|
417
|
+
end
|
|
331
418
|
--* What Server > Status needs (devtools ServerFacts.assets).
|
|
332
419
|
local function summarizeAssets(report)
|
|
333
420
|
if report.manifest == "none" and #report.errors == 0 then
|
|
@@ -368,7 +455,9 @@ return {
|
|
|
368
455
|
pathSegments = pathSegments,
|
|
369
456
|
realmOfPath = realmOfPath,
|
|
370
457
|
parseManifest = parseManifest,
|
|
458
|
+
placeMatchesManifest = placeMatchesManifest,
|
|
371
459
|
decideAsset = decideAsset,
|
|
460
|
+
assetCounts = assetCounts,
|
|
372
461
|
summarizeAssets = summarizeAssets,
|
|
373
462
|
ASSET_TAG_PREFIX = ASSET_TAG_PREFIX,
|
|
374
463
|
ATTR_KEY = ATTR_KEY,
|
|
@@ -376,5 +465,8 @@ return {
|
|
|
376
465
|
ATTR_HASH = ATTR_HASH,
|
|
377
466
|
ATTR_VERSION = ATTR_VERSION,
|
|
378
467
|
ATTR_PATH = ATTR_PATH,
|
|
468
|
+
BACKUP_NAME = BACKUP_NAME,
|
|
469
|
+
ATTR_BACKUP_PLACE_VERSION = ATTR_BACKUP_PLACE_VERSION,
|
|
470
|
+
ATTR_BACKUP_SAVED_AS = ATTR_BACKUP_SAVED_AS,
|
|
379
471
|
MAX_KEY_LENGTH = MAX_KEY_LENGTH,
|
|
380
472
|
}
|
package/out/assets/sync.luau
CHANGED
|
@@ -4,6 +4,7 @@ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
|
4
4
|
local CollectionService = _services.CollectionService
|
|
5
5
|
local HttpService = _services.HttpService
|
|
6
6
|
local InsertService = _services.InsertService
|
|
7
|
+
local ServerStorage = _services.ServerStorage
|
|
7
8
|
local _dom = TS.import(script, script.Parent, "dom")
|
|
8
9
|
local liveCopies = _dom.liveCopies
|
|
9
10
|
local numberAttribute = _dom.numberAttribute
|
|
@@ -15,10 +16,14 @@ local ATTR_HASH = _manifest.ATTR_HASH
|
|
|
15
16
|
local ATTR_ID = _manifest.ATTR_ID
|
|
16
17
|
local ATTR_KEY = _manifest.ATTR_KEY
|
|
17
18
|
local ATTR_PATH = _manifest.ATTR_PATH
|
|
19
|
+
local ATTR_BACKUP_PLACE_VERSION = _manifest.ATTR_BACKUP_PLACE_VERSION
|
|
20
|
+
local ATTR_BACKUP_SAVED_AS = _manifest.ATTR_BACKUP_SAVED_AS
|
|
18
21
|
local ATTR_VERSION = _manifest.ATTR_VERSION
|
|
22
|
+
local BACKUP_NAME = _manifest.BACKUP_NAME
|
|
19
23
|
local decideAsset = _manifest.decideAsset
|
|
20
24
|
local keyOfTag = _manifest.keyOfTag
|
|
21
25
|
local parseManifest = _manifest.parseManifest
|
|
26
|
+
local placeMatchesManifest = _manifest.placeMatchesManifest
|
|
22
27
|
local summarizeAssets = _manifest.summarizeAssets
|
|
23
28
|
--[[
|
|
24
29
|
*
|
|
@@ -217,6 +222,17 @@ local function readManifest(kernel, deadline)
|
|
|
217
222
|
from = "payload",
|
|
218
223
|
}
|
|
219
224
|
end
|
|
225
|
+
--* What the place's backup build says about its hot-asset copies (BackupVouch), if anything.
|
|
226
|
+
local function backupVouch()
|
|
227
|
+
local backup = ServerStorage:FindFirstChild(BACKUP_NAME)
|
|
228
|
+
if backup == nil then
|
|
229
|
+
return nil
|
|
230
|
+
end
|
|
231
|
+
return {
|
|
232
|
+
placeVersion = numberAttribute(backup, ATTR_BACKUP_PLACE_VERSION),
|
|
233
|
+
savedAs = numberAttribute(backup, ATTR_BACKUP_SAVED_AS),
|
|
234
|
+
}
|
|
235
|
+
end
|
|
220
236
|
--* Marks `instance` as the live copy of `entry` (attributes first, so they replicate with the tag).
|
|
221
237
|
local function stamp(instance, entry, version)
|
|
222
238
|
instance:SetAttribute(ATTR_KEY, entry.key)
|
|
@@ -283,7 +299,7 @@ local function loadCopy(entry)
|
|
|
283
299
|
container:Destroy()
|
|
284
300
|
local removed = stripScripts(root)
|
|
285
301
|
if removed > 0 then
|
|
286
|
-
warn("[src/assets/sync.ts:
|
|
302
|
+
warn("[src/assets/sync.ts:215]", `hot asset {entry.key}: removed {removed} scripts`)
|
|
287
303
|
end
|
|
288
304
|
return root
|
|
289
305
|
end
|
|
@@ -409,7 +425,7 @@ local function syncEntry(entry, status, placeMatches, ownEpoch, threads)
|
|
|
409
425
|
if _condition == nil then
|
|
410
426
|
_condition = entry.ver
|
|
411
427
|
end
|
|
412
|
-
warn("[src/assets/sync.ts:
|
|
428
|
+
warn("[src/assets/sync.ts:298]", `hot asset {_exp_1} (version {_condition}) failed: {reason}`)
|
|
413
429
|
return nil
|
|
414
430
|
end
|
|
415
431
|
-- The new copy is live: the copies it replaces go.
|
|
@@ -422,7 +438,7 @@ local function syncEntry(entry, status, placeMatches, ownEpoch, threads)
|
|
|
422
438
|
status.ms = math.floor((os.clock() - started) * 1000)
|
|
423
439
|
if late then
|
|
424
440
|
status.late = true
|
|
425
|
-
print("[src/assets/sync.ts:
|
|
441
|
+
print("[src/assets/sync.ts:311]", `hot asset {entry.key} loaded late ({status.ms} ms)`)
|
|
426
442
|
end
|
|
427
443
|
end)
|
|
428
444
|
table.insert(_threads, _arg0)
|
|
@@ -471,7 +487,16 @@ local function run(kernel, deadline, ownEpoch, threads)
|
|
|
471
487
|
end
|
|
472
488
|
setManifest(manifest)
|
|
473
489
|
report.manifest = "ok"
|
|
474
|
-
|
|
490
|
+
-- A publish that changed nothing outside the kernel slots (the CLI's backup refresh) doesn't move servers to the
|
|
491
|
+
-- upload path: the backup vouches for the version it saved (2026-10-13 incident).
|
|
492
|
+
local place = placeMatchesManifest(manifest.placeVersion, game.PlaceVersion, backupVouch())
|
|
493
|
+
report.place = place
|
|
494
|
+
report.manifestPlaceVersion = manifest.placeVersion
|
|
495
|
+
report.placeVersion = game.PlaceVersion
|
|
496
|
+
local placeMatches = place == "same" or place == "vouched"
|
|
497
|
+
if place == "differs" then
|
|
498
|
+
warn("[src/assets/sync.ts:356]", `hot assets: the manifest is for place v{manifest.placeVersion}, this server runs v{game.PlaceVersion}: keys without a matching baked copy load from their uploaded versions (typetorch assets sync --deploy <branch> fixes it)`)
|
|
499
|
+
end
|
|
475
500
|
local loads = {}
|
|
476
501
|
for _, entry in manifest.entries do
|
|
477
502
|
local status = {
|
|
@@ -490,7 +515,7 @@ local function run(kernel, deadline, ownEpoch, threads)
|
|
|
490
515
|
if not ok then
|
|
491
516
|
status.source = "failed"
|
|
492
517
|
status.error = shortError(result)
|
|
493
|
-
warn("[src/assets/sync.ts:
|
|
518
|
+
warn("[src/assets/sync.ts:369]", `hot asset {entry.key} failed: {status.error}`)
|
|
494
519
|
elseif result ~= nil then
|
|
495
520
|
table.insert(loads, result)
|
|
496
521
|
end
|
|
@@ -524,7 +549,7 @@ local function run(kernel, deadline, ownEpoch, threads)
|
|
|
524
549
|
end
|
|
525
550
|
report.timedOut = true
|
|
526
551
|
load.fail("timed out")
|
|
527
|
-
warn("[src/assets/sync.ts:
|
|
552
|
+
warn("[src/assets/sync.ts:378]", `hot asset {load.status.key}: still loading; the old copy stays until it finishes`)
|
|
528
553
|
end
|
|
529
554
|
report.unmanaged = unmanagedKeys()
|
|
530
555
|
end
|
|
@@ -571,7 +596,7 @@ local function syncHotAssets(kernel, trove)
|
|
|
571
596
|
local _errors = report.errors
|
|
572
597
|
local _arg0 = `AssetSync failed: {shortError(err)}`
|
|
573
598
|
table.insert(_errors, _arg0)
|
|
574
|
-
warn("[src/assets/sync.ts:
|
|
599
|
+
warn("[src/assets/sync.ts:407]", `AssetSync failed: {err}`)
|
|
575
600
|
end
|
|
576
601
|
report.running = false
|
|
577
602
|
report.ms = math.floor((os.clock() - started) * 1000)
|
|
@@ -592,7 +617,7 @@ local function syncHotAssets(kernel, trove)
|
|
|
592
617
|
table.insert(parts, _arg0)
|
|
593
618
|
end
|
|
594
619
|
table.sort(parts)
|
|
595
|
-
print("[src/assets/sync.ts:
|
|
620
|
+
print("[src/assets/sync.ts:417]", `hot assets: {table.concat(parts, ", ")} in {report.ms} ms (place {report.place or "?"})`)
|
|
596
621
|
end
|
|
597
622
|
end
|
|
598
623
|
return {
|
|
@@ -63,6 +63,19 @@ local function drawServer(target, report)
|
|
|
63
63
|
target:field("Manifest", if ok then `{#report.entries} keys{if report.from ~= nil then ` ({report.from})` else ""}` else report.manifest, if ok then COLORS.text else COLORS.warn)
|
|
64
64
|
local sync = if report.running then "Running" elseif report.ms ~= nil then `{report.ms} ms{if report.timedOut == true then " timed out" else ""}` else "-"
|
|
65
65
|
target:field("Sync", sync, if report.timedOut == true then COLORS.warn else COLORS.text)
|
|
66
|
+
if report.place ~= nil then
|
|
67
|
+
local _condition = report.manifestPlaceVersion
|
|
68
|
+
if _condition == nil then
|
|
69
|
+
_condition = "?"
|
|
70
|
+
end
|
|
71
|
+
local _condition_1 = report.placeVersion
|
|
72
|
+
if _condition_1 == nil then
|
|
73
|
+
_condition_1 = "?"
|
|
74
|
+
end
|
|
75
|
+
local versions = `manifest v{_condition}, place v{_condition_1}`
|
|
76
|
+
local place = if report.place == "differs" then `differs ({versions}): keys load from uploads` elseif report.place == "vouched" then `vouched by the backup ({versions})` else report.place
|
|
77
|
+
target:field("Place", place, if report.place == "differs" then COLORS.warn else COLORS.text)
|
|
78
|
+
end
|
|
66
79
|
for _, why in report.errors do
|
|
67
80
|
target:text(why, COLORS.warn)
|
|
68
81
|
end
|
|
@@ -188,7 +201,7 @@ local function renderAssetsTab(tab, options)
|
|
|
188
201
|
end
|
|
189
202
|
end)
|
|
190
203
|
if not ok then
|
|
191
|
-
warn("[src/devtools/assets-ui.ts:
|
|
204
|
+
warn("[src/devtools/assets-ui.ts:143]", `[devtools] assets refresh failed: {err}`)
|
|
192
205
|
end
|
|
193
206
|
task.wait(REFRESH)
|
|
194
207
|
end
|
package/out/devtools/health.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AnalyticsStats } from "../analytics/schema";
|
|
|
2
2
|
import type { AssetFacts } from "../assets/manifest";
|
|
3
3
|
import type { DetachedStatus, ErrorReportsStatus, FleetSenderInfo, HealthInfo, KernelStatus, PerfSummary, SettingsStatus } from "../kernel";
|
|
4
4
|
/** The newest kernel this framework release knows about. Bump it with every kernel release. */
|
|
5
|
-
export declare const LATEST_KERNEL = "0.5.
|
|
5
|
+
export declare const LATEST_KERNEL = "0.5.3";
|
|
6
6
|
/**
|
|
7
7
|
* The live health window while it is open (kernel 0.3.7 thresholds; older kernels: 3): "1/3 errors, 24 s left", plus
|
|
8
8
|
* ", no rollback" when a failure would only mark the server degraded. Undefined once the window closed.
|
package/out/devtools/health.luau
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local describeFleetError = TS.import(script, script.Parent.Parent, "analytics", "hints").describeFleetError
|
|
4
4
|
--* The newest kernel this framework release knows about. Bump it with every kernel release.
|
|
5
|
-
local LATEST_KERNEL = "0.5.
|
|
5
|
+
local LATEST_KERNEL = "0.5.3"
|
|
6
6
|
--* Kernels before 0.3.7 use fixed thresholds: 3 errors within 30 s of ready roll back.
|
|
7
7
|
local OLD_HEALTH_ERRORS = 3
|
|
8
8
|
local OLD_HEALTH_WINDOW = 30
|
package/out/version.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* This framework's version, as package.json says (scripts/test-state-inspect.luau checks they match). Shown in
|
|
3
3
|
* uploaded log headers (Logs > Upload); bump it with package.json.
|
|
4
4
|
*/
|
|
5
|
-
export declare const FRAMEWORK_VERSION = "0.5.
|
|
5
|
+
export declare const FRAMEWORK_VERSION = "0.5.3";
|
package/out/version.luau
CHANGED
package/package.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@typetorch/framework",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "TypeTorch framework for roblox-ts: hot-swappable modules (dependency injection, lifecycle, troves), guarded networking, hot assets and the in-game dev menu. Ships inside every TypeTorch artifact.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "TypeTorch",
|
|
7
|
-
"homepage": "https://github.com/typetorch/framework#readme",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/typetorch/framework.git"
|
|
11
|
-
},
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/typetorch/framework/issues"
|
|
14
|
-
},
|
|
15
|
-
"keywords": ["roblox", "roblox-ts", "rbxts", "hot-reload", "hot-swap", "dependency-injection", "typetorch"],
|
|
16
|
-
"main": "out/init.luau",
|
|
17
|
-
"types": "out/index.d.ts",
|
|
18
|
-
"files": ["out", "!**/*.tsbuildinfo"],
|
|
19
|
-
"publishConfig": {
|
|
20
|
-
"access": "public"
|
|
21
|
-
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "rbxtsc --type package",
|
|
24
|
-
"watch": "rbxtsc --type package -w",
|
|
25
|
-
"clean": "node -e \"require('fs').rmSync('out',{recursive:true,force:true})\"",
|
|
26
|
-
"prepublishOnly": "node -e \"require('fs').rmSync('out',{recursive:true,force:true})\" && rbxtsc --type package"
|
|
27
|
-
},
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
30
|
-
"@typetorch/transformer": "^0.2.0",
|
|
31
|
-
"roblox-ts": "^3.0.0"
|
|
32
|
-
},
|
|
33
|
-
"peerDependenciesMeta": {
|
|
34
|
-
"@typetorch/transformer": {
|
|
35
|
-
"optional": true
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@rbxts/services": "^1.6.0",
|
|
40
|
-
"@rbxts/t": "^3.2.1",
|
|
41
|
-
"@rbxts/trove": "^1.3.0"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
45
|
-
"@rbxts/types": "^1.0.956",
|
|
46
|
-
"rbxts-transform-debug": "2.2.0",
|
|
47
|
-
"roblox-ts": "^3.0.0",
|
|
48
|
-
"typescript": "5.5.3"
|
|
49
|
-
},
|
|
50
|
-
"overrides": {
|
|
51
|
-
"typescript": "5.5.3"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@typetorch/framework",
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "TypeTorch framework for roblox-ts: hot-swappable modules (dependency injection, lifecycle, troves), guarded networking, hot assets and the in-game dev menu. Ships inside every TypeTorch artifact.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "TypeTorch",
|
|
7
|
+
"homepage": "https://github.com/typetorch/framework#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/typetorch/framework.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/typetorch/framework/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["roblox", "roblox-ts", "rbxts", "hot-reload", "hot-swap", "dependency-injection", "typetorch"],
|
|
16
|
+
"main": "out/init.luau",
|
|
17
|
+
"types": "out/index.d.ts",
|
|
18
|
+
"files": ["out", "!**/*.tsbuildinfo"],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rbxtsc --type package",
|
|
24
|
+
"watch": "rbxtsc --type package -w",
|
|
25
|
+
"clean": "node -e \"require('fs').rmSync('out',{recursive:true,force:true})\"",
|
|
26
|
+
"prepublishOnly": "node -e \"require('fs').rmSync('out',{recursive:true,force:true})\" && rbxtsc --type package"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
30
|
+
"@typetorch/transformer": "^0.2.0",
|
|
31
|
+
"roblox-ts": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@typetorch/transformer": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@rbxts/services": "^1.6.0",
|
|
40
|
+
"@rbxts/t": "^3.2.1",
|
|
41
|
+
"@rbxts/trove": "^1.3.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
45
|
+
"@rbxts/types": "^1.0.956",
|
|
46
|
+
"rbxts-transform-debug": "2.2.0",
|
|
47
|
+
"roblox-ts": "^3.0.0",
|
|
48
|
+
"typescript": "5.5.3"
|
|
49
|
+
},
|
|
50
|
+
"overrides": {
|
|
51
|
+
"typescript": "5.5.3"
|
|
52
|
+
}
|
|
53
|
+
}
|