Skip to content

Commit f0d1f16

Browse files
committed
add Free Proxy API
1 parent c466a8b commit f0d1f16

File tree

11 files changed

+1016
-19
lines changed

11 files changed

+1016
-19
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,9 @@ MIT license (see `LICENSE`).
660660

661661
Created by [Martin Kleppmann](https://martin.kleppmann.com/) and
662662
[many great contributors](https://github.com/automerge/automerge/graphs/contributors).
663+
664+
665+
# Proxy Free API
666+
Automerge uses JS Proxy extensively for its front-end API. However, to be able to support multiple JS runtime which does not support `Proxy` you can use the **Proxy Free API**.
667+
668+
To use the Proxy Free API, you will only need to change a flag by calling `Automerge.useProxyFreeAPI()`. Read more documentation on this API on [`proxy_free.md`]https://github.com/automerge/automerge/blob/main/proxy_free.md).

backend/encoding.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,47 @@
1+
12
/**
2-
* UTF-8 decoding and encoding using API that is supported in Node >= 12 and modern browsers:
3-
* https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encode
4-
* https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
5-
* If you're running in an environment where it's not available, please use a polyfill, such as:
6-
* https://github.com/anonyco/FastestSmallestTextEncoderDecoder
3+
* UTF-8 decoding and encoding
74
*/
8-
const utf8encoder = new TextEncoder()
9-
const utf8decoder = new TextDecoder('utf-8')
105

116
function stringToUtf8(string) {
12-
return utf8encoder.encode(string)
7+
if (typeof TextEncoder === 'function' && typeof TextDecoder === 'function') {
8+
// Modern web browsers:
9+
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encode
10+
const utf8encoder = new TextEncoder()
11+
return utf8encoder.encode(string)
12+
13+
} else if (typeof Buffer === 'function') {
14+
// Node.js:
15+
// https://nodejs.org/api/buffer.html
16+
return Buffer.from(string, 'utf8')
17+
18+
} else {
19+
// Could use a polyfill? e.g. https://github.com/anonyco/FastestSmallestTextEncoderDecoder
20+
throw new Error('Platform does not provide UTF-8 encoding/decoding feature')
21+
}
1322
}
1423

1524
function utf8ToString(buffer) {
16-
return utf8decoder.decode(buffer)
25+
if (typeof TextEncoder === 'function' && typeof TextDecoder === 'function') {
26+
// Modern web browsers:
27+
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
28+
const utf8decoder = new TextDecoder('utf-8')
29+
return utf8decoder.decode(buffer)
30+
31+
} else if (typeof Buffer === 'function') {
32+
// Node.js:
33+
// https://nodejs.org/api/string_decoder.html
34+
const { StringDecoder } = require('string_decoder')
35+
const utf8decoder = new StringDecoder('utf8')
36+
// In Node >= 10 we can simply do "utf8decoder.end(buffer)". However, in Node 8 there
37+
// is a bug that causes an Uint8Array to be incorrectly decoded when passed directly to
38+
// StringDecoder.end(). Wrapping in an additional "Buffer.from()" works around this bug.
39+
return utf8decoder.end(Buffer.from(buffer))
40+
41+
} else {
42+
// Could use a polyfill? e.g. https://github.com/anonyco/FastestSmallestTextEncoderDecoder
43+
throw new Error('Platform does not provide UTF-8 encoding/decoding feature')
44+
}
1745
}
1846

1947
/**

frontend/index.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const { OPTIONS, CACHE, STATE, OBJECT_ID, CONFLICTS, CHANGE, ELEM_IDS } = requir
22
const { isObject, copyObject } = require('../src/common')
33
const uuid = require('../src/uuid')
44
const { interpretPatch, cloneRootObject } = require('./apply_patch')
5-
const { rootObjectProxy } = require('./proxies')
5+
const { rootObjectProxy, setProxyFree } = require('./proxies')
66
const { Context } = require('./context')
77
const { Text } = require('./text')
88
const { Table } = require('./table')
@@ -160,6 +160,13 @@ function applyPatchToDoc(doc, patch, state, fromBackend) {
160160
return updateRootObject(doc, updated, state)
161161
}
162162

163+
/**
164+
* This function will set syntax defined by `ListProxyPolyfill`/`MapProxyPolyfill` as frontend interface
165+
*/
166+
function useProxyFreeAPI() {
167+
setProxyFree(true)
168+
}
169+
163170
/**
164171
* Creates an empty document object with no changes.
165172
*/
@@ -325,12 +332,21 @@ function applyPatch(doc, patch, backendState = undefined) {
325332
return updateRootObject(doc, {}, state)
326333
}
327334
}
335+
/**
336+
* Returns the Automerge value associated with `key` of the given object.
337+
*/
338+
function get(object, key) {
339+
if (typeof object.get === 'function') {
340+
return object.get(key)
341+
}
342+
return object[key]
343+
}
328344

329345
/**
330346
* Returns the Automerge object ID of the given object.
331347
*/
332348
function getObjectId(object) {
333-
return object[OBJECT_ID]
349+
return get(object, OBJECT_ID)
334350
}
335351

336352
/**
@@ -343,17 +359,17 @@ function getObjectById(doc, objectId) {
343359
// However, that requires knowing the path from the root to the current
344360
// object, which we don't have if we jumped straight to the object by its ID.
345361
// If we maintained an index from object ID to parent ID we could work out the path.
346-
if (doc[CHANGE]) {
362+
if (get(doc, CHANGE)) {
347363
throw new TypeError('Cannot use getObjectById in a change callback')
348364
}
349-
return doc[CACHE][objectId]
365+
return get(get(doc, CACHE), objectId)
350366
}
351367

352368
/**
353369
* Returns the Automerge actor ID of the given document.
354370
*/
355371
function getActorId(doc) {
356-
return doc[STATE].actorId || doc[OPTIONS].actorId
372+
return get(doc, STATE).actorId || get(doc, OPTIONS).actorId
357373
}
358374

359375
/**
@@ -409,7 +425,7 @@ function getElementIds(list) {
409425
}
410426

411427
module.exports = {
412-
init, from, change, emptyChange, applyPatch,
428+
useProxyFreeAPI, init, from, change, emptyChange, applyPatch,
413429
getObjectId, getObjectById, getActorId, setActorId, getConflicts, getLastLocalChange,
414430
getBackendState, getElementIds,
415431
Text, Table, Counter, Observable, Float64, Int, Uint

frontend/proxies.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ const { OBJECT_ID, CHANGE, STATE } = require('./constants')
22
const { createArrayOfNulls } = require('../src/common')
33
const { Text } = require('./text')
44
const { Table } = require('./table')
5+
const { ListProxyPolyfill, MapProxyPolyfill } = require('./proxy_polyfill')
6+
7+
/**
8+
* This variable express if interface will be defined by `ListProxyPolyfill`/`MapProxyPolyfill` (if `true`) or native `Proxy` (if `false`)
9+
*/
10+
let ProxyFree = false
11+
12+
/**
13+
* This function will set global varible `ProxyFree` which will express if interface will be defined by `ListProxyPolyfill`/`MapProxyPolyfill` (if `true`) or native `Proxy` (if `false`)
14+
*/
15+
function setProxyFree(value) {
16+
ProxyFree = value
17+
}
518

619
function parseListIndex(key) {
720
if (typeof key === 'string' && /^[0-9]+$/.test(key)) key = parseInt(key, 10)
@@ -30,7 +43,10 @@ function listMethods(context, listId, path) {
3043
},
3144

3245
indexOf(o, start = 0) {
33-
const id = o[OBJECT_ID]
46+
let id = o[OBJECT_ID]
47+
if (typeof o.get === 'function') {
48+
id = o.get(OBJECT_ID)
49+
}
3450
if (id) {
3551
const list = context.getObject(listId)
3652
for (let index = start; index < list.length; index++) {
@@ -231,10 +247,16 @@ const ListHandler = {
231247
}
232248

233249
function mapProxy(context, objectId, path, readonly) {
250+
if (ProxyFree) {
251+
return new MapProxyPolyfill({context, objectId, path, readonly}, MapHandler)
252+
}
234253
return new Proxy({context, objectId, path, readonly}, MapHandler)
235254
}
236255

237256
function listProxy(context, objectId, path) {
257+
if (ProxyFree) {
258+
return new ListProxyPolyfill([context, objectId, path], ListHandler, listMethods)
259+
}
238260
return new Proxy([context, objectId, path], ListHandler)
239261
}
240262

@@ -260,4 +282,4 @@ function rootObjectProxy(context) {
260282
return mapProxy(context, '_root', [])
261283
}
262284

263-
module.exports = { rootObjectProxy }
285+
module.exports = { rootObjectProxy, setProxyFree }

0 commit comments

Comments
 (0)