@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.
- package/LICENSE +21 -0
- package/README.md +456 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +3 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -0
- package/build/errors.d.ts +10 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +18 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/resource.d.ts +6 -0
- package/build/resource.d.ts.map +1 -0
- package/build/resource.js +208 -0
- package/build/resource.js.map +1 -0
- package/build/types.d.ts +39 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +2 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/model.d.ts +4 -0
- package/build/utils/model.d.ts.map +1 -0
- package/build/utils/model.js +30 -0
- package/build/utils/model.js.map +1 -0
- package/package.json +34 -0
- package/src/consts.ts +3 -0
- package/src/errors.ts +23 -0
- package/src/index.ts +6 -0
- package/src/resource.ts +256 -0
- package/src/types.ts +49 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/model.ts +42 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -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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"}
|
package/build/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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
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
package/src/resource.ts
ADDED
|
@@ -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
|
+
}
|