@marianmeres/ownsuite 1.0.1

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.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @module domains/base
3
+ *
4
+ * Base domain manager. Provides reactive state, state-machine transitions,
5
+ * optimistic update pattern, and event emission. Mirrors the shape of
6
+ * `@marianmeres/ecsuite`'s `BaseDomainManager` so consumers already familiar
7
+ * with ecsuite can read/subscribe to ownsuite domains identically.
8
+ */
9
+ import { type Clog } from "@marianmeres/clog";
10
+ import { type StoreLike } from "@marianmeres/store";
11
+ import { type PubSub } from "@marianmeres/pubsub";
12
+ import type { DomainError, DomainState, DomainStateWrapper, OwnsuiteContext } from "../types/state.js";
13
+ import type { DomainName, OwnsuiteEvent } from "../types/events.js";
14
+ /** Base options for domain managers. */
15
+ export interface BaseDomainOptions {
16
+ /** Initial context passed to adapters. */
17
+ context?: OwnsuiteContext;
18
+ /** Shared pubsub instance for events. */
19
+ pubsub?: PubSub;
20
+ }
21
+ /**
22
+ * Abstract base class for ownsuite domain managers.
23
+ *
24
+ * @typeParam TData - The domain data shape (for ownsuite, typically `OwnedCollectionState<TRow>`).
25
+ * @typeParam TAdapter - The adapter interface type for server communication.
26
+ */
27
+ export declare abstract class BaseDomainManager<TData, TAdapter> {
28
+ protected readonly store: StoreLike<DomainStateWrapper<TData>>;
29
+ protected readonly pubsub: PubSub;
30
+ protected readonly domainName: DomainName;
31
+ protected readonly clog: Clog;
32
+ protected adapter: TAdapter | null;
33
+ protected context: OwnsuiteContext;
34
+ constructor(domainName: DomainName, options?: BaseDomainOptions);
35
+ /** Svelte-compatible subscribe method. */
36
+ get subscribe(): StoreLike<DomainStateWrapper<TData>>["subscribe"];
37
+ /** Get current state synchronously. */
38
+ get(): DomainStateWrapper<TData>;
39
+ setAdapter(adapter: TAdapter): void;
40
+ getAdapter(): TAdapter | null;
41
+ setContext(context: OwnsuiteContext): void;
42
+ getContext(): OwnsuiteContext;
43
+ /** Transition to a new state. */
44
+ protected setState(state: DomainState): void;
45
+ /** Update data and optionally flip to ready. */
46
+ protected setData(data: TData, markReady?: boolean): void;
47
+ /** Set error state. */
48
+ protected setError(error: DomainError): void;
49
+ /** Mark as synced. */
50
+ protected markSynced(): void;
51
+ /** Emit an event via pubsub. */
52
+ protected emit(event: OwnsuiteEvent): void;
53
+ /**
54
+ * Execute an async operation with the optimistic-update pattern:
55
+ * 1. capture current data for rollback
56
+ * 2. apply optimistic update immediately
57
+ * 3. flip to "syncing"
58
+ * 4. on success: mark synced, call onSuccess
59
+ * 5. on error: restore previous data, set error, call onError
60
+ */
61
+ protected withOptimisticUpdate<T>(operation: string, optimisticUpdate: () => void, serverSync: () => Promise<T>, onSuccess?: (result: T) => void, onError?: (error: DomainError) => void): Promise<void>;
62
+ abstract initialize(): Promise<void>;
63
+ reset(): void;
64
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @module domains/base
3
+ *
4
+ * Base domain manager. Provides reactive state, state-machine transitions,
5
+ * optimistic update pattern, and event emission. Mirrors the shape of
6
+ * `@marianmeres/ecsuite`'s `BaseDomainManager` so consumers already familiar
7
+ * with ecsuite can read/subscribe to ownsuite domains identically.
8
+ */
9
+ import { createClog } from "@marianmeres/clog";
10
+ import { createStore } from "@marianmeres/store";
11
+ import { createPubSub } from "@marianmeres/pubsub";
12
+ /**
13
+ * Abstract base class for ownsuite domain managers.
14
+ *
15
+ * @typeParam TData - The domain data shape (for ownsuite, typically `OwnedCollectionState<TRow>`).
16
+ * @typeParam TAdapter - The adapter interface type for server communication.
17
+ */
18
+ export class BaseDomainManager {
19
+ store;
20
+ pubsub;
21
+ domainName;
22
+ clog;
23
+ adapter = null;
24
+ context = {};
25
+ constructor(domainName, options = {}) {
26
+ this.domainName = domainName;
27
+ this.clog = createClog(`ownsuite:${domainName}`, { color: "auto" });
28
+ this.pubsub = options.pubsub ?? createPubSub();
29
+ this.context = options.context ?? {};
30
+ const initialState = {
31
+ state: "initializing",
32
+ data: null,
33
+ error: null,
34
+ lastSyncedAt: null,
35
+ };
36
+ this.store = createStore(initialState);
37
+ }
38
+ /** Svelte-compatible subscribe method. */
39
+ get subscribe() {
40
+ return this.store.subscribe;
41
+ }
42
+ /** Get current state synchronously. */
43
+ get() {
44
+ return this.store.get();
45
+ }
46
+ setAdapter(adapter) {
47
+ this.adapter = adapter;
48
+ }
49
+ getAdapter() {
50
+ return this.adapter;
51
+ }
52
+ setContext(context) {
53
+ this.context = { ...this.context, ...context };
54
+ }
55
+ getContext() {
56
+ return { ...this.context };
57
+ }
58
+ /** Transition to a new state. */
59
+ setState(state) {
60
+ const current = this.store.get();
61
+ if (current.state !== state) {
62
+ this.store.update((s) => ({ ...s, state }));
63
+ this.emit({
64
+ type: "domain:state:changed",
65
+ domain: this.domainName,
66
+ timestamp: Date.now(),
67
+ previousState: current.state,
68
+ newState: state,
69
+ });
70
+ }
71
+ }
72
+ /** Update data and optionally flip to ready. */
73
+ setData(data, markReady = true) {
74
+ this.store.update((s) => ({
75
+ ...s,
76
+ data,
77
+ state: markReady ? "ready" : s.state,
78
+ error: null,
79
+ }));
80
+ }
81
+ /** Set error state. */
82
+ setError(error) {
83
+ this.clog.error("error", {
84
+ code: error.code,
85
+ message: error.message,
86
+ operation: error.operation,
87
+ });
88
+ this.store.update((s) => ({ ...s, state: "error", error }));
89
+ this.emit({
90
+ type: "domain:error",
91
+ domain: this.domainName,
92
+ timestamp: Date.now(),
93
+ error,
94
+ });
95
+ }
96
+ /** Mark as synced. */
97
+ markSynced() {
98
+ this.store.update((s) => ({
99
+ ...s,
100
+ state: "ready",
101
+ lastSyncedAt: Date.now(),
102
+ }));
103
+ this.emit({
104
+ type: "domain:synced",
105
+ domain: this.domainName,
106
+ timestamp: Date.now(),
107
+ });
108
+ }
109
+ /** Emit an event via pubsub. */
110
+ emit(event) {
111
+ this.pubsub.publish(event.type, event);
112
+ }
113
+ /**
114
+ * Execute an async operation with the optimistic-update pattern:
115
+ * 1. capture current data for rollback
116
+ * 2. apply optimistic update immediately
117
+ * 3. flip to "syncing"
118
+ * 4. on success: mark synced, call onSuccess
119
+ * 5. on error: restore previous data, set error, call onError
120
+ */
121
+ async withOptimisticUpdate(operation, optimisticUpdate, serverSync, onSuccess, onError) {
122
+ const previousData = this.store.get().data;
123
+ optimisticUpdate();
124
+ this.setState("syncing");
125
+ try {
126
+ const result = await serverSync();
127
+ this.markSynced();
128
+ onSuccess?.(result);
129
+ }
130
+ catch (e) {
131
+ if (previousData !== null)
132
+ this.setData(previousData, false);
133
+ const error = {
134
+ code: "SYNC_FAILED",
135
+ message: e instanceof Error ? e.message : "Unknown error",
136
+ originalError: e,
137
+ operation,
138
+ };
139
+ this.setError(error);
140
+ onError?.(error);
141
+ }
142
+ }
143
+ reset() {
144
+ this.store.set({
145
+ state: "initializing",
146
+ data: null,
147
+ error: null,
148
+ lastSyncedAt: null,
149
+ });
150
+ }
151
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./base.js";
2
+ export * from "./owned-collection.js";
@@ -0,0 +1,2 @@
1
+ export * from "./base.js";
2
+ export * from "./owned-collection.js";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module domains/owned-collection
3
+ *
4
+ * Generic manager for a single owner-scoped collection domain. Operates on
5
+ * any row shape `TRow` via an injected `OwnedCollectionAdapter`. All CRUD
6
+ * operations are implicitly scoped to the authenticated subject by the
7
+ * server — the client never sets `owner_id`.
8
+ */
9
+ import type { OwnedCollectionAdapter } from "../types/adapter.js";
10
+ import type { OwnedCollectionState } from "../types/state.js";
11
+ import { BaseDomainManager, type BaseDomainOptions } from "./base.js";
12
+ export interface OwnedCollectionManagerOptions<TRow, TCreate, TUpdate> extends BaseDomainOptions {
13
+ adapter?: OwnedCollectionAdapter<TRow, TCreate, TUpdate>;
14
+ /** Function that extracts the row id from a row. Defaults to `row.model_id`. */
15
+ getRowId?: (row: TRow) => string;
16
+ }
17
+ /**
18
+ * Generic domain manager for one owner-scoped collection.
19
+ *
20
+ * State shape: `{ rows: TRow[]; meta: {...} }`. List operations replace
21
+ * rows wholesale; single-row operations (create/update/delete) apply
22
+ * in-place mutations so the list stays stable without a re-fetch.
23
+ */
24
+ export declare class OwnedCollectionManager<TRow = Record<string, unknown>, TCreate = Partial<TRow>, TUpdate = Partial<TRow>> extends BaseDomainManager<OwnedCollectionState<TRow>, OwnedCollectionAdapter<TRow, TCreate, TUpdate>> {
25
+ #private;
26
+ constructor(domainName: string, options?: OwnedCollectionManagerOptions<TRow, TCreate, TUpdate>);
27
+ /** Initialize by fetching the list from the server. */
28
+ initialize(): Promise<void>;
29
+ /** Refresh the list from server. Same as initialize but re-entrant. */
30
+ refresh(query?: Record<string, unknown>): Promise<void>;
31
+ /** Fetch a single row by id; returns the row but does not update the list. */
32
+ getOne(id: string): Promise<TRow | null>;
33
+ /** Create a new row. Optimistically prepends to the list. */
34
+ create(data: TCreate): Promise<TRow | null>;
35
+ /** Update a row. Optimistically merges into the list. */
36
+ update(id: string, data: TUpdate): Promise<TRow | null>;
37
+ /** Delete a row. Optimistically removes from the list. */
38
+ delete(id: string): Promise<boolean>;
39
+ /** Snapshot of current rows (empty array if not loaded). */
40
+ getRows(): TRow[];
41
+ /** Find a row by id in the current list (without hitting the server). */
42
+ findRow(id: string): TRow | undefined;
43
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * @module domains/owned-collection
3
+ *
4
+ * Generic manager for a single owner-scoped collection domain. Operates on
5
+ * any row shape `TRow` via an injected `OwnedCollectionAdapter`. All CRUD
6
+ * operations are implicitly scoped to the authenticated subject by the
7
+ * server — the client never sets `owner_id`.
8
+ */
9
+ import { BaseDomainManager } from "./base.js";
10
+ const defaultGetRowId = (row) => {
11
+ const r = row;
12
+ const id = r.model_id ?? r.id;
13
+ if (typeof id !== "string") {
14
+ throw new Error("OwnedCollectionManager: row has no string `model_id` or `id`; " +
15
+ "pass a custom `getRowId` in options.");
16
+ }
17
+ return id;
18
+ };
19
+ /**
20
+ * Generic domain manager for one owner-scoped collection.
21
+ *
22
+ * State shape: `{ rows: TRow[]; meta: {...} }`. List operations replace
23
+ * rows wholesale; single-row operations (create/update/delete) apply
24
+ * in-place mutations so the list stays stable without a re-fetch.
25
+ */
26
+ export class OwnedCollectionManager extends BaseDomainManager {
27
+ #getRowId;
28
+ constructor(domainName, options = {}) {
29
+ super(domainName, options);
30
+ this.#getRowId = options.getRowId ?? defaultGetRowId;
31
+ if (options.adapter)
32
+ this.adapter = options.adapter;
33
+ }
34
+ /** Initialize by fetching the list from the server. */
35
+ async initialize() {
36
+ if (!this.adapter) {
37
+ this.setState("ready");
38
+ return;
39
+ }
40
+ this.setState("syncing");
41
+ try {
42
+ const res = await this.adapter.list(this.context);
43
+ this.setData({ rows: res.data, meta: res.meta });
44
+ this.markSynced();
45
+ this.emit({
46
+ type: "own:list:fetched",
47
+ domain: this.domainName,
48
+ timestamp: Date.now(),
49
+ count: res.data.length,
50
+ });
51
+ }
52
+ catch (e) {
53
+ this.setError({
54
+ code: "FETCH_FAILED",
55
+ message: e instanceof Error ? e.message : "Failed to fetch list",
56
+ originalError: e,
57
+ operation: "initialize",
58
+ });
59
+ }
60
+ }
61
+ /** Refresh the list from server. Same as initialize but re-entrant. */
62
+ async refresh(query) {
63
+ if (!this.adapter)
64
+ return;
65
+ this.setState("syncing");
66
+ try {
67
+ const res = await this.adapter.list(this.context, query);
68
+ this.setData({ rows: res.data, meta: res.meta });
69
+ this.markSynced();
70
+ this.emit({
71
+ type: "own:list:fetched",
72
+ domain: this.domainName,
73
+ timestamp: Date.now(),
74
+ count: res.data.length,
75
+ });
76
+ }
77
+ catch (e) {
78
+ this.setError({
79
+ code: "FETCH_FAILED",
80
+ message: e instanceof Error ? e.message : "Failed to refresh list",
81
+ originalError: e,
82
+ operation: "refresh",
83
+ });
84
+ }
85
+ }
86
+ /** Fetch a single row by id; returns the row but does not update the list. */
87
+ async getOne(id) {
88
+ if (!this.adapter)
89
+ return null;
90
+ try {
91
+ const res = await this.adapter.getOne(id, this.context);
92
+ this.emit({
93
+ type: "own:row:fetched",
94
+ domain: this.domainName,
95
+ timestamp: Date.now(),
96
+ rowId: id,
97
+ });
98
+ return res.data;
99
+ }
100
+ catch (e) {
101
+ this.setError({
102
+ code: "FETCH_FAILED",
103
+ message: e instanceof Error ? e.message : "Failed to fetch row",
104
+ originalError: e,
105
+ operation: "getOne",
106
+ });
107
+ return null;
108
+ }
109
+ }
110
+ /** Create a new row. Optimistically prepends to the list. */
111
+ async create(data) {
112
+ if (!this.adapter)
113
+ return null;
114
+ const current = this.store.get().data ?? { rows: [], meta: {} };
115
+ let result = null;
116
+ await this.withOptimisticUpdate("create", () => {
117
+ // No optimistic row insertion: we don't know the server-assigned id.
118
+ // Just keep the list unchanged; flip to syncing is handled for us.
119
+ }, async () => {
120
+ const res = await this.adapter.create(data, this.context);
121
+ return res.data;
122
+ }, (serverRow) => {
123
+ result = serverRow;
124
+ this.setData({
125
+ rows: [serverRow, ...current.rows],
126
+ meta: current.meta,
127
+ });
128
+ this.emit({
129
+ type: "own:row:created",
130
+ domain: this.domainName,
131
+ timestamp: Date.now(),
132
+ rowId: this.#getRowId(serverRow),
133
+ });
134
+ });
135
+ return result;
136
+ }
137
+ /** Update a row. Optimistically merges into the list. */
138
+ async update(id, data) {
139
+ if (!this.adapter)
140
+ return null;
141
+ const current = this.store.get().data ?? { rows: [], meta: {} };
142
+ const idx = current.rows.findIndex((r) => this.#getRowId(r) === id);
143
+ let result = null;
144
+ await this.withOptimisticUpdate("update", () => {
145
+ if (idx === -1)
146
+ return;
147
+ const optimistic = { ...current.rows[idx], ...data };
148
+ const rows = current.rows.slice();
149
+ rows[idx] = optimistic;
150
+ this.setData({ rows, meta: current.meta }, false);
151
+ }, async () => {
152
+ const res = await this.adapter.update(id, data, this.context);
153
+ return res.data;
154
+ }, (serverRow) => {
155
+ result = serverRow;
156
+ const rows = idx === -1
157
+ ? [serverRow, ...current.rows]
158
+ : current.rows.map((r, i) => (i === idx ? serverRow : r));
159
+ this.setData({ rows, meta: current.meta });
160
+ this.emit({
161
+ type: "own:row:updated",
162
+ domain: this.domainName,
163
+ timestamp: Date.now(),
164
+ rowId: id,
165
+ });
166
+ });
167
+ return result;
168
+ }
169
+ /** Delete a row. Optimistically removes from the list. */
170
+ async delete(id) {
171
+ if (!this.adapter)
172
+ return false;
173
+ const current = this.store.get().data ?? { rows: [], meta: {} };
174
+ let ok = false;
175
+ await this.withOptimisticUpdate("delete", () => {
176
+ this.setData({
177
+ rows: current.rows.filter((r) => this.#getRowId(r) !== id),
178
+ meta: current.meta,
179
+ }, false);
180
+ }, async () => {
181
+ return await this.adapter.delete(id, this.context);
182
+ }, (serverOk) => {
183
+ ok = serverOk;
184
+ this.emit({
185
+ type: "own:row:deleted",
186
+ domain: this.domainName,
187
+ timestamp: Date.now(),
188
+ rowId: id,
189
+ });
190
+ });
191
+ return ok;
192
+ }
193
+ /** Snapshot of current rows (empty array if not loaded). */
194
+ getRows() {
195
+ return this.store.get().data?.rows ?? [];
196
+ }
197
+ /** Find a row by id in the current list (without hitting the server). */
198
+ findRow(id) {
199
+ return this.getRows().find((r) => this.#getRowId(r) === id);
200
+ }
201
+ }
package/dist/mod.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module @marianmeres/ownsuite
3
+ *
4
+ * Client-side helper library for owner-scoped UIs. Generic domain managers
5
+ * with optimistic updates, Svelte-compatible stores, and adapter-based
6
+ * server sync — mirrors the shape of `@marianmeres/ecsuite` but applies to
7
+ * arbitrary owner-scoped collections instead of hard-coded e-commerce
8
+ * domains.
9
+ *
10
+ * Pairs with the server-side `ownsuite` module in
11
+ * `@marianmeres/stack-common` and the `ownerIdScope` hook in
12
+ * `@marianmeres/collection`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { createOwnsuite, createMockOwnedCollectionAdapter } from "@marianmeres/ownsuite";
17
+ *
18
+ * const suite = createOwnsuite({
19
+ * domains: {
20
+ * orders: { adapter: myOrdersAdapter },
21
+ * },
22
+ * });
23
+ * await suite.initialize();
24
+ * suite.domain("orders").subscribe((s) => render(s.data?.rows));
25
+ * await suite.domain("orders").create({ data: { total: 99 } });
26
+ * ```
27
+ */
28
+ export * from "./ownsuite.js";
29
+ export * from "./domains/mod.js";
30
+ export * from "./types/mod.js";
31
+ export * from "./adapters/mod.js";
package/dist/mod.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module @marianmeres/ownsuite
3
+ *
4
+ * Client-side helper library for owner-scoped UIs. Generic domain managers
5
+ * with optimistic updates, Svelte-compatible stores, and adapter-based
6
+ * server sync — mirrors the shape of `@marianmeres/ecsuite` but applies to
7
+ * arbitrary owner-scoped collections instead of hard-coded e-commerce
8
+ * domains.
9
+ *
10
+ * Pairs with the server-side `ownsuite` module in
11
+ * `@marianmeres/stack-common` and the `ownerIdScope` hook in
12
+ * `@marianmeres/collection`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { createOwnsuite, createMockOwnedCollectionAdapter } from "@marianmeres/ownsuite";
17
+ *
18
+ * const suite = createOwnsuite({
19
+ * domains: {
20
+ * orders: { adapter: myOrdersAdapter },
21
+ * },
22
+ * });
23
+ * await suite.initialize();
24
+ * suite.domain("orders").subscribe((s) => render(s.data?.rows));
25
+ * await suite.domain("orders").create({ data: { total: 99 } });
26
+ * ```
27
+ */
28
+ export * from "./ownsuite.js";
29
+ export * from "./domains/mod.js";
30
+ export * from "./types/mod.js";
31
+ export * from "./adapters/mod.js";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @module ownsuite
3
+ *
4
+ * Ownsuite — orchestrator for owner-scoped domain managers.
5
+ *
6
+ * Unlike ecsuite (which hard-codes six e-commerce domains), ownsuite is
7
+ * generic: consumers register arbitrary owner-scoped collection domains by
8
+ * name. Each domain operates through an adapter that talks to a server
9
+ * mount (conventionally `/me/<collection-path>`) with owner scoping
10
+ * enforced by the server via `@marianmeres/collection`'s `ownerIdScope`
11
+ * hook and `@marianmeres/stack-common`'s `ownsuiteOptions()` helper.
12
+ */
13
+ import { type Subscriber, type Unsubscriber } from "@marianmeres/pubsub";
14
+ import type { OwnsuiteContext } from "./types/state.js";
15
+ import type { OwnedCollectionAdapter } from "./types/adapter.js";
16
+ import type { OwnsuiteEventType } from "./types/events.js";
17
+ import { OwnedCollectionManager } from "./domains/owned-collection.js";
18
+ /**
19
+ * Configuration for a single domain at construction time. The caller
20
+ * provides a unique name and an adapter. A custom `getRowId` is optional
21
+ * (defaults to reading `row.model_id` or `row.id`).
22
+ */
23
+ export interface OwnsuiteDomainConfig<TRow = any, TCreate = any, TUpdate = any> {
24
+ adapter: OwnedCollectionAdapter<TRow, TCreate, TUpdate>;
25
+ getRowId?: (row: TRow) => string;
26
+ }
27
+ /** Top-level ownsuite configuration. */
28
+ export interface OwnsuiteConfig {
29
+ /** Initial context passed to every adapter call. */
30
+ context?: OwnsuiteContext;
31
+ /** Domain registry. Keys are domain names (arbitrary labels). */
32
+ domains?: Record<string, OwnsuiteDomainConfig>;
33
+ /** Auto-initialize all registered domains on creation (default: false). */
34
+ autoInitialize?: boolean;
35
+ }
36
+ /**
37
+ * Main Ownsuite class — coordinates owner-scoped domain managers.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const suite = createOwnsuite({
42
+ * context: { subjectId: "user-123" },
43
+ * domains: {
44
+ * orders: { adapter: myOrdersAdapter },
45
+ * addresses: { adapter: myAddressesAdapter },
46
+ * },
47
+ * });
48
+ * await suite.initialize(); // or pass autoInitialize: true
49
+ *
50
+ * suite.domain("orders").subscribe((s) => console.log(s.state, s.data?.rows));
51
+ * await suite.domain("orders").create({ data: { ... } });
52
+ * ```
53
+ */
54
+ export declare class Ownsuite {
55
+ #private;
56
+ constructor(config?: OwnsuiteConfig);
57
+ /** Register a new domain after construction. */
58
+ registerDomain<TRow = any, TCreate = any, TUpdate = any>(name: string, cfg: OwnsuiteDomainConfig<TRow, TCreate, TUpdate>): OwnedCollectionManager<TRow, TCreate, TUpdate>;
59
+ /** Look up a domain manager by name. Throws if unknown. */
60
+ domain<TRow = any, TCreate = any, TUpdate = any>(name: string): OwnedCollectionManager<TRow, TCreate, TUpdate>;
61
+ /** True if a domain by this name is registered. */
62
+ hasDomain(name: string): boolean;
63
+ /** List registered domain names. */
64
+ domainNames(): string[];
65
+ /**
66
+ * Initialize all registered domains (or a subset). Runs in parallel.
67
+ * Individual domain errors land in that domain's error state — they
68
+ * do not reject the overall promise.
69
+ */
70
+ initialize(names?: string[]): Promise<void>;
71
+ /** Update shared context and propagate to all domain managers. */
72
+ setContext(ctx: OwnsuiteContext): void;
73
+ getContext(): OwnsuiteContext;
74
+ /** Subscribe to a specific event type. */
75
+ on(type: OwnsuiteEventType, subscriber: Subscriber): Unsubscriber;
76
+ /**
77
+ * Subscribe to all events. Wildcard subscribers receive an envelope
78
+ * `{ event: string, data: OwnsuiteEvent }` — see `@marianmeres/pubsub`.
79
+ */
80
+ onAny(subscriber: Subscriber): Unsubscriber;
81
+ /** Reset all domains to initializing state. */
82
+ reset(): void;
83
+ }
84
+ /** Convenience factory matching the ecsuite `createECSuite` convention. */
85
+ export declare function createOwnsuite(config?: OwnsuiteConfig): Ownsuite;