@peers-app/peers-sdk 0.26.6 → 0.27.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 (182) hide show
  1. package/README.md +3 -1
  2. package/bin/peers-package.js +13 -0
  3. package/define/package.json +4 -0
  4. package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
  5. package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/also-implements.test.js +110 -0
  7. package/dist/contracts/__tests__/builder.test.js +45 -0
  8. package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
  9. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
  10. package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
  11. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
  12. package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
  14. package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
  15. package/dist/contracts/__tests__/registry.test.js +68 -0
  16. package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
  17. package/dist/contracts/__tests__/target-dispatch.js +24 -0
  18. package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
  19. package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
  20. package/dist/contracts/__tests__/validate.test.js +177 -7
  21. package/dist/contracts/__tests__/workflow-context.test.js +0 -11
  22. package/dist/contracts/builder.d.ts +16 -2
  23. package/dist/contracts/builder.js +44 -3
  24. package/dist/contracts/contract-definition-schema.d.ts +251 -0
  25. package/dist/contracts/contract-definition-schema.js +48 -0
  26. package/dist/contracts/contract-provider-router.d.ts +2 -0
  27. package/dist/contracts/contract-provider-router.js +5 -2
  28. package/dist/contracts/contract-proxy.d.ts +52 -18
  29. package/dist/contracts/contract-proxy.js +144 -203
  30. package/dist/contracts/contract-resolvers.d.ts +6 -4
  31. package/dist/contracts/contract-resolvers.js +32 -9
  32. package/dist/contracts/frozen-contracts.d.ts +59 -0
  33. package/dist/contracts/frozen-contracts.js +86 -0
  34. package/dist/contracts/host-contract-client.d.ts +134 -0
  35. package/dist/contracts/host-contract-client.js +396 -0
  36. package/dist/contracts/host-contract-transport.d.ts +2 -0
  37. package/dist/contracts/host-contract-transport.js +8 -0
  38. package/dist/contracts/index.d.ts +6 -4
  39. package/dist/contracts/index.js +11 -5
  40. package/dist/contracts/registry.d.ts +31 -1
  41. package/dist/contracts/registry.js +100 -6
  42. package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
  43. package/dist/contracts/system/contract-catalog-contract.js +17 -5
  44. package/dist/contracts/tool-catalog-service.js +26 -6
  45. package/dist/contracts/tool-catalog.d.ts +1 -1
  46. package/dist/contracts/tool-catalog.js +4 -1
  47. package/dist/contracts/types.d.ts +32 -2
  48. package/dist/contracts/validate.d.ts +16 -1
  49. package/dist/contracts/validate.js +139 -25
  50. package/dist/data/change-tracking.d.ts +17 -5
  51. package/dist/data/change-tracking.js +22 -6
  52. package/dist/data/change-tracking.test.d.ts +1 -0
  53. package/dist/data/change-tracking.test.js +62 -0
  54. package/dist/data/orm/sql.data-source.d.ts +9 -0
  55. package/dist/data/orm/sql.data-source.js +47 -2
  56. package/dist/data/orm/sql.data-source.test.js +142 -0
  57. package/dist/data/orm/table-container.js +9 -9
  58. package/dist/data/orm/table-container.test.js +44 -0
  59. package/dist/data/orm/types.d.ts +41 -1
  60. package/dist/data/orm/types.js +159 -28
  61. package/dist/data/orm/types.test.js +129 -0
  62. package/dist/data/peer-types.d.ts +8 -62
  63. package/dist/data/persistent-vars.d.ts +4 -1
  64. package/dist/data/persistent-vars.js +7 -2
  65. package/dist/data/tools.d.ts +29 -221
  66. package/dist/define/also-implements.test.d.ts +1 -0
  67. package/dist/define/also-implements.test.js +123 -0
  68. package/dist/define/context.d.ts +77 -0
  69. package/dist/define/context.js +2 -0
  70. package/dist/define/contract-ref.d.ts +177 -0
  71. package/dist/define/contract-ref.js +67 -0
  72. package/dist/define/declare-contract.d.ts +167 -0
  73. package/dist/define/declare-contract.js +220 -0
  74. package/dist/define/declare-contract.test.d.ts +1 -0
  75. package/dist/define/declare-contract.test.js +573 -0
  76. package/dist/define/define-package.d.ts +203 -0
  77. package/dist/define/define-package.js +289 -0
  78. package/dist/define/define.test.d.ts +1 -0
  79. package/dist/define/define.test.js +1010 -0
  80. package/dist/define/empty-fields.d.ts +15 -0
  81. package/dist/define/empty-fields.js +134 -0
  82. package/dist/define/empty-fields.test.d.ts +1 -0
  83. package/dist/define/empty-fields.test.js +55 -0
  84. package/dist/define/events-observables.test.d.ts +1 -0
  85. package/dist/define/events-observables.test.js +1013 -0
  86. package/dist/define/events.d.ts +81 -0
  87. package/dist/define/events.js +81 -0
  88. package/dist/define/generated-field.d.ts +20 -0
  89. package/dist/define/generated-field.js +25 -0
  90. package/dist/define/index.d.ts +28 -0
  91. package/dist/define/index.js +62 -0
  92. package/dist/define/lazy-members.d.ts +8 -0
  93. package/dist/define/lazy-members.js +38 -0
  94. package/dist/define/manifest.d.ts +63 -0
  95. package/dist/define/manifest.js +653 -0
  96. package/dist/define/observables.d.ts +146 -0
  97. package/dist/define/observables.js +187 -0
  98. package/dist/define/serve-members.d.ts +21 -0
  99. package/dist/define/serve-members.js +152 -0
  100. package/dist/define/serve-members.test.d.ts +1 -0
  101. package/dist/define/serve-members.test.js +630 -0
  102. package/dist/define/serve.d.ts +22 -0
  103. package/dist/define/serve.js +58 -0
  104. package/dist/define/system-contracts.d.ts +41 -0
  105. package/dist/define/system-contracts.js +21 -0
  106. package/dist/define/table-handle.d.ts +13 -0
  107. package/dist/define/table-handle.js +36 -0
  108. package/dist/define/table.d.ts +128 -0
  109. package/dist/define/table.js +122 -0
  110. package/dist/define/tool.d.ts +129 -0
  111. package/dist/define/tool.js +91 -0
  112. package/dist/define/vars.d.ts +82 -0
  113. package/dist/define/vars.js +256 -0
  114. package/dist/define/vars.test.d.ts +1 -0
  115. package/dist/define/vars.test.js +39 -0
  116. package/dist/define/wire-value.d.ts +12 -0
  117. package/dist/define/wire-value.js +76 -0
  118. package/dist/define/worker-state.d.ts +15 -0
  119. package/dist/define/worker-state.js +45 -0
  120. package/dist/device/tx-encoding.test.js +10 -0
  121. package/dist/index.d.ts +3 -0
  122. package/dist/index.js +5 -1
  123. package/dist/observable.d.ts +42 -2
  124. package/dist/observable.js +25 -7
  125. package/dist/package-build/build-package.d.ts +60 -0
  126. package/dist/package-build/build-package.js +312 -0
  127. package/dist/package-build/build-package.test.d.ts +1 -0
  128. package/dist/package-build/build-package.test.js +204 -0
  129. package/dist/package-build/cli.d.ts +7 -0
  130. package/dist/package-build/cli.js +218 -0
  131. package/dist/package-build/contract-add.d.ts +35 -0
  132. package/dist/package-build/contract-add.js +97 -0
  133. package/dist/package-build/contract-add.test.d.ts +1 -0
  134. package/dist/package-build/contract-add.test.js +467 -0
  135. package/dist/package-build/contract-source.d.ts +59 -0
  136. package/dist/package-build/contract-source.js +471 -0
  137. package/dist/package-build/freeze-contracts.d.ts +56 -0
  138. package/dist/package-build/freeze-contracts.js +198 -0
  139. package/dist/package-build/freeze-contracts.test.d.ts +1 -0
  140. package/dist/package-build/freeze-contracts.test.js +355 -0
  141. package/dist/package-installer/package-creator.d.ts +4 -0
  142. package/dist/package-installer/package-creator.js +57 -16
  143. package/dist/package-installer/package-creator.test.js +86 -0
  144. package/dist/package-loader/contract-members.d.ts +53 -0
  145. package/dist/package-loader/contract-members.js +31 -0
  146. package/dist/package-loader/contract-package-loader.js +5 -1
  147. package/dist/package-loader/contract-package-loader.test.js +11 -0
  148. package/dist/package-loader/host-authority-loading.test.js +9 -5
  149. package/dist/package-loader/index.d.ts +1 -0
  150. package/dist/package-loader/index.js +1 -0
  151. package/dist/package-loader/isolated-artifact.d.ts +80 -17
  152. package/dist/package-loader/isolated-artifact.js +257 -51
  153. package/dist/package-loader/isolated-artifact.test.js +294 -21
  154. package/dist/package-loader/isolated-runtime.d.ts +22 -7
  155. package/dist/package-loader/isolated-table-gateway.js +30 -11
  156. package/dist/package-loader/isolated-table-gateway.test.js +243 -0
  157. package/dist/package-loader/package-loader.d.ts +8 -1
  158. package/dist/package-loader/package-loader.js +39 -20
  159. package/dist/package-loader/package-loader.test.js +47 -0
  160. package/dist/peer-id.d.ts +25 -0
  161. package/dist/peer-id.js +99 -0
  162. package/dist/serial-json.compat.test.d.ts +1 -0
  163. package/dist/serial-json.compat.test.js +369 -0
  164. package/dist/serial-json.d.ts +41 -5
  165. package/dist/serial-json.js +216 -157
  166. package/dist/serial-json.oracle.d.ts +21 -0
  167. package/dist/serial-json.oracle.js +222 -0
  168. package/dist/serial-json.test.js +315 -0
  169. package/dist/testing/index.d.ts +4 -0
  170. package/dist/testing/index.js +8 -0
  171. package/dist/testing/mock-host-contract.d.ts +123 -0
  172. package/dist/testing/mock-host-contract.js +434 -0
  173. package/dist/testing/mock-host-contract.test.d.ts +1 -0
  174. package/dist/testing/mock-host-contract.test.js +782 -0
  175. package/dist/types/field-type.d.ts +35 -18
  176. package/dist/types/field-type.js +4 -1
  177. package/dist/types/zod-types.d.ts +9 -0
  178. package/dist/types/zod-types.js +8 -2
  179. package/dist/utils.d.ts +1 -25
  180. package/dist/utils.js +10 -90
  181. package/package.json +9 -2
  182. package/testing/package.json +4 -0
