doxum 0.1.17 → 0.1.18

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/dist/index.cjs CHANGED
@@ -3,7 +3,7 @@ const require_issue = require("./issue-DhrNdQNg.cjs");
3
3
  const require_scope = require("./scope-Ezs-Y3TY.cjs");
4
4
  const require_notification = require("./notification-CDSUfiip.cjs");
5
5
  const require_driver = require("./driver-BCyfqxsl.cjs");
6
- const require_definition = require("./definition-De6rhTlZ.cjs");
6
+ const require_advanced = require("./advanced-Dgu8-bPS.cjs");
7
7
  //#region core/src/schema.ts
8
8
  const node = (value) => Object.freeze(value);
9
9
  const field = (validator) => node({
@@ -1196,7 +1196,9 @@ const createScheduler = (onError) => {
1196
1196
  phase = "compute";
1197
1197
  lock(true);
1198
1198
  try {
1199
- pending.forEach((source) => source.consumers.forEach(enqueue));
1199
+ pending.forEach((source) => {
1200
+ if (source.shouldSettle?.() ?? true) source.consumers.forEach(enqueue);
1201
+ });
1200
1202
  while (heap.length) {
1201
1203
  const node = pop();
1202
1204
  if (node.disposed) continue;
@@ -1338,12 +1340,29 @@ const createScheduler = (onError) => {
1338
1340
  unregisterSource(handle) {
1339
1341
  const record = records.get(handle);
1340
1342
  if (record) {
1343
+ assertIdle();
1344
+ if (record.consumers.size) throw new Error("Projection source is still used by a node.");
1341
1345
  record.disposed = true;
1342
1346
  pending.delete(record);
1343
1347
  record.clear();
1344
1348
  records.delete(handle);
1345
1349
  }
1346
1350
  },
1351
+ releaseNode(handle) {
1352
+ assertIdle();
1353
+ const node = records.get(handle);
1354
+ if (!node || !nodes.has(node)) throw new Error("Unknown projection node.");
1355
+ if (node.consumers.size) throw new Error("Projection node is still used by another node.");
1356
+ node.disposed = true;
1357
+ node.sources.forEach((source) => source.consumers.delete(node));
1358
+ node.release();
1359
+ node.clear();
1360
+ nodes.delete(node);
1361
+ records.delete(handle);
1362
+ pending.delete(node);
1363
+ queued.delete(node);
1364
+ emissions.delete(node);
1365
+ },
1347
1366
  source(handle) {
1348
1367
  assertActive();
1349
1368
  const source = records.get(handle);
@@ -1685,6 +1704,149 @@ const createSources = (scheduler) => {
1685
1704
  }
1686
1705
  };
1687
1706
  };
1707
+ const collectionInput = (initial) => {
1708
+ const values = new Map(initial);
1709
+ const touched = /* @__PURE__ */ new Map();
1710
+ let beforeIds;
1711
+ let revision = 0;
1712
+ let cause;
1713
+ const handle = Object.freeze({ kind: "collection" });
1714
+ const change = () => {
1715
+ const added = [];
1716
+ const updated = [];
1717
+ const removed = [];
1718
+ for (const [key, before] of touched) {
1719
+ const afterPresent = values.has(key);
1720
+ if (!before.present && afterPresent) added.push({
1721
+ key,
1722
+ after: values.get(key)
1723
+ });
1724
+ else if (before.present && !afterPresent) removed.push({
1725
+ key,
1726
+ before: before.value
1727
+ });
1728
+ else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1729
+ key,
1730
+ before: before.value,
1731
+ after: values.get(key)
1732
+ });
1733
+ }
1734
+ let order;
1735
+ if (beforeIds) {
1736
+ const afterIds = [...values.keys()];
1737
+ const beforeKeys = new Set(beforeIds);
1738
+ const afterKeys = new Set(afterIds);
1739
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1740
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1741
+ if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1742
+ before: beforeIds,
1743
+ after: Object.freeze(afterIds)
1744
+ });
1745
+ }
1746
+ if (!added.length && !updated.length && !removed.length && !order) return void 0;
1747
+ return Object.freeze({
1748
+ kind: "incremental",
1749
+ added: Object.freeze(added),
1750
+ updated: Object.freeze(updated),
1751
+ removed: Object.freeze(removed),
1752
+ ...order ? { order } : {}
1753
+ });
1754
+ };
1755
+ const record = {
1756
+ consumers: /* @__PURE__ */ new Set(),
1757
+ disposed: false,
1758
+ fault: void 0,
1759
+ revision: () => revision,
1760
+ reset: () => false,
1761
+ shouldSettle: () => change() !== void 0,
1762
+ clear: () => {
1763
+ touched.clear();
1764
+ beforeIds = void 0;
1765
+ cause = void 0;
1766
+ },
1767
+ context: (active) => {
1768
+ assertScope(active);
1769
+ const read = Object.freeze({
1770
+ get: (key) => {
1771
+ assertScope(active);
1772
+ return values.get(key);
1773
+ },
1774
+ has: (key) => {
1775
+ assertScope(active);
1776
+ return values.has(key);
1777
+ },
1778
+ ids: () => {
1779
+ assertScope(active);
1780
+ return Object.freeze([...values.keys()]);
1781
+ }
1782
+ });
1783
+ return Object.freeze({
1784
+ kind: "collection",
1785
+ read,
1786
+ change: change(),
1787
+ revision,
1788
+ cause
1789
+ });
1790
+ }
1791
+ };
1792
+ scheduler.register(handle, record);
1793
+ return {
1794
+ source: handle,
1795
+ update(run) {
1796
+ scheduler.assertIdle();
1797
+ const staged = /* @__PURE__ */ new Map();
1798
+ let active = true;
1799
+ const draft = Object.freeze({
1800
+ get: (key) => {
1801
+ assertScope(() => active);
1802
+ const entry = staged.get(key);
1803
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
1804
+ },
1805
+ has: (key) => {
1806
+ assertScope(() => active);
1807
+ const entry = staged.get(key);
1808
+ return entry ? entry.present : values.has(key);
1809
+ },
1810
+ set: (key, value) => {
1811
+ assertScope(() => active);
1812
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1813
+ staged.set(key, {
1814
+ present: true,
1815
+ value
1816
+ });
1817
+ },
1818
+ remove: (key) => {
1819
+ assertScope(() => active);
1820
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1821
+ staged.set(key, { present: false });
1822
+ }
1823
+ });
1824
+ try {
1825
+ assertSynchronous(run(draft));
1826
+ } finally {
1827
+ active = false;
1828
+ }
1829
+ let changed = false;
1830
+ for (const [key, entry] of staged) {
1831
+ const present = values.has(key);
1832
+ if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1833
+ if (!touched.has(key)) touched.set(key, {
1834
+ present,
1835
+ value: values.get(key)
1836
+ });
1837
+ if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1838
+ if (entry.present) values.set(key, entry.value);
1839
+ else values.delete(key);
1840
+ changed = true;
1841
+ }
1842
+ if (!changed) return;
1843
+ revision++;
1844
+ cause = scheduler.batchContext()?.cause ?? cause;
1845
+ scheduler.capture(record);
1846
+ scheduler.run();
1847
+ }
1848
+ };
1849
+ };
1688
1850
  const externalValueSource = (port, equal) => {
1689
1851
  scheduler.assertIdle();
1690
1852
  const old = externalSources.get(port);
@@ -1877,6 +2039,7 @@ const createSources = (scheduler) => {
1877
2039
  set
1878
2040
  });
1879
2041
  },
