quad-roblox가 quad-base를 런타임 주입(QuadRoblox(Quad): QuadRoblox)으로만 받으면 pesde 의존 선언이 필요 없어 보이지만, 타입 참조용 require도 런타임에 실제 실행됨을 실측 확인 — dev-dependency로 두면 게시 후 소비자 환경에서 크래시함. 해법으로 구현 없는 타입 계약 전용 워크스페이스 패키지 quad-types 신설, quad-base/quad-roblox 모두 이것만 의존하도록 전환. AddPlugin<Self,P>(self:Self,fn:(Self)->P):Self&P — 제네릭 self로 둬야 체이닝이 누적됨을 실측 확인(고정하면 이전 확장을 잃음), quad-base에 실제 mutate 기반 구현 반영. CheckedQuad<T> 버전 체크는 배선하며 세 번 깨짐 — error() 대신 print+types.never, 함수 본문 로컬 별칭 대신 리턴 타입 표현식에 직접, 그리고 가장 중요하게 type function을 한 번이라도 거친 값(패스스루 포함)은 이후 AddPlugin 같은 제네릭 self 체이닝이 조용히 깨진다는 새 Luau 함정 발견 — typing-limits.md §6으로 승격. 최종 설계(검증 결과를 원본과 격리된 가상 필드로)만 AddPlugin과 완전히 호환. 부수로 quad-base 자신도 quad-types workspace 의존 때문에 CLI symlink 함정(지난 세션 발견)에 걸림 — 로컬 테스트용 symlink 실체화로 임시 우회. Co-authored-by: qwreey <me@qwreey.moe>
54 lines
1.9 KiB
Text
54 lines
1.9 KiB
Text
--[[
|
|
quad-base 최상위 진입점.
|
|
`.claude/base/architecture.md` 확정 결정 13 / `.claude/base/module-lifecycle-plan.md`
|
|
"New()의 내부 구성 — InitXxx 팩토리 체이닝" / "RunInit — 함수 자체를
|
|
릴레이션 키로 쓰는 멱등 가드" / "AddPlugin" 절 그대로.
|
|
|
|
`require(quad-base)`는 이미 만들어진 기본 인스턴스 자체다 — 다중
|
|
인스턴스화가 필요한 드문 경우에만 반환값의 `.New()`를 명시적으로 호출.
|
|
|
|
`Quad` 타입 자체는 여기서 선언하지 않고 `quad-types`(워크스페이스
|
|
패키지, 구현 없이 타입 계약만)에서 그대로 가져와 씀 — quad-roblox 같은
|
|
백엔드 패키지가 이 무거운 quad-base 전체 대신 `quad-types`만 의존할 수
|
|
있게 하기 위함(`.claude/base/project-setup-plan.md` "quad-types" 절).
|
|
]]
|
|
|
|
local Relate = require("@self/Relate")
|
|
local QuadTypes = require("./roblox_packages/quad_types")
|
|
local InitDebug = require("@self/Debug")
|
|
|
|
type Quad = QuadTypes.Quad
|
|
|
|
-- (module, initFn) -> 이미 실행됐는가. New()가 몇 번 불려도 module마다
|
|
-- weak-키잉되므로 별도 정리 불필요(module이 GC되면 이 기록도 같이 사라짐).
|
|
local runInitRelate = Relate()
|
|
|
|
local function New(): Quad
|
|
local module = {
|
|
New = New,
|
|
Version = "0.0.0",
|
|
} :: Quad
|
|
|
|
function module.RunInit(self, initFn)
|
|
if runInitRelate:GetStrong(self, initFn) then
|
|
return -- 이 module 인스턴스에 이 initFn은 이미 실행됨, no-op
|
|
end
|
|
runInitRelate:SetStrong(self, initFn, true) -- 실제 실행 전에 먼저 표시(순환 의존 대비)
|
|
initFn(self)
|
|
end
|
|
|
|
function module.AddPlugin(self, pluginFn)
|
|
local extension = pluginFn(self)
|
|
for k, v in extension :: any do
|
|
(self :: any)[k] = v
|
|
end
|
|
return self :: any
|
|
end
|
|
|
|
module:RunInit(InitDebug)
|
|
-- 서브시스템이 늘어날 때마다 이 자리에 module:RunInit(InitXxx)를 순서 무관하게 추가
|
|
|
|
return module
|
|
end
|
|
|
|
return New()
|