@opetope/runtime 0.1.1 → 0.4.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.
- package/CHANGELOG.md +62 -0
- package/README.md +19 -9
- package/README.ru.md +21 -9
- package/dist/feature-authoring-types.d.ts +6 -10
- package/dist/feature-authoring.js +1 -1
- package/dist/feature-authoring.js.map +1 -1
- package/dist/feature-body.d.ts +1 -1
- package/dist/feature-body.js.map +1 -1
- package/dist/feature-contribution.d.ts +5 -3
- package/dist/feature-contribution.js +1 -1
- package/dist/feature-contribution.js.map +1 -1
- package/dist/feature-definition-api.d.ts +1 -1
- package/dist/feature-generation.js +1 -1
- package/dist/feature-generation.js.map +1 -1
- package/dist/feature-lazy-generation.d.ts +1 -1
- package/dist/feature-lazy-generation.js +1 -1
- package/dist/feature-lazy-generation.js.map +1 -1
- package/dist/feature-own-values.d.ts +10 -0
- package/dist/feature-own-values.js +2 -0
- package/dist/feature-own-values.js.map +1 -0
- package/dist/feature-port-binding.d.ts +2 -1
- package/dist/feature-port-binding.js +1 -1
- package/dist/feature-port-binding.js.map +1 -1
- package/dist/feature-port.d.ts +13 -23
- package/dist/feature-port.js +1 -1
- package/dist/feature-port.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +1 -1
- package/dist/public-module-types.d.ts +1 -1
- package/dist/public-module.d.ts +1 -1
- package/docs/agent-guide.md +11 -4
- package/docs/agent-guide.ru.md +11 -4
- package/docs/cookbook.md +9 -7
- package/docs/cookbook.ru.md +9 -7
- package/docs/decisions.md +40 -0
- package/docs/how-it-works.md +30 -5
- package/docs/how-it-works.ru.md +29 -5
- package/docs/spec.md +60 -18
- package/docs/spec.ru.md +61 -19
- package/package.json +3 -3
package/dist/feature-port.js.map
CHANGED
|
@@ -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"}
|
package/dist/internal.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type { FeatureContract } from './feature-contract.js';
|
|
|
17
17
|
export type { ImportDemand, OnDemandHostContract, OptionalHostContract } from './feature-contract.js';
|
|
18
18
|
export type { FeatureContributionDescriptor } from './feature-contribution.js';
|
|
19
19
|
export { openContributionModels, requireContributionModelPlans } from './feature-contribution-model.js';
|
|
20
|
+
export { reportRuntimeFailure } from './runtime-error-reporting.js';
|
|
21
|
+
export type { RuntimeErrorReporter } from './runtime-error-reporting.js';
|
|
20
22
|
export type { ContributionModel, ContributionModelBundle, ContributionModelPlan } from './feature-contribution-model.js';
|
|
21
23
|
export type { FeatureReady, FeatureReadyOf } from './feature-generation.js';
|
|
22
24
|
export { bindFeatureResource } from './feature-materialization-binding.js';
|
package/dist/internal.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getApplicationPlan as t}from"./application-definition.js";import{createAttachmentCall as n}from"./attachment-call-declaration.js";import{createScopeBlueprint as i}from"./attachment-declaration.js";import{compileModuleTemplate as p,createLocalAttachmentModuleDefinition as m}from"./compile-module-template.js";import{createControlSession as
|
|
1
|
+
import{getApplicationPlan as t}from"./application-definition.js";import{createAttachmentCall as n}from"./attachment-call-declaration.js";import{createScopeBlueprint as i}from"./attachment-declaration.js";import{compileModuleTemplate as p,createLocalAttachmentModuleDefinition as m}from"./compile-module-template.js";import{createControlSession as f}from"./control-registry.js";import{openContributionModels as u,requireContributionModelPlans as x}from"./feature-contribution-model.js";import{reportRuntimeFailure as M}from"./runtime-error-reporting.js";import{bindFeatureResource as C}from"./feature-materialization-binding.js";import{createInspectionSession as O}from"./inspection-registry.js";import{createInstanceDemandCoordinator as b}from"./instance-demand.js";import{instantiateModuleTemplate as T}from"./module-instance.js";import{bindAttachmentLifecycleToOwner as B,createOwnerGenerationSlot as D,openOwnerBoundAttachment as G,retryOwnerBoundAttachmentClose as y}from"./owner-generation.js";import{acknowledgeOwnerGenerationDrain as I,retireOwnedGeneration as L}from"./owner-generation-retirement.js";import{defineModule as R}from"./public-module-definition.js";import{instantiateModule as q,resolveModuleCallTarget as v}from"./public-module-instance.js";export{I as acknowledgeOwnerGenerationDrain,B as bindAttachmentLifecycleToOwner,C as bindFeatureResource,p as compileModuleTemplate,n as createAttachmentCall,f as createControlSession,O as createInspectionSession,b as createInstanceDemandCoordinator,m as createLocalAttachmentModuleDefinition,D as createOwnerGenerationSlot,i as createScopeBlueprint,R as defineModule,t as getApplicationPlan,q as instantiateModule,T as instantiateModuleTemplate,u as openContributionModels,G as openOwnerBoundAttachment,M as reportRuntimeFailure,x as requireContributionModelPlans,v as resolveModuleCallTarget,L as retireOwnedGeneration,y as retryOwnerBoundAttachmentClose};
|
|
2
2
|
//# sourceMappingURL=internal.js.map
|
|
@@ -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,
|
|
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, };
|
package/dist/public-module.d.ts
CHANGED
|
@@ -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,
|
|
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';
|
package/docs/agent-guide.md
CHANGED
|
@@ -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
|
-
|
|
48
|
-
factories and model factories) receive materialized values.
|
|
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, {
|
|
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
|
-
`({
|
|
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
|
package/docs/agent-guide.ru.md
CHANGED
|
@@ -41,10 +41,17 @@ policy?, within })`; данные по ключу → `resource`; поток →
|
|
|
41
41
|
загружается при открытии экземпляра (D186, D207). `defineFeature.preload(feature)` подтягивает этот код заранее,
|
|
42
42
|
ничего не открывая; для фичи без тела это успешный no-op (D208).
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
вкладов и
|
|
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
|
-
|
|
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
|
-
`({
|
|
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
|
|
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: ({
|
|
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
|
|
184
|
-
and
|
|
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: ({
|
|
191
|
-
submit: port(SubmitPort, {
|
|
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, ({
|
|
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
|
package/docs/cookbook.ru.md
CHANGED
|
@@ -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
|
|
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: ({
|
|
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
|
-
жизни провайдера ограничивает выбранный вызов, даже
|
|
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: ({
|
|
190
|
-
submit: port(SubmitPort, {
|
|
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, ({
|
|
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,43 @@ 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).
|
|
1452
|
+
|
|
1453
|
+
<a id="d256"></a>
|
|
1454
|
+
|
|
1455
|
+
## D256 — сбой вклада изолируется его монтированием
|
|
1456
|
+
|
|
1457
|
+
Рендер и коммит одного вклада больше не могут остановить приложение. `ContributionMount` сам является границей ошибок: сбой компонента, отказ `useModel` в невыданной модели, сбой чтения источника и сбой создания UI-моделей этого монтирования перехватываются на нём и не доходят до границы хоста. Соседние вклады того же слота и остальное дерево продолжают рендериться. Изоляция безусловна: она не зависит от того, объявил ли хост содержимое ошибки.
|
|
1458
|
+
|
|
1459
|
+
Содержимое сбоя объявляется один раз на всё приложение. `ContributionBoundary` из `@opetope/react/integration` принимает `error` — узел или колбэк `({ error, retry }) => ReactNode` той же формы, что ветвь `error` у `FeatureBoundary` ([D142](#d142), [D177](#d177), [D198](#d198)): хост пишет один вид содержимого ошибки для обоих. Без провайдера упавший вклад рендерит пустоту. `retry` пересобирает монтирование целиком — поддерево монтируется заново, поэтому модели вклада создаются заново, а не переиспользуют сборку, которая упала.
|
|
1460
|
+
|
|
1461
|
+
Содержимое живёт в стабильной ссылке, а не в значении контекста: хост пишет ветвь ошибки инлайном, и контекст, меняющийся вместе с ней, перерисовывал бы каждое монтирование приложения и отменял бы memo на монтирование ([D130](#d130)). Монтирование читает ссылку один раз, уже упав, поэтому переписанное во время сбоя содержимое попадает в следующий сбой ([D214](#d214)). Сбой опознаётся флагом, а не наличием `error`: чужой код вправе бросить `undefined`, и sentinel принял бы это за исправное монтирование.
|
|
1462
|
+
|
|
1463
|
+
Содержимое ошибки — тоже чужой код. Оно рендерится во второй, вложенной границе, которая существует только после сбоя: если само содержимое падает, монтирование сообщает и этот сбой, после чего рендерит пустоту. Поэтому провайдер, доступный в одном месте дерева и недоступный в другом, не превращает изоляцию обратно в эскалацию. Успешный путь платит одну границу на монтирование, а не две.
|
|
1464
|
+
|
|
1465
|
+
Диагностика принадлежит владельцу: authority поколения ([D70](#d70)) несёт reporter фичи, опубликовавшей вклад, туда уходит перехваченный сбой, и по тому же адресу впервые открываются UI-модели монтирования. Без authority сбой остаётся detached, как и любая другая ничья ошибка. Тестовый `renderSlot` принимает `reporter` ради тех же диагностик: у компонентного теста нет фичи, которой можно сообщить. Уточняет [D70](#d70), [D85](#d85), [D188](#d188).
|
|
1466
|
+
|
|
1467
|
+
<a id="d257"></a>
|
|
1468
|
+
|
|
1469
|
+
## D257 — изолированный сбой называет себя
|
|
1470
|
+
|
|
1471
|
+
`ContributionFailure` несёт идентичность вместе с ошибкой: `contribution` — id опубликованной записи вида `<фича>.<ключ provides>`, `target` — слот, в котором вклад рендерился, `feature` — id опубликовавшей его фичи или `undefined` у тестовой фикстуры. Одна корневая ветвь ошибки отвечает по поверхности, не перечисляя вклады поимённо: слой уведомлений молчит, поверхность страницы показывает карточку. Все три известны только монтированию: id записи и цель знает `Slot`, фичу несёт authority поколения ([D70](#d70)).
|
|
1472
|
+
|
|
1473
|
+
Диагностика получает то же самое, но обёрткой. `RuntimeErrorReporter` принимает один аргумент, а один reporter приложения обслуживает все фичи, поэтому голый `TypeError` в нём не назвал бы ни поверхность, ни владельца. Монтирование сообщает `ContributionError` с теми же тремя полями и исходной ошибкой в `cause`; расширять сам reporter до `(error, context)` значило бы менять контракт всех отчётов core и runtime ради одного случая. Показу обёртка не нужна: содержимое ошибки по-прежнему получает `error` сырым.
|
|
1474
|
+
|
|
1475
|
+
Отмена остаётся отменой: если изолированный сбой помечен как cancellation ([D69](#d69)), обёртка несёт тот же бренд. Метка живёт на экземпляре, а обёртка создаёт новый объект, поэтому без явной перестановки `isCancellation` перестал бы быть одной проверкой для каждого субъекта. Саму проверку безопасный вход пока не отдаёт, так что свойство держит внутренние проверки фреймворка; фильтр отмен в reporter хоста потребовал бы отдельного решения о публикации `isCancellation`.
|
|
1476
|
+
|
|
1477
|
+
Кодов два, а не один: `render-failed` говорит, что сломан вклад, `error-content-failed` — что сломано содержимое ошибки самого хоста. Это разные сигналы, и второй заслуживает более громкой реакции, чем первый; различать их по тексту сообщения хост не должен. Уточняет [D256](#d256), [D69](#d69).
|
package/docs/how-it-works.md
CHANGED
|
@@ -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,
|
|
108
|
-
|
|
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.
|
|
185
|
-
call
|
|
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
|
|
@@ -395,6 +410,16 @@ component's own props, `models` supplies the per-mount UI models, and a componen
|
|
|
395
410
|
declares which of them it needs, which is checked by the type at the contribution boundary. A mount is memoized:
|
|
396
411
|
publishing or withdrawing one contribution does not re-render the other mounts of the same target (D130).
|
|
397
412
|
|
|
413
|
+
Above every mount sits its own error boundary, so a contribution that throws stops there instead of reaching the host
|
|
414
|
+
(D256). It wraps `ContributionMount` rather than the component inside it, which is why it also contains the commit that
|
|
415
|
+
builds the mount — a model factory that throws is the same kind of failure as a component that throws — and why a retry
|
|
416
|
+
remounts the subtree and creates those models again. The failure goes to the reporter carried by the authority of the
|
|
417
|
+
publishing generation, the same reporter the mount's UI models now open with, wrapped in a `ContributionError` that
|
|
418
|
+
names the entry, its feature and its slot and keeps the original on `cause` (D257). What the mount shows in its place is the
|
|
419
|
+
`error` content of the nearest `ContributionBoundary`, and nothing when a host declared none. That content renders in a
|
|
420
|
+
second boundary, created only after a failure, so error content that throws is contained as well and the successful path
|
|
421
|
+
still costs one boundary per mount.
|
|
422
|
+
|
|
398
423
|
### 6.3 `fold` on a pipe
|
|
399
424
|
|
|
400
425
|
`fold(value, meta, read?)` walks the handlers in order; with a reader passed inside a `computed`, the dependencies
|
|
@@ -402,7 +427,7 @@ include both the entry list and everything the handlers read through their `{ re
|
|
|
402
427
|
their sources change rather than only when contributions are re-registered. Without a reader the reads are untracked.
|
|
403
428
|
|
|
404
429
|
A handler is declared as a descriptor, `pipe(target, { fold })`, and the runtime binds one evaluation context to it
|
|
405
|
-
when the contribution publishes: `
|
|
430
|
+
when the contribution publishes: `imports` and the materialized `own` are resolved once and the reader is
|
|
406
431
|
the only thing that changes from call to call, so folding over many handlers allocates no per-handler context
|
|
407
432
|
(D223). A `fold` is the only thing that runs a handler: declaring, preloading and publishing do not.
|
|
408
433
|
|
package/docs/how-it-works.ru.md
CHANGED
|
@@ -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 породил запись.
|
|
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
|
-
|
|
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` и проверки пустоты её не видят; переключение публикует одной
|
|
@@ -384,6 +398,16 @@ disposer и слив очереди.
|
|
|
384
398
|
`requiresModels([...])` объявляет, какие из них ему нужны, и это сверяется типом на границе вклада. Монтирование мемоизировано: публикация или
|
|
385
399
|
снятие одного вклада не перерисовывает остальные монтирования той же цели (D130).
|
|
386
400
|
|
|
401
|
+
Над каждым монтированием стоит его собственная граница ошибок, поэтому упавший вклад останавливается на ней и не
|
|
402
|
+
доходит до хоста (D256). Она оборачивает `ContributionMount`, а не компонент внутри него: поэтому она держит и коммит,
|
|
403
|
+
который собирает монтирование — упавшая фабрика модели это тот же сбой, что упавший компонент, — и поэтому retry
|
|
404
|
+
перемонтирует поддерево и создаёт эти модели заново. Сбой уходит в reporter, который несёт authority публикующего
|
|
405
|
+
поколения; с этим же reporter теперь открываются UI-модели монтирования. В отчёт он едет `ContributionError`, который
|
|
406
|
+
называет запись, её фичу и её слот и держит исходную ошибку в `cause` (D257). Вместо вклада показывается содержимое `error`
|
|
407
|
+
ближайшего `ContributionBoundary`, а если хост его не объявил — ничего. Это содержимое рендерится во второй границе,
|
|
408
|
+
создаваемой только после сбоя, поэтому упавшее содержимое ошибки тоже изолировано, а успешный путь по-прежнему стоит
|
|
409
|
+
одну границу на монтирование.
|
|
410
|
+
|
|
387
411
|
### 6.3 `fold` у pipe
|
|
388
412
|
|
|
389
413
|
`fold(value, meta, read?)` проходит обработчики по порядку; с переданным читателем внутри `computed` в зависимости
|
|
@@ -391,7 +415,7 @@ disposer и слив очереди.
|
|
|
391
415
|
при смене их источников, а не только при перерегистрации вкладов. Без читателя чтение нетрекаемое.
|
|
392
416
|
|
|
393
417
|
Обработчик объявляется дескриптором, `pipe(target, { fold })`, и рантайм связывает с ним один контекст вычисления в
|
|
394
|
-
момент публикации вклада: `
|
|
418
|
+
момент публикации вклада: `imports` и материализованный `own` разрешаются один раз, от вызова к вызову
|
|
395
419
|
меняется только читатель, поэтому свёртка по многим обработчикам не аллоцирует контекст на обработчик (D223).
|
|
396
420
|
Обработчик запускает только свёртка: объявление, предзагрузка и публикация его не вызывают.
|
|
397
421
|
|