@relations-dil/relations 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,66 @@
1
+ /**
2
+ * Double underscore access notation.
3
+ *
4
+ * Port of the Python `overscore` library. Lets you retrieve (and store) values in
5
+ * multi-dimensional data using a single string, so the access path survives being
6
+ * passed as a function argument, a URL parameter, or a filter key.
7
+ *
8
+ * ```ts
9
+ * import * as overscore from '@relations-dil/relations/overscore'
10
+ *
11
+ * const data = { things: { a: { b: [{ '1': 'yep' }] } } }
12
+ *
13
+ * overscore.get(data, 'things__a__b__0____1') // 'yep'
14
+ * ```
15
+ *
16
+ * All places are separated by double underscores. Extra underscores say how to read
17
+ * the place that follows:
18
+ *
19
+ * | Underscores | Following | Meaning | Example | Equivalent |
20
+ * | ----------- | --------- | -------------------- | -------- | -------------- |
21
+ * | 2 | word | key | `a__b` | `['a']['b']` |
22
+ * | 2 | number | index | `a__1` | `['a'][1]` |
23
+ * | 3 | number | negative index | `a___2` | `['a'][-2]` |
24
+ * | 4 | number | numerical key | `a____3` | `['a']['3']` |
25
+ * | 5 | number | negative numeric key | `a_____4`| `['a']['-4']` |
26
+ */
27
+ export declare class OverscoreError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ /** A single step in a path: a string key or a numeric (possibly negative) index. */
31
+ export type Place = string | number;
32
+ /** A resolved path, or the double-underscored string form of one. */
33
+ export type Path = Place[] | string;
34
+ /**
35
+ * Parse double-underscored text into a list of keys and indexes.
36
+ *
37
+ * ```ts
38
+ * parse('a-b__0___1____2_____3') // ['a-b', 0, -1, '2', '-3']
39
+ * ```
40
+ */
41
+ export declare function parse(text: string): Place[];
42
+ /**
43
+ * Compile a list of keys and indexes back into double-underscored text.
44
+ *
45
+ * ```ts
46
+ * compile(['a-b', 0, -1, '2', '-3']) // 'a-b__0___1____2_____3'
47
+ * ```
48
+ */
49
+ export declare function compile(path: Place[]): string;
50
+ /** True when `data` is a plain object (the JS stand-in for a Python dict). */
51
+ export declare function isDict(data: unknown): data is Record<string, unknown>;
52
+ /** Whether `path` exists in `data`. */
53
+ export declare function has(data: unknown, path: Path): boolean;
54
+ /** The value in `data` at `path`, or `null` when it isn't there. */
55
+ export declare function get(data: unknown, path: Path): any;
56
+ /**
57
+ * Store `value` in `data` at `path`, creating the objects and arrays along the way.
58
+ *
59
+ * ```ts
60
+ * const data = {}
61
+ * set(data, 'things__a__b___2____1', 'yep')
62
+ * // { things: { a: { b: [{ '1': 'yep' }, null] } } }
63
+ * ```
64
+ */
65
+ export declare function set(data: unknown, path: Path, value: unknown): void;
66
+ //# sourceMappingURL=overscore.d.ts.map
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Double underscore access notation.
3
+ *
4
+ * Port of the Python `overscore` library. Lets you retrieve (and store) values in
5
+ * multi-dimensional data using a single string, so the access path survives being
6
+ * passed as a function argument, a URL parameter, or a filter key.
7
+ *
8
+ * ```ts
9
+ * import * as overscore from '@relations-dil/relations/overscore'
10
+ *
11
+ * const data = { things: { a: { b: [{ '1': 'yep' }] } } }
12
+ *
13
+ * overscore.get(data, 'things__a__b__0____1') // 'yep'
14
+ * ```
15
+ *
16
+ * All places are separated by double underscores. Extra underscores say how to read
17
+ * the place that follows:
18
+ *
19
+ * | Underscores | Following | Meaning | Example | Equivalent |
20
+ * | ----------- | --------- | -------------------- | -------- | -------------- |
21
+ * | 2 | word | key | `a__b` | `['a']['b']` |
22
+ * | 2 | number | index | `a__1` | `['a'][1]` |
23
+ * | 3 | number | negative index | `a___2` | `['a'][-2]` |
24
+ * | 4 | number | numerical key | `a____3` | `['a']['3']` |
25
+ * | 5 | number | negative numeric key | `a_____4`| `['a']['-4']` |
26
+ */
27
+ export class OverscoreError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = 'OverscoreError';
31
+ }
32
+ }
33
+ const NUMBER = /^\d+$/;
34
+ const WORD = /^[\w-]+$/;
35
+ function isDigit(letter) {
36
+ return letter >= '0' && letter <= '9';
37
+ }
38
+ /**
39
+ * Parse double-underscored text into a list of keys and indexes.
40
+ *
41
+ * ```ts
42
+ * parse('a-b__0___1____2_____3') // ['a-b', 0, -1, '2', '-3']
43
+ * ```
44
+ */
45
+ export function parse(text) {
46
+ let count = 0;
47
+ let place = [];
48
+ const places = [];
49
+ let state = 'place';
50
+ for (const letter of text) {
51
+ if (state === 'place') {
52
+ place.push(letter);
53
+ if (letter !== '_') {
54
+ state = 'placing';
55
+ }
56
+ }
57
+ else {
58
+ count = letter === '_' ? count + 1 : 0;
59
+ if (count === 2 && letter === '_') {
60
+ places.push(place.slice(0, -1).join(''));
61
+ place = [];
62
+ count = 0;
63
+ state = 'place';
64
+ }
65
+ else {
66
+ place.push(letter);
67
+ }
68
+ }
69
+ }
70
+ if (place.length) {
71
+ places.push(place.join(''));
72
+ }
73
+ const path = [];
74
+ for (const found of places) {
75
+ if (isDigit(found[0])) {
76
+ path.push(parseInt(found, 10));
77
+ }
78
+ else if (found[0] === '_' && found[1] !== undefined && isDigit(found[1]) && found.slice(0, 2) !== '__') {
79
+ path.push(-parseInt(found.slice(1), 10));
80
+ }
81
+ else if (found.slice(0, 2) === '__' && found[2] !== undefined && isDigit(found[2]) && found.slice(0, 3) !== '___') {
82
+ path.push(found.slice(2));
83
+ }
84
+ else if (found.slice(0, 3) === '___' && found[3] !== undefined && isDigit(found[3])) {
85
+ path.push(String(-parseInt(found.slice(3), 10)));
86
+ }
87
+ else {
88
+ path.push(found);
89
+ }
90
+ }
91
+ return path;
92
+ }
93
+ /**
94
+ * Compile a list of keys and indexes back into double-underscored text.
95
+ *
96
+ * ```ts
97
+ * compile(['a-b', 0, -1, '2', '-3']) // 'a-b__0___1____2_____3'
98
+ * ```
99
+ */
100
+ export function compile(path) {
101
+ const places = [];
102
+ for (const place of path) {
103
+ if (typeof place === 'number' && Number.isInteger(place) && place > -1) {
104
+ places.push(String(place));
105
+ }
106
+ else if (typeof place === 'number' && Number.isInteger(place)) {
107
+ places.push(`_${Math.abs(place)}`);
108
+ }
109
+ else if (typeof place === 'string' && place.length && place[0] === '-' && NUMBER.test(place.slice(1))) {
110
+ places.push(`___${place.slice(1)}`);
111
+ }
112
+ else if (typeof place === 'string' && NUMBER.test(place)) {
113
+ places.push(`__${place}`);
114
+ }
115
+ else if (typeof place === 'string' && WORD.test(place)) {
116
+ places.push(place);
117
+ }
118
+ else {
119
+ throw new OverscoreError(`cannot compile ${String(place)}`);
120
+ }
121
+ }
122
+ return places.join('__');
123
+ }
124
+ /** True when `data` is a plain object (the JS stand-in for a Python dict). */
125
+ export function isDict(data) {
126
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
127
+ return false;
128
+ }
129
+ const proto = Object.getPrototypeOf(data);
130
+ return proto === Object.prototype || proto === null;
131
+ }
132
+ function resolve(path) {
133
+ return typeof path === 'string' ? parse(path) : path;
134
+ }
135
+ /** Whether `path` exists in `data`. */
136
+ export function has(data, path) {
137
+ let current = data;
138
+ for (const place of resolve(path)) {
139
+ if (typeof place === 'number') {
140
+ if (!Array.isArray(current) ||
141
+ (place >= 0 && current.length < place + 1) ||
142
+ (place < 0 && current.length < Math.abs(place))) {
143
+ return false;
144
+ }
145
+ current = current.at(place);
146
+ }
147
+ else {
148
+ if (!isDict(current) || !Object.prototype.hasOwnProperty.call(current, place)) {
149
+ return false;
150
+ }
151
+ current = current[place];
152
+ }
153
+ }
154
+ return true;
155
+ }
156
+ /** The value in `data` at `path`, or `null` when it isn't there. */
157
+ export function get(data, path) {
158
+ let current = data;
159
+ for (const place of resolve(path)) {
160
+ if (typeof place === 'number') {
161
+ if (!Array.isArray(current) ||
162
+ (place >= 0 && current.length < place + 1) ||
163
+ (place < 0 && current.length < Math.abs(place))) {
164
+ return null;
165
+ }
166
+ current = current.at(place);
167
+ }
168
+ else {
169
+ if (!isDict(current) || !Object.prototype.hasOwnProperty.call(current, place)) {
170
+ return null;
171
+ }
172
+ current = current[place];
173
+ }
174
+ }
175
+ return current === undefined ? null : current;
176
+ }
177
+ /**
178
+ * Store `value` in `data` at `path`, creating the objects and arrays along the way.
179
+ *
180
+ * ```ts
181
+ * const data = {}
182
+ * set(data, 'things__a__b___2____1', 'yep')
183
+ * // { things: { a: { b: [{ '1': 'yep' }, null] } } }
184
+ * ```
185
+ */
186
+ export function set(data, path, value) {
187
+ const places = resolve(path);
188
+ let current = data;
189
+ for (let index = 0; index < places.length; index++) {
190
+ const place = places[index];
191
+ const last = index === places.length - 1;
192
+ const fallback = last ? value : typeof places[index + 1] === 'string' ? {} : [];
193
+ if (isDict(current)) {
194
+ if (typeof place === 'number') {
195
+ throw new OverscoreError(`index ${place} invalid for object ${JSON.stringify(current)}`);
196
+ }
197
+ if (!Object.prototype.hasOwnProperty.call(current, place)) {
198
+ current[place] = fallback;
199
+ }
200
+ }
201
+ else if (Array.isArray(current)) {
202
+ if (typeof place === 'string') {
203
+ throw new OverscoreError(`key ${place} invalid for array ${JSON.stringify(current)}`);
204
+ }
205
+ while ((place >= 0 && current.length < place + 1) || (place < 0 && current.length < Math.abs(place))) {
206
+ current.push(null);
207
+ }
208
+ const at = place < 0 ? current.length + place : place;
209
+ if (current[at] === null || current[at] === undefined) {
210
+ current[at] = fallback;
211
+ }
212
+ }
213
+ else {
214
+ throw new OverscoreError(`cannot set ${String(place)} on ${JSON.stringify(current)}`);
215
+ }
216
+ const at = Array.isArray(current) && typeof place === 'number' && place < 0 ? current.length + place : place;
217
+ const holder = current;
218
+ if (!last) {
219
+ current = holder[at];
220
+ }
221
+ else {
222
+ holder[at] = value;
223
+ }
224
+ }
225
+ }
226
+ //# sourceMappingURL=overscore.js.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Records: an ordered set of fields, and the marshalling between them and a source.
3
+ */
4
+ import { Field } from './field.js';
5
+ /** What a record is currently for. */
6
+ export type Action = 'create' | 'retrieve' | 'update' | 'delete';
7
+ /** An ordered, name-addressable set of fields. */
8
+ export declare class Record {
9
+ /** Fields in order. */
10
+ _order: Field[];
11
+ /** Fields by name. */
12
+ _names: Map<string, Field>;
13
+ /** What's being done with this record. */
14
+ _action: Action | null;
15
+ /** Add a field at a specific position. */
16
+ insert(index: number, field: Field): void;
17
+ /** Add a field at the end. */
18
+ append(field: Field): void;
19
+ /** Number of fields. */
20
+ get size(): number;
21
+ /** Field names, in order. */
22
+ [Symbol.iterator](): IterableIterator<string>;
23
+ /** Field names, in order. */
24
+ keys(): string[];
25
+ /** The field itself, by name or position. */
26
+ field(key: string | number): Field | undefined;
27
+ /** Whether a field exists, by name or position. */
28
+ has(key: string | number): boolean;
29
+ /** An independent copy of this record and every field in it. */
30
+ copy(): Record;
31
+ /** A value, by field name, position, or double-underscored path into a field. */
32
+ get(key: string | number): any;
33
+ /** Set a value, by field name, position, or double-underscored path into a field. */
34
+ set(key: string | number, value: any): void;
35
+ /** Definitions for every field, in order. */
36
+ define(): globalThis.Record<string, any>[];
37
+ /** Set a criterion on a field, by name, position, or `name__operator`. */
38
+ filter(criterion: string | number, value: any): void;
39
+ /** Every field's value, keyed by field name. */
40
+ export(): globalThis.Record<string, any>;
41
+ /** The store name of the field an injected field lives inside. */
42
+ private injected;
43
+ /** Write every field's value for a create. */
44
+ create(values: globalThis.Record<string, any>): globalThis.Record<string, any>;
45
+ /** Whether a stored record satisfies every field's criteria. */
46
+ retrieve(values: globalThis.Record<string, any>): boolean;
47
+ /** Whether a stored record fuzzy-matches on any of the title fields. */
48
+ like(values: globalThis.Record<string, any>, titles: string[], like: any, parents: globalThis.Record<string, any[]>): boolean;
49
+ /** Load every field's value from a stored record. */
50
+ read(values: globalThis.Record<string, any>): void;
51
+ /** Write every changed field's value for an update. */
52
+ update(values: globalThis.Record<string, any>): globalThis.Record<string, any>;
53
+ /** Write every set field's value for a mass update. */
54
+ mass(values: globalThis.Record<string, any>): globalThis.Record<string, any>;
55
+ /** Write every set tie field's value for a many-to-many update. */
56
+ tie(values: globalThis.Record<string, any>): globalThis.Record<string, any>;
57
+ }
58
+ //# sourceMappingURL=record.d.ts.map
package/dist/record.js ADDED
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Records: an ordered set of fields, and the marshalling between them and a source.
3
+ */
4
+ import * as overscore from './overscore.js';
5
+ import { RecordError } from './errors.js';
6
+ /** An ordered, name-addressable set of fields. */
7
+ export class Record {
8
+ /** Fields in order. */
9
+ _order = [];
10
+ /** Fields by name. */
11
+ _names = new Map();
12
+ /** What's being done with this record. */
13
+ _action = null;
14
+ /** Add a field at a specific position. */
15
+ insert(index, field) {
16
+ this._order.splice(index, 0, field);
17
+ this._names.set(field.name, field);
18
+ }
19
+ /** Add a field at the end. */
20
+ append(field) {
21
+ this.insert(this._order.length, field);
22
+ }
23
+ /** Number of fields. */
24
+ get size() {
25
+ return this._order.length;
26
+ }
27
+ /** Field names, in order. */
28
+ *[Symbol.iterator]() {
29
+ for (const field of this._order) {
30
+ yield field.name;
31
+ }
32
+ }
33
+ /** Field names, in order. */
34
+ keys() {
35
+ return this._order.map((field) => field.name);
36
+ }
37
+ /** The field itself, by name or position. */
38
+ field(key) {
39
+ if (typeof key === 'number') {
40
+ return this._order[key < 0 ? this._order.length + key : key];
41
+ }
42
+ return this._names.get(key);
43
+ }
44
+ /** Whether a field exists, by name or position. */
45
+ has(key) {
46
+ if (typeof key === 'number') {
47
+ return key < this._order.length;
48
+ }
49
+ return this._names.has(key);
50
+ }
51
+ /** An independent copy of this record and every field in it. */
52
+ copy() {
53
+ const record = new Record();
54
+ record._action = this._action;
55
+ for (const field of this._order) {
56
+ record.append(field.copy());
57
+ }
58
+ return record;
59
+ }
60
+ /** A value, by field name, position, or double-underscored path into a field. */
61
+ get(key) {
62
+ if (typeof key === 'number') {
63
+ if (key < this._order.length) {
64
+ return this._order[key].value;
65
+ }
66
+ throw new RecordError(this, `unknown field '${key}'`);
67
+ }
68
+ const field = this._names.get(key);
69
+ if (field !== undefined) {
70
+ return field.value;
71
+ }
72
+ const access = overscore.parse(key);
73
+ const root = this._names.get(String(access[0]));
74
+ if (root !== undefined) {
75
+ return root.access(access.slice(1));
76
+ }
77
+ throw new RecordError(this, `unknown field '${key}'`);
78
+ }
79
+ /** Set a value, by field name, position, or double-underscored path into a field. */
80
+ set(key, value) {
81
+ if (typeof key === 'number') {
82
+ if (key < this._order.length) {
83
+ this._order[key].value = value;
84
+ return;
85
+ }
86
+ throw new RecordError(this, `unknown field '${key}'`);
87
+ }
88
+ const field = this._names.get(key);
89
+ if (field !== undefined) {
90
+ field.value = value;
91
+ return;
92
+ }
93
+ const apply = overscore.parse(key);
94
+ const root = this._names.get(String(apply[0]));
95
+ if (root !== undefined) {
96
+ root.apply(apply.slice(1), value);
97
+ return;
98
+ }
99
+ throw new RecordError(this, `unknown field '${key}'`);
100
+ }
101
+ /** Definitions for every field, in order. */
102
+ define() {
103
+ return this._order.map((field) => field.define());
104
+ }
105
+ /** Set a criterion on a field, by name, position, or `name__operator`. */
106
+ filter(criterion, value) {
107
+ if (typeof criterion === 'number') {
108
+ if (criterion < this._order.length) {
109
+ this._order[criterion].filter(value);
110
+ return;
111
+ }
112
+ }
113
+ else {
114
+ if (criterion.includes('__')) {
115
+ const index = criterion.indexOf('__');
116
+ const name = criterion.slice(0, index);
117
+ const operator = criterion.slice(index + 2);
118
+ const field = this._names.get(name);
119
+ if (field !== undefined) {
120
+ field.filter(value, operator);
121
+ return;
122
+ }
123
+ }
124
+ const field = this._names.get(criterion);
125
+ if (field !== undefined) {
126
+ field.filter(value);
127
+ return;
128
+ }
129
+ }
130
+ throw new RecordError(this, `unknown criterion '${criterion}'`);
131
+ }
132
+ /** Every field's value, keyed by field name. */
133
+ export() {
134
+ const values = {};
135
+ for (const field of this._order) {
136
+ values[field.name] = field.export();
137
+ }
138
+ return values;
139
+ }
140
+ /** The store name of the field an injected field lives inside. */
141
+ injected(field) {
142
+ const root = String(field.inject).split('__')[0];
143
+ return this._names.get(root)?.store;
144
+ }
145
+ /** Write every field's value for a create. */
146
+ create(values) {
147
+ const inject = [];
148
+ for (const field of this._order) {
149
+ if (field.inject) {
150
+ inject.push(field);
151
+ }
152
+ else {
153
+ field.create(values);
154
+ }
155
+ }
156
+ for (const field of inject) {
157
+ field.create(values[this.injected(field)]);
158
+ }
159
+ return values;
160
+ }
161
+ /** Whether a stored record satisfies every field's criteria. */
162
+ retrieve(values) {
163
+ for (const field of this._order) {
164
+ if (!field.retrieve(values)) {
165
+ return false;
166
+ }
167
+ }
168
+ return true;
169
+ }
170
+ /** Whether a stored record fuzzy-matches on any of the title fields. */
171
+ like(values, titles, like, parents) {
172
+ for (const title of titles) {
173
+ const path = overscore.parse(title);
174
+ const field = this._names.get(String(path[0]));
175
+ if (field !== undefined && field.like(values, like, parents, path.slice(1))) {
176
+ return true;
177
+ }
178
+ }
179
+ return false;
180
+ }
181
+ /** Load every field's value from a stored record. */
182
+ read(values) {
183
+ for (const field of this._order) {
184
+ if (field.inject) {
185
+ field.read(values[this.injected(field)] ?? {});
186
+ }
187
+ else {
188
+ field.read(values);
189
+ }
190
+ }
191
+ }
192
+ /** Write every changed field's value for an update. */
193
+ update(values) {
194
+ const inject = [];
195
+ for (const field of this._order) {
196
+ if (field.inject) {
197
+ inject.push(field);
198
+ }
199
+ else {
200
+ field.update(values);
201
+ }
202
+ }
203
+ for (const field of inject) {
204
+ if (field.delta() || field.refresh) {
205
+ const root = String(field.inject).split('__')[0];
206
+ const store = this._names.get(root)?.store;
207
+ if (!(store in values)) {
208
+ this._names.get(root)?.write(values);
209
+ }
210
+ field.update(values[store]);
211
+ }
212
+ }
213
+ return values;
214
+ }
215
+ /** Write every set field's value for a mass update. */
216
+ mass(values) {
217
+ const inject = [];
218
+ for (const field of this._order) {
219
+ if (field.inject) {
220
+ inject.push(field);
221
+ }
222
+ else {
223
+ field.mass(values);
224
+ }
225
+ }
226
+ for (const field of inject) {
227
+ if (field.changed || field.refresh) {
228
+ const root = String(field.inject).split('__')[0];
229
+ const store = this._names.get(root)?.store;
230
+ if (!(store in values)) {
231
+ this._names.get(root)?.write(values);
232
+ }
233
+ field.mass(values[store]);
234
+ }
235
+ }
236
+ return values;
237
+ }
238
+ /** Write every set tie field's value for a many-to-many update. */
239
+ tie(values) {
240
+ for (const field of this._order) {
241
+ field.tie(values);
242
+ }
243
+ return values;
244
+ }
245
+ }
246
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The global source registry.
3
+ *
4
+ * A model names its source with a string. Which actual source that string refers to is
5
+ * decided at runtime, by whatever registers itself under that name. That indirection is
6
+ * the whole point of relations: the same model runs against a database in one service
7
+ * and against a REST API in another.
8
+ */
9
+ /** Anything registered as a source. Kept loose to avoid a circular import. */
10
+ export interface Registered {
11
+ name: string;
12
+ [member: string]: any;
13
+ }
14
+ /** Every registered source, keyed by name. */
15
+ export declare const SOURCES: Map<string, Registered>;
16
+ /** Register a source under its name, replacing any source already there. */
17
+ export declare function register(source: Registered): void;
18
+ /** The source registered under a name, or `undefined`. */
19
+ export declare function source(name: string | null | undefined): Registered | undefined;
20
+ /** Forget a source. Mostly useful between tests. */
21
+ export declare function unregister(name: string): boolean;
22
+ /** Forget every source. Mostly useful between tests. */
23
+ export declare function clear(): void;
24
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The global source registry.
3
+ *
4
+ * A model names its source with a string. Which actual source that string refers to is
5
+ * decided at runtime, by whatever registers itself under that name. That indirection is
6
+ * the whole point of relations: the same model runs against a database in one service
7
+ * and against a REST API in another.
8
+ */
9
+ /** Every registered source, keyed by name. */
10
+ export const SOURCES = new Map();
11
+ /** Register a source under its name, replacing any source already there. */
12
+ export function register(source) {
13
+ SOURCES.set(source.name, source);
14
+ }
15
+ /** The source registered under a name, or `undefined`. */
16
+ export function source(name) {
17
+ return name === null || name === undefined ? undefined : SOURCES.get(name);
18
+ }
19
+ /** Forget a source. Mostly useful between tests. */
20
+ export function unregister(name) {
21
+ return SOURCES.delete(name);
22
+ }
23
+ /** Forget every source. Mostly useful between tests. */
24
+ export function clear() {
25
+ SOURCES.clear();
26
+ }
27
+ //# sourceMappingURL=registry.js.map