2042
+ collectionInput,
1880
2043
  fromReadable: (readable, options) => {
1881
2044
  scheduler.assertIdle();
1882
2045
  if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
@@ -2497,11 +2660,15 @@ const createProjectionGraph = (options) => {
2497
2660
  fromSource: (source, options) => sources.fromSource(source, options),
2498
2661
  fromCollectionSource: (source) => sources.fromCollectionSource(source),
2499
2662
  input: sources.input,
2663
+ collectionInput: sources.collectionInput,
2500
2664
  fromReadable: sources.fromReadable,
2501
2665
  value,
2502
2666
  collection: (spec) => createCollection(scheduler, spec),
2503
2667
  map,
2504
2668
  batch: scheduler.batch,
2669
+ assertIdle: scheduler.assertIdle,
2670
+ releaseNode: scheduler.releaseNode,
2671
+ releaseInput: scheduler.unregisterSource,
2505
2672
  dispose: () => {
2506
2673
  try {
2507
2674
  scheduler.dispose();
@@ -2671,16 +2838,27 @@ const sourceOf = (instance) => instance;
2671
2838
  const createProjectionRuntime = (options) => {
2672
2839
  const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2673
2840
  const materialized = /* @__PURE__ */ new WeakMap();
2674
- const materialize = (projection) => {
2841
+ let disposed = false;
2842
+ const scopes = /* @__PURE__ */ new Set();
2843
+ const materialize = (projection, requester) => {
2844
+ const owner = require_advanced.ownerOf(projection);
2845
+ if (owner) {
2846
+ if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2847
+ if (!requester.active) throw new ProjectionDisposedError();
2848
+ }
2675
2849
  const old = materialized.get(projection);
2676
2850
  if (old) return old.node;
2677
- const definition = require_definition.definitionOf(projection);
2851
+ const definition = require_advanced.definitionOf(projection);
2678
2852
  let instance;
2679
- let setter;
2853
+ let write;
2680
2854
  switch (definition.kind) {
2681
2855
  case "input": {
2682
2856
  const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2683
- setter = input.set;
2857
+ write = {
2858
+ kind: "value-input",
2859
+ source: input.source,
2860
+ set: input.set
2861
+ };
2684
2862
  instance = runtime.value({
2685
2863
  sources: { input: input.source },
2686
2864
  build: (values) => ({
@@ -2693,6 +2871,16 @@ const createProjectionRuntime = (options) => {
2693
2871
  });
2694
2872
  break;
2695
2873
  }
2874
+ case "collection-input": {
2875
+ const source = runtime.collectionInput(definition.initial);
2876
+ write = {
2877
+ kind: "collection-input",
2878
+ source: source.source,
2879
+ update: source.update
2880
+ };
2881
+ instance = runtime.map(source.source, (_key, value) => value);
2882
+ break;
2883
+ }
2696
2884
  case "readable": {
2697
2885
  const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2698
2886
  instance = runtime.value({
@@ -2764,7 +2952,7 @@ const createProjectionRuntime = (options) => {
2764
2952
  break;
2765
2953
  }
2766
2954
  case "derive": {
2767
- const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency))]));
2955
+ const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2768
2956
  instance = runtime.value({
2769
2957
  sources,
2770
2958
  build: (values) => ({
@@ -2778,7 +2966,7 @@ const createProjectionRuntime = (options) => {
2778
2966
  break;
2779
2967
  }
2780
2968
  case "incremental-value": {
2781
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2969
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2782
2970
  instance = runtime.value({
2783
2971
  sources,
2784
2972
  build: definition.build
@@ -2786,7 +2974,7 @@ const createProjectionRuntime = (options) => {
2786
2974
  break;
2787
2975
  }
2788
2976
  case "incremental-collection": {
2789
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2977
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2790
2978
  instance = runtime.collection({
2791
2979
  sources,
2792
2980
  build: definition.build,
@@ -2796,14 +2984,19 @@ const createProjectionRuntime = (options) => {
2796
2984
  break;
2797
2985
  }
2798
2986
  }
2799
- materialized.set(projection, {
2987
+ const record = write ? {
2800
2988
  node: instance,
2801
- set: setter
2802
- });
2989
+ ...write
2990
+ } : {
2991
+ kind: "projection",
2992
+ node: instance
2993
+ };
2994
+ materialized.set(projection, record);
2995
+ if (owner) owner.materialized.push(projection);
2803
2996
  return instance;
2804
2997
  };
2805
- const get = (projection) => {
2806
- const instance = materialize(projection);
2998
+ const get = (projection, requester) => {
2999
+ const instance = materialize(projection, requester);
2807
3000
  const current = instance.current();
2808
3001
  if (instance.kind === "collection") {
2809
3002
  const record = materialized.get(projection) ?? materialized.get(instance);
@@ -2831,11 +3024,11 @@ const createProjectionRuntime = (options) => {
2831
3024
  }
2832
3025
  return current;
2833
3026
  };
2834
- function readable(projection, selector, equality = Object.is) {
3027
+ const makeReadable = (projection, selector, equality = Object.is, requester) => {
2835
3028
  if (!selector) return Object.freeze({
2836
- current: () => get(projection),
2837
- revision: () => materialize(projection).revision(),
2838
- subscribe: (listener) => materialize(projection).subscribe(listener)
3029
+ current: () => get(projection, requester),
3030
+ revision: () => materialize(projection, requester).revision(),
3031
+ subscribe: (listener) => materialize(projection, requester).subscribe(listener)
2839
3032
  });
2840
3033
  let initialized = false;
2841
3034
  let value;
@@ -2850,7 +3043,7 @@ const createProjectionRuntime = (options) => {
2850
3043
  let unsubscribeSource;
2851
3044
  const listeners = /* @__PURE__ */ new Set();
2852
3045
  const updateCollectionSnapshot = () => {
2853
- const current = get(projection);
3046
+ const current = get(projection, requester);
2854
3047
  previousCollection = isMapLike(current) ? current : void 0;
2855
3048
  };
2856
3049
  const evaluate = () => {
@@ -2864,7 +3057,7 @@ const createProjectionRuntime = (options) => {
2864
3057
  let source;
2865
3058
  let next;
2866
3059
  try {
2867
- source = get(projection);
3060
+ source = get(projection, requester);
2868
3061
  next = selector(source);
2869
3062
  } finally {
2870
3063
  activeTracker = previousTracker;
@@ -2881,30 +3074,30 @@ const createProjectionRuntime = (options) => {
2881
3074
  }
2882
3075
  selection = nextSelection;
2883
3076
  initialized = true;
2884
- sourceRevision = materialize(projection).revision();
3077
+ sourceRevision = materialize(projection, requester).revision();
2885
3078
  updateCollectionSnapshot();
2886
3079
  return changed;
2887
3080
  };
2888
3081
  const ensureCurrent = () => {
2889
- const revision = materialize(projection).revision();
3082
+ const revision = materialize(projection, requester).revision();
2890
3083
  if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
2891
3084
  return value;
2892
3085
  };
2893
3086
  const onProjectionChange = (incoming) => {
2894
3087
  let change = incoming;
2895
3088
  if (!change && previousCollection) {
2896
- const current = get(projection);
3089
+ const current = get(projection, requester);
2897
3090
  if (isMapLike(current)) {
2898
3091
  change = mapChange(previousCollection, current);
2899
3092
  previousCollection = current;
2900
3093
  if (!change) {
2901
- sourceRevision = materialize(projection).revision();
3094
+ sourceRevision = materialize(projection, requester).revision();
2902
3095
  return;
2903
3096
  }
2904
3097
  } else previousCollection = void 0;
2905
3098
  }
2906
3099
  if (!selectionAffects(selection, change)) {
2907
- sourceRevision = materialize(projection).revision();
3100
+ sourceRevision = materialize(projection, requester).revision();
2908
3101
  updateCollectionSnapshot();
2909
3102
  return;
2910
3103
  }
@@ -2912,12 +3105,12 @@ const createProjectionRuntime = (options) => {
2912
3105
  const changed = evaluate();
2913
3106
  if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
2914
3107
  unsubscribeSource();
2915
- unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3108
+ unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2916
3109
  }
2917
3110
  if (changed) Array.from(listeners).forEach((listener) => listener());
2918
3111
  };
2919
3112
  const installSource = () => {
2920
- if (!unsubscribeSource) unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3113
+ if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2921
3114
  };
2922
3115
  return Object.freeze({
2923
3116
  current: ensureCurrent,
@@ -2938,18 +3131,134 @@ const createProjectionRuntime = (options) => {
2938
3131
  };
2939
3132
  }
2940
3133
  });
3134
+ };
3135
+ function readable(projection, selector, equality) {
3136
+ return makeReadable(projection, selector, equality);
2941
3137
  }
3138
+ const setInput = (target, value, requester) => {
3139
+ materialize(target, requester);
3140
+ const record = materialized.get(target);
3141
+ if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3142
+ record.set(value);
3143
+ };
3144
+ const updateInput = (target, run, requester) => {
3145
+ materialize(target, requester);
3146
+ const record = materialized.get(target);
3147
+ if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3148
+ record.update(run);
3149
+ };
3150
+ const createScope = () => {
3151
+ runtime.assertIdle();
3152
+ const state = {
3153
+ active: true,
3154
+ materialized: [],
3155
+ subscriptions: /* @__PURE__ */ new Set()
3156
+ };
3157
+ scopes.add(state);
3158
+ const assertActive = () => {
3159
+ if (!state.active) throw new ProjectionDisposedError();
3160
+ };
3161
+ const own = (projection) => {
3162
+ assertActive();
3163
+ const definition = require_advanced.definitionOf(projection);
3164
+ const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : [];
3165
+ for (const dependency of dependencies) {
3166
+ const dependencyOwner = require_advanced.ownerOf(dependency);
3167
+ if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3168
+ }
3169
+ require_advanced.ownDefinition(projection, state);
3170
+ return projection;
3171
+ };
3172
+ const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
3173
+ const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
3174
+ const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), { collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)) });
3175
+ function scopeReadable(projection, selector, equality) {
3176
+ assertActive();
3177
+ materialize(projection, state);
3178
+ const source = makeReadable(projection, selector, equality, state);
3179
+ return Object.freeze({
3180
+ current: () => {
3181
+ assertActive();
3182
+ return source.current();
3183
+ },
3184
+ revision: () => {
3185
+ assertActive();
3186
+ return source.revision();
3187
+ },
3188
+ subscribe: (listener) => {
3189
+ assertActive();
3190
+ const stop = source.subscribe(listener);
3191
+ let subscribed = true;
3192
+ const unsubscribe = () => {
3193
+ if (!subscribed) return;
3194
+ subscribed = false;
3195
+ state.subscriptions.delete(unsubscribe);
3196
+ stop();
3197
+ };
3198
+ state.subscriptions.add(unsubscribe);
3199
+ return unsubscribe;
3200
+ }
3201
+ });
3202
+ }
3203
+ function scopeBatch(optionsOrCallback, maybeCallback) {
3204
+ assertActive();
3205
+ return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
3206
+ }
3207
+ return Object.freeze({
3208
+ input: scopedInput,
3209
+ derive: scopedDerive,
3210
+ incremental: scopedIncremental,
3211
+ get: (projection) => {
3212
+ assertActive();
3213
+ return get(projection, state);
3214
+ },
3215
+ readable: scopeReadable,
3216
+ set: (target, value) => {
3217
+ assertActive();
3218
+ setInput(target, value, state);
3219
+ },
3220
+ update: (target, run) => {
3221
+ assertActive();
3222
+ updateInput(target, run, state);
3223
+ },
3224
+ batch: scopeBatch,
3225
+ dispose: () => {
3226
+ if (!state.active) return;
3227
+ runtime.assertIdle();
3228
+ state.active = false;
3229
+ state.subscriptions.forEach((unsubscribe) => unsubscribe());
3230
+ for (const definition of state.materialized.reverse()) {
3231
+ const record = materialized.get(definition);
3232
+ if (!record) continue;
3233
+ runtime.releaseNode(record.node);
3234
+ if (record.kind !== "projection") runtime.releaseInput(record.source);
3235
+ materialized.delete(definition);
3236
+ }
3237
+ state.materialized.length = 0;
3238
+ scopes.delete(state);
3239
+ }
3240
+ });
3241
+ };
2942
3242
  const store = {
2943
3243
  get,
2944
3244
  readable,
2945
- set: ((input, value) => {
2946
- materialize(input);
2947
- const target = materialized.get(input);
2948
- if (!target?.set) throw new TypeError("Projection is not an input.");
2949
- target.set(value);
2950
- }),
3245
+ set: setInput,
3246
+ update: updateInput,
2951
3247
  batch: runtime.batch,
2952
- dispose: runtime.dispose
3248
+ scope: createScope,
3249
+ dispose: () => {
3250
+ if (disposed) return;
3251
+ runtime.assertIdle();
3252
+ disposed = true;
3253
+ for (const scope of scopes) {
3254
+ scope.active = false;
3255
+ scope.subscriptions.forEach((unsubscribe) => unsubscribe());
3256
+ scope.subscriptions.clear();
3257
+ scope.materialized.length = 0;
3258
+ }
3259
+ scopes.clear();
3260
+ runtime.dispose();
3261
+ }
2953
3262
  };
2954
3263
  return Object.freeze(store);
2955
3264
  };
@@ -2963,13 +3272,13 @@ exports.TransactionRejected = require_notification.TransactionRejected;
2963
3272
  exports.asReadable = asReadable;
2964
3273
  exports.createDocument = createDocument;
2965
3274
  exports.createProjectionRuntime = createProjectionRuntime;
2966
- exports.derive = require_definition.derive;
3275
+ exports.derive = require_advanced.derive;
2967
3276
  exports.field = field;
2968
- exports.input = require_definition.input;
3277
+ exports.input = require_advanced.input;
2969
3278
  exports.list = list;
2970
3279
  exports.map = map;
2971
3280
  exports.object = object;
2972
- exports.observe = require_definition.observe;
3281
+ exports.observe = require_advanced.observe;
2973
3282
  exports.optional = optional;
2974
3283
  exports.parse = require_scope.parse;
2975
3284
  exports.read = read;