@typetorch/framework 0.5.3 → 0.5.4

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.
@@ -13,5 +13,13 @@ export declare function resolveParent(path: string, create: boolean): Instance |
13
13
  * cloning a UI template into PlayerGui) keep the tag and the attributes but live elsewhere, so they never count.
14
14
  */
15
15
  export declare function isLiveCopy(instance: Instance): boolean;
16
+ /**
17
+ * Copies the CollectionService tags of `from` and its descendants onto the instances at the same place in `to` (matched
18
+ * by name, and by order among siblings of the same name); AssetSync's own `__typetorch_asset:` tags stay out. A loaded
19
+ * hot-asset version arrives without the tags its place copy has (2026-10-13: a game that finds its UI by tag lost it
20
+ * on every server that loaded the uploads), so the load gives them back from the place's copy before it goes live.
21
+ * Returns how many tags were added.
22
+ */
23
+ export declare function restoreTags(from: Instance, to: Instance): number;
16
24
  /** The live copies tagged for `key` (normally one; two for a moment while a new version replaces the old). */
17
25
  export declare function liveCopies(key: string): Instance[];
@@ -2,6 +2,7 @@
2
2
  local TS = _G[script]
3
3
  local CollectionService = TS.import(script, TS.getModule(script, "@rbxts", "services")).CollectionService
4
4
  local _manifest = TS.import(script, script.Parent, "manifest")
5
+ local ASSET_TAG_PREFIX = _manifest.ASSET_TAG_PREFIX
5
6
  local assetTag = _manifest.assetTag
6
7
  local ATTR_PATH = _manifest.ATTR_PATH
7
8
  local pathSegments = _manifest.pathSegments
@@ -73,6 +74,68 @@ local function isLiveCopy(instance)
73
74
  local parent = instance.Parent
74
75
  return path ~= nil and parent ~= nil and resolveParent(path, false) == parent
75
76
  end
77
+ --[[
78
+ *
79
+ * Copies the CollectionService tags of `from` and its descendants onto the instances at the same place in `to` (matched
80
+ * by name, and by order among siblings of the same name); AssetSync's own `__typetorch_asset:` tags stay out. A loaded
81
+ * hot-asset version arrives without the tags its place copy has (2026-10-13: a game that finds its UI by tag lost it
82
+ * on every server that loaded the uploads), so the load gives them back from the place's copy before it goes live.
83
+ * Returns how many tags were added.
84
+
85
+ ]]
86
+ local function restoreTags(from, to)
87
+ local added = 0
88
+ local visit
89
+ visit = function(source, target)
90
+ for _, tag in source:GetTags() do
91
+ local _arg1 = #ASSET_TAG_PREFIX
92
+ local _condition = string.sub(tag, 1, _arg1) == ASSET_TAG_PREFIX
93
+ if not _condition then
94
+ _condition = target:HasTag(tag)
95
+ end
96
+ if _condition then
97
+ continue
98
+ end
99
+ target:AddTag(tag)
100
+ added += 1
101
+ end
102
+ local byName = {}
103
+ for _, child in target:GetChildren() do
104
+ local _name = child.Name
105
+ local list = byName[_name]
106
+ if list == nil then
107
+ local _name_1 = child.Name
108
+ local _arg1 = { child }
109
+ byName[_name_1] = _arg1
110
+ else
111
+ table.insert(list, child)
112
+ end
113
+ end
114
+ local seen = {}
115
+ for _, child in source:GetChildren() do
116
+ local _name = child.Name
117
+ local _condition = seen[_name]
118
+ if _condition == nil then
119
+ _condition = 0
120
+ end
121
+ local index = _condition
122
+ local _name_1 = child.Name
123
+ local _arg1 = index + 1
124
+ seen[_name_1] = _arg1
125
+ local _name_2 = child.Name
126
+ local _match = byName[_name_2]
127
+ if _match ~= nil then
128
+ _match = _match[index + 1]
129
+ end
130
+ local match = _match
131
+ if match ~= nil then
132
+ visit(child, match)
133
+ end
134
+ end
135
+ end
136
+ visit(from, to)
137
+ return added
138
+ end
76
139
  --* The live copies tagged for `key` (normally one; two for a moment while a new version replaces the old).
