imccyu 2 ماه پیش
والد
کامیت
004a988168
1فایلهای تغییر یافته به همراه30 افزوده شده و 74 حذف شده
  1. 30 74
      packages/client/runtime/src/client/slots.ts

+ 30 - 74
packages/client/runtime/src/client/slots.ts

@@ -18,8 +18,7 @@ import { Service } from 'cordis'
 import type { Context } from 'cordis'
 import { SlotCore } from '@deepseek-ai/dsh-client-ui-slots'
 import type {
-  ChildrenDecl, ComposedProps, HandleOf, InjectParams, KindOptions, OwnerOf,
-  SlotComponent, SlotEntryDef, SlotMap, SlotRenderer, SlotRendererHost,
+  OwnerOf, SlotEntryDef, SlotMap, SlotRenderer, SlotRendererHost,
   SlotScope, SlotSpec, StoreDecl, StoredEntry, StoreInstanceLike,
 } from '@deepseek-ai/dsh-client-ui-slots'
 
@@ -57,34 +56,6 @@ interface StoreAxisRecord {
   instances: Map<string, EngineStoreInstance>
 }
 
-/**
- * Register options as the service face declares them (structurally the
- * core's BaseOptions, re-declared because ui-slots keeps it private).
- * FIXME(slot-parity): dedupe once ui-slots exports its options type.
- */
-type RegisterOptions<K extends keyof SlotMap & string, D extends ChildrenDecl, H> = {
-  /** Target slot key (the entry contributes INTO this slot). */
-  name: K
-  /** Child-slot declaration + render authorization + runtime spec, in one table. */
-  children?: D
-  /** Store seat: a shared handle (apply-constructed) or an exclusive factory (framework-called per entry). */
-  store?: H
-  /** Registrant identity label for diagnostics (defaults to the caller's fiber name). */
-  registrant?: string
-} & KindOptions<SlotMap[K]>
-
-/**
- * Compile-time presence check: an entry declaring children MUST consume
- * `renderSlot` (declaring is claiming). Structural copy of the core's
- * private RendersCheck; same FIXME as {@link RegisterOptions}.
- */
-type RendersCheck<C, D> =
-  [keyof D & keyof SlotMap & string] extends [never] ? unknown
-    : C extends (props: infer P) => unknown
-      ? ('renderSlot' extends keyof P ? unknown
-        : { 'children declared but the component consumes no renderSlot': keyof D & keyof SlotMap & string })
-      : unknown
-
 /** Type-erased options view the implementation works with (the typed overloads proved the shares). */
 interface ErasedRegisterOptions {
   name: string
@@ -118,46 +89,23 @@ export class SlotsService extends Service {
   }
 
   /**
-   * The single registration API (see SlotCore.register for the full
-   * semantics: children declaration, store seat, inject face, load-time
-   * validation, unload cascade). This layer adds: disposal through the
-   * caller's ctx.effect (fiber unload = cascade), exclusive-factory minting
-   * (`store: createXxxStore` becomes a per-entry handle), the registrant
-   * diagnostics stamp, and store-instance lifecycle on the entry axis.
-   * @param options - name + children + store + inject (+ kind-shaped key/id/order/label).
-   * @param component - pure component typed by the four-share composed props.
-   * @returns disposer (idempotent; stale calls after fiber teardown are no-ops).
+   * The single registration API. The typed face IS the core's register
+   * (both overloads reused verbatim — one authority, no structural copy;
+   * see SlotCore.register for children declaration, store seat, inject
+   * face, load-time validation, and the unload cascade). This layer adds:
+   * disposal through the caller's ctx.effect (fiber unload = cascade),
+   * exclusive-factory minting (`store: createXxxStore` becomes a per-entry
+   * handle), the registrant diagnostics stamp, and store-instance lifecycle
+   * on the entry axis.
+   *
+   * Declared here, implemented by prototype assignment below the class: it
+   * MUST stay a prototype method (never an instance arrow) — the cordis
+   * service proxy binds `this.ctx` to the CALLER's context at call time,
+   * which is what routes the effect (and the unload cascade) into the
+   * caller's fiber. An arrow property would freeze `this` to the service's
+   * own root ctx and silently break per-plugin disposal.
    */
-  register<
-    K extends keyof SlotMap & string,
-    const D extends ChildrenDecl = Record<never, never>,
-    H extends StoreDecl | undefined = undefined,
-    C extends SlotComponent<never> = SlotComponent<never>,
-  >(
-    options: RegisterOptions<K, D, H> & { inject?: undefined },
-    component: C
-      & SlotComponent<ComposedProps<K, keyof NoInfer<D> & keyof SlotMap & string, HandleOf<NoInfer<H>>, object>>
-      & RendersCheck<C, D>,
-  ): () => void
-  register<
-    K extends keyof SlotMap & string,
-    I extends object,
-    const D extends ChildrenDecl = Record<never, never>,
-    H extends StoreDecl | undefined = undefined,
-    C extends SlotComponent<never> = SlotComponent<never>,
-  >(
-    options: RegisterOptions<K, D, H> & { inject: (...args: InjectParams<K, H>) => I },
-    component: C
-      & SlotComponent<ComposedProps<K, keyof NoInfer<D> & keyof SlotMap & string, HandleOf<NoInfer<H>>, I>>
-      & RendersCheck<C, D>,
-  ): () => void
-  register(rawOptions: object, component: unknown): () => void {
-    // The typed overloads above proved the shares; the implementation works
-    // on the erased view (same pattern as the core's register).
-    const options = rawOptions as ErasedRegisterOptions
-    // eslint-disable-next-line @typescript-eslint/no-misused-promises -- synchronous cleanup; direct return preserves disposer identity
-    return this.ctx.effect(() => this._register(options, component), 'slots.register()')
-  }
+  declare readonly register: SlotCore['register']
 
   /**
    * Install the shell's renderer (web-react's createSlotRenderer product).
@@ -253,11 +201,6 @@ export class SlotsService extends Service {
     return this._core.getVersion(key)
   }
 
-  /** The wrapped pure core (invariant checks read through this). */
-  get core(): SlotCore {
-    return this._core
-  }
-
   /** Delegating registration path: factory minting + registrant stamp + core write + instance-axis bookkeeping. */
   private _register(options: ErasedRegisterOptions, component: unknown): () => void {
     // Exclusive stores pass the factory itself: minted here into a per-entry
@@ -356,3 +299,16 @@ export class SlotsService extends Service {
     if (record.refs === 0) this._stores.delete(handle)
   }
 }
+
+// register's implementation (prototype assignment pairs with the `declare`
+// inside the class — see its JSDoc for why it must live on the prototype).
+// Element access reaches the private _register legally and keeps it a
+// TS-visible read.
+;(SlotsService.prototype as { register: (options: object, component: unknown) => () => void }).register
+  = function register(this: SlotsService, rawOptions: object, component: unknown): () => void {
+    // The core's overloads proved the shares; the implementation works on
+    // the erased view (same pattern as the core's own implementation arm).
+    const options = rawOptions as ErasedRegisterOptions
+    // eslint-disable-next-line @typescript-eslint/no-misused-promises -- synchronous cleanup; direct return preserves disposer identity
+    return this.ctx.effect(() => this['_register'](options, component), 'slots.register()')
+  }