@owlmeans/state 0.1.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.
@@ -0,0 +1,208 @@
1
+ import { appendContextual } from '@owlmeans/context';
2
+ import { MisshapedRecord, RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource';
3
+ import { DEFAULT_ALIAS, DEFAULT_ID } from './consts';
4
+ import { StateListenerError } from './errors.js';
5
+ import { createStateModel } from './utils/model.js';
6
+ export const createStateResource = (alias = DEFAULT_ALIAS) => {
7
+ const location = `state-resource:${alias}`;
8
+ const store = {};
9
+ const listeners = [];
10
+ const recordToListener = new Map();
11
+ const listenerToRecord = new Map();
12
+ const globalListeners = [];
13
+ const systemToListeners = {};
14
+ const notifyGlobalListenrs = (records) => {
15
+ globalListeners.forEach(listener => listener(records.map(record => createStateModel(record, resource))));
16
+ };
17
+ const _usubscribe = (params) => () => {
18
+ const index = listeners.indexOf(params.listener);
19
+ if (index >= 0) {
20
+ const ids = listenerToRecord.get(params.listener);
21
+ listeners.splice(index, 1);
22
+ listenerToRecord.delete(params.listener);
23
+ ids?.forEach(id => {
24
+ const listeners = recordToListener.get(id);
25
+ if (listeners != null) {
26
+ listeners.delete(params.listener);
27
+ if (listeners.size === 0) {
28
+ recordToListener.delete(id);
29
+ }
30
+ }
31
+ });
32
+ }
33
+ Object.entries(systemToListeners).some(([key, listener]) => {
34
+ if (listener === params.listener) {
35
+ delete systemToListeners[key];
36
+ return true;
37
+ }
38
+ return false;
39
+ });
40
+ };
41
+ const resource = appendContextual(alias, {
42
+ get: async (id, field, opts) => {
43
+ const record = await resource.load(id, field, opts);
44
+ if (record == null) {
45
+ throw new UnknownRecordError(id);
46
+ }
47
+ return record;
48
+ },
49
+ load: async (id, field, opts) => {
50
+ if (field != null) {
51
+ throw new UnsupportedArgumentError(`${location}:get:filed`);
52
+ }
53
+ if (opts != null) {
54
+ throw new UnsupportedArgumentError(`${location}:get:opts`);
55
+ }
56
+ const record = store[id];
57
+ if (record == null) {
58
+ return null;
59
+ }
60
+ return record;
61
+ },
62
+ list: async (criteria, opts) => {
63
+ if (criteria != null) {
64
+ throw new UnsupportedArgumentError(`${location}:list:criteria`);
65
+ }
66
+ if (opts != null) {
67
+ throw new UnsupportedArgumentError(`${location}:list:opts`);
68
+ }
69
+ const result = {
70
+ items: Object.entries(store).map(([, record]) => record)
71
+ };
72
+ return result;
73
+ },
74
+ save: async (record, opts) => {
75
+ const id = record.id ?? DEFAULT_ID;
76
+ if (store[id] != null) {
77
+ return resource.update(record, opts);
78
+ }
79
+ return resource.create(record, opts);
80
+ },
81
+ create: async (record, opts) => {
82
+ if (!("id" in record) || record.id == null) {
83
+ record.id = DEFAULT_ID;
84
+ }
85
+ if (store[record.id] != null) {
86
+ throw new RecordExists(record.id);
87
+ }
88
+ if (opts != null) {
89
+ throw new UnsupportedArgumentError(`${location}:create:opts`);
90
+ }
91
+ store[record.id] = record;
92
+ notifyGlobalListenrs([store[record.id]]);
93
+ return record;
94
+ },
95
+ update: async (record, opts) => {
96
+ if (opts != null) {
97
+ throw new UnsupportedArgumentError(`${location}:update:opts`);
98
+ }
99
+ if (!("id" in record) || record.id == null) {
100
+ record.id = DEFAULT_ID;
101
+ }
102
+ const reference = store[record.id];
103
+ if (reference == null) {
104
+ throw new UnknownRecordError(record.id);
105
+ }
106
+ Object.assign(reference, record);
107
+ recordToListener.get(record.id)?.forEach(listener => listener([createStateModel(reference, resource)]));
108
+ notifyGlobalListenrs([reference]);
109
+ return reference;
110
+ },
111
+ delete: async (id, opts) => {
112
+ const _id = typeof id === 'string' ? id : id.id;
113
+ if (_id == null) {
114
+ throw new UnsupportedArgumentError('id');
115
+ }
116
+ const record = store[_id];
117
+ if (record == null) {
118
+ return null;
119
+ }
120
+ if (opts != null) {
121
+ throw new UnsupportedArgumentError(`${location}:delete:opts`);
122
+ }
123
+ delete store[_id];
124
+ const listeners = recordToListener.get(_id);
125
+ if (listeners != null) {
126
+ listeners.forEach(listener => {
127
+ listener([createStateModel(record, resource)]);
128
+ // const listeners = listenerToRecord.get(listener)
129
+ // if (listeners != null) {
130
+ // const idx = listeners.indexOf(_id)
131
+ // if (idx > 0) {
132
+ // listeners.splice(idx, 1)
133
+ // }
134
+ // if (listeners.length < 1) {
135
+ // listenerToRecord.delete(listener)
136
+ // }
137
+ // }
138
+ });
139
+ // listeners.clear()
140
+ // recordToListener.delete(_id)
141
+ }
142
+ notifyGlobalListenrs([record]);
143
+ return record;
144
+ },
145
+ pick: async (id, opts) => {
146
+ const _id = typeof id === 'string' ? id : id.id;
147
+ if (_id == null) {
148
+ throw new MisshapedRecord('id');
149
+ }
150
+ const record = await resource.delete(_id, opts);
151
+ if (record == null) {
152
+ throw new UnknownRecordError(_id);
153
+ }
154
+ return record;
155
+ },
156
+ subscribe: params => {
157
+ const id = params.id ?? DEFAULT_ID;
158
+ const ids = Array.isArray(id) ? id : [id];
159
+ const records = ids.map(id => {
160
+ if (store[id] == null) {
161
+ store[id] = { ...params.default, id };
162
+ }
163
+ return createStateModel(store[id], resource);
164
+ });
165
+ if (params._systemId != null) {
166
+ if (systemToListeners[params._systemId] != null) {
167
+ return [_usubscribe(params), records];
168
+ }
169
+ systemToListeners[params._systemId] = params.listener;
170
+ }
171
+ if (listeners.includes(params.listener)) {
172
+ throw new StateListenerError('subscribed');
173
+ }
174
+ listeners.push(params.listener);
175
+ listenerToRecord.set(params.listener, ids);
176
+ ids.forEach(id => {
177
+ if (!recordToListener.has(id)) {
178
+ recordToListener.set(id, new Set());
179
+ }
180
+ recordToListener.get(id)?.add(params.listener);
181
+ });
182
+ return [_usubscribe(params), records];
183
+ },
184
+ listen: listener => {
185
+ globalListeners.push(listener);
186
+ return () => {
187
+ const index = globalListeners.indexOf(listener);
188
+ if (index >= 0) {
189
+ globalListeners.splice(index, 1);
190
+ }
191
+ };
192
+ },
193
+ erase: async () => {
194
+ await Promise.all(Object.keys(store).map(key => resource.delete(key)));
195
+ }
196
+ });
197
+ return resource;
198
+ };
199
+ export const appendStateResource = (ctx, alias = DEFAULT_ALIAS) => {
200
+ const resource = createStateResource(alias);
201
+ const _ctx = ctx;
202
+ _ctx.registerResource(resource);
203
+ if (_ctx.getStateResource == null) {
204
+ _ctx.getStateResource = alias => ctx.resource(alias ?? resource.alias);
205
+ }
206
+ return _ctx;
207
+ };
208
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { ListCriteria, Resource, ResourceRecord } from '@owlmeans/resource';
2
+ export interface StateResource<T extends ResourceRecord> extends Resource<T> {
3
+ /**
4
+ * @returns unsubscribe function
5
+ */
6
+ subscribe: (params: StateSubscriptionOption<T>) => [() => void, StateModel<T>[]];
7
+ listen: (listener: StateListener<T>) => () => void;
8
+ erase: () => Promise<void>;
9
+ }
10
+ export interface StateSubscriptionOption<T extends ResourceRecord> {
11
+ id?: string | string[];
12
+ _systemId?: string;
13
+ query?: ListCriteria;
14
+ default?: Partial<T>;
15
+ listener: StateListener<T>;
16
+ }
17
+ export interface StateListener<T extends ResourceRecord> {
18
+ (record: StateModel<T>[]): void | Promise<void>;
19
+ }
20
+ export interface StateModel<T extends ResourceRecord> {
21
+ record: T;
22
+ commit: (force?: boolean) => void;
23
+ update: (data?: Partial<T>) => void;
24
+ clear: () => void;
25
+ }
26
+ export interface StateResourceAppend {
27
+ getStateResource: <T extends ResourceRecord>(alias?: string) => StateResource<T>;
28
+ }
29
+ export interface UseStoreHelper {
30
+ <T extends ResourceRecord>(id?: string | UseStoreHelperOptions<T>, opts?: string | boolean | UseStoreHelperOptions<T>): StateModel<T>;
31
+ }
32
+ export interface UseStoreListHelper {
33
+ <T extends ResourceRecord>(id?: string | string[] | UseStoreHelperOptions<T>, opts?: string | boolean | UseStoreHelperOptions<T>): StateModel<T>[];
34
+ }
35
+ export interface UseStoreHelperOptions<T extends ResourceRecord> extends Omit<StateSubscriptionOption<T>, "listener"> {
36
+ listen?: boolean;
37
+ resource?: string;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEhF,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC1E;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAChF,MAAM,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,CAAA;IAClD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,cAAc;IAC/D,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc;IACrD,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,cAAc;IAClD,MAAM,EAAE,CAAC,CAAC;IAEV,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAEjC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAEnC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,CAAA;CACjF;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,CAAC,SAAS,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,qBAAqB,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;CACtI;AAED,MAAM,WAAW,kBAAkB;IACjC,CAAC,CAAC,SAAS,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,qBAAqB,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;CACnJ;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;IACnH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { ResourceRecord } from '@owlmeans/resource';
2
+ import type { StateModel, StateResource } from '../types.js';
3
+ export declare const createStateModel: <T extends ResourceRecord>(record: T, resource: StateResource<T>) => StateModel<T>;
4
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/utils/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE5D,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,cAAc,UAC/C,CAAC,YAAY,aAAa,CAAC,CAAC,CAAC,KACpC,UAAU,CAAC,CAAC,CAmCd,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { MisshapedRecord } from '@owlmeans/resource';
2
+ export const createStateModel = (record, resource) => {
3
+ let before = record;
4
+ const model = {
5
+ record: { ...record },
6
+ commit: force => {
7
+ if (force !== true) {
8
+ if (!Object.entries(before).reduce((changed, [key, value]) => changed || model.record[key] !== value, false) && !Object.entries(model.record).reduce((changed, [key, value]) => changed || before[key] !== value, false)) {
9
+ return;
10
+ }
11
+ }
12
+ if (record.id == null) {
13
+ throw new MisshapedRecord('id');
14
+ }
15
+ before = model.record;
16
+ void resource.load(record.id).then(exists => {
17
+ exists != null && resource.update(model.record);
18
+ });
19
+ },
20
+ update: data => {
21
+ Object.assign(model.record, data);
22
+ model.commit();
23
+ },
24
+ clear: () => {
25
+ void resource.delete(record);
26
+ }
27
+ };
28
+ return model;
29
+ };
30
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/utils/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,MAAS,EAAE,QAA0B,EACtB,EAAE;IACjB,IAAI,MAAM,GAAM,MAAM,CAAA;IACtB,MAAM,KAAK,GAAkB;QAC3B,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE;QAErB,MAAM,EAAE,KAAK,CAAC,EAAE;YACd,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAChC,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,GAAc,CAAC,KAAK,KAAK,EAAE,KAAK,CACpF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CACvC,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM,CAAC,GAAc,CAAC,KAAK,KAAK,EAAE,KAAK,CAC9E,EAAE,CAAC;oBACF,OAAM;gBACR,CAAC;YACH,CAAC;YACD,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;gBACtB,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;YACjC,CAAC;YACD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;YACrB,KAAK,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;gBAC1C,MAAM,IAAI,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACjD,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,EAAE,IAAI,CAAC,EAAE;YACb,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACjC,KAAK,CAAC,MAAM,EAAE,CAAA;QAChB,CAAC;QAED,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC9B,CAAC;KACF,CAAA;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@owlmeans/state",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -b",
7
+ "dev": "sleep 342 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
8
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ },
10
+ "main": "build/index.js",
11
+ "module": "build/index.js",
12
+ "types": "build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./build/index.js",
16
+ "require": "./build/index.js",
17
+ "default": "./build/index.js",
18
+ "module": "./build/index.js",
19
+ "types": "./build/index.d.ts"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@owlmeans/context": "^0.1.0",
24
+ "@owlmeans/resource": "^0.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "nodemon": "^3.1.7",
28
+ "typescript": "^5.6.3"
29
+ },
30
+ "private": false,
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,3 @@
1
+
2
+ export const DEFAULT_ID = '_default'
3
+ export const DEFAULT_ALIAS = 'state'
package/src/errors.ts ADDED
@@ -0,0 +1,23 @@
1
+
2
+ import { ResourceError } from '@owlmeans/resource'
3
+
4
+ export class StateToolingError extends ResourceError {
5
+ public static override typeName = `${ResourceError.typeName}Tooling`
6
+
7
+ constructor(msg: string) {
8
+ super(`tooling:${msg}`)
9
+ this.type = StateToolingError.typeName
10
+ }
11
+ }
12
+
13
+ export class StateListenerError extends StateToolingError {
14
+ public static override typeName = `${StateToolingError.typeName}Listener`
15
+
16
+ constructor(msg: string) {
17
+ super(`listener:${msg}`)
18
+ this.type = StateListenerError.typeName
19
+ }
20
+ }
21
+
22
+ ResourceError.registerErrorClass(StateToolingError)
23
+ ResourceError.registerErrorClass(StateListenerError)
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ export type * from './types.js'
3
+
4
+ export * from './consts.js'
5
+ export * from './errors.js'
6
+ export * from './resource.js'
@@ -0,0 +1,256 @@
1
+ import { appendContextual } from '@owlmeans/context'
2
+ import { MisshapedRecord, RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource'
3
+ import type { LifecycleOptions, ListResult, ResourceRecord } from '@owlmeans/resource'
4
+ import type { StateListener, StateResource, StateResourceAppend, StateSubscriptionOption } from './types.js'
5
+ import { DEFAULT_ALIAS, DEFAULT_ID } from './consts'
6
+ import { StateListenerError } from './errors.js'
7
+ import { createStateModel } from './utils/model.js'
8
+ import type { BasicContext as Context, BasicConfig as Config } from '@owlmeans/context'
9
+
10
+ export const createStateResource = <R extends ResourceRecord>(alias: string = DEFAULT_ALIAS): StateResource<R> => {
11
+ const location = `state-resource:${alias}`
12
+
13
+ const store: { [id: string]: R } = {}
14
+ const listeners: StateListener<R>[] = []
15
+ const recordToListener = new Map<string, Set<StateListener<R>>>()
16
+ const listenerToRecord = new Map<StateListener<R>, string[]>()
17
+ const globalListeners: StateListener<R>[] = []
18
+ const systemToListeners: Record<string, StateListener<R>> = {}
19
+
20
+ type StoreKey = keyof typeof store
21
+
22
+ const notifyGlobalListenrs = (records: R[]) => {
23
+ globalListeners.forEach(listener => listener(
24
+ records.map(record => createStateModel(record, resource))
25
+ ))
26
+ }
27
+
28
+ const _usubscribe = (params: StateSubscriptionOption<R>) => () => {
29
+ const index = listeners.indexOf(params.listener)
30
+ if (index >= 0) {
31
+ const ids = listenerToRecord.get(params.listener)
32
+ listeners.splice(index, 1)
33
+ listenerToRecord.delete(params.listener)
34
+ ids?.forEach(id => {
35
+ const listeners = recordToListener.get(id)
36
+ if (listeners != null) {
37
+ listeners.delete(params.listener)
38
+ if (listeners.size === 0) {
39
+ recordToListener.delete(id)
40
+ }
41
+ }
42
+ })
43
+ }
44
+ Object.entries(systemToListeners).some(([key, listener]) => {
45
+ if (listener === params.listener) {
46
+ delete systemToListeners[key]
47
+ return true
48
+ }
49
+
50
+ return false
51
+ })
52
+ }
53
+
54
+ const resource: StateResource<R> = appendContextual<StateResource<R>>(alias, {
55
+ get: async (id, field, opts) => {
56
+ const record = await resource.load(id, field, opts)
57
+ if (record == null) {
58
+ throw new UnknownRecordError(id)
59
+ }
60
+
61
+ return record as any
62
+ },
63
+
64
+ load: async (id, field, opts) => {
65
+ if (field != null) {
66
+ throw new UnsupportedArgumentError(`${location}:get:filed`)
67
+ }
68
+ if (opts != null) {
69
+ throw new UnsupportedArgumentError(`${location}:get:opts`)
70
+ }
71
+ const record = store[id] as R | undefined
72
+
73
+ if (record == null) {
74
+ return null
75
+ }
76
+
77
+ return record as any
78
+ },
79
+
80
+ list: async (criteria, opts) => {
81
+ if (criteria != null) {
82
+ throw new UnsupportedArgumentError(`${location}:list:criteria`)
83
+ }
84
+ if (opts != null) {
85
+ throw new UnsupportedArgumentError(`${location}:list:opts`)
86
+ }
87
+
88
+ const result: ListResult<R> = {
89
+ items: Object.entries(store).map(([, record]) => record) as R[]
90
+ }
91
+
92
+ return result as any
93
+ },
94
+
95
+ save: async (record, opts) => {
96
+ const id = record.id ?? DEFAULT_ID
97
+ if (store[id] != null) {
98
+ return resource.update(record, opts)
99
+ }
100
+
101
+ return resource.create(record, opts as LifecycleOptions)
102
+ },
103
+
104
+ create: async (record, opts) => {
105
+ if (!("id" in record) || record.id == null) {
106
+ record.id = DEFAULT_ID
107
+ }
108
+ if (store[record.id as StoreKey] != null) {
109
+ throw new RecordExists(record.id)
110
+ }
111
+ if (opts != null) {
112
+ throw new UnsupportedArgumentError(`${location}:create:opts`)
113
+ }
114
+ store[record.id as StoreKey] = record as unknown as R
115
+
116
+ notifyGlobalListenrs([store[record.id as StoreKey]])
117
+
118
+ return record as any
119
+ },
120
+
121
+ update: async (record, opts) => {
122
+ if (opts != null) {
123
+ throw new UnsupportedArgumentError(`${location}:update:opts`)
124
+ }
125
+ if (!("id" in record) || record.id == null) {
126
+ record.id = DEFAULT_ID
127
+ }
128
+ const reference = store[record.id as StoreKey]
129
+ if (reference == null) {
130
+ throw new UnknownRecordError(record.id)
131
+ }
132
+ Object.assign(reference, record)
133
+
134
+ recordToListener.get(record.id)?.forEach(
135
+ listener => listener([createStateModel(reference, resource)])
136
+ )
137
+
138
+ notifyGlobalListenrs([reference])
139
+
140
+ return reference as any
141
+ },
142
+
143
+ delete: async (id, opts) => {
144
+ const _id = typeof id === 'string' ? id : id.id
145
+ if (_id == null) {
146
+ throw new UnsupportedArgumentError('id')
147
+ }
148
+ const record: R | null = store[_id as StoreKey] as R
149
+ if (record == null) {
150
+ return null
151
+ }
152
+ if (opts != null) {
153
+ throw new UnsupportedArgumentError(`${location}:delete:opts`)
154
+ }
155
+ delete store[_id as StoreKey]
156
+
157
+ const listeners = recordToListener.get(_id)
158
+ if (listeners != null) {
159
+ listeners.forEach(listener => {
160
+ listener([createStateModel(record, resource)])
161
+ // const listeners = listenerToRecord.get(listener)
162
+ // if (listeners != null) {
163
+ // const idx = listeners.indexOf(_id)
164
+ // if (idx > 0) {
165
+ // listeners.splice(idx, 1)
166
+ // }
167
+ // if (listeners.length < 1) {
168
+ // listenerToRecord.delete(listener)
169
+ // }
170
+ // }
171
+ })
172
+ // listeners.clear()
173
+ // recordToListener.delete(_id)
174
+ }
175
+ notifyGlobalListenrs([record])
176
+
177
+ return record as any
178
+ },
179
+
180
+ pick: async (id, opts) => {
181
+ const _id = typeof id === 'string' ? id : id.id
182
+ if (_id == null) {
183
+ throw new MisshapedRecord('id')
184
+ }
185
+ const record = await resource.delete(_id, opts)
186
+ if (record == null) {
187
+ throw new UnknownRecordError(_id)
188
+ }
189
+
190
+ return record as any
191
+ },
192
+
193
+ subscribe: params => {
194
+ const id = params.id ?? DEFAULT_ID
195
+ const ids = Array.isArray(id) ? id : [id]
196
+ const records = ids.map(id => {
197
+ if (store[id] == null) {
198
+ store[id as StoreKey] = { ...params.default, id } as R
199
+ }
200
+ return createStateModel(store[id], resource)
201
+ })
202
+ if (params._systemId != null) {
203
+ if (systemToListeners[params._systemId] != null) {
204
+ return [_usubscribe(params), records]
205
+ }
206
+
207
+ systemToListeners[params._systemId] = params.listener
208
+ }
209
+ if (listeners.includes(params.listener)) {
210
+ throw new StateListenerError('subscribed')
211
+ }
212
+ listeners.push(params.listener)
213
+ listenerToRecord.set(params.listener, ids)
214
+ ids.forEach(id => {
215
+ if (!recordToListener.has(id)) {
216
+ recordToListener.set(id, new Set())
217
+ }
218
+ recordToListener.get(id)?.add(params.listener)
219
+ })
220
+
221
+ return [_usubscribe(params), records]
222
+ },
223
+
224
+ listen: listener => {
225
+ globalListeners.push(listener)
226
+
227
+ return () => {
228
+ const index = globalListeners.indexOf(listener)
229
+ if (index >= 0) {
230
+ globalListeners.splice(index, 1)
231
+ }
232
+ }
233
+ },
234
+
235
+ erase: async () => {
236
+ await Promise.all(Object.keys(store).map(key => resource.delete(key)))
237
+ }
238
+ })
239
+
240
+ return resource
241
+ }
242
+
243
+ export const appendStateResource = <C extends Config, T extends Context<C>>(
244
+ ctx: T, alias: string = DEFAULT_ALIAS
245
+ ): T & StateResourceAppend => {
246
+ const resource = createStateResource(alias)
247
+
248
+ const _ctx = ctx as T & StateResourceAppend
249
+
250
+ _ctx.registerResource(resource)
251
+ if (_ctx.getStateResource == null) {
252
+ _ctx.getStateResource = alias => ctx.resource(alias ?? resource.alias)
253
+ }
254
+
255
+ return _ctx
256
+ }