77
140
  local function liveCopies(key)
78
141
  local _exp = CollectionService:GetTagged(assetTag(key))
@@ -93,5 +156,6 @@ return {
93
156
  stringAttribute = stringAttribute,
94
157
  resolveParent = resolveParent,
95
158
  isLiveCopy = isLiveCopy,
159
+ restoreTags = restoreTags,
96
160
  liveCopies = liveCopies,
97
161
  }
@@ -144,6 +144,8 @@ export interface AssetStatus {
144
144
  ms?: number;
145
145
  /** Loaded after the start-up wait ran out. */
146
146
  late?: boolean;
147
+ /** CollectionService tags a loaded copy got back from the place's copy (restoreTags). */
148
+ tags?: number;
147
149
  }
148
150
  export interface AssetSyncReport {
149
151
  /** none: the artifact has no Assets attribute; invalid: unusable; unread: reading it failed (see errors). */
@@ -9,6 +9,7 @@ local _dom = TS.import(script, script.Parent, "dom")
9
9
  local liveCopies = _dom.liveCopies
10
10
  local numberAttribute = _dom.numberAttribute
11
11
  local resolveParent = _dom.resolveParent
12
+ local restoreTags = _dom.restoreTags
12
13
  local stringAttribute = _dom.stringAttribute
13
14
  local _manifest = TS.import(script, script.Parent, "manifest")
14
15
  local assetTag = _manifest.assetTag
@@ -409,6 +410,50 @@ local function syncEntry(entry, status, placeMatches, ownEpoch, threads)
409
410
  if target == nil then
410
411
  error(`no {string.split(entry.path, "/")[1]} service`, 0)
411
412
  end
413
+ -- The uploaded version has no CollectionService tags: the place's copy (on a hot swap, the copy being
414
+ -- replaced, which got them the same way) gives them back (restoreTags).
415
+ local _result_1
416
+ if fallback ~= nil and fallback.Parent ~= nil then
417
+ _result_1 = fallback
418
+ else
419
+ -- ▼ ReadonlyArray.find ▼
420
+ local _callback_2 = function(candidate)
421
+ return candidate.Parent ~= nil
422
+ end
423
+ local _result_2
424
+ for _i, _v in baked do
425
+ if _callback_2(_v, _i - 1, baked) == true then
426
+ _result_2 = _v
427
+ break
428
+ end
429
+ end
430
+ -- ▲ ReadonlyArray.find ▲
431
+ _result_1 = _result_2
432
+ end
433
+ local source = _result_1
434
+ do
435
+ local index = #tagged - 1
436
+ local _shouldIncrement = false
437
+ while true do
438
+ if _shouldIncrement then
439
+ index -= 1
440
+ else
441
+ _shouldIncrement = true
442
+ end
443
+ if not (source == nil and index >= 0) then
444
+ break
445
+ end
446
+ if tagged[index + 1].Parent ~= nil then
447
+ source = tagged[index + 1]
448
+ end
449
+ end
450
+ end
451
+ if source ~= nil then
452
+ local restored = restoreTags(source, copy)
453
+ if restored > 0 then
454
+ status.tags = restored
455
+ end
456
+ end
412
457
  stamp(copy, entry, entry.ver)
413
458
  copy.Parent = target
414
459
  end)
@@ -425,7 +470,7 @@ local function syncEntry(entry, status, placeMatches, ownEpoch, threads)
425
470
  if _condition == nil then
426
471
  _condition = entry.ver
427
472
  end
428
- warn("[src/assets/sync.ts:298]", `hot asset {_exp_1} (version {_condition}) failed: {reason}`)
473
+ warn("[src/assets/sync.ts:308]", `hot asset {_exp_1} (version {_condition}) failed: {reason}`)
429
474
  return nil