@@ -0,0 +1,146 @@
1
+ import type { z } from "zod";
2
+ import type { Observable } from "../observable";
3
+ import type { FieldType } from "../types/field-type";
4
+ import type { IPackageContext } from "./context";
5
+ import { type IPackageVarBinding } from "./vars";
6
+ /** Settings every observable declaration shares. */
7
+ interface IPackageObservableOptions {
8
+ /** Observable name, unique on its contract. */
9
+ name: string;
10
+ /** What the value is, for people and agents reading it. */
11
+ description: string;
12
+ /** Consumers may write the value. Defaults to `false`. */
13
+ writable?: boolean;
14
+ /**
15
+ * What holds the value. A package variable (`deviceVar`, `groupVar`, …) is stored, and the
16
+ * provider reads and writes it in its worker. A plain `observable()` is held in memory by
17
+ * the package's worker: reading it starts the worker, and it starts over when the worker does.
18
+ */
19
+ source: Observable<any>;
20
+ }
21
+ /** An observable declared with a zod schema for its value. */
22
+ export interface IPackageObservableSchemaDefinition<S extends z.ZodTypeAny = z.ZodTypeAny> extends IPackageObservableOptions {
23
+ /**
24
+ * The value's schema. The manifest carries its type as IR; a consumer's write is parsed
25
+ * with it before the source takes it.
26
+ */
27
+ schema: S;
28
+ }
29
+ /** An observable declared with a Peers IR value type, for a contract defined elsewhere. */
30
+ export interface IPackageObservableTypeDefinition extends IPackageObservableOptions {
31
+ /** The value's type: `string`, `number`, `boolean`, `object`, `id`, or `any`. */
32
+ valueType: FieldType;
33
+ }
34
+ /** Either form of observable declaration. */
35
+ export type PackageObservableDefinition = IPackageObservableSchemaDefinition | IPackageObservableTypeDefinition;
36
+ /** An observable's shape from a zod schema, without its source: what {@link declareObservable} takes. */
37
+ export type IPackageObservableSchemaShape<S extends z.ZodTypeAny = z.ZodTypeAny> = Omit<IPackageObservableSchemaDefinition<S>, "source">;
38
+ /** An observable's shape in Peers IR, without its source: what {@link declareObservable} takes. */
39
+ export type IPackageObservableTypeShape = Omit<IPackageObservableTypeDefinition, "source">;
40
+ /** Either form of observable shape. */
41
+ export type PackageObservableShape = IPackageObservableSchemaShape | IPackageObservableTypeShape;
42
+ /**
43
+ * An observable's shape, from {@link declareObservable}: what a contract declaration lists, with
44
+ * no source. `T` is its value, `N` its name, and `W` whether consumers may write it.
45
+ */
46
+ export interface IPackageObservableDeclaration<T = unknown, N extends string = string, W extends boolean = boolean> {
47
+ /** Marks an observable. */
48
+ readonly kind: "observable";
49
+ /** What was passed to {@link declareObservable} (or {@link defineObservable}). */
50
+ readonly definition: Readonly<PackageObservableShape> & {
51
+ readonly name: N;
52
+ readonly writable?: W;
53
+ };
54
+ /** Type-only: the value consumers read. Always undefined at runtime. */
55
+ readonly types?: {
56
+ value: T;
57
+ };
58
+ }
59
+ /**
60
+ * An observable declared with {@link defineObservable}: its shape and its source. List it on a
61
+ * provided contract. `T` is its value, `N` its name, and `W` whether consumers may write it.
62
+ */
63
+ export interface IPackageObservable<T = unknown, N extends string = string, W extends boolean = boolean> extends IPackageObservableDeclaration<T, N, W> {
64
+ /** What was passed to {@link defineObservable}. */
65
+ readonly definition: Readonly<PackageObservableDefinition> & {
66
+ readonly name: N;
67
+ readonly writable?: W;
68
+ };
69
+ /** Where the value is stored, when a package variable holds it; undefined for in-memory. */
70
+ readonly persistentVar?: IPackageVarBinding;
71
+ }
72
+ /**
73
+ * Apply a consumer's write to an in-memory observable this package serves, as its write handler
74
+ * does: the schema checks the value, the source takes it, and the stored value is published and
75
+ * returned. Undefined before the observable is served, or when it isn't writable.
76
+ *
77
+ * @param observable - A provided in-memory observable.
78
+ */
79
+ export declare function inMemoryObservableWriter(observable: IPackageObservable): ((value: unknown) => Promise<unknown>) | undefined;
80
+ /**
81
+ * Declare an observable from a zod schema for its value.
82
+ *
83
+ * @param definition - Name, description, schema, `writable`, and source.
84
+ */
85
+ export declare function defineObservable<S extends z.ZodTypeAny, N extends string, W extends boolean = false>(definition: IPackageObservableSchemaDefinition<S> & {
86
+ name: N;
87
+ writable?: W;
88
+ }): IPackageObservable<z.output<S>, NoInfer<N>, NoInfer<W>>;
89
+ /**
90
+ * Declare an observable from a Peers IR value type, for a contract defined elsewhere. Consumers
91
+ * read the source's type.
92
+ *
93
+ * @param definition - Name, description, `valueType`, `writable`, and source.
94
+ */
95
+ export declare function defineObservable<T, N extends string, W extends boolean = false>(definition: IPackageObservableTypeDefinition & {
96
+ name: N;
97
+ writable?: W;
98
+ source: Observable<T>;
99
+ }): IPackageObservable<T, NoInfer<N>, NoInfer<W>>;
100
+ /**
101
+ * Declare an observable's shape from a zod schema, with no source, for a contract declared with
102
+ * `declareContract`: each package that implements the contract supplies the source.
103
+ *
104
+ * @param definition - Name, description, schema, and `writable`.
105
+ */
106
+ export declare function declareObservable<S extends z.ZodTypeAny, N extends string, W extends boolean = false>(definition: IPackageObservableSchemaShape<S> & {
107
+ name: N;
108
+ writable?: W;
109
+ }): IPackageObservableDeclaration<z.output<S>, NoInfer<N>, NoInfer<W>>;
110
+ /**
111
+ * Declare an observable's shape from a Peers IR value type, with no source.
112
+ *
113
+ * @param definition - Name, description, `valueType`, and `writable`.
114
+ */
115
+ export declare function declareObservable<N extends string, W extends boolean = false>(definition: IPackageObservableTypeShape & {
116
+ name: N;
117
+ writable?: W;
118
+ }): IPackageObservableDeclaration<unknown, NoInfer<N>, NoInfer<W>>;
119
+ /**
120
+ * True when `observable` has a source: from {@link defineObservable}, not
121
+ * {@link declareObservable}.
122
+ *
123
+ * @param observable - An observable from either.
124
+ */
125
+ export declare function isImplementedObservable(observable: IPackageObservableDeclaration): observable is IPackageObservable;
126
+ /**
127
+ * True for an observable from {@link declareObservable} or {@link defineObservable}, as
128
+ * opposed to Peers IR. Use {@link isImplementedObservable} to check whether it has a source.
129
+ *
130
+ * @param value - An entry in a contract's `observables`.
131
+ */
132
+ export declare function isPackageObservable(value: unknown): value is IPackageObservableDeclaration<any>;
133
+ /**
134
+ * Publish each in-memory observable's value now and after every change, and take consumers'
135
+ * writes to the writable ones. Called by `startPackage`; variable-backed observables need
136
+ * nothing here: their member handlers read and write the package variable.
137
+ *
138
+ * @param provides - The package's provided contracts.
139
+ * @param ctx - The worker's context, bound to no call.
140
+ */
141
+ export declare function servePackageObservables(provides: readonly {
142
+ contractId: string;
143
+ version: number;
144
+ observables: readonly unknown[];
145
+ }[], ctx: IPackageContext): Promise<void>;
146
+ export {};
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inMemoryObservableWriter = inMemoryObservableWriter;
4
+ exports.defineObservable = defineObservable;
5
+ exports.declareObservable = declareObservable;
6
+ exports.isImplementedObservable = isImplementedObservable;
7
+ exports.isPackageObservable = isPackageObservable;
8
+ exports.servePackageObservables = servePackageObservables;
9
+ const empty_fields_1 = require("./empty-fields");
10
+ const vars_1 = require("./vars");
11
+ const wire_value_1 = require("./wire-value");
12
+ /** Stops serving an observable from an earlier start (tests start packages more than once). */
13
+ const serving = new WeakMap();
14
+ /** Each served writable in-memory observable's write: the schema check, the source, a publish. */
15
+ const writers = new WeakMap();
16
+ /**
17
+ * Apply a consumer's write to an in-memory observable this package serves, as its write handler
18
+ * does: the schema checks the value, the source takes it, and the stored value is published and
19
+ * returned. Undefined before the observable is served, or when it isn't writable.
20
+ *
21
+ * @param observable - A provided in-memory observable.
22
+ */
23
+ function inMemoryObservableWriter(observable) {
24
+ return writers.get(observable);
25
+ }
26
+ /**
27
+ * Declare an observable a provided contract exposes: one value, read and watched by other
28
+ * packages and the UI, over a `source` the package already uses. The package reads and writes
29
+ * the source as usual.
30
+ *
31
+ * @param definition - Name, description, schema (or IR `valueType`), `writable`, and source.
32
+ */
33
+ function defineObservable(definition) {
34
+ checkObservableShape(definition, "defineObservable");
35
+ const { name, source } = definition;
36
+ if (typeof source !== "function" || typeof source.subscribe !== "function") {
37
+ throw new Error(`Observable ${name} needs a source: a package variable or an observable()`);
38
+ }
39
+ const persistentVar = (0, vars_1.packageVarBinding)(source);
40
+ if (persistentVar && persistentVar.defaultValue === undefined) {
41
+ throw new Error(`Observable ${name} is stored in variable "${persistentVar.name}", which needs a defaultValue`);
42
+ }
43
+ return Object.freeze({
44
+ kind: "observable",
45
+ definition: Object.freeze({ ...definition }),
46
+ ...(persistentVar ? { persistentVar } : {}),
47
+ });
48
+ }
49
+ /**
50
+ * Declare an observable's shape, with no source.
51
+ *
52
+ * @param definition - Name, description, schema (or IR `valueType`), and `writable`.
53
+ */
54
+ function declareObservable(definition) {
55
+ checkObservableShape(definition, "declareObservable");
56
+ if ("source" in definition) {
57
+ throw new Error(`Observable ${definition.name}: declareObservable takes no source; implementContract supplies it`);
58
+ }
59
+ return Object.freeze({
60
+ kind: "observable",
61
+ definition: Object.freeze({ ...definition }),
62
+ });
63
+ }
64
+ /**
65
+ * True when `observable` has a source: from {@link defineObservable}, not
66
+ * {@link declareObservable}.
67
+ *
68
+ * @param observable - An observable from either.
69
+ */
70
+ function isImplementedObservable(observable) {
71
+ return typeof observable.definition.source === "function";
72
+ }
73
+ /** Check what every observable, declared or defined, needs: a name, a description, one form. */
74
+ function checkObservableShape(definition, what) {
75
+ if (typeof definition?.name !== "string" || definition.name.trim() === "") {
76
+ throw new Error(`${what} requires a name`);
77
+ }
78
+ const { name } = definition;
79
+ if (typeof definition.description !== "string") {
80
+ throw new Error(`Observable ${name} needs a description`);
81
+ }
82
+ const withSchema = "schema" in definition && definition.schema !== undefined;
83
+ const withType = "valueType" in definition && definition.valueType !== undefined;
84
+ if (withSchema === withType) {
85
+ throw new Error(`Observable ${name} needs either a schema or a valueType`);
86
+ }
87
+ if (withSchema &&
88
+ typeof definition.schema.safeParse !== "function") {
89
+ throw new Error(`Observable ${name} schema must be a zod schema`);
90
+ }
91
+ if (withType && typeof definition.valueType !== "string") {
92
+ throw new Error(`Observable ${name} valueType must be a FieldType`);
93
+ }
94
+ if (definition.writable !== undefined && typeof definition.writable !== "boolean") {
95
+ throw new Error(`Observable ${name} writable must be a boolean`);
96
+ }
97
+ }
98
+ /**
99
+ * True for an observable from {@link declareObservable} or {@link defineObservable}, as
100
+ * opposed to Peers IR. Use {@link isImplementedObservable} to check whether it has a source.
101
+ *
102
+ * @param value - An entry in a contract's `observables`.
103
+ */
104
+ function isPackageObservable(value) {
105
+ return value?.kind === "observable";
106
+ }
107
+ /**
108
+ * Publish each in-memory observable's value now and after every change, and take consumers'
109
+ * writes to the writable ones. Called by `startPackage`; variable-backed observables need
110
+ * nothing here: their member handlers read and write the package variable.
111
+ *
112
+ * @param provides - The package's provided contracts.
113
+ * @param ctx - The worker's context, bound to no call.
114
+ */
115
+ async function servePackageObservables(provides, ctx) {
116
+ const targets = new Map();
117
+ for (const contract of provides) {
118
+ for (const observable of contract.observables) {
119
+ if (isPackageObservable(observable) &&
120
+ isImplementedObservable(observable) &&
121
+ !observable.persistentVar) {
122
+ const list = targets.get(observable) ?? [];
123
+ list.push({ contractId: contract.contractId, version: contract.version });
124
+ targets.set(observable, list);
125
+ }
126
+ }
127
+ }
128
+ await Promise.all([...targets].map(([observable, list]) => serveObservable(observable, list, ctx)));
129
+ }
130
+ async function serveObservable(observable, targets, ctx) {
131
+ serving.get(observable)?.();
132
+ const { definition } = observable;
133
+ const { name, source } = definition;
134
+ const schema = "schema" in definition ? definition.schema : undefined;
135
+ /** True while a consumer's write is applied: that write publishes once, itself. */
136
+ let applying = false;
137
+ let sending = Promise.resolve();
138
+ // One at a time, so the host sees the values in the order they were set.
139
+ const publish = (value) => {
140
+ // Capture now, before waiting for earlier sends: the package may mutate this object again.
141
+ let stored;
142
+ try {
143
+ stored = (0, wire_value_1.snapshotPackageValue)((0, empty_fields_1.withoutEmptySchemaFields)(schema, value));
144
+ }
145
+ catch (error) {
146
+ return Promise.reject(error);
147
+ }
148
+ const sent = sending.then(async () => {
149
+ await Promise.all(targets.map((target) => ctx.publishOwnedObservable(target.contractId, target.version, name, stored)));
150
+ return stored;
151
+ });
152
+ sending = sent.then(() => undefined, () => undefined);
153
+ return sent;
154
+ };
155
+ const report = (error) => {
156
+ console.error(`Publishing observable "${name}" failed`, error);
157
+ };
158
+ const subscription = source.subscribe((value) => {
159
+ if (!applying) {
160
+ publish(value).catch(report);
161
+ }
162
+ });
163
+ serving.set(observable, () => subscription.dispose());
164
+ if (definition.writable) {
165
+ // Guest API 3 calls this from its member handler under the caller's authority; guest
166
+ // API 2 uses onOwnedObservableSet without a caller frame. Both parse before writing.
167
+ const write = async (value) => {
168
+ const next = schema ? schema.parse((0, empty_fields_1.withoutEmptySchemaFields)(schema, value)) : value;
169
+ applying = true;
170
+ try {
171
+ source(next);
172
+ }
173
+ finally {
174
+ applying = false;
175
+ }
176
+ // Publish what the source holds now: its own subscribers may have adjusted it.
177
+ return publish(source());
178
+ };
179
+ writers.set(observable, write);
180
+ for (const target of targets) {
181
+ ctx.onOwnedObservableSet(target.contractId, target.version, name, write);
182
+ }
183
+ }
184
+ // A successful start guarantees readers have a published value. Later background failures
185
+ // are logged, but a failed initial publish must fail startup rather than silently serve null.
186
+ await publish(source());
187
+ }
@@ -0,0 +1,21 @@
1
+ import type { IPackageContext } from "./context";
2
+ import type { IPackageContract } from "./define-package";
3
+ /** Registers a guest handler the host invokes by name with `(args, ctx)`. */
4
+ type RegisterHandler = (name: string, handler: (args: unknown, ctx: IPackageContext) => unknown) => void;
5
+ /**
6
+ * Serve one provided contract's tables, observables, and events from this worker (guest API 3):
7
+ * the host sends every consumer call on them here, under the caller's authority, and passes
8
+ * every change, event, and observable value bound for a subscriber through here first, under
9
+ * that subscriber's. Tools have their own handlers.
10
+ *
11
+ * Tables answer through their class (`PackageTable` or the package's subclass), so an override
12
+ * is the logic every caller gets. Observables read and write their source (or, for IR, the package variable they
13
+ * name). Events pass through unchanged. Rows passed to an override, returned, or sent, and
14
+ * observable values leave out the empty fields their schema lets them leave out, as stored
15
+ * rows and reads do. Subscriptions are accepted.
16
+ *
17
+ * @param contract - A contract the package provides.
18
+ * @param register - The worker's `registerHandler`.
19
+ */
20
+ export declare function serveContractMembers(contract: IPackageContract, register: RegisterHandler): void;
21
+ export {};
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serveContractMembers = serveContractMembers;
4
+ const contract_members_1 = require("../package-loader/contract-members");
5
+ const empty_fields_1 = require("./empty-fields");
6
+ const observables_1 = require("./observables");
7
+ const vars_1 = require("./vars");
8
+ const wire_value_1 = require("./wire-value");
9
+ const TABLE_METHODS = new Set(["get", "list", "count", "findOne", "save", "delete"]);
10
+ const SUBSCRIPTION_OPERATIONS = new Set(["subscribe", "unsubscribe"]);
11
+ /**
12
+ * Serve one provided contract's tables, observables, and events from this worker (guest API 3):
13
+ * the host sends every consumer call on them here, under the caller's authority, and passes
14
+ * every change, event, and observable value bound for a subscriber through here first, under
15
+ * that subscriber's. Tools have their own handlers.
16
+ *
17
+ * Tables answer through their class (`PackageTable` or the package's subclass), so an override
18
+ * is the logic every caller gets. Observables read and write their source (or, for IR, the package variable they
19
+ * name). Events pass through unchanged. Rows passed to an override, returned, or sent, and
20
+ * observable values leave out the empty fields their schema lets them leave out, as stored
21
+ * rows and reads do. Subscriptions are accepted.
22
+ *
23
+ * @param contract - A contract the package provides.
24
+ * @param register - The worker's `registerHandler`.
25
+ */
26
+ function serveContractMembers(contract, register) {
27
+ const tables = new Map();
28
+ for (const table of contract.tables)
29
+ tables.set(table.definition.name, table);
30
+ const observables = new Map();
31
+ for (const observable of contract.observables) {
32
+ observables.set((0, observables_1.isPackageObservable)(observable) ? observable.definition.name : observable.name, observable);
33
+ }
34
+ const events = new Set(contract.events.map((event) => ("definition" in event ? event.definition.name : event.name)));
35
+ const label = `${contract.name} (${contract.contractId}@${contract.version})`;
36
+ register((0, contract_members_1.contractMemberHandlerName)(contract.contractId, contract.version), async (raw, ctx) => {
37
+ const call = raw;
38
+ const args = Array.isArray(call.args) ? call.args : [];
39
+ if (call.member === "table") {
40
+ const table = tables.get(call.name);
41
+ if (!table)
42
+ throw new Error(`${label} has no table '${call.name}'`);
43
+ if (SUBSCRIPTION_OPERATIONS.has(call.operation))
44
+ return null;
45
+ if (!TABLE_METHODS.has(call.operation)) {
46
+ throw new Error(`Table method '${call.name}.${call.operation}' is not callable`);
47
+ }
48
+ const instance = table(ctx);
49
+ // An override sees the same missing fields as super.save, before it runs package code.
50
+ const input = call.operation === "save"
51
+ ? [(0, empty_fields_1.withoutEmptySchemaFields)(table.definition.schema, args[0]), ...args.slice(1)]
52
+ : args;
53
+ // An override's reply crosses the bridge, which carries no undefined fields, and leaves
54
+ // out empty fields as stored rows do.
55
+ const result = await instance[call.operation](...input);
56
+ return result === undefined ? null : (0, wire_value_1.snapshotPackageValue)(withoutEmptyRows(table, result));
57
+ }
58
+ if (call.member === "observable") {
59
+ const observable = observables.get(call.name);
60
+ if (!observable)
61
+ throw new Error(`${label} has no observable '${call.name}'`);
62
+ if (SUBSCRIPTION_OPERATIONS.has(call.operation))
63
+ return null;
64
+ if (call.operation === "get")
65
+ return readObservable(observable, ctx);
66
+ if (call.operation === "set")
67
+ return writeObservable(observable, args[0], ctx);
68
+ throw new Error(`Unsupported observable operation '${call.operation}' on '${call.name}'`);
69
+ }
70
+ if (call.member === "event") {
71
+ if (!events.has(call.name))
72
+ throw new Error(`${label} has no event '${call.name}'`);
73
+ if (SUBSCRIPTION_OPERATIONS.has(call.operation))
74
+ return null;
75
+ throw new Error(`Unsupported event operation '${call.operation}' on '${call.name}'`);
76
+ }
77
+ throw new Error(`Unsupported contract member '${String(call.member)}'`);
78
+ });
79
+ register((0, contract_members_1.contractDeliveryHandlerName)(contract.contractId, contract.version), async (raw, ctx) => {
80
+ const delivery = raw;
81
+ if (delivery.member === "table") {
82
+ const table = tables.get(delivery.name);
83
+ if (!table)
84
+ throw new Error(`${label} has no table '${delivery.name}'`);
85
+ const change = delivery.value;
86
+ const event = await table(ctx).dataChanged({
87
+ ...change,
88
+ dataObject: withoutEmptyRows(table, change.dataObject),
89
+ });
90
+ if (event === undefined || event === null)
91
+ return { send: false };
92
+ const dataObject = withoutEmptyRows(table, event.dataObject);
93
+ return { send: true, value: (0, wire_value_1.snapshotPackageValue)({ ...event, dataObject }) };
94
+ }
95
+ if (delivery.member === "observable") {
96
+ // A package variable's value comes from storage, which the package's own code or an
97
+ // older build may have written with empty fields: send it as a read returns it.
98
+ const observable = observables.get(delivery.name);
99
+ const schema = observable && (0, observables_1.isPackageObservable)(observable) && "schema" in observable.definition
100
+ ? observable.definition.schema
101
+ : undefined;
102
+ return { send: true, value: (0, empty_fields_1.withoutEmptySchemaFields)(schema, delivery.value) };
103
+ }
104
+ return { send: true, value: delivery.value };
105
+ });
106
+ }
107
+ /** A table reply (a row or a list of rows) without the empty fields its schema lets a row leave out. */
108
+ function withoutEmptyRows(table, value) {
109
+ const { schema } = table.definition;
110
+ return Array.isArray(value)
111
+ ? value.map((row) => (0, empty_fields_1.withoutEmptySchemaFields)(schema, row))
112
+ : (0, empty_fields_1.withoutEmptySchemaFields)(schema, value);
113
+ }
114
+ /** The value a consumer reads: the source the package keeps, or the IR variable's stored value. */
115
+ async function readObservable(observable, ctx) {
116
+ if ((0, observables_1.isPackageObservable)(observable)) {
117
+ const { definition } = observable;
118
+ const source = definition.source;
119
+ const schema = "schema" in definition ? definition.schema : undefined;
120
+ // A package variable may still be loading; its first value then comes from storage.
121
+ const value = observable.persistentVar ? await (0, vars_1.readPackageVar)(source) : source();
122
+ return (0, wire_value_1.snapshotPackageValue)((0, empty_fields_1.withoutEmptySchemaFields)(schema, value));
123
+ }
124
+ const binding = observable.persistentVar;
125
+ if (!binding)
126
+ throw new Error(`Observable '${observable.name}' has no source`);
127
+ const stored = await ctx.getOwnedPersistentVar(binding.scope, binding.name);
128
+ return stored === undefined || stored === null ? binding.defaultValue : stored;
129
+ }
130
+ /** A consumer's write, after the host checked that it's writable and fits the value type. */
131
+ async function writeObservable(observable, value, ctx) {
132
+ if (!(0, observables_1.isPackageObservable)(observable)) {
133
+ const binding = observable.persistentVar;
134
+ if (!binding)
135
+ throw new Error(`Observable '${observable.name}' has no source`);
136
+ await ctx.setOwnedPersistentVar(binding.scope, binding.name, value);
137
+ return value;
138
+ }
139
+ const { definition } = observable;
140
+ if (!definition.writable)
141
+ throw new Error(`Observable '${definition.name}' is read-only`);
142
+ if (!observable.persistentVar) {
143
+ const write = (0, observables_1.inMemoryObservableWriter)(observable);
144
+ if (!write)
145
+ throw new Error(`Observable '${definition.name}' isn't served yet`);
146
+ return write(value);
147
+ }
148
+ const source = definition.source;
149
+ const schema = "schema" in definition ? definition.schema : undefined;
150
+ const next = schema ? schema.parse((0, empty_fields_1.withoutEmptySchemaFields)(schema, value)) : value;
151
+ return (0, wire_value_1.snapshotPackageValue)(await (0, vars_1.writePackageVar)(source, next));
152
+ }
@@ -0,0 +1 @@
1
+ export {};