The original framing of this issue proposed a new "admin vs. app" initialization API to prevent infrastructure concerns from leaking into app code. On reflection, the current design already handles this correctly — the concern is worth keeping but the proposed solution is the wrong fix.
What's already true
The "leak" in the current API is confined to two lines of bootstrap code:
const adapter = await SQLiteAdapter.initialize({ path, entityId, timezone });
const stack = await Stack.create(adapter);
After Stack.create(adapter), the app is fully adapter-agnostic. Creating records, querying, migrating, associating — none of that touches the adapter. The coupling is minimal and confined to one place, where it belongs: the entry point.
Why the proposed fix doesn't help
A URL-dispatch scheme like Stack.open('file://./my-stack.db') or Stack.open('https://...') doesn't eliminate the infrastructure concern — it relocates it. Auth tokens, connection options, and initialization-vs-open semantics don't flatten neatly into a URL string. Adding a new abstraction layer here would introduce its own edge cases without meaningfully reducing the app author's burden.
The goal — "an app written for local SQLite should work unmodified against a remote stack" — is already achievable. The only thing that changes between backends is the two lines of bootstrap code, and that code should change: choosing a backend is an infrastructure decision that belongs at the entry point, not buried in app logic. That's dependency injection by convention.
The real concern worth addressing
If the vision includes a plugin or extension model — third-party "app" code that receives a Stack it didn't create — then there's a legitimate need to ensure Stack is passable as an opaque handle without requiring the app to depend on any adapter package.
That's a TypeScript interface concern, not an initialization API concern. The actionable question is:
Does the public Stack type leak any adapter internals? If so, clean those up so Stack can be passed as a dependency without pulling in adapter-specific types.
That's likely a small, targeted change — and it directly addresses the use case without a new factory abstraction.
The original framing of this issue proposed a new "admin vs. app" initialization API to prevent infrastructure concerns from leaking into app code. On reflection, the current design already handles this correctly — the concern is worth keeping but the proposed solution is the wrong fix.
What's already true
The "leak" in the current API is confined to two lines of bootstrap code:
After
Stack.create(adapter), the app is fully adapter-agnostic. Creating records, querying, migrating, associating — none of that touches the adapter. The coupling is minimal and confined to one place, where it belongs: the entry point.Why the proposed fix doesn't help
A URL-dispatch scheme like
Stack.open('file://./my-stack.db')orStack.open('https://...')doesn't eliminate the infrastructure concern — it relocates it. Auth tokens, connection options, and initialization-vs-open semantics don't flatten neatly into a URL string. Adding a new abstraction layer here would introduce its own edge cases without meaningfully reducing the app author's burden.The goal — "an app written for local SQLite should work unmodified against a remote stack" — is already achievable. The only thing that changes between backends is the two lines of bootstrap code, and that code should change: choosing a backend is an infrastructure decision that belongs at the entry point, not buried in app logic. That's dependency injection by convention.
The real concern worth addressing
If the vision includes a plugin or extension model — third-party "app" code that receives a
Stackit didn't create — then there's a legitimate need to ensureStackis passable as an opaque handle without requiring the app to depend on any adapter package.That's a TypeScript interface concern, not an initialization API concern. The actionable question is:
Does the public
Stacktype leak any adapter internals? If so, clean those up soStackcan be passed as a dependency without pulling in adapter-specific types.That's likely a small, targeted change — and it directly addresses the use case without a new factory abstraction.