invariant.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. /**
  2. * Package-owned invariant companion for `@deepseek-ai/dsh-client-runtime`.
  3. * @module @deepseek-ai/dsh-client-runtime/invariant
  4. */
  5. /* jscpd:ignore-start */
  6. /* eslint-disable @typescript-eslint/no-redundant-type-constituents --
  7. * `keyof SlotMap & string` is the declare-merge key pattern: SlotMap is empty
  8. * in this compilation unit (intersection reads `never`) but consumers merge
  9. * keys in; the rule fires on the empty-map view, not on real redundancy. */
  10. import type { Context } from 'cordis'
  11. import type { SlotMap } from '@deepseek-ai/dsh-client-ui-slots'
  12. import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
  13. const PACKAGE_NAME = '@deepseek-ai/dsh-client-runtime'
  14. /** Cordis companion plugin name. */
  15. export const name = 'client-runtime-invariant'
  16. /** Service required before the companion can register. */
  17. export const inject = ['invariants']
  18. /**
  19. * Owned relation: every 'slots/changed'(key) emission must observe the
  20. * mutation already applied — SlotCore bumps the key's version synchronously
  21. * before the service re-emits, so a zero version at dispatch time means the
  22. * event fired without (or ahead of) its mutation.
  23. */
  24. const install: InvariantInstaller = (ctx, fail) => {
  25. ctx.on('internal/dispatch', (_mode, eventName, args) => {
  26. if (eventName !== 'slots/changed') return
  27. const key: unknown = args[0]
  28. if (typeof key !== 'string' || key === '') {
  29. fail("'slots/changed' dispatched without a slot key argument")
  30. return
  31. }
  32. const slots = ctx.get('slots')
  33. // Event payloads carry keys as plain strings; getVersion is statically
  34. // keyed, so restore the SlotMap-key type after the runtime string check.
  35. if (slots !== undefined && slots.getVersion(key as keyof SlotMap & string) === 0) {
  36. fail(`'slots/changed' fired for "${key}" before any mutation bumped its version — emission must follow the applied mutation`)
  37. }
  38. }, { global: true })
  39. }
  40. /**
  41. * Register this package's invariant companion.
  42. * @param ctx - Cordis context carrying the invariant service.
  43. * @returns the installed registration's disposer after setup succeeds.
  44. */
  45. export const apply = (ctx: Context): Promise<() => void> =>
  46. Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
  47. /* jscpd:ignore-end */