430
475
  end
431
476
  -- The new copy is live: the copies it replaces go.
@@ -438,7 +483,7 @@ local function syncEntry(entry, status, placeMatches, ownEpoch, threads)
438
483
  status.ms = math.floor((os.clock() - started) * 1000)
439
484
  if late then
440
485
  status.late = true
441
- print("[src/assets/sync.ts:311]", `hot asset {entry.key} loaded late ({status.ms} ms)`)
486
+ print("[src/assets/sync.ts:321]", `hot asset {entry.key} loaded late ({status.ms} ms)`)
442
487
  end
443
488
  end)
444
489
  table.insert(_threads, _arg0)
@@ -495,7 +540,7 @@ local function run(kernel, deadline, ownEpoch, threads)
495
540
  report.placeVersion = game.PlaceVersion
496
541
  local placeMatches = place == "same" or place == "vouched"
497
542
  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)`)
543
+ warn("[src/assets/sync.ts:366]", `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
544
  end
500
545
  local loads = {}
501
546
  for _, entry in manifest.entries do
@@ -515,7 +560,7 @@ local function run(kernel, deadline, ownEpoch, threads)
515
560
  if not ok then
516
561
  status.source = "failed"
517
562
  status.error = shortError(result)
518
- warn("[src/assets/sync.ts:369]", `hot asset {entry.key} failed: {status.error}`)
563
+ warn("[src/assets/sync.ts:379]", `hot asset {entry.key} failed: {status.error}`)
519
564
  elseif result ~= nil then
520
565
  table.insert(loads, result)
521
566
  end
@@ -549,7 +594,7 @@ local function run(kernel, deadline, ownEpoch, threads)
549
594
  end
550
595
  report.timedOut = true
551
596
  load.fail("timed out")
552
- warn("[src/assets/sync.ts:378]", `hot asset {load.status.key}: still loading; the old copy stays until it finishes`)
597
+ warn("[src/assets/sync.ts:388]", `hot asset {load.status.key}: still loading; the old copy stays until it finishes`)
553
598
  end
554
599
  report.unmanaged = unmanagedKeys()
555
600
  end
@@ -596,7 +641,7 @@ local function syncHotAssets(kernel, trove)
596
641
  local _errors = report.errors
597
642
  local _arg0 = `AssetSync failed: {shortError(err)}`
598
643
  table.insert(_errors, _arg0)
599
- warn("[src/assets/sync.ts:407]", `AssetSync failed: {err}`)
644
+ warn("[src/assets/sync.ts:417]", `AssetSync failed: {err}`)
600
645
  end
601
646
  report.running = false
602
647
  report.ms = math.floor((os.clock() - started) * 1000)
@@ -617,7 +662,7 @@ local function syncHotAssets(kernel, trove)
617
662
  table.insert(parts, _arg0)
618
663
  end
619
664
  table.sort(parts)
620
- print("[src/assets/sync.ts:417]", `hot assets: {table.concat(parts, ", ")} in {report.ms} ms (place {report.place or "?"})`)
665
+ print("[src/assets/sync.ts:427]", `hot assets: {table.concat(parts, ", ")} in {report.ms} ms (place {report.place or "?"})`)
621
666
  end
622
667
  end
623
668
  return {
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.3";
5
+ export declare const FRAMEWORK_VERSION = "0.5.4";
package/out/version.luau CHANGED
@@ -5,7 +5,7 @@
5
5
  * uploaded log headers (Logs > Upload); bump it with package.json.
6
6
 
7
7
  ]]
8
- local FRAMEWORK_VERSION = "0.5.3"
8
+ local FRAMEWORK_VERSION = "0.5.4"
9
9
  return {
10
10
  FRAMEWORK_VERSION = FRAMEWORK_VERSION,
11
11
  }
package/package.json CHANGED
@@ -1,53 +1,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
- }
1
+ {
2
+ "name": "@typetorch/framework",
3
+ "version": "0.5.4",
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
+ }