@opetope/runtime 0.1.1 → 0.2.0

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +19 -9
  3. package/README.ru.md +21 -9
  4. package/dist/feature-authoring-types.d.ts +6 -10
  5. package/dist/feature-authoring.js +1 -1
  6. package/dist/feature-authoring.js.map +1 -1
  7. package/dist/feature-body.d.ts +1 -1
  8. package/dist/feature-body.js.map +1 -1
  9. package/dist/feature-contribution.d.ts +5 -3
  10. package/dist/feature-contribution.js +1 -1
  11. package/dist/feature-contribution.js.map +1 -1
  12. package/dist/feature-definition-api.d.ts +1 -1
  13. package/dist/feature-generation.js +1 -1
  14. package/dist/feature-generation.js.map +1 -1
  15. package/dist/feature-lazy-generation.d.ts +1 -1
  16. package/dist/feature-lazy-generation.js +1 -1
  17. package/dist/feature-lazy-generation.js.map +1 -1
  18. package/dist/feature-own-values.d.ts +10 -0
  19. package/dist/feature-own-values.js +2 -0
  20. package/dist/feature-own-values.js.map +1 -0
  21. package/dist/feature-port-binding.d.ts +2 -1
  22. package/dist/feature-port-binding.js +1 -1
  23. package/dist/feature-port-binding.js.map +1 -1
  24. package/dist/feature-port.d.ts +13 -23
  25. package/dist/feature-port.js +1 -1
  26. package/dist/feature-port.js.map +1 -1
  27. package/dist/public-module-types.d.ts +1 -1
  28. package/dist/public-module.d.ts +1 -1
  29. package/docs/agent-guide.md +11 -4
  30. package/docs/agent-guide.ru.md +11 -4
  31. package/docs/cookbook.md +9 -7
  32. package/docs/cookbook.ru.md +9 -7
  33. package/docs/decisions.md +14 -0
  34. package/docs/how-it-works.md +20 -5
  35. package/docs/how-it-works.ru.md +19 -5
  36. package/docs/spec.md +31 -17
  37. package/docs/spec.ru.md +32 -18
  38. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"feature-port.js","sources":["../src/feature-port.ts"],"sourcesContent":["import type { Call, ModelOf, Port, PortRef } from '@opetope/core';\nimport type { ModelIdentity, PortIdentity, PortInput, PortOutput } from '@opetope/core/internal';\nimport { bindPortRef, isPort, isPortRefFor } from '@opetope/core/internal';\n\nimport { isAttachmentCallRef } from './attachment-call-declaration';\nimport type {\n FeatureContribution,\n FeaturePipeBuilder,\n FeatureRegisterBuilder,\n FeatureSlotBuilder,\n} from './feature-contribution';\nimport { isFeatureDataRef } from './feature-model';\nimport type { FeatureModelRef } from './feature-model';\nimport { assertExactDataKeys, assertPlainRecord, dataEntries } from './feature-record';\nimport type { ModuleCallRef, ModuleCallRefIdentity } from './public-module';\n\ndeclare const optionalPortBrand: unique symbol;\ndeclare const portProviderBrand: unique symbol;\n\ntype AnyPort = PortIdentity;\n\n/**\n * `optional(port)` is the weak edge of a port: the application may have no provider, and a call without one\n * settles with `CallError` code `unavailable` instead of failing the consumer (D105).\n */\ninterface OptionalPort<PortType extends AnyPort> {\n readonly [optionalPortBrand]: readonly [PortType];\n}\n\ntype PortRequirementRecord = Readonly<Record<string, AnyPort | OptionalPort<AnyPort>>>;\ntype RequiredPortValues<Requires extends PortRequirementRecord> = {\n readonly [Key in keyof Requires]: Requires[Key] extends OptionalPort<infer PortType>\n ? PortRef<PortInput<PortType>, PortOutput<PortType>> | undefined\n : Requires[Key] extends AnyPort\n ? PortRef<PortInput<Requires[Key]>, PortOutput<Requires[Key]>>\n : never;\n};\n/** Every requirement is a `Call` of this feature: the application chooses the provider, the author calls the ref. */\ntype FeatureRequiredCalls<Id extends string, Requires extends PortRequirementRecord> = {\n readonly [Key in keyof Requires]: Requires[Key] extends OptionalPort<infer PortType>\n ? ModuleCallRef<PortInput<PortType>, PortOutput<PortType>, Id>\n : Requires[Key] extends AnyPort\n ? ModuleCallRef<PortInput<Requires[Key]>, PortOutput<Requires[Key]>, Id>\n : never;\n};\n\ninterface PortProviderBinding<PortType extends AnyPort, Id extends string> {\n readonly [portProviderBrand]: readonly [PortType, Id];\n}\n\ninterface FeaturePortBuilder<Id extends string> {\n <PortType extends AnyPort>(\n port: PortType,\n target: ModuleCallRef<PortInput<NoInfer<PortType>>, PortOutput<NoInfer<PortType>>, Id>,\n ): PortProviderBinding<PortType, Id>;\n <PortType extends AnyPort, Declaration extends ModelIdentity>(\n port: PortType,\n projection: {\n readonly from: FeatureModelRef<Declaration, Id>;\n readonly select: (\n model: ModelOf<NoInfer<Declaration>>,\n ) => Call<PortInput<NoInfer<PortType>>, PortOutput<NoInfer<PortType>>>;\n },\n ): PortProviderBinding<PortType, Id>;\n}\n\ntype PortProviderTarget =\n | { readonly kind: 'call'; readonly ref: ModuleCallRefIdentity }\n | { readonly kind: 'model'; readonly ref: object; readonly select: (model: unknown) => unknown };\n\ntype PortProviderDescriptor = {\n readonly key: string;\n readonly port: AnyPort;\n readonly target: PortProviderTarget;\n};\n/** One section for every extension point: `port` for ports, `slot`, `pipe` and `register` for contributions. */\ntype FeatureProvidesFactory<\n Id extends string,\n Context,\n Evaluation,\n Runtime extends Readonly<Record<string, object>>,\n Provided extends FeatureProvidedRecord<Id>,\n> = (context: {\n readonly own: Runtime;\n readonly pipe: FeaturePipeBuilder<Evaluation>;\n readonly port: FeaturePortBuilder<Id>;\n readonly register: FeatureRegisterBuilder<Context, Evaluation>;\n readonly slot: FeatureSlotBuilder<Context, Evaluation>;\n}) => Provided;\n\ntype FeatureProvidedRecord<Id extends string> = Readonly<\n Record<string, FeatureContribution | PortProviderBinding<AnyPort, Id>>\n>;\n\nconst requiredPorts = new WeakMap<object, AnyPort>();\nconst optionalPorts = new WeakSet<object>();\nconst portProviders = new WeakMap<object, Readonly<{ port: AnyPort; target: PortProviderTarget }>>();\n\n/** An explicit wrapper marks the weak requirement; an authentic bare Port is the strong requirement. */\nfunction optionalPort<PortType extends AnyPort>(port: PortType): OptionalPort<PortType> {\n if (!isPort(port)) throw new TypeError('optional expects an authentic Port.');\n\n const requirement = Object.freeze({}) as OptionalPort<PortType>;\n requiredPorts.set(requirement, port);\n optionalPorts.add(requirement);\n\n return requirement;\n}\n\n/** Tolerant on purpose: the caller may hold any requirement declaration or none at all. */\nfunction isOptionalPortRequirement(requirement: unknown): boolean {\n return typeof requirement === 'object' && requirement !== null && optionalPorts.has(requirement);\n}\n\nfunction getRequiredPort(requirement: unknown): AnyPort {\n if (isPort(requirement)) return requirement;\n\n if (typeof requirement !== 'object' || requirement === null) {\n throw new TypeError('Feature requirement is not an authentic required Port.');\n }\n\n const port = requiredPorts.get(requirement);\n\n if (port === undefined) throw new TypeError('Feature requirement is not an authentic required Port.');\n\n return port;\n}\n\nfunction createPortBuilder<Id extends string>(): FeaturePortBuilder<Id> {\n return <PortType extends AnyPort>(port: PortType, value: unknown) => {\n if (!isPort(port)) throw new TypeError('provides.port takes a port created by definePort.');\n\n let target: PortProviderTarget;\n\n if (isAttachmentCallRef(value)) {\n target = Object.freeze({ kind: 'call', ref: value as unknown as ModuleCallRefIdentity });\n } else {\n assertPlainRecord(value, 'Feature port model projection');\n assertExactDataKeys(\n value,\n dataEntries(value, 'Feature port model projection'),\n ['from', 'select'],\n 'Feature port model projection',\n );\n\n if (!isFeatureDataRef(value['from']) || typeof value['select'] !== 'function') {\n throw new TypeError('provides.port takes an owned Call ref or a model projection with from and select.');\n }\n\n target = Object.freeze({\n kind: 'model',\n ref: value['from'] as object,\n select: value['select'] as (model: unknown) => unknown,\n });\n }\n\n const binding = Object.freeze({}) as PortProviderBinding<PortType, Id>;\n portProviders.set(binding, Object.freeze({ port, target }));\n\n return binding;\n };\n}\n\nfunction snapshotPortProviders(\n runtimeValues: ReadonlySet<object>,\n entries: readonly (readonly [string, unknown])[],\n): readonly PortProviderDescriptor[] {\n const descriptors = entries.map(([key, binding]) => {\n if (typeof binding !== 'object' || binding === null) {\n throw new TypeError(`Feature provider ${key} is not an authentic Port provider.`);\n }\n\n const definition = portProviders.get(binding);\n\n if (definition === undefined) throw new TypeError(`Feature provider ${key} is not an authentic Port provider.`);\n\n if (!runtimeValues.has(definition.target.ref)) {\n throw new TypeError(`Feature provider ${key} must select a call or model ref from its public own.`);\n }\n\n return Object.freeze({ key, port: definition.port, target: definition.target });\n });\n\n return Object.freeze(descriptors);\n}\n\nfunction bindProvidedPort(descriptor: PortProviderDescriptor, target: Call<never, unknown>): PortRef<never, unknown> {\n return bindPortRef(descriptor.port as Port<never, unknown>, target);\n}\n\nfunction assertRequiredPortValue(\n port: AnyPort,\n value: unknown,\n key: string,\n isOptional: boolean,\n): asserts value is PortRef<never, unknown> | undefined {\n if (isOptional && value === undefined) return;\n\n if (!isPortRefFor(port as Port<never, unknown>, value)) {\n throw new TypeError(`openFeature requirement ${key} must be a PortRef for ${port.id}.`);\n }\n}\n\nexport {\n assertRequiredPortValue,\n bindProvidedPort,\n createPortBuilder,\n getRequiredPort,\n isOptionalPortRequirement,\n optionalPort,\n snapshotPortProviders,\n};\nexport type {\n AnyPort,\n FeatureProvidedRecord,\n FeatureProvidesFactory,\n FeatureRequiredCalls,\n OptionalPort,\n PortProviderDescriptor,\n PortRequirementRecord,\n RequiredPortValues,\n};\n"],"names":["requiredPorts","optionalPorts","portProviders","optionalPort","port","isPort","requirement","isOptionalPortRequirement","getRequiredPort","createPortBuilder","value","target","isAttachmentCallRef","assertPlainRecord","assertExactDataKeys","dataEntries","isFeatureDataRef","binding","snapshotPortProviders","runtimeValues","entries","descriptors","key","definition","bindProvidedPort","descriptor","bindPortRef","assertRequiredPortValue","isOptional","isPortRefFor"],"mappings":"kTA8FA,MAAMA,EAAgB,IAAI,QACpBC,EAAgB,IAAI,QACpBC,EAAgB,IAAI,QAG1B,SAASC,EAAuCC,EAAc,CAC5D,GAAI,CAACC,EAAOD,CAAI,EAAG,MAAM,IAAI,UAAU,qCAAqC,EAE5E,MAAME,EAAc,OAAO,OAAO,EAAE,EACpC,OAAAN,EAAc,IAAIM,EAAaF,CAAI,EACnCH,EAAc,IAAIK,CAAW,EAEtBA,CACT,CAGA,SAASC,EAA0BD,EAAoB,CACrD,OAAO,OAAOA,GAAgB,UAAYA,IAAgB,MAAQL,EAAc,IAAIK,CAAW,CACjG,CAEA,SAASE,EAAgBF,EAAoB,CAC3C,GAAID,EAAOC,CAAW,EAAG,OAAOA,EAEhC,GAAI,OAAOA,GAAgB,UAAYA,IAAgB,KACrD,MAAM,IAAI,UAAU,wDAAwD,EAG9E,MAAMF,EAAOJ,EAAc,IAAIM,CAAW,EAE1C,GAAIF,IAAS,OAAW,MAAM,IAAI,UAAU,wDAAwD,EAEpG,OAAOA,CACT,CAEA,SAASK,GAAiB,CACxB,MAAO,CAA2BL,EAAgBM,IAAkB,CAClE,GAAI,CAACL,EAAOD,CAAI,EAAG,MAAM,IAAI,UAAU,mDAAmD,EAE1F,IAAIO,EAEJ,GAAIC,EAAoBF,CAAK,EAC3BC,EAAS,OAAO,OAAO,CAAE,KAAM,OAAQ,IAAKD,EAA2C,MAClF,CASL,GARAG,EAAkBH,EAAO,+BAA+B,EACxDI,EACEJ,EACAK,EAAYL,EAAO,+BAA+B,EAClD,CAAC,OAAQ,QAAQ,EACjB,+BAA+B,EAG7B,CAACM,EAAiBN,EAAM,IAAO,GAAK,OAAOA,EAAM,QAAc,WACjE,MAAM,IAAI,UAAU,mFAAmF,EAGzGC,EAAS,OAAO,OAAO,CACrB,KAAM,QACN,IAAKD,EAAM,KACX,OAAQA,EAAM,MACf,CAAA,CACH,CAEA,MAAMO,EAAU,OAAO,OAAO,EAAE,EAChC,OAAAf,EAAc,IAAIe,EAAS,OAAO,OAAO,CAAE,KAAAb,EAAM,OAAAO,CAAM,CAAE,CAAC,EAEnDM,CACT,CACF,CAEA,SAASC,EACPC,EACAC,EAAgD,CAEhD,MAAMC,EAAcD,EAAQ,IAAI,CAAC,CAACE,EAAKL,CAAO,IAAK,CACjD,GAAI,OAAOA,GAAY,UAAYA,IAAY,KAC7C,MAAM,IAAI,UAAU,oBAAoBK,CAAG,qCAAqC,EAGlF,MAAMC,EAAarB,EAAc,IAAIe,CAAO,EAE5C,GAAIM,IAAe,OAAW,MAAM,IAAI,UAAU,oBAAoBD,CAAG,qCAAqC,EAE9G,GAAI,CAACH,EAAc,IAAII,EAAW,OAAO,GAAG,EAC1C,MAAM,IAAI,UAAU,oBAAoBD,CAAG,uDAAuD,EAGpG,OAAO,OAAO,OAAO,CAAE,IAAAA,EAAK,KAAMC,EAAW,KAAM,OAAQA,EAAW,OAAQ,CAChF,CAAC,EAED,OAAO,OAAO,OAAOF,CAAW,CAClC,CAEA,SAASG,EAAiBC,EAAoCd,EAA4B,CACxF,OAAOe,EAAYD,EAAW,KAA8Bd,CAAM,CACpE,CAEA,SAASgB,EACPvB,EACAM,EACAY,EACAM,EAAmB,CAEnB,GAAI,EAAAA,GAAclB,IAAU,SAExB,CAACmB,EAAazB,EAA8BM,CAAK,EACnD,MAAM,IAAI,UAAU,2BAA2BY,CAAG,0BAA0BlB,EAAK,EAAE,GAAG,CAE1F"}
1
+ {"version":3,"file":"feature-port.js","sources":["../src/feature-port.ts"],"sourcesContent":["import type { Call, Port, PortRef } from '@opetope/core';\nimport type { PortIdentity, PortInput, PortOutput } from '@opetope/core/internal';\nimport { bindPortRef, isPort, isPortRefFor } from '@opetope/core/internal';\n\nimport type { FeatureOwnValues } from './feature-authoring-types';\nimport type {\n FeatureContribution,\n FeaturePipeBuilder,\n FeatureRegisterBuilder,\n FeatureSlotBuilder,\n} from './feature-contribution';\nimport type { ModuleCallRef } from './public-module';\n\ndeclare const optionalPortBrand: unique symbol;\ndeclare const portProviderBrand: unique symbol;\n\ntype AnyPort = PortIdentity;\n\n/**\n * `optional(port)` is the weak edge of a port: the application may have no provider, and a call without one\n * settles with `CallError` code `unavailable` instead of failing the consumer (D105).\n */\ninterface OptionalPort<PortType extends AnyPort> {\n readonly [optionalPortBrand]: readonly [PortType];\n}\n\ntype PortRequirementRecord = Readonly<Record<string, AnyPort | OptionalPort<AnyPort>>>;\ntype RequiredPortValues<Requires extends PortRequirementRecord> = {\n readonly [Key in keyof Requires]: Requires[Key] extends OptionalPort<infer PortType>\n ? PortRef<PortInput<PortType>, PortOutput<PortType>> | undefined\n : Requires[Key] extends AnyPort\n ? PortRef<PortInput<Requires[Key]>, PortOutput<Requires[Key]>>\n : never;\n};\n/** Every requirement is a `Call` of this feature: the application chooses the provider, the author calls the ref. */\ntype FeatureRequiredCalls<Id extends string, Requires extends PortRequirementRecord> = {\n readonly [Key in keyof Requires]: Requires[Key] extends OptionalPort<infer PortType>\n ? ModuleCallRef<PortInput<PortType>, PortOutput<PortType>, Id>\n : Requires[Key] extends AnyPort\n ? ModuleCallRef<PortInput<Requires[Key]>, PortOutput<Requires[Key]>, Id>\n : never;\n};\n\ninterface PortProviderBinding<PortType extends AnyPort, Id extends string> {\n readonly [portProviderBrand]: readonly [PortType, Id];\n}\n\ntype FeaturePortBuilder<Id extends string, Own> = <PortType extends AnyPort>(\n port: PortType,\n select: (context: { readonly own: Own }) => Call<PortInput<NoInfer<PortType>>, PortOutput<NoInfer<PortType>>>,\n) => PortProviderBinding<PortType, Id>;\n\ntype PortProviderTarget = (context: { readonly own: Readonly<Record<string, unknown>> }) => unknown;\n\ntype PortProviderDescriptor = {\n readonly key: string;\n readonly port: AnyPort;\n readonly target: PortProviderTarget;\n};\n/** One section for every extension point: `port` for ports, `slot`, `pipe` and `register` for contributions. */\ntype FeatureProvidesFactory<\n Id extends string,\n Context,\n Evaluation,\n Runtime extends Readonly<Record<string, object>>,\n Provided extends FeatureProvidedRecord<Id>,\n> = (context: {\n readonly pipe: FeaturePipeBuilder<Evaluation>;\n readonly port: FeaturePortBuilder<Id, FeatureOwnValues<Id, Runtime>>;\n readonly register: FeatureRegisterBuilder<Context, Evaluation>;\n readonly slot: FeatureSlotBuilder<Context, Evaluation>;\n}) => Provided;\n\ntype FeatureProvidedRecord<Id extends string> = Readonly<\n Record<string, FeatureContribution | PortProviderBinding<AnyPort, Id>>\n>;\n\nconst requiredPorts = new WeakMap<object, AnyPort>();\nconst optionalPorts = new WeakSet<object>();\nconst portProviders = new WeakMap<object, Readonly<{ port: AnyPort; target: PortProviderTarget }>>();\n\n/** An explicit wrapper marks the weak requirement; an authentic bare Port is the strong requirement. */\nfunction optionalPort<PortType extends AnyPort>(port: PortType): OptionalPort<PortType> {\n if (!isPort(port)) throw new TypeError('optional expects an authentic Port.');\n\n const requirement = Object.freeze({}) as OptionalPort<PortType>;\n requiredPorts.set(requirement, port);\n optionalPorts.add(requirement);\n\n return requirement;\n}\n\n/** Tolerant on purpose: the caller may hold any requirement declaration or none at all. */\nfunction isOptionalPortRequirement(requirement: unknown): boolean {\n return typeof requirement === 'object' && requirement !== null && optionalPorts.has(requirement);\n}\n\nfunction getRequiredPort(requirement: unknown): AnyPort {\n if (isPort(requirement)) return requirement;\n\n if (typeof requirement !== 'object' || requirement === null) {\n throw new TypeError('Feature requirement is not an authentic required Port.');\n }\n\n const port = requiredPorts.get(requirement);\n\n if (port === undefined) throw new TypeError('Feature requirement is not an authentic required Port.');\n\n return port;\n}\n\nfunction createPortBuilder<Id extends string, Own>(): FeaturePortBuilder<Id, Own> {\n return <PortType extends AnyPort>(port: PortType, value: unknown) => {\n if (!isPort(port)) throw new TypeError('provides.port takes a port created by definePort.');\n\n if (typeof value !== 'function') throw new TypeError('provides.port takes a selector of an owned Call.');\n\n const target = value as PortProviderTarget;\n\n const binding = Object.freeze({}) as PortProviderBinding<PortType, Id>;\n portProviders.set(binding, Object.freeze({ port, target }));\n\n return binding;\n };\n}\n\nfunction snapshotPortProviders(entries: readonly (readonly [string, unknown])[]): readonly PortProviderDescriptor[] {\n const descriptors = entries.map(([key, binding]) => {\n if (typeof binding !== 'object' || binding === null) {\n throw new TypeError(`Feature provider ${key} is not an authentic Port provider.`);\n }\n\n const definition = portProviders.get(binding);\n\n if (definition === undefined) throw new TypeError(`Feature provider ${key} is not an authentic Port provider.`);\n\n return Object.freeze({ key, port: definition.port, target: definition.target });\n });\n\n return Object.freeze(descriptors);\n}\n\nfunction bindProvidedPort(descriptor: PortProviderDescriptor, target: Call<never, unknown>): PortRef<never, unknown> {\n return bindPortRef(descriptor.port as Port<never, unknown>, target);\n}\n\nfunction assertRequiredPortValue(\n port: AnyPort,\n value: unknown,\n key: string,\n isOptional: boolean,\n): asserts value is PortRef<never, unknown> | undefined {\n if (isOptional && value === undefined) return;\n\n if (!isPortRefFor(port as Port<never, unknown>, value)) {\n throw new TypeError(`openFeature requirement ${key} must be a PortRef for ${port.id}.`);\n }\n}\n\nexport {\n assertRequiredPortValue,\n bindProvidedPort,\n createPortBuilder,\n getRequiredPort,\n isOptionalPortRequirement,\n optionalPort,\n snapshotPortProviders,\n};\nexport type {\n AnyPort,\n FeatureProvidedRecord,\n FeatureProvidesFactory,\n FeatureRequiredCalls,\n OptionalPort,\n PortProviderDescriptor,\n PortRequirementRecord,\n RequiredPortValues,\n};\n"],"names":["requiredPorts","optionalPorts","portProviders","optionalPort","port","isPort","requirement","isOptionalPortRequirement","getRequiredPort","createPortBuilder","value","target","binding","snapshotPortProviders","entries","descriptors","key","definition","bindProvidedPort","descriptor","bindPortRef","assertRequiredPortValue","isOptional","isPortRefFor"],"mappings":"mFA6EA,MAAMA,EAAgB,IAAI,QACpBC,EAAgB,IAAI,QACpBC,EAAgB,IAAI,QAG1B,SAASC,EAAuCC,EAAc,CAC5D,GAAI,CAACC,EAAOD,CAAI,EAAG,MAAM,IAAI,UAAU,qCAAqC,EAE5E,MAAME,EAAc,OAAO,OAAO,EAAE,EACpC,OAAAN,EAAc,IAAIM,EAAaF,CAAI,EACnCH,EAAc,IAAIK,CAAW,EAEtBA,CACT,CAGA,SAASC,EAA0BD,EAAoB,CACrD,OAAO,OAAOA,GAAgB,UAAYA,IAAgB,MAAQL,EAAc,IAAIK,CAAW,CACjG,CAEA,SAASE,EAAgBF,EAAoB,CAC3C,GAAID,EAAOC,CAAW,EAAG,OAAOA,EAEhC,GAAI,OAAOA,GAAgB,UAAYA,IAAgB,KACrD,MAAM,IAAI,UAAU,wDAAwD,EAG9E,MAAMF,EAAOJ,EAAc,IAAIM,CAAW,EAE1C,GAAIF,IAAS,OAAW,MAAM,IAAI,UAAU,wDAAwD,EAEpG,OAAOA,CACT,CAEA,SAASK,GAAiB,CACxB,MAAO,CAA2BL,EAAgBM,IAAkB,CAClE,GAAI,CAACL,EAAOD,CAAI,EAAG,MAAM,IAAI,UAAU,mDAAmD,EAE1F,GAAI,OAAOM,GAAU,WAAY,MAAM,IAAI,UAAU,kDAAkD,EAEvG,MAAMC,EAASD,EAETE,EAAU,OAAO,OAAO,EAAE,EAChC,OAAAV,EAAc,IAAIU,EAAS,OAAO,OAAO,CAAE,KAAAR,EAAM,OAAAO,CAAM,CAAE,CAAC,EAEnDC,CACT,CACF,CAEA,SAASC,EAAsBC,EAAgD,CAC7E,MAAMC,EAAcD,EAAQ,IAAI,CAAC,CAACE,EAAKJ,CAAO,IAAK,CACjD,GAAI,OAAOA,GAAY,UAAYA,IAAY,KAC7C,MAAM,IAAI,UAAU,oBAAoBI,CAAG,qCAAqC,EAGlF,MAAMC,EAAaf,EAAc,IAAIU,CAAO,EAE5C,GAAIK,IAAe,OAAW,MAAM,IAAI,UAAU,oBAAoBD,CAAG,qCAAqC,EAE9G,OAAO,OAAO,OAAO,CAAE,IAAAA,EAAK,KAAMC,EAAW,KAAM,OAAQA,EAAW,OAAQ,CAChF,CAAC,EAED,OAAO,OAAO,OAAOF,CAAW,CAClC,CAEA,SAASG,EAAiBC,EAAoCR,EAA4B,CACxF,OAAOS,EAAYD,EAAW,KAA8BR,CAAM,CACpE,CAEA,SAASU,EACPjB,EACAM,EACAM,EACAM,EAAmB,CAEnB,GAAI,EAAAA,GAAcZ,IAAU,SAExB,CAACa,EAAanB,EAA8BM,CAAK,EACnD,MAAM,IAAI,UAAU,2BAA2BM,CAAG,0BAA0BZ,EAAK,EAAE,GAAG,CAE1F"}
@@ -292,4 +292,4 @@ type ModuleScopeHandleState<Value> = {
292
292
  record: ModuleInstanceRecord | undefined;
293
293
  readonly runtime: ModuleScopeInstance<Value>;
294
294
  };
295
- export type { Awaitable, CleanupFailurePolicy, ErrorReporter, ExactInput, ExactNonUnionRuntime, InternalModuleRuntimeRecord, InternalModuleRuntimeRef, ModuleAttachmentCleanupFailureDiagnostic, ModuleAttachmentCloseContext, ModuleAttachmentOpenContext, ModuleAttachmentOptions, ModuleAttachmentRef, ModuleAttachmentRefIdentity, ModuleAttachmentRetirementFailure, ModuleCallBuilder, ModuleCallContext, ModuleCallOptions, ModuleCallRef, ModuleCallRefIdentity, ModuleCleanupErrorDiagnostic, ModuleCleanupFailureDiagnostic, ModuleDefinition, ModuleDefinitionOptions, ModuleDefinitionRecord, ModuleDrainOutcome, ModuleInstance, ModuleInstanceOptions, ModuleInstanceRecord, ModuleInstanceRef, ModuleParticipantRetirementFailure, ModuleRetirement, ModuleRetirementFailure, ModuleRetirementHandleState, ModuleRetirementParticipant, ModuleRetirementParticipantHandle, ModuleRetirementQuarantinedOutcome, ModuleRetirementRecord, ModuleRuntimeBuilder, ModuleRuntimeRecord, ModuleRuntimeRef, ModuleRuntimeRefIdentity, ModuleScopeBuilder, ModuleScopeDefinitionEntry, ModuleScopeDrainOutcome, ModuleScopeHandleState, ModuleScopeInstance, ModuleScopeNotReadyOutcome, ModuleScopeOpenOutcome, ModuleScopeRef, ModuleScopeRefIdentity, ModuleScopeRetirement, ModuleScopeRetirementFailure, ParticipantFenceFrontier, ParticipantHandleFrontier, RegisteredParticipant, RetirementFrontier, ScopeFenceFrontier, ScopeFrontier, };
295
+ export type { Awaitable, CleanupFailurePolicy, ErrorReporter, ExactInput, ExactNonUnionRuntime, InternalModuleRuntimeRecord, InternalModuleRuntimeRef, ModuleAttachmentCleanupFailureDiagnostic, ModuleAttachmentCloseContext, ModuleAttachmentOpenContext, ModuleAttachmentOptions, ModuleAttachmentRef, ModuleAttachmentRefIdentity, ModuleAttachmentRetirementFailure, ModuleCallBuilder, ModuleCallContext, ModuleCallOptions, ModuleCallRef, ModuleCleanupErrorDiagnostic, ModuleCleanupFailureDiagnostic, ModuleDefinition, ModuleDefinitionOptions, ModuleDefinitionRecord, ModuleDrainOutcome, ModuleInstance, ModuleInstanceOptions, ModuleInstanceRecord, ModuleInstanceRef, ModuleParticipantRetirementFailure, ModuleRetirement, ModuleRetirementFailure, ModuleRetirementHandleState, ModuleRetirementParticipant, ModuleRetirementParticipantHandle, ModuleRetirementQuarantinedOutcome, ModuleRetirementRecord, ModuleRuntimeBuilder, ModuleRuntimeRecord, ModuleRuntimeRef, ModuleRuntimeRefIdentity, ModuleScopeBuilder, ModuleScopeDefinitionEntry, ModuleScopeDrainOutcome, ModuleScopeHandleState, ModuleScopeInstance, ModuleScopeNotReadyOutcome, ModuleScopeOpenOutcome, ModuleScopeRef, ModuleScopeRefIdentity, ModuleScopeRetirement, ModuleScopeRetirementFailure, ParticipantFenceFrontier, ParticipantHandleFrontier, RegisteredParticipant, RetirementFrontier, ScopeFenceFrontier, ScopeFrontier, };
@@ -1,4 +1,4 @@
1
1
  export { defineModule, getModuleScopeEntries, getModuleTemplateIR } from './public-module-definition.js';
2
2
  export { instantiateModule, registerModuleRetirementParticipant, resolveModuleCallTarget, } from './public-module-instance.js';
3
3
  export { projectModuleCleanupFailures } from './public-module-retirement-diagnostics.js';
4
- export type { CleanupFailurePolicy, ErrorReporter, ExactNonUnionRuntime, ModuleAttachmentCleanupFailureDiagnostic, ModuleAttachmentCloseContext, ModuleAttachmentOpenContext, ModuleAttachmentOptions, ModuleAttachmentRef, ModuleAttachmentRefIdentity, ModuleCallContext, ModuleCallRef, ModuleCallRefIdentity, ModuleCleanupErrorDiagnostic, ModuleCleanupFailureDiagnostic, ModuleDefinition, ModuleDefinitionOptions, ModuleDrainOutcome, ModuleInstance, ModuleInstanceOptions, ModuleInstanceRef, ModuleParticipantRetirementFailure, ModuleRetirement, ModuleRetirementFailure, ModuleRetirementParticipant, ModuleRetirementParticipantHandle, ModuleRetirementQuarantinedOutcome, ModuleRuntimeBuilder, ModuleRuntimeRecord, ModuleRuntimeRef, ModuleRuntimeRefIdentity, ModuleScopeDrainOutcome, ModuleScopeInstance, ModuleScopeOpenOutcome, ModuleScopeRef, ModuleScopeRetirement, ModuleScopeRetirementFailure, } from './public-module-types.js';
4
+ export type { CleanupFailurePolicy, ErrorReporter, ExactNonUnionRuntime, ModuleAttachmentCleanupFailureDiagnostic, ModuleAttachmentCloseContext, ModuleAttachmentOpenContext, ModuleAttachmentOptions, ModuleAttachmentRef, ModuleAttachmentRefIdentity, ModuleCallContext, ModuleCallRef, ModuleCleanupErrorDiagnostic, ModuleCleanupFailureDiagnostic, ModuleDefinition, ModuleDefinitionOptions, ModuleDrainOutcome, ModuleInstance, ModuleInstanceOptions, ModuleInstanceRef, ModuleParticipantRetirementFailure, ModuleRetirement, ModuleRetirementFailure, ModuleRetirementParticipant, ModuleRetirementParticipantHandle, ModuleRetirementQuarantinedOutcome, ModuleRuntimeBuilder, ModuleRuntimeRecord, ModuleRuntimeRef, ModuleRuntimeRefIdentity, ModuleScopeDrainOutcome, ModuleScopeInstance, ModuleScopeOpenOutcome, ModuleScopeRef, ModuleScopeRetirement, ModuleScopeRetirementFailure, } from './public-module-types.js';
@@ -44,10 +44,17 @@ A feature whose implementation is heavy is written as a header and a body: the h
44
44
  the body loads when an instance opens (D186, D207). `defineFeature.preload(feature)` fetches that code early without
45
45
  opening anything; on a feature with no body it is a successful no-op (D208).
46
46
 
47
- Declaration stages (`own` and outer `provides`) see refs. Instance stages (`exports`, nested contribution
48
- factories and model factories) receive materialized values. Compose model dependencies with a readonly map of
47
+ The `own` builder sees refs for imports and requirements. The outer `provides` receives only `{ port, register, slot, pipe }`.
48
+ Instance stages (`exports`, port selectors, contribution factories and model factories) receive materialized values.
49
+ Use a nested factory's `own` to register a private
50
+ model call: `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))`; no export is required.
51
+ `register` receives `{ own, imports }`; `slot` receives `{ own, imports, model }`, with `model` declaring a
52
+ per-mount UI model. Their `own` belongs to that exact instance. Keep `exports` for the API other features import;
53
+ contribution contexts have no `exports` or `instance` field (D255).
54
+ Compose model dependencies with a readonly map of
49
55
  current-feature imports and call refs, including `requires.x`; optional imports keep their lookup projection.
50
- Use `port(Port, { from: own.model, select: value => value.call })` for a model's call. `calls(imports.x, keys)`
56
+ Use `port(Port, ({ own }) => own.model.call)` or `port(Port, ({ own }) => own.call)`; the selector receives only
57
+ `{ own }` and runs once during instance preparation, before readiness and publication (D255). `calls(imports.x, keys)`
51
58
  accepts host methods, exported calls of a hard feature import, and exported calls over `optional(feature)`, where
52
59
  they answer `CallError` `unavailable` while nothing provides them. Read the data of a weak edge with
53
60
  `fromOptional(source, select, { missing })` instead of unfolding `lookup.kind` by hand (D187). For resources and streams, `target` selects a
@@ -61,7 +68,7 @@ destructuring and custom context types together; factory `call`, `calls` and the
61
68
  The nested invocation inherits cancellation, authority and the lane stack; it does not choose a new policy.
62
69
 
63
70
  A contribution's `when` is either a `Readable<boolean>` or a pure, synchronous predicate of the instance —
64
- `({ exports, imports, own, read }) => boolean`. Its `read` only records what the answer depends on: do not write,
71
+ `({ own, imports, read }) => boolean`. Its `read` only records what the answer depends on: do not write,
65
72
  call or await inside it, and expect it to run again whenever a source it read changes. In that context `own` is
66
73
  materialized, so a model field is a `Readable` and not the declaration ref (D220). A `pipe` declares its handler as
67
74
  a descriptor with one key — `pipe(target, { fold: (value, meta, context) => next })` — whose `context` is that same
@@ -41,10 +41,17 @@ policy?, within })`; данные по ключу → `resource`; поток →
41
41
  загружается при открытии экземпляра (D186, D207). `defineFeature.preload(feature)` подтягивает этот код заранее,
42
42
  ничего не открывая; для фичи без тела это успешный no-op (D208).
43
43
 
44
- Стадии декларации (`own` и внешняя `provides`) видят refs. Стадии экземпляра (`exports`, вложенные фабрики
45
- вкладов и фабрики моделей) получают материализованные значения. Зависимости модели собираются readonly-записью
44
+ Builder `own` видит refs импортов и требований. Внешняя `provides` получает только `{ port, register, slot, pipe }`.
45
+ Стадии экземпляра (`exports`, селекторы портов, фабрики вкладов и моделей) получают материализованные значения.
46
+ Для регистрации вызова внутренней модели
47
+ берите `own` вложенной фабрики: `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))`;
48
+ экспорт не нужен. `register` получает `{ own, imports }`, а `slot` — `{ own, imports, model }`, где `model`
49
+ объявляет UI-модель на каждое монтирование. Их `own` принадлежит конкретному экземпляру. Оставляйте `exports`
50
+ для API, который импортируют другие фичи; в контекстах вкладов нет полей `exports` и `instance` (D255).
51
+ Зависимости модели собираются readonly-записью
46
52
  импортов и call refs текущей фичи, включая `requires.x`; optional-импорты сохраняют lookup-проекцию.
47
- Для вызова модели используется `port(Port, { from: own.model, select: value => value.call })`.
53
+ Используйте `port(Port, ({ own }) => own.model.call)` или `port(Port, ({ own }) => own.call)`; селектор получает
54
+ только `{ own }` и выполняется один раз при подготовке экземпляра, до readiness и публикации (D255).
48
55
  `calls(imports.x, keys)` принимает методы хоста, экспортированные вызовы жёсткого импорта фичи и экспортированные
49
56
  вызовы поверх `optional(feature)` — там они отвечают `CallError` `unavailable`, пока провайдера нет. Данные слабого
50
57
  ребра читайте через `fromOptional(source, select, { missing })`, а не разворачивая `lookup.kind` руками (D187). У ресурсов
@@ -58,7 +65,7 @@ policy?, within })`; данные по ключу → `resource`; поток →
58
65
  Вложенный вызов наследует отмену, авторитет и стек lane; новую политику он не выбирает.
59
66
 
60
67
  `when` у вклада это либо `Readable<boolean>`, либо чистый синхронный предикат экземпляра —
61
- `({ exports, imports, own, read }) => boolean`. Его `read` только записывает, от чего зависит ответ: внутри нельзя
68
+ `({ own, imports, read }) => boolean`. Его `read` только записывает, от чего зависит ответ: внутри нельзя
62
69
  писать, вызывать и ждать, а сам предикат выполнится снова при смене любого прочитанного источника. В этом контексте
63
70
  `own` материализован: поле модели это `Readable`, а не ref декларации (D220). `pipe` объявляет обработчик
64
71
  дескриптором с одним ключом — `pipe(target, { fold: (value, meta, context) => next })`, — где `context` это тот же
package/docs/cookbook.md CHANGED
@@ -20,7 +20,7 @@ const feature = defineFeature({
20
20
  requires: { resolve: resolveItemPort },
21
21
  own: ({ imports, requires, calls, attach, call, lane, effect, event, resource, stream, scope, model }) => ({ … }),
22
22
  exports: ({ own }) => ({ x: own.x }), // Call | Readable | Resource out of own; a whole model is not allowed
23
- provides: ({ slot, pipe, register, port, own }) => ({ … }),
23
+ provides: ({ slot, pipe, register, port }) => ({ … }),
24
24
  });
25
25
  ```
26
26
 
@@ -46,7 +46,7 @@ const catalogResolveFeature = defineFeature({
46
46
  id: catalogResolveFeatureId,
47
47
  imports: { platform: catalogResolveSource },
48
48
  own: ({ calls, imports }) => ({ ...calls(imports.platform, ['resolveItem']) }), // host methods as Call
49
- provides: ({ own, port }) => ({ catalog: port(resolveItemPort, own.resolveItem) }),
49
+ provides: ({ port }) => ({ catalog: port(resolveItemPort, ({ own }) => own.resolveItem) }),
50
50
  });
51
51
  ```
52
52
 
@@ -180,15 +180,16 @@ parameters are supported. A single parameter is always the input: for a signal-o
180
180
  with `context.call({ run: (_input: void, { signal }) => deps.load(signal) })` or use an explicit adapter.
181
181
 
182
182
  A model can compose a host import and a required call without receiving the feature context. Its factory declares
183
- its own dependency interface. A model call may also implement a port; the projection runs once before publication
184
- and the provider's lifetime fences the selected call even when it is passed through from a dependency (D169).
183
+ its own dependency interface. A model call may also implement a port; the selector receives `{ own }` and runs once
184
+ during instance preparation, before readiness and publication. The provider's lifetime fences the selected call
185
+ even when it is passed through from a dependency (D169, D255).
185
186
 
186
187
  ```ts
187
188
  own: ({ imports, model, requires }) => ({
188
189
  order: model(OrderModel, { platform: imports.platform, lookup: requires.lookup }, createOrderModel),
189
190
  }),
190
- provides: ({ own, port }) => ({
191
- submit: port(SubmitPort, { from: own.order, select: order => order.submit }),
191
+ provides: ({ port }) => ({
192
+ submit: port(SubmitPort, ({ own }) => own.order.submit),
192
193
  }),
193
194
  ```
194
195
 
@@ -624,7 +625,7 @@ const paymentMethods = defineRegistry<string, PaymentMethodEntry>({ id: 'checkou
624
625
 
625
626
  // feature.ts — the entry is a contribution, so it opens and closes with the instance that made it
626
627
  provides: ({ register }) => ({
627
- card: register(paymentMethods, ({ exports }) => ({ key: 'card', value: { submit: exports.submit, title: 'Card' } })),
628
+ card: register(paymentMethods, ({ own }) => ({ key: 'card', value: { submit: own.order.submit, title: 'Card' } })),
628
629
  }),
629
630
 
630
631
  // UI reads the target, never the provider's module
@@ -632,6 +633,7 @@ const method = useReadable(paymentMethods.select(selectedKey));
632
633
  const methods = useReadable(paymentMethods.list);
633
634
  ```
634
635
 
636
+ `own.order` is the ready model of this feature; registering its `submit` Call requires no public export (D255).
635
637
  The publication is atomic and owned: the entry lands when the instance is ready, the fence withdraws it on close, a
636
638
  duplicate key is refused before anything moves, the order is `priority` and then id, and the inspector lists the
637
639
  entry with the feature that owns it (D127, D196). `select(key)` answers the same `Lookup` as a weak edge, so an
@@ -19,7 +19,7 @@ const feature = defineFeature({
19
19
  requires: { resolve: resolveItemPort },
20
20
  own: ({ imports, requires, calls, attach, call, lane, effect, event, resource, stream, scope, model }) => ({ … }),
21
21
  exports: ({ own }) => ({ x: own.x }), // Call | Readable | Resource из own; модель целиком — нельзя
22
- provides: ({ slot, pipe, register, port, own }) => ({ … }),
22
+ provides: ({ slot, pipe, register, port }) => ({ … }),
23
23
  });
24
24
  ```
25
25
 
@@ -44,7 +44,7 @@ const catalogResolveFeature = defineFeature({
44
44
  id: catalogResolveFeatureId,
45
45
  imports: { platform: catalogResolveSource },
46
46
  own: ({ calls, imports }) => ({ ...calls(imports.platform, ['resolveItem']) }), // методы хоста как Call
47
- provides: ({ own, port }) => ({ catalog: port(resolveItemPort, own.resolveItem) }),
47
+ provides: ({ port }) => ({ catalog: port(resolveItemPort, ({ own }) => own.resolveItem) }),
48
48
  });
49
49
  ```
50
50
 
@@ -179,15 +179,16 @@ exports: ({ own }) => ({ tasks: own.tasks }), // Resource наружу; вла
179
179
  `context.call({ run: (_input: void, { signal }) => deps.load(signal) })` или используйте явный адаптер.
180
180
 
181
181
  Модель может соединить импорт хоста и требуемый вызов, не получая контекст фичи. Фабрика объявляет свой интерфейс
182
- зависимостей. Вызов модели также может реализовать порт; проекция выполняется один раз до публикации, а время
183
- жизни провайдера ограничивает выбранный вызов, даже переданный напрямую из зависимости (D169).
182
+ зависимостей. Вызов модели также может реализовать порт; селектор получает `{ own }` и выполняется один раз при
183
+ подготовке экземпляра, до readiness и публикации. Время жизни провайдера ограничивает выбранный вызов, даже
184
+ переданный напрямую из зависимости (D169, D255).
184
185
 
185
186
  ```ts
186
187
  own: ({ imports, model, requires }) => ({
187
188
  order: model(OrderModel, { platform: imports.platform, lookup: requires.lookup }, createOrderModel),
188
189
  }),
189
- provides: ({ own, port }) => ({
190
- submit: port(SubmitPort, { from: own.order, select: order => order.submit }),
190
+ provides: ({ port }) => ({
191
+ submit: port(SubmitPort, ({ own }) => own.order.submit),
191
192
  }),
192
193
  ```
193
194
 
@@ -620,7 +621,7 @@ const paymentMethods = defineRegistry<string, PaymentMethodEntry>({ id: 'checkou
620
621
 
621
622
  // feature.ts — запись это вклад, поэтому она открывается и закрывается с экземпляром, который её сделал
622
623
  provides: ({ register }) => ({
623
- card: register(paymentMethods, ({ exports }) => ({ key: 'card', value: { submit: exports.submit, title: 'Card' } })),
624
+ card: register(paymentMethods, ({ own }) => ({ key: 'card', value: { submit: own.order.submit, title: 'Card' } })),
624
625
  }),
625
626
 
626
627
  // UI читает цель, а не модуль провайдера
@@ -628,6 +629,7 @@ const method = useReadable(paymentMethods.select(selectedKey));
628
629
  const methods = useReadable(paymentMethods.list);
629
630
  ```
630
631
 
632
+ `own.order` — готовая модель этой фичи; регистрация её Call `submit` не требует публичного экспорта (D255).
631
633
  Публикация атомарна и принадлежит владельцу: запись ложится, когда экземпляр готов, fence снимает её на закрытии,
632
634
  дубль ключа отвергается до того, как что-то сдвинулось, порядок это `priority`, затем id, а инспектор показывает
633
635
  запись вместе с фичей-владельцем (D127, D196). `select(key)` отвечает тем же `Lookup`, что и слабое ребро, поэтому
package/docs/decisions.md CHANGED
@@ -1435,3 +1435,17 @@ Schema 11 заменяет смешанный метод [D252](#d252): для
1435
1435
  ## D254 — npm поставляет минифицированный ESM с исходными картами
1436
1436
 
1437
1437
  Пакеты собирают JavaScript с `minify: true` и `keepNames: false`; декларации `.d.ts` сохраняются отдельно, source maps включают исходный TypeScript в `sourcesContent`. Значения `fn.name` и `constructor.name`, полученные через рефлексию, не входят в стабильный API. Явные ID деклараций, публичные `error.name`/`error.code` и проверки `instanceof` с экспортируемыми классами ошибок сохраняют контракт и проверяются на реальных tarball-потребителях. Source-map-aware инструменты позволяют найти исходный TypeScript; карты раскрывают реализацию, а не скрывают её. Политика уменьшает несжатый JavaScript, не обещая уменьшения tarball или итогового бандла хоста: эти размеры измеряются отдельно.
1438
+
1439
+ <a id="d255"></a>
1440
+
1441
+ ## D255 — `provides` объявляет цели, вложенные колбэки выбирают готовый `own`
1442
+
1443
+ Внешняя `provides` получает ровно `{ port, register, slot, pipe }`: только builders, без `own` или других значений экземпляра. Цели, ids и приоритеты фиксируются синхронно при объявлении и известны компилятору до открытия. `port(Port, ({ own }) => own.model.call)` и `port(Port, ({ own }) => own.call)` используют единственную форму селектора: он получает только `{ own }` и один раз выбирает подлинный Call текущего экземпляра при подготовке, после создания моделей, до readiness и публикации вкладов. Прямые refs и дескрипторы `{ from, select }` больше не принимаются. Выбранный вызов сохраняет фенс провайдера, включая переданный напрямую вызов зависимости.
1444
+
1445
+ Результат селектора должен быть подлинным Call из прочитанных им значений `own`, включая поля моделей. Подлинный, но чужой Call, обычная функция и другое значение отвергаются. Чтение для этой проверки остаётся ленивым: соседние lifecycle-only поля не раскрываются. Актуальность экземпляра проверяется до и после пользовательского селектора; синхронный retirement внутри него не допускает следующие селекторы, а уже владеемая работа дренируется.
1446
+
1447
+ Вложенная фабрика `register` получает `{ own, imports }`, фабрика `slot` — `{ own, imports, model }`, а предикат `when` и обработчик `pipe.fold` — `{ own, imports, read }`. Это точные поверхности без aliases: `exports` и `instance` удалены из контекстов вкладов, `model` доступен только фабрике `slot`, где объявляет UI-модель на каждое монтирование. `exports` остаётся секцией публичного API фичи для её потребителей; внутренней композиции доступны `own` и объявленные `imports`.
1448
+
1449
+ Все вложенные контексты получают тот же материализованный `own`, что секция `exports`: модель становится записью её полей, call ref — готовым `Call`, resource/stream ref — ресурсом. Регистрация вызова внутренней модели не требует публикации этого вызова в `exports`. Модели создаются до селекторов портов и фабрик вкладов, каждый экземпляр получает свои значения, запись раскрывает поля лениво, а lifetime вызовов и read-only проекция owned state сохраняются. Builder секции `own` по-прежнему использует refs импортов и требований для декларации зависимостей.
1450
+
1451
+ Миграция меняет исходный код: внешний `own` убирается из `provides`, выбор вызова переносится во второй аргумент `port` как `({ own }) => own.call` или `({ own }) => own.model.call`. Вместо `context.exports.submit` выбирается исходный `context.own.order.submit`; только внутренние экспорты удаляются из секции `exports`. Вместо `context.instance` с внутренним bind/resolve выбирается готовое значение из `own`. План модели монтирования объявляется через `model` фабрики `slot`. Уточняет [D88](#d88), [D169](#d169), [D220](#d220), [D223](#d223).
@@ -104,8 +104,11 @@ to these nodes:
104
104
 
105
105
  `exports` is a factory of the form `({ own }) => …`. It runs when the instance opens, against a live `own`, and every
106
106
  field of the record it returns must be a live value form — a `Call`, a `Readable` or a `Resource` — checked key by key
107
- (D88). `provides` runs once, synchronously, and is split into ports and contributions by the builder that produced each
108
- entry. Every attachment of a feature is critical: the instance is not ready until all of them are.
107
+ (D88). `provides` runs once, synchronously, with exactly `{ port, register, slot, pipe }`, and is split into ports and
108
+ contributions by the builder that produced each entry. Its outer context contains no `own` or instance values.
109
+ Every target remains known to the compiler before opening; a `port` selector is recorded for preparation, while
110
+ contribution values are recorded for publication (D255). Every attachment of a feature is critical: the instance is
111
+ not ready until all of them are.
109
112
 
110
113
  ### 2.2 Opening an instance
111
114
 
@@ -181,8 +184,13 @@ that.
181
184
 
182
185
  The dependency descriptor is a map of authentic current-feature import and call refs. Materialization resolves
183
186
  it once to a readonly map before calling the factory; optional imports stay lookup readables. The zero-dependency
184
- form is `model(Decl, factory)`. Model-to-model refs are not a dependency graph. A model-backed port selects its
185
- call once after model creation and wraps even a passthrough call with the provider instance's fence (D169).
187
+ form is `model(Decl, factory)`. Model-to-model refs are not a dependency graph. `port(Port, ({ own }) => own.model.call)`
188
+ or `port(Port, ({ own }) => own.call)` receives only `{ own }` and selects an authentic Call of the current instance
189
+ once during preparation, after model creation and before readiness or contribution publication. The binding wraps
190
+ even a passthrough call with the provider instance's fence (D169, D255).
191
+ The result must be a Call exposed by the `own` values the selector accessed. Reading only those values keeps
192
+ lifecycle-only entries lazy. The runtime checks currentness before and after each selector; retirement inside one
193
+ skips later selectors and drains the work already owned.
186
194
 
187
195
  The kernel owns rollback before entering user code, so a factory that creates nodes and then throws still drains
188
196
  them. The same rule holds for per-mount UI models. A fence synchronously closes every owned state and rejects
@@ -375,6 +383,13 @@ closing waits for the disposer and for the queue to drain.
375
383
  | 5 | **after `ready`** the factories run with the instance context, the value is marked with its owner, and `when` is resolved: a predicate becomes one computed readable of that instance |
376
384
  | 6 | every affected target is updated **in one transaction**, and listeners are called afterwards |
377
385
 
386
+ The factories receive only the capabilities needed by their target: `register` gets `{ own, imports }`, while
387
+ `slot` gets `{ own, imports, model }` for declaring per-mount UI models. The `own` record is the same lazy
388
+ materialization used by `exports`: model records, Calls and resources belong to the concrete instance, and
389
+ unselected lifecycle-only keys do not prevent access to them. A factory can therefore register its own model's Call without
390
+ making it part of the feature's public exports. `when` and `pipe.fold` use `{ own, imports, read }`; these
391
+ contribution contexts carry no `exports` or instance handle (D255).
392
+
378
393
  Entry order: `priority` ascending, ties broken by id. `when` (a `Readable<boolean>`, or a predicate of the instance
379
394
  that the runtime lowers to one): an entry whose value is `false`
380
395
  is not part of `entries`, so `Slot`, `fold`, `select` and emptiness checks never see it; a flip publishes in one
@@ -402,7 +417,7 @@ include both the entry list and everything the handlers read through their `{ re
402
417
  their sources change rather than only when contributions are re-registered. Without a reader the reads are untracked.
403
418
 
404
419
  A handler is declared as a descriptor, `pipe(target, { fold })`, and the runtime binds one evaluation context to it
405
- when the contribution publishes: `exports`, `imports` and the materialized `own` are resolved once and the reader is
420
+ when the contribution publishes: `imports` and the materialized `own` are resolved once and the reader is
406
421
  the only thing that changes from call to call, so folding over many handlers allocates no per-handler context
407
422
  (D223). A `fold` is the only thing that runs a handler: declaring, preloading and publishing do not.
408
423
 
@@ -99,8 +99,10 @@ Reconciler группы это один сериализованный worker с
99
99
  | `requires.x` | один call на скрытом attachment-е требований, который пересылает в провайдера порта; у `optional(port)` он оседает `CallError` `unavailable`, пока провайдера нет (D105) |
100
100
  | `model(...)` | **не узел kernel-а**, а описатель данных; фабрика модели здесь не выполняется; владеемое состояние живёт только в модели (`ctx.state`, D139) |
101
101
 
102
- `exports` проверяется по форме каждого поля (`Call`, `Readable` или `Resource`), идентичность с `own` намеренно не проверяется; фасад экспортов вычисляется при открытии экземпляра с живым `own` (D88). `provides` выполняется один раз, синхронно, и делится на порты и вклады по тому,
103
- какой builder породил запись. Каждый attachment фичи критичен: экземпляр не готов, пока не готовы все.
102
+ `exports` проверяется по форме каждого поля (`Call`, `Readable` или `Resource`), идентичность с `own` намеренно не проверяется; фасад экспортов вычисляется при открытии экземпляра с живым `own` (D88). `provides` выполняется один раз, синхронно, с ровно `{ port, register, slot, pipe }` и делится на порты и вклады по тому,
103
+ какой builder породил запись. Во внешнем контексте нет `own` или значений экземпляра. Все цели остаются известны
104
+ компилятору до открытия: селектор `port` записывается для подготовки экземпляра, значения вкладов — для публикации
105
+ (D255). Каждый attachment фичи критичен: экземпляр не готов, пока не готовы все.
104
106
 
105
107
  ### 2.2 Открытие экземпляра
106
108
 
@@ -174,8 +176,13 @@ Retire идемпотентен и не может быть запущен из
174
176
 
175
177
  Описатель зависимостей — запись подлинных импортов и call refs текущей фичи. Материализация один раз разрешает
176
178
  её в readonly-запись перед фабрикой; optional-импорты остаются lookup-readable. Форма без зависимостей —
177
- `model(Decl, factory)`. Refs моделей не образуют граф зависимостей. Порт из модели один раз выбирает вызов после
178
- создания модели и ограничивает фенсом экземпляра-провайдера даже переданный напрямую вызов (D169).
179
+ `model(Decl, factory)`. Refs моделей не образуют граф зависимостей. `port(Port, ({ own }) => own.model.call)` или
180
+ `port(Port, ({ own }) => own.call)` получает только `{ own }` и один раз выбирает подлинный Call текущего экземпляра
181
+ при подготовке, после создания моделей и до readiness или публикации вкладов. Привязка оборачивает даже переданный
182
+ напрямую вызов фенсом экземпляра-провайдера (D169, D255).
183
+ Результат должен быть Call из значений `own`, к которым обратился селектор. Чтение только выбранных значений
184
+ сохраняет ленивость полей для lifecycle. Runtime проверяет актуальность до и после каждого селектора; retirement
185
+ внутри одного из них пропускает последующие селекторы и дренирует уже владеемую работу.
179
186
 
180
187
  Kernel получает владельца отката до входа в пользовательский код, поэтому узлы фабрики дренируются, даже если
181
188
  она после их создания бросила. То же правило держат per-mount UI-модели. Фенс синхронно закрывает каждое
@@ -365,6 +372,13 @@ disposer и слив очереди.
365
372
  | 5 | **после `ready`** фабрики выполняются с контекстом экземпляра, значение помечается владельцем, `when` резолвится: предикат становится одним computed-readable этого экземпляра |
366
373
  | 6 | все затронутые цели обновляются **в одной транзакции**, слушатели зовутся после |
367
374
 
375
+ Фабрики получают только возможности, нужные их цели: `register` — `{ own, imports }`, а `slot` —
376
+ `{ own, imports, model }` для объявления UI-моделей на каждое монтирование. Запись `own` использует ту же ленивую
377
+ материализацию, что `exports`: записи моделей, Calls и ресурсы принадлежат конкретному экземпляру, а невыбранные
378
+ поля только для lifecycle не мешают обращаться к ним. Поэтому фабрика может зарегистрировать Call своей модели без его
379
+ включения в публичные экспорты фичи. `when` и `pipe.fold` используют `{ own, imports, read }`; в этих контекстах
380
+ вкладов нет `exports` или handle экземпляра (D255).
381
+
368
382
  Порядок записей: `priority` по возрастанию, при равенстве по id. `when` (`Readable<boolean>` либо предикат
369
383
  экземпляра, который рантайм понижает в такой readable): запись с `false` не
370
384
  входит в `entries`, поэтому `Slot`, `fold`, `select` и проверки пустоты её не видят; переключение публикует одной
@@ -391,7 +405,7 @@ disposer и слив очереди.
391
405
  при смене их источников, а не только при перерегистрации вкладов. Без читателя чтение нетрекаемое.
392
406
 
393
407
  Обработчик объявляется дескриптором, `pipe(target, { fold })`, и рантайм связывает с ним один контекст вычисления в
394
- момент публикации вклада: `exports`, `imports` и материализованный `own` разрешаются один раз, от вызова к вызову
408
+ момент публикации вклада: `imports` и материализованный `own` разрешаются один раз, от вызова к вызову
395
409
  меняется только читатель, поэтому свёртка по многим обработчикам не аллоцирует контекст на обработчик (D223).
396
410
  Обработчик запускает только свёртка: объявление, предзагрузка и публикация его не вызывают.
397
411
 
package/docs/spec.md CHANGED
@@ -57,8 +57,8 @@ const catalogCatalogFeature = defineFeature({
57
57
  ...calls(imports.platform, ['resolveItem']), // host methods as Calls, one to one
58
58
  }),
59
59
  exports: ({ own }) => ({ resolveItem: own.resolveItem }), // for features that import this definition
60
- provides: ({ port, own }) => ({
61
- resolve: port(resolveItemPort, own.resolveItem), // the port for whoever requires it
60
+ provides: ({ port }) => ({
61
+ resolve: port(resolveItemPort, ({ own }) => own.resolveItem), // the port for whoever requires it
62
62
  }),
63
63
  });
64
64
  ```
@@ -105,17 +105,18 @@ const createFormFeature = defineFeature({
105
105
  order: model(OrderModel, { platform: imports.platform }, (ctx, { platform }) => createOrderModel(ctx, platform)),
106
106
  lookup: requires.resolveItem, // inside `own` a port stays a ref
107
107
  }),
108
- // here `own` is already materialized: both the port and the model field arrived as live `Call`s
109
- exports: ({ own }) => ({ lookup: own.lookup, submit: own.order.submit }),
108
+ // consumers get the public lookup Call; submit stays inside this feature
109
+ exports: ({ own }) => ({ lookup: own.lookup }),
110
110
  provides: ({ slot, pipe }) => ({
111
111
  // a contribution is a value, an instance factory or a pipe descriptor; `priority` and `when` go in the options
112
- content: slot(orderFormContentSlot, ({ exports, model }) => ({
112
+ content: slot(orderFormContentSlot, ({ own, model }) => ({
113
113
  Component: CreateForm,
114
114
  // the contribution's UI model: created per mount, and it sees the `props` of exactly that mount
115
115
  models: [
116
116
  model(OrderActions, (ctx, props: Readable<OrderFormProps>) => ({
117
117
  submit: ctx.call({
118
- run: (amount: number, { invoke }) => invoke(exports.submit, { amount, itemId: props.getSnapshot().itemId }),
118
+ run: (amount: number, { invoke }) =>
119
+ invoke(own.order.submit, { amount, itemId: props.getSnapshot().itemId }),
119
120
  }),
120
121
  })),
121
122
  ],
@@ -181,7 +182,7 @@ publication barrier. All of it belongs to the runtime and its integration layer.
181
182
  | `id` | What the feature is called | `'feature.entity'`, dots only, no suffixes |
182
183
  | `imports` | What the outside gives me | a record of `defineHostContract` contracts and other feature definitions; `optional(x)` is a weak edge, `onDemand(hostContract)` defers binding to the first use |
183
184
  | `requires` | Which port I need | a record of `port` or `optional(port)`; each `requires.x` is a ref, and the `Call` appears on the instance |
184
- | ref vs Call | What is visible at which stage | `own` and the outer `provides` factory see refs; `exports` and nested contribution factories see materialized instance values |
185
+ | ref vs Call | What is visible at which stage | the `own` builder sees dependency refs; `provides` sees only builders; `exports`, port selectors and contribution factories see materialized instance values |
185
186
  | `own` | What I own | builder: `model`, `call`, `calls`, `lane`, `effect`, `event`, `resource`, `stream`, `scope`, `attach` |
186
187
  | `exports` | What I hand to features that import me | `({ own }) => a record of Call, Readable and Resource` computed when the instance opens |
187
188
  | `provides` | What I offer to extension points | `({ port, slot, pipe, register, own }) => ...` |
@@ -269,14 +270,18 @@ the second factory argument is a readonly map of resolved values with inferred t
269
270
  source, or `model(Decl, create)` for none. Only authentic imports and call refs of the current feature are accepted;
270
271
  optional imports retain `Readable<Lookup<…>>`. Raw host objects, port declarations, foreign refs and model refs do
271
272
  not form dependency entries. A named factory may declare its own dependency interface without seeing the feature
272
- context. `provides.port(SubmitPort, { from: own.order, select: order => order.submit })` selects an authentic call
273
- once from the materialized model before publication and adds the provider's lifetime fence, including passthrough
274
- calls. A local call ref still uses `port(SubmitPort, own.submit)` (D169).
273
+ context. `port(SubmitPort, ({ own }) => own.order.submit)` selects an authentic call from the current instance's
274
+ `own` once during preparation, before readiness and contribution publication, and adds the provider's lifetime
275
+ fence, including passthrough calls. `port(SubmitPort, ({ own }) => own.submit)` selects a call declared directly in
276
+ `own`. The selector receives only `{ own }`; its result must be an authentic Call exposed by a value it selects
277
+ from this `own`, including a model field. Foreign Calls and plain functions are rejected. Direct refs and
278
+ `{ from, select }` descriptors are not accepted. Retirement during a selector prevents later selectors from running
279
+ and drains the work already owned (D255).
275
280
 
276
281
  A contribution has two kinds of model, both declared with the same `defineModel`. `own` models live with the feature
277
282
  instance and are served to a mount automatically. A contribution's UI model lives with the mount: it is listed in the
278
283
  contribution's `models`, its factory receives the `Readable` of that mount's props as its second argument and either
279
- takes fields as they are (`submit: exports.submit`) or wraps them in `ctx.call` when the UI contract differs in
284
+ takes fields as they are (`submit: own.order.submit`) or wraps them in `ctx.call` when the UI contract differs in
280
285
  input, in result shape or in the number of feature calls. There is no separate View concept.
281
286
 
282
287
  ### 2.3 UI
@@ -437,17 +442,26 @@ remain. Resource retry and feature-demand retry keep their own meaning. The form
437
442
  `event` the source and the carrier of type inference are positional: `resource(from, target, {…})`,
438
443
  `stream(from, target, {…})`, `event(from, subscribe, {…})`; the key order inside the options object is free. One
439
444
  option carries the same word for both: a stream requires `backpressure: latest()` and an `event` admits it. The
440
- methods of `provides`: `port`, `slot`, `pipe`, `register`; contributions take the target first, a value or an
445
+ outer `provides` factory receives exactly `{ port, slot, pipe, register }`: only builders, with no `own` or other
446
+ instance values. `port(Port, ({ own }) => own.model.call)` records the target at declaration and selects a Call
447
+ when the instance prepares. Contributions take the target first, a value or an
441
448
  instance factory second, and options third: `slot(target, contribution, { priority, when })`,
442
449
  `pipe(target, { fold }, { priority, when })`, `register(target, entry, { priority, when })`, where `pipe`'s second
443
450
  argument is a descriptor because a bare function would be indistinguishable from the instance factory. The
444
- contribution factory context is `{ exports, imports, instance, model, own }`, and its `model(Decl, create)` builds
445
- the mount's UI model, where `create` receives `(ctx, props)`. `when` is a `Readable<boolean>` or a predicate of the
446
- instance `({ exports, imports, own, read }) => boolean` whose `read` records what the answer depends on: while
451
+ `register` factory receives `{ own, imports }`; a `slot` factory receives `{ own, imports, model }`, where
452
+ `model(Decl, create)` builds the mount's UI model and `create` receives `(ctx, props)`. These contexts contain only
453
+ the fields named here: there is no `exports` or `instance`, and `register` has no model builder (D255).
454
+ In either nested factory, `own` contains this instance's materialized models, calls and resources, just as in `exports`.
455
+ `register(target, ({ own }) => ({ key: 'submit', value: own.order.submit }))` can publish a private model call
456
+ without an `exports` section. Both port selectors and contribution factories select live values through their
457
+ own callback context. Attachments, scopes, effects and events
458
+ have no readable value form; unselected entries do not prevent access to the model or call alongside them.
459
+ `when` is a `Readable<boolean>` or a predicate of the
460
+ instance — `({ own, imports, read }) => boolean` — whose `read` records what the answer depends on: while
447
461
  the answer is `false` the contribution does not enter the target's `entries`. In that evaluation context `own` is
448
- the materialized form `exports` sees, so a model field is a `Readable` and not the ref the factory context carries.
462
+ the materialized form `exports` sees, so a model field is a `Readable` and not the ref returned by the `own` builder.
449
463
  A `pipe` descriptor's `fold` receives that same evaluation context as its third argument —
450
- `(value, meta, { exports, imports, own, read }) => …` — bound to the instance once when the contribution publishes
464
+ `(value, meta, { own, imports, read }) => …` — bound to the instance once when the contribution publishes
451
465
  and called only by a `fold`, never at declaration or preload; `target.fold(value, meta, read?)` accepts the reader
452
466
  as its third parameter, so `computed({ read: read => target.fold(0, undefined, read) })` subscribes both to
453
467
  `entries` and to everything the handlers read; without a reader the handlers read the current snapshot. Contexts: