From d42583cd409742dfcde5cb1309d662652ad4d15e Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:26:22 +0900 Subject: [PATCH 01/22] change DataType for new query format --- src/core/DataType.ts | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/core/DataType.ts b/src/core/DataType.ts index 6570e3f..8397b1c 100644 --- a/src/core/DataType.ts +++ b/src/core/DataType.ts @@ -16,37 +16,47 @@ interface ExtraFieldErrorType { type DataTypeExtractFromQueryHash = '*' extends keyof QueryType ? { - [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '_params' | '*'>]: (key extends keyof BaseType - ? (key extends keyof QueryType - ? (QueryType[key] extends true - ? DataTypeExtractField - : DataTypeFromQuery) - : DataTypeExtractField) - : ExtraFieldErrorType) - } - : { - [key in keyof QueryType]: (key extends keyof BaseType - ? (QueryType[key] extends true - ? DataTypeExtractField - : DataTypeFromQuery) - : ExtraFieldErrorType) + [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '*'>]: ( + key extends keyof QueryType + ? _DataTypePickField + : key extends keyof BaseType + ? DataTypeExtractField + : ExtraFieldErrorType + ) } + : { [key in keyof QueryType]: _DataTypePickField } + +type _DataTypePickField = + SubQuery extends { field: infer N, query?: infer Q } + ? ( + N extends keyof BaseType + ? ( + IsAnyCompareLeftType extends Q + ? DataTypeExtractField + : DataTypeFromQuery) + : ExtraFieldErrorType + ) + : ( + Key extends keyof BaseType + ? (SubQuery extends true + ? DataTypeExtractField + : DataTypeFromQuery) + : ExtraFieldErrorType + ) type _DataTypeFromQuery = QueryType extends keyof BaseType | '*' ? DataTypeExtractFieldsFromQuery : QueryType extends Readonly<(keyof BaseType | '*')[]> ? DataTypeExtractFieldsFromQuery> - : QueryType extends { as: string } - ? { error: 'type for alias field is not supported' } | undefined : DataTypeExtractFromQueryHash export type DataTypeFromQuery = BaseType extends any[] ? CheckAttributesField[] : null extends BaseType ? CheckAttributesField | null - : CheckAttributesField + : CheckAttributesField -type CheckAttributesField = Q extends { attributes: infer R } +type CheckAttributesField = Q extends { query: infer R } ? _DataTypeFromQuery : _DataTypeFromQuery @@ -70,7 +80,7 @@ type _ValidateDataTypeExtraFileds = SelectString> ext : { error: { extraFields: SelectString> } } type ValidateDataTypeExtraFileds = _ValidateDataTypeExtraFileds, Type> -type RequestBase = { api: string; query: any; params?: any; _meta?: { data: any } } +type RequestBase = { field: string; query?: any; params?: any; _meta?: { data: any } } type DataTypeBaseFromRequestType = R extends { _meta?: { data: infer DataType } } ? DataType : never export type DataTypeFromRequest = ValidateDataTypeExtraFileds< DataTypeFromQuery, R['query']> From 359ca150187269f84e1c00d6fb9d7ecb43dc49ad Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:43:55 +0900 Subject: [PATCH 02/22] change generated ts output --- lib/ar_sync/type_script.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ar_sync/type_script.rb b/lib/ar_sync/type_script.rb index 8464c35..d55067a 100644 --- a/lib/ar_sync/type_script.rb +++ b/lib/ar_sync/type_script.rb @@ -35,7 +35,7 @@ def self.request_type_definition(api_class) multiple = field.type.is_a? ArSerializer::GraphQL::ListTypeClass definitions << <<~CODE export interface #{request_type_name} { - api: '#{field.name}' + field: '#{field.name}' params?: #{field.args_ts_type} query: Type#{association_type.name}Query _meta?: { data: Type#{field.type.association_type.name}#{'[]' if multiple} } @@ -59,7 +59,7 @@ def self.generate_model_script(mode) import ArSyncModelBase from 'ar_sync/#{mode}/ArSyncModel' export default class ArSyncModel extends ArSyncModelBase<{}> { constructor(r: R) { super(r) } - data: DataTypeFromRequest | null + data: DataTypeFromRequest | null } CODE end @@ -70,10 +70,10 @@ def self.generate_hooks_script(mode) import { DataTypeFromRequest } from 'ar_sync/core/DataType' import { useArSyncModel as useArSyncModelBase, useArSyncFetch as useArSyncFetchBase } from 'ar_sync/#{mode}/hooks' export function useArSyncModel(request: R | null) { - return useArSyncModelBase>(request) + return useArSyncModelBase>(request) } export function useArSyncFetch(request: R | null) { - return useArSyncFetchBase>(request) + return useArSyncFetchBase>(request) } CODE end From eac25a1e0869fe7ddf2ef889c74802c53f0cec1f Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:44:21 +0900 Subject: [PATCH 03/22] change core ts query --- src/core/ArSyncModelBase.ts | 2 +- src/core/hooksBase.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ArSyncModelBase.ts b/src/core/ArSyncModelBase.ts index fdafcb9..f5b23aa 100644 --- a/src/core/ArSyncModelBase.ts +++ b/src/core/ArSyncModelBase.ts @@ -1,4 +1,4 @@ -interface Request { api: string; query: any; params?: any } +interface Request { field: string; params?: any; query?: any } type Path = (string | number)[] interface Change { path: Path; value: any } type ChangeCallback = (change: Change) => void diff --git a/src/core/hooksBase.ts b/src/core/hooksBase.ts index 8e61232..39cffdf 100644 --- a/src/core/hooksBase.ts +++ b/src/core/hooksBase.ts @@ -3,7 +3,7 @@ import ArSyncAPI from './ArSyncApi' interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean } export type DataAndStatus = [T | null, ModelStatus] -export interface Request { api: string; params?: any; query: any } +export interface Request { field: string; params?: any; query?: any } interface ArSyncModel { data: T | null From de82724a200c49fa38280c293aba8859926af223 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:44:34 +0900 Subject: [PATCH 04/22] add alias field tests --- test/type_test.ts | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/test/type_test.ts b/test/type_test.ts index 3e88118..8ad692b 100644 --- a/test/type_test.ts +++ b/test/type_test.ts @@ -1,43 +1,50 @@ import ArSyncModel from './generated_typed_files/ArSyncModel' import { useArSyncModel, useArSyncFetch } from './generated_typed_files/hooks' -const [hooksData1] = useArSyncModel({ api: 'currentUser', query: 'id' }) +const [hooksData1] = useArSyncModel({ field: 'currentUser', query: 'id' }) hooksData1!.id -const [hooksData2] = useArSyncModel({ api: 'currentUser', query: { '*': true, foo: true } }) +const [hooksData2] = useArSyncModel({ field: 'currentUser', query: { '*': true, foo: true } }) hooksData2!.error.extraFields = 'foo' -const [hooksData3] = useArSyncFetch({ api: 'currentUser', query: 'id' }) +const [hooksData3] = useArSyncFetch({ field: 'currentUser', query: 'id' }) hooksData3!.id -const [hooksData4] = useArSyncFetch({ api: 'currentUser', query: { '*': true, foo: true } }) +const [hooksData4] = useArSyncFetch({ field: 'currentUser', query: { '*': true, foo: true } }) hooksData4!.error.extraFields = 'foo' -const data1 = new ArSyncModel({ api: 'currentUser', query: 'id' }).data! +const data1 = new ArSyncModel({ field: 'currentUser', query: 'id' }).data! data1.id -const data2 = new ArSyncModel({ api: 'currentUser', query: ['id', 'name'] }).data! +const data2 = new ArSyncModel({ field: 'currentUser', query: ['id', 'name'] }).data! data2.id; data2.name -const data3 = new ArSyncModel({ api: 'currentUser', query: '*' }).data! +const data3 = new ArSyncModel({ field: 'currentUser', query: '*' }).data! data3.id; data3.name; data3.posts -const data4 = new ArSyncModel({ api: 'currentUser', query: { posts: 'id' } }).data! +const data4 = new ArSyncModel({ field: 'currentUser', query: { posts: 'id' } }).data! data4.posts[0].id -const data5 = new ArSyncModel({ api: 'currentUser', query: { posts: '*' } }).data! +const data5 = new ArSyncModel({ field: 'currentUser', query: { posts: '*' } }).data! data5.posts[0].id; data5.posts[0].user; data5.posts[0].body -const data6 = new ArSyncModel({ api: 'currentUser', query: { posts: { '*': true, comments: 'user' } } }).data! +const data6 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, comments: 'user' } } }).data! data6.posts[0].id; data6.posts[0].user; data6.posts[0].comments[0].user -const data7 = new ArSyncModel({ api: 'currentUser', query: { name: true, poosts: true } }).data! +const data7 = new ArSyncModel({ field: 'currentUser', query: { name: true, poosts: true } }).data! data7.error.extraFields = 'poosts' -const data8 = new ArSyncModel({ api: 'currentUser', query: { posts: { id: true, commmments: true, titllle: true } } }).data! +const data8 = new ArSyncModel({ field: 'currentUser', query: { posts: { id: true, commmments: true, titllle: true } } }).data! data8.error.extraFields = 'commmments' data8.error.extraFields = 'titllle' -const data9 = new ArSyncModel({ api: 'currentUser', query: { '*': true, posts: { id: true, commmments: true } } }).data! +const data9 = new ArSyncModel({ field: 'currentUser', query: { '*': true, posts: { id: true, commmments: true } } }).data! data9.error.extraFields = 'commmments' -const data10 = new ArSyncModel({ api: 'users', query: { '*': true, posts: { id: true, comments: '*' } } }).data! +const data10 = new ArSyncModel({ field: 'users', query: { '*': true, posts: { id: true, comments: '*' } } }).data! data10[0].posts[0].comments[0].id -const data11 = new ArSyncModel({ api: 'users', query: { '*': true, posts: { id: true, comments: '*', commmments: true } } }).data! +const data11 = new ArSyncModel({ field: 'users', query: { '*': true, posts: { id: true, comments: '*', commmments: true } } }).data! data11.error.extraFields = 'commmments' -const data12 = new ArSyncModel({ api: 'currentUser', query: { posts: { params: { limit: 4 }, attributes: 'title' } } }).data! +const data12 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: 'title' } } }).data! data12.posts[0].title -const data13 = new ArSyncModel({ api: 'currentUser', query: { posts: { params: { limit: 4 }, attributes: ['id', 'title'] } } }).data! +const data13 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: ['id', 'title'] } } }).data! data13.posts[0].title -const data14 = new ArSyncModel({ api: 'currentUser', query: { posts: { params: { limit: 4 }, attributes: { id: true, title: true } } } }).data! +const data14 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: { id: true, title: true } } } }).data! data14.posts[0].title -const data15 = new ArSyncModel({ api: 'currentUser', query: { posts: ['id', 'title'] } } as const).data! +const data15 = new ArSyncModel({ field: 'currentUser', query: { posts: ['id', 'title'] } } as const).data! data15.posts[0].title + +const data16 = new ArSyncModel({ field: 'currentUser', query: { id: { field: 'name' }, name: { field: 'id' }, id2: { field: 'id' }, name2: { field: 'name' } } }).data! +data16.name = data16.id2 = 0 +data16.id = data16.name2 = 'name' +const data17 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, hoge: { field: 'comments', query: 'id' }, comments: { field: 'title' } } } }).data! +data17.posts[0].comments = 'title' +data17.posts[0].hoge[0].id = 0 From 8906cbe68b6e6df1f2bd48502ebf8c309984e5e5 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:46:37 +0900 Subject: [PATCH 05/22] fix rails endpoint for new query --- lib/ar_sync/rails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ar_sync/rails.rb b/lib/ar_sync/rails.rb index 0ade33b..9af2d2c 100644 --- a/lib/ar_sync/rails.rb +++ b/lib/ar_sync/rails.rb @@ -99,7 +99,7 @@ def _api_call(type) end responses = params[:requests].map do |request| begin - api_name = request[:api] + api_name = request[:field] info = self.class._serializer_field_info api_name raise ArSync::ApiNotFound, "#{type.to_s.capitalize} API named `#{api_name}` not configured" unless info api_params = (request[:params].as_json || {}).transform_keys(&:to_sym) From 8c8291461a72d13e642f15f70884dd8dc47c8ef3 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:49:37 +0900 Subject: [PATCH 06/22] fix path extract for new query --- lib/ar_sync/core.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/ar_sync/core.rb b/lib/ar_sync/core.rb index 008dfbc..b294949 100644 --- a/lib/ar_sync/core.rb +++ b/lib/ar_sync/core.rb @@ -119,13 +119,13 @@ def self.sync_api(model, current_user, args) def self._extract_paths(args) parsed = ArSerializer::Serializer.parse_args args paths = [] - extract = lambda do |path, attributes| + extract = lambda do |path, query| paths << path - attributes.each do |key, value| - sub_attributes = value[:attributes] - next unless sub_attributes - sub_path = [*path, key] - extract.call sub_path, sub_attributes + query&.each do |key, value| + sub_query = value[:attributes] + next unless sub_query + sub_path = [*path, (value[:field_name] || key)] + extract.call sub_path, sub_query end end extract.call [], parsed[:attributes] From e312f3f66d18a3aa58ee670a01ec2a3e7be1c5cb Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 21:57:14 +0900 Subject: [PATCH 07/22] =?UTF-8?q?tree:=20query,=20attributes=20=E2=86=92?= =?UTF-8?q?=20request,=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree/ArSyncStore.ts | 66 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/tree/ArSyncStore.ts b/src/tree/ArSyncStore.ts index 040248c..4e8eaf5 100644 --- a/src/tree/ArSyncStore.ts +++ b/src/tree/ArSyncStore.ts @@ -167,11 +167,11 @@ class Updator { export default class ArSyncStore { data - query + request immutable - constructor(query, data, option = {} as { immutable?: boolean }) { + constructor(request, data, option = {} as { immutable?: boolean }) { this.data = option.immutable ? Updator.createFrozenObject(data) : data - this.query = ArSyncStore.parseQuery(query) + this.request = ArSyncStore.parseQuery(request) this.immutable = option.immutable } replaceData(data) { @@ -187,13 +187,13 @@ export default class ArSyncStore { update(patch) { return this.batchUpdate([patch]) } - _slicePatch(patchData, query) { + _slicePatch(patchData, request) { const obj = {} for (const key in patchData) { - if (key === 'id' || query.attributes['*']) { + if (key === 'id' || request.query['*']) { obj[key] = patchData[key] } else { - const subq = query.attributes[key] + const subq = request.query[key] if (subq) { obj[subq.column || key] = patchData[key] } @@ -201,11 +201,11 @@ export default class ArSyncStore { } return obj } - _applyPatch(data, accessKeys, actualPath, updator, query, patchData) { + _applyPatch(data, accessKeys, actualPath, updator, request, patchData) { for (const key in patchData) { - const subq = query.attributes[key] + const subq = request.query[key] const value = patchData[key] - if (subq || query.attributes['*']) { + if (subq || request.query['*']) { const subcol = (subq && subq.column) || key if (data[subcol] !== value) { this.data = updator.add(this.data, accessKeys, actualPath, subcol, value) @@ -216,7 +216,7 @@ export default class ArSyncStore { _update(patch, updator, events) { const { action, path } = patch const patchData = patch.data - let query = this.query + let request = this.request let data = this.data const actualPath: (string | number)[] = [] const accessKeys: (string | number)[] = [] @@ -229,10 +229,10 @@ export default class ArSyncStore { accessKeys.push(idx) data = data[idx] } else { - const { attributes } = query - if (!attributes[nameOrId]) return - const column = attributes[nameOrId].column || nameOrId - query = attributes[nameOrId] + const { query } = request + if (!query[nameOrId]) return + const column = query[nameOrId].column || nameOrId + request = query[nameOrId] actualPath.push(column) accessKeys.push(column) data = data[column] @@ -246,20 +246,20 @@ export default class ArSyncStore { idx = data.findIndex(o => o.id === id) target = data[idx] } else if (nameOrId) { - const { attributes } = query - if (!attributes[nameOrId]) return - column = attributes[nameOrId].column || nameOrId - query = attributes[nameOrId] + const { query } = request + if (!query[nameOrId]) return + column = query[nameOrId].column || nameOrId + request = query[nameOrId] target = data[column] } if (action === 'create') { - const obj = this._slicePatch(patchData, query) + const obj = this._slicePatch(patchData, request) if (column) { this.data = updator.add(this.data, accessKeys, actualPath, column, obj) } else if (!target) { const ordering = Object.assign({}, patch.ordering) - const limitOverride = query.params && query.params.limit - ordering.order = query.params && query.params.order || ordering.order + const limitOverride = request.params && request.params.limit + ordering.order = request.params && request.params.order || ordering.order if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) ordering.limit = limitOverride this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering) } @@ -282,42 +282,42 @@ export default class ArSyncStore { accessKeys.push(idx) } if (action === 'update') { - this._applyPatch(target, accessKeys, actualPath, updator, query, patchData) + this._applyPatch(target, accessKeys, actualPath, updator, request, patchData) } else { const eventData = { target, path: actualPath, data: patchData.data } events.push({ type: patchData.type, data: eventData }) } } - static parseQuery(query, attrsonly?){ - const attributes = {} + static parseQuery(request, attrsonly?){ + const query = {} let column = null let params = null - if (query.constructor !== Array) query = [query] - for (const arg of query) { + if (request.constructor !== Array) request = [request] + for (const arg of request) { if (typeof(arg) === 'string') { - attributes[arg] = {} + query[arg] = {} } else if (typeof(arg) === 'object') { for (const key in arg){ const value = arg[key] if (attrsonly) { - attributes[key] = this.parseQuery(value) + query[key] = this.parseQuery(value) continue } - if (key === 'attributes') { + if (key === 'query') { const child = this.parseQuery(value, true) - for (const k in child) attributes[k] = child[k] + for (const k in child) query[k] = child[k] } else if (key === 'as') { column = value } else if (key === 'params') { params = value } else { - attributes[key] = this.parseQuery(value) + query[key] = this.parseQuery(value) } } } } - if (attrsonly) return attributes - return { attributes, column, params } + if (attrsonly) return query + return { query, column, params } } } From 0155f6843dc30881eb1633b6a9d85b2fdc1aafb6 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 22:07:12 +0900 Subject: [PATCH 08/22] refactor parseQuery in both tree & graph --- src/core/parseRequest.ts | 32 ++++++++++++++++++++++++++++++++ src/graph/ArSyncStore.ts | 35 ++--------------------------------- src/tree/ArSyncStore.ts | 35 ++--------------------------------- 3 files changed, 36 insertions(+), 66 deletions(-) create mode 100644 src/core/parseRequest.ts diff --git a/src/core/parseRequest.ts b/src/core/parseRequest.ts new file mode 100644 index 0000000..654ff1d --- /dev/null +++ b/src/core/parseRequest.ts @@ -0,0 +1,32 @@ +export function parseRequest(request, attrsonly?){ + const query = {} + let field = null + let params = null + if (!request) request = [] + if (request.constructor !== Array) request = [request] + for (const arg of request) { + if (typeof(arg) === 'string') { + query[arg] = {} + } else if (typeof(arg) === 'object') { + for (const key in arg){ + const value = arg[key] + if (attrsonly) { + query[key] = parseRequest(value) + continue + } + if (key === 'query') { + const child = parseRequest(value, true) + for (const k in child) query[k] = child[k] + } else if (key === 'field') { + field = value + } else if (key === 'params') { + params = value + } else { + query[key] = parseRequest(value) + } + } + } + } + if (attrsonly) return query + return { query, field, params } +} diff --git a/src/graph/ArSyncStore.ts b/src/graph/ArSyncStore.ts index 091af14..b328902 100644 --- a/src/graph/ArSyncStore.ts +++ b/src/graph/ArSyncStore.ts @@ -1,4 +1,5 @@ import ArSyncAPI from '../core/ArSyncApi' +import { parseRequest } from '../core/parseRequest' const ModelBatchRequest = { timer: null, @@ -96,40 +97,8 @@ class ArSyncContainerBase { for (const l of this.listeners) l.unsubscribe() this.listeners = [] } - static parseQuery(query, attrsonly = false){ - const attributes = {} - let column = null - let params = null - if (!query) query = [] - if (query.constructor !== Array) query = [query] - for (const arg of query) { - if (typeof(arg) === 'string') { - attributes[arg] = {} - } else if (typeof(arg) === 'object') { - for (const key in arg){ - const value = arg[key] - if (attrsonly) { - attributes[key] = this.parseQuery(value) - continue - } - if (key === 'attributes') { - const child = this.parseQuery(value, true) - for (const k in child) attributes[k] = child[k] - } else if (key === 'as') { - column = value - } else if (key === 'params') { - params = value - } else { - attributes[key] = this.parseQuery(value) - } - } - } - } - if (attrsonly) return attributes - return { attributes, as: column, params } - } static _load({ api, id, params, query }, root) { - const parsedQuery = ArSyncRecord.parseQuery(query) + const parsedQuery = parseRequest(query) if (id) { return ModelBatchRequest.fetch(api, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)) } else { diff --git a/src/tree/ArSyncStore.ts b/src/tree/ArSyncStore.ts index 4e8eaf5..8a81e7a 100644 --- a/src/tree/ArSyncStore.ts +++ b/src/tree/ArSyncStore.ts @@ -1,3 +1,4 @@ +import { parseRequest } from '../core/parseRequest' class Updator { changes markedForFreezeObjects @@ -171,7 +172,7 @@ export default class ArSyncStore { immutable constructor(request, data, option = {} as { immutable?: boolean }) { this.data = option.immutable ? Updator.createFrozenObject(data) : data - this.request = ArSyncStore.parseQuery(request) + this.request = parseRequest(request) this.immutable = option.immutable } replaceData(data) { @@ -288,36 +289,4 @@ export default class ArSyncStore { events.push({ type: patchData.type, data: eventData }) } } - - static parseQuery(request, attrsonly?){ - const query = {} - let column = null - let params = null - if (request.constructor !== Array) request = [request] - for (const arg of request) { - if (typeof(arg) === 'string') { - query[arg] = {} - } else if (typeof(arg) === 'object') { - for (const key in arg){ - const value = arg[key] - if (attrsonly) { - query[key] = this.parseQuery(value) - continue - } - if (key === 'query') { - const child = this.parseQuery(value, true) - for (const k in child) query[k] = child[k] - } else if (key === 'as') { - column = value - } else if (key === 'params') { - params = value - } else { - query[key] = this.parseQuery(value) - } - } - } - } - if (attrsonly) return query - return { query, column, params } - } } From 606f606ef8c01fa9a47412b0294028647b5697b1 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 22:24:47 +0900 Subject: [PATCH 09/22] =?UTF-8?q?graph:=20query,=20attributes=20=E2=86=92?= =?UTF-8?q?=20request,=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/graph/ArSyncStore.ts | 64 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/graph/ArSyncStore.ts b/src/graph/ArSyncStore.ts index b328902..cb7e9be 100644 --- a/src/graph/ArSyncStore.ts +++ b/src/graph/ArSyncStore.ts @@ -4,7 +4,7 @@ import { parseRequest } from '../core/parseRequest' const ModelBatchRequest = { timer: null, apiRequests: {} as { - [api: string]: { + [key: string]: { [queryJSON: string]: { query requests: { @@ -17,11 +17,11 @@ const ModelBatchRequest = { } } }, - fetch(api, query, id) { + fetch(field, query, id) { this.setTimer() return new Promise(resolve => { const queryJSON = JSON.stringify(query) - const apiRequest = this.apiRequests[api] = this.apiRequests[api] || {} + const apiRequest = this.apiRequests[field] = this.apiRequests[field] || {} const queryRequests = apiRequest[queryJSON] = apiRequest[queryJSON] || { query, requests: {} } const request = queryRequests.requests[id] = queryRequests.requests[id] || { id, callbacks: [] } request.callbacks.push(resolve) @@ -29,11 +29,11 @@ const ModelBatchRequest = { }, batchFetch() { const { apiRequests } = this as typeof ModelBatchRequest - for (const api in apiRequests) { - const apiRequest = apiRequests[api] + for (const field in apiRequests) { + const apiRequest = apiRequests[field] for (const { query, requests } of Object.values(apiRequest)) { const ids = Object.values(requests).map(({ id }) => id) - ArSyncAPI.syncFetch({ api, query, params: { ids } }).then((models: any[]) => { + ArSyncAPI.syncFetch({ field, query, params: { ids } }).then((models: any[]) => { for (const model of models) requests[model.id].model = model for (const { model, callbacks } of Object.values(requests)) { for (const callback of callbacks) callback(model) @@ -97,15 +97,15 @@ class ArSyncContainerBase { for (const l of this.listeners) l.unsubscribe() this.listeners = [] } - static _load({ api, id, params, query }, root) { - const parsedQuery = parseRequest(query) + static _load({ field, id, params, query }, root) { + const parsedQuery = parseRequest(query, true) if (id) { - return ModelBatchRequest.fetch(api, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)) + return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)) } else { - const request = { api, query, params } + const request = { field, query, params } return ArSyncAPI.syncFetch(request).then((response: any) => { if (response.collection && response.order) { - return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, response, request, root) + return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, params, response, request, root) } else { return new ArSyncRecord(parsedQuery, response, request, root) } @@ -137,10 +137,10 @@ class ArSyncRecord extends ArSyncContainerBase { sync_keys paths reloadQueryCache - constructor(query, data, request, root) { + constructor(query, data, initialRequest, root) { super() this.root = root - if (request) this.initForReload(request) + if (initialRequest) this.initForReload(initialRequest) this.query = query this.data = {} this.children = {} @@ -160,16 +160,16 @@ class ArSyncRecord extends ArSyncContainerBase { this.data.id = data.id } this.paths = [] - for (const key in this.query.attributes) { - const subQuery = this.query.attributes[key] - const aliasName = subQuery.as || key + for (const key in this.query) { + const queryField = this.query[key] + const aliasName = queryField.as || key const subData = data[aliasName] if (key === 'sync_keys') continue - if (subQuery.attributes && (subData instanceof Array || (subData && subData.collection && subData.order))) { + if (queryField.query && (subData instanceof Array || (subData && subData.collection && subData.order))) { if (this.children[aliasName]) { this.children[aliasName].replaceData(subData, this.sync_keys) } else { - const collection = new ArSyncCollection(this.sync_keys, key, subQuery, subData, null, this.root) + const collection = new ArSyncCollection(this.sync_keys, key, queryField.query, queryField.params, subData, null, this.root) this.mark() this.children[aliasName] = collection this.data[aliasName] = collection.data @@ -177,12 +177,12 @@ class ArSyncRecord extends ArSyncContainerBase { collection.parentKey = aliasName } } else { - if (subQuery.attributes && Object.keys(subQuery.attributes).length > 0) this.paths.push(key); + if (queryField.query && Object.keys(queryField.query).length > 0) this.paths.push(key); if (subData && subData.sync_keys) { if (this.children[aliasName]) { this.children[aliasName].replaceData(subData) } else { - const model = new ArSyncRecord(subQuery, subData, null, this.root) + const model = new ArSyncRecord(queryField.query, subData, null, this.root) this.mark() this.children[aliasName] = model this.data[aliasName] = model.data @@ -221,7 +221,7 @@ class ArSyncRecord extends ArSyncContainerBase { this.onChange([path], null) } else if (action === 'add') { if (this.data.id === id) return - const query = this.query.attributes[path] + const query = this.query[path] ModelBatchRequest.fetch(class_name, query, id).then(data => { if (!data) return const model = new ArSyncRecord(query, data, null, this.root) @@ -251,14 +251,14 @@ class ArSyncRecord extends ArSyncContainerBase { } reloadQuery() { if (this.reloadQueryCache) return this.reloadQueryCache - const reloadQuery = this.reloadQueryCache = { attributes: [] as any[] } - for (const key in this.query.attributes) { + const reloadQuery = this.reloadQueryCache = { query: [] as any[] } + for (const key in this.query) { if (key === 'sync_keys') continue - const val = this.query.attributes[key] - if (!val || !val.attributes) { - reloadQuery.attributes.push(key) - } else if (!val.params && Object.keys(val.attributes).length === 0) { - reloadQuery.attributes.push({ [key]: val }) + const val = this.query[key] + if (!val || !val.query) { + reloadQuery.query.push(key) + } else if (!val.params && Object.keys(val.query).length === 0) { + reloadQuery.query.push({ [key]: val }) } } return reloadQuery @@ -295,13 +295,13 @@ class ArSyncCollection extends ArSyncContainerBase { data children sync_keys - constructor(sync_keys, path, query, data, request, root){ + constructor(sync_keys, path, query, params, data, initialRequest, root){ super() this.root = root this.path = path - if (request) this.initForReload(request) - if (query.params && (query.params.order || query.params.limit)) { - this.order = { limit: query.params.limit, mode: query.params.order || 'asc' } + if (initialRequest) this.initForReload(initialRequest) + if (params && (params.order || params.limit)) { + this.order = { limit: params.limit, mode: params.order || 'asc' } } else { this.order = { limit: null, mode: 'asc' } } From 0fb2c5897f378c9b8c889358a562fc99ca5bad16 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 22:30:10 +0900 Subject: [PATCH 10/22] =?UTF-8?q?graph:=20as=20=E2=86=92=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/graph/ArSyncStore.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/graph/ArSyncStore.ts b/src/graph/ArSyncStore.ts index cb7e9be..6ab0a14 100644 --- a/src/graph/ArSyncStore.ts +++ b/src/graph/ArSyncStore.ts @@ -162,41 +162,41 @@ class ArSyncRecord extends ArSyncContainerBase { this.paths = [] for (const key in this.query) { const queryField = this.query[key] - const aliasName = queryField.as || key - const subData = data[aliasName] + const aliasName = queryField.field || key + const subData = data[key] if (key === 'sync_keys') continue if (queryField.query && (subData instanceof Array || (subData && subData.collection && subData.order))) { - if (this.children[aliasName]) { - this.children[aliasName].replaceData(subData, this.sync_keys) + if (this.children[key]) { + this.children[key].replaceData(subData, this.sync_keys) } else { - const collection = new ArSyncCollection(this.sync_keys, key, queryField.query, queryField.params, subData, null, this.root) + const collection = new ArSyncCollection(this.sync_keys, aliasName, queryField.query, queryField.params, subData, null, this.root) this.mark() - this.children[aliasName] = collection - this.data[aliasName] = collection.data + this.children[key] = collection + this.data[key] = collection.data collection.parentModel = this - collection.parentKey = aliasName + collection.parentKey = key } } else { if (queryField.query && Object.keys(queryField.query).length > 0) this.paths.push(key); if (subData && subData.sync_keys) { - if (this.children[aliasName]) { - this.children[aliasName].replaceData(subData) + if (this.children[key]) { + this.children[key].replaceData(subData) } else { const model = new ArSyncRecord(queryField.query, subData, null, this.root) this.mark() - this.children[aliasName] = model - this.data[aliasName] = model.data + this.children[key] = model + this.data[key] = model.data model.parentModel = this - model.parentKey = aliasName + model.parentKey = key } } else { - if(this.children[aliasName]) { - this.children[aliasName].release() - delete this.children[aliasName] + if(this.children[key]) { + this.children[key].release() + delete this.children[key] } - if (this.data[aliasName] !== subData) { + if (this.data[key] !== subData) { this.mark() - this.data[aliasName] = subData + this.data[key] = subData } } } From a4952ff2859a4e0ba6ae2300d031c0e5b1461a4f Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 May 2019 23:15:50 +0900 Subject: [PATCH 11/22] =?UTF-8?q?tree:=20as=20=E2=86=92=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tree/ArSyncStore.ts | 156 +++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/src/tree/ArSyncStore.ts b/src/tree/ArSyncStore.ts index 8a81e7a..adc49b2 100644 --- a/src/tree/ArSyncStore.ts +++ b/src/tree/ArSyncStore.ts @@ -189,104 +189,110 @@ export default class ArSyncStore { return this.batchUpdate([patch]) } _slicePatch(patchData, request) { - const obj = {} - for (const key in patchData) { - if (key === 'id' || request.query['*']) { - obj[key] = patchData[key] - } else { - const subq = request.query[key] - if (subq) { - obj[subq.column || key] = patchData[key] - } - } + const obj = request.query && request.query['*'] ? { ...patchData } : {} + for (const key in request) { + const fieldQuery = request[key] + const field = (fieldQuery && fieldQuery.field) || key + if (field in patchData) obj[key] = patchData[field] } + if (patchData.id) obj.id = patchData.id return obj } - _applyPatch(data, accessKeys, actualPath, updator, request, patchData) { + _applyPatch(data, accessKeys, actualPath, updator, query, patchData) { for (const key in patchData) { - const subq = request.query[key] - const value = patchData[key] - if (subq || request.query['*']) { - const subcol = (subq && subq.column) || key - if (data[subcol] !== value) { - this.data = updator.add(this.data, accessKeys, actualPath, subcol, value) + const subq = query[key] + const value = patchData[(subq && subq.field) || key] + if (subq || query['*']) { + if (data[key] !== value) { + this.data = updator.add(this.data, accessKeys, actualPath, key, value) } } } } - _update(patch, updator, events) { + _update(patch: { action: string; path: (string | number)[]; ordering; data }, updator, events) { const { action, path } = patch const patchData = patch.data let request = this.request let data = this.data - const actualPath: (string | number)[] = [] - const accessKeys: (string | number)[] = [] - for (let i = 0; i < path.length - 1; i++) { + const trace = (i: number, actualPath: (string | number)[], accessKeys: (string | number)[], query, data) => { const nameOrId = path[i] + const lastStep = i === path.length - 1 if (typeof(nameOrId) === 'number') { const idx = data.findIndex(o => o.id === nameOrId) - if (idx < 0) return - actualPath.push(nameOrId) - accessKeys.push(idx) - data = data[idx] + if (lastStep) { + apply(accessKeys, actualPath, query, null, idx, data[idx]) + } else { + if (idx < 0) return + actualPath.push(nameOrId) + accessKeys.push(idx) + const data2 = data[idx] + trace(i + 1, actualPath, accessKeys, query, data2) + } } else { - const { query } = request - if (!query[nameOrId]) return - const column = query[nameOrId].column || nameOrId - request = query[nameOrId] - actualPath.push(column) - accessKeys.push(column) - data = data[column] + const matchedKeys: string[] = [] + for (const key in query) { + const field = query[key].field || key + if (field === nameOrId) matchedKeys.push(key) + } + const fork = matchedKeys.length > 1 + for (const key of matchedKeys) { + const queryField = query[key] + if (lastStep) { + if (!queryField) return + apply(accessKeys, actualPath, query[key], key, null, data[key]) + } else { + const data2 = data[key] + if (!data2) return + const actualPath2 = fork ? [...actualPath] : actualPath + const accessKeys2 = fork ? [...accessKeys] : accessKeys + actualPath2.push(key) + accessKeys2.push(key) + trace(i + 1, actualPath2, accessKeys2, queryField.query, data2) + } + } } - if (!data) return - } - const nameOrId = path[path.length - 1] - let id, idx, column, target = data - if (typeof(nameOrId) === 'number') { - id = nameOrId - idx = data.findIndex(o => o.id === id) - target = data[idx] - } else if (nameOrId) { - const { query } = request - if (!query[nameOrId]) return - column = query[nameOrId].column || nameOrId - request = query[nameOrId] - target = data[column] } - if (action === 'create') { - const obj = this._slicePatch(patchData, request) - if (column) { - this.data = updator.add(this.data, accessKeys, actualPath, column, obj) - } else if (!target) { - const ordering = Object.assign({}, patch.ordering) - const limitOverride = request.params && request.params.limit - ordering.order = request.params && request.params.order || ordering.order - if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) ordering.limit = limitOverride - this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering) + const apply = (accessKeys: (string | number)[], actualPath: (string | number)[], query, column: string | null, idx: number | null, target) => { + if (action === 'create') { + const obj = this._slicePatch(patchData, query) + if (column) { + this.data = updator.add(this.data, accessKeys, actualPath, column, obj) + } else if (!target) { + const ordering = Object.assign({}, patch.ordering) + const limitOverride = request.params && request.params.limit + ordering.order = request.params && request.params.order || ordering.order + if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) ordering.limit = limitOverride + this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering) + } + return } - return - } - if (action === 'destroy') { + if (action === 'destroy') { + if (column) { + this.data = updator.remove(this.data, accessKeys, actualPath, column) + } else if (idx != null) { + this.data = updator.remove(this.data, accessKeys, actualPath, idx) + } + return + } + if (!target) return if (column) { - this.data = updator.remove(this.data, accessKeys, actualPath, column) - } else if (idx >= 0) { - this.data = updator.remove(this.data, accessKeys, actualPath, idx) + actualPath.push(column) + accessKeys.push(column) + } else if (idx != null && patchData.id) { + actualPath.push(patchData.id) + accessKeys.push(idx) + } + if (action === 'update') { + this._applyPatch(target, accessKeys, actualPath, updator, query, patchData) + } else { + const eventData = { target, path: actualPath, data: patchData.data } + events.push({ type: patchData.type, data: eventData }) } - return - } - if (!target) return - if (column) { - actualPath.push(column) - accessKeys.push(column) - } else if (id) { - actualPath.push(id) - accessKeys.push(idx) } - if (action === 'update') { - this._applyPatch(target, accessKeys, actualPath, updator, request, patchData) + if (path.length === 0) { + apply([], [], request.query, null, null, data) } else { - const eventData = { target, path: actualPath, data: patchData.data } - events.push({ type: patchData.type, data: eventData }) + trace(0, [], [], request.query, data) } } } From b7a7a271a05528ac0894de567b6410e5fd544eff Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 27 May 2019 01:52:34 +0900 Subject: [PATCH 12/22] fix testcode query --- test/ar_sync_test.rb | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/test/ar_sync_test.rb b/test/ar_sync_test.rb index b024fad..03fe0a1 100644 --- a/test/ar_sync_test.rb +++ b/test/ar_sync_test.rb @@ -9,9 +9,29 @@ end end -query = [name: { as: '名前' }, posts: [:user, :title, as: :articles, my_comments: [:star_count, as: :my_opinions], comments: [:star_count, :user, my_stars: :id, my_star: { as: :my_reaction }]]] -post_query = [:user, :title, comments: [:body, as: :cmnts]] -collection_query = [:user, :title, my_comments: [:star_count, as: :my_opinions], comments: [:star_count, :user, my_stars: :id, my_star: { as: :my_reaction }]] +query = { + 名前: { field: :name }, + articles: { + field: :posts, + query: { + user: true, title: true, + my_opinions: { field: :my_comments, query: :star_count }, + comments: { + star_count: true, user: true, my_stars: :id, + my_reaction: { field: :my_star } + } + } + } +} +post_query = { user: true, title: true, cmnts: { field: :comments, query: :body } } +collection_query = { + user: true, title: true, + my_opinions: { field: :my_comments, query: :star_count }, + comments: { + star_count: true, user: true, my_stars: :id, + my_reaction: { field: :my_star } + } +} $test_cases = { user: [Tree::User.first, query], From 252aabd26b5d12148b72ff258d47809890a5b292 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 27 May 2019 03:23:09 +0900 Subject: [PATCH 13/22] add parseRequest to assetspipeline dependencies --- vendor/assets/javascripts/ar_sync_graph.js.erb | 2 ++ vendor/assets/javascripts/ar_sync_tree.js.erb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/vendor/assets/javascripts/ar_sync_graph.js.erb b/vendor/assets/javascripts/ar_sync_graph.js.erb index f05520c..55f6353 100644 --- a/vendor/assets/javascripts/ar_sync_graph.js.erb +++ b/vendor/assets/javascripts/ar_sync_graph.js.erb @@ -3,6 +3,8 @@ var modules = {} function require(name) { return modules[name] } <% require = -> (path) { path = File.dirname(__FILE__) + "/../../../#{path}"; depend_on path; File.read path } %> + <%= require.call 'core/parseRequest.js' %> + modules['../core/parseRequest'] = { parseRequest: exports.parseRequest } <%= require.call 'core/ArSyncApi.js' %> window.ArSyncAPI = exports.default modules['../core/ArSyncApi'] = { default: exports.default } diff --git a/vendor/assets/javascripts/ar_sync_tree.js.erb b/vendor/assets/javascripts/ar_sync_tree.js.erb index 45fafe6..5ac39f5 100644 --- a/vendor/assets/javascripts/ar_sync_tree.js.erb +++ b/vendor/assets/javascripts/ar_sync_tree.js.erb @@ -3,6 +3,8 @@ var modules = {} function require(name) { return modules[name] } <% require = -> (path) { path = File.dirname(__FILE__) + "/../../../#{path}"; depend_on path; File.read path } %> + <%= require.call 'core/parseRequest.js' %> + modules['../core/parseRequest'] = { parseRequest: exports.parseRequest } <%= require.call 'core/ArSyncApi.js' %> window.ArSyncAPI = exports.default modules['../core/ArSyncApi'] = { default: exports.default } From 58f754c4b210d56cdda99bf198bda562a426decc Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 27 May 2019 03:50:11 +0900 Subject: [PATCH 14/22] graph bugfix --- src/graph/ArSyncStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graph/ArSyncStore.ts b/src/graph/ArSyncStore.ts index 6ab0a14..9e93b41 100644 --- a/src/graph/ArSyncStore.ts +++ b/src/graph/ArSyncStore.ts @@ -221,7 +221,7 @@ class ArSyncRecord extends ArSyncContainerBase { this.onChange([path], null) } else if (action === 'add') { if (this.data.id === id) return - const query = this.query[path] + const query = this.query[path].query ModelBatchRequest.fetch(class_name, query, id).then(data => { if (!data) return const model = new ArSyncRecord(query, data, null, this.root) From 4b8d26b9841978ee2fa8eeedf6ce8d335c7baea9 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 27 May 2019 04:06:24 +0900 Subject: [PATCH 15/22] tree bugfix --- src/tree/ArSyncStore.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tree/ArSyncStore.ts b/src/tree/ArSyncStore.ts index adc49b2..4a82e31 100644 --- a/src/tree/ArSyncStore.ts +++ b/src/tree/ArSyncStore.ts @@ -188,10 +188,10 @@ export default class ArSyncStore { update(patch) { return this.batchUpdate([patch]) } - _slicePatch(patchData, request) { - const obj = request.query && request.query['*'] ? { ...patchData } : {} - for (const key in request) { - const fieldQuery = request[key] + _slicePatch(patchData, query) { + const obj = query && query['*'] ? { ...patchData } : {} + for (const key in query) { + const fieldQuery = query[key] const field = (fieldQuery && fieldQuery.field) || key if (field in patchData) obj[key] = patchData[field] } @@ -239,7 +239,7 @@ export default class ArSyncStore { const queryField = query[key] if (lastStep) { if (!queryField) return - apply(accessKeys, actualPath, query[key], key, null, data[key]) + apply(accessKeys, actualPath, queryField.query, key, null, data[key]) } else { const data2 = data[key] if (!data2) return From 8cb7d5e97b325071d10e9266ed0bc5c074503b62 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:07:47 +0900 Subject: [PATCH 16/22] ts test all generated files --- test/ts_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ts_test.rb b/test/ts_test.rb index 2145b30..c729c9d 100644 --- a/test/ts_test.rb +++ b/test/ts_test.rb @@ -21,12 +21,12 @@ def test_typed_files dir = 'test/generated_typed_files' Dir.mkdir dif unless Dir.exist? dir ArSync::TypeScript.generate_typed_files Schema, mode: :tree, dir: dir - ['hooks.ts', 'ArSyncModel.ts'].each do |file| + ['hooks.ts', 'ArSyncModel.ts', 'types.ts'].each do |file| path = File.join dir, file File.write path, File.read(path).gsub('ar_sync/', '../../src/') end output = `./node_modules/typescript/bin/tsc --strict --lib es2017 --noEmit test/type_test.ts` - output = output.lines.grep(/type_test/) + output = output.lines.grep(/test/) puts output assert output.empty? end From e09ae165f7d5b6f26cfa62fe13f57bfa140c076a Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:08:44 +0900 Subject: [PATCH 17/22] update DataType.ts, change generation code --- lib/ar_sync/type_script.rb | 67 +++++++++++++------------------------- src/core/DataType.ts | 57 +++++++++++++++++--------------- 2 files changed, 52 insertions(+), 72 deletions(-) diff --git a/lib/ar_sync/type_script.rb b/lib/ar_sync/type_script.rb index d55067a..cc2ccb5 100644 --- a/lib/ar_sync/type_script.rb +++ b/lib/ar_sync/type_script.rb @@ -11,69 +11,46 @@ def self.generate_typed_files(api_class, dir:, mode: nil, comment: nil) end def self.generate_type_definition(api_class) + schema = Class.new + schema.define_singleton_method(:name) { 'Schema' } + schema.define_singleton_method(:ancestors) { api_class.ancestors } + schema.define_singleton_method(:method_missing) { |*args| api_class.send(*args) } + classes = [schema] + api_related_classes(api_class) - [api_class] [ - ArSerializer::TypeScript.generate_type_definition(api_related_classes(api_class)), - request_type_definition(api_class) + "import { DataTypeFromQueryPair } from 'ar_sync/core/DataType'", + ArSerializer::TypeScript.generate_type_definition(classes), + <<~CODE + type ExtractData = T extends { data: infer D } ? D : T + export type DataTypeFromRootQuery = + ExtractData> + export type TypeRootQuery = TypeSchemaAliasFieldQuery + CODE ].join "\n" end def self.api_related_classes(api_class) - classes = ArSerializer::TypeScript.related_serializer_types([api_class]).map(&:type) - classes - [api_class] - end - - def self.request_type_definition(api_class) - type = ArSerializer::GraphQL::TypeClass.from api_class - definitions = [] - request_types = {} - type.fields.each do |field| - association_type = field.type.association_type - next unless association_type - prefix = 'Class' if field.name.match?(/\A[A-Z]/) # for class reload query - request_type_name = "Type#{prefix}#{field.name.camelize}Request" - request_types[field.name] = request_type_name - multiple = field.type.is_a? ArSerializer::GraphQL::ListTypeClass - definitions << <<~CODE - export interface #{request_type_name} { - field: '#{field.name}' - params?: #{field.args_ts_type} - query: Type#{association_type.name}Query - _meta?: { data: Type#{field.type.association_type.name}#{'[]' if multiple} } - } - CODE - end - [ - 'export type TypeRequest = ', - request_types.values.map { |value| " | #{value}" }, - 'export type ApiNameRequests = {', - request_types.map { |key, value| " #{key}: #{value}" }, - '}', - definitions - ].join("\n") + ArSerializer::TypeScript.related_serializer_types([api_class]).map(&:type) end def self.generate_model_script(mode) <<~CODE - import { TypeRequest, ApiNameRequests } from './types' - import { DataTypeFromRequest } from 'ar_sync/core/DataType' + import { TypeSchema, TypeRootQuery, DataTypeFromRootQuery } from './types' import ArSyncModelBase from 'ar_sync/#{mode}/ArSyncModel' - export default class ArSyncModel extends ArSyncModelBase<{}> { - constructor(r: R) { super(r) } - data: DataTypeFromRequest | null + export default class ArSyncModel extends ArSyncModelBase> { + constructor(q: Q) { super(q) } } CODE end def self.generate_hooks_script(mode) <<~CODE - import { TypeRequest, ApiNameRequests } from './types' - import { DataTypeFromRequest } from 'ar_sync/core/DataType' + import { TypeSchema, TypeRootQuery, DataTypeFromRootQuery } from './types' import { useArSyncModel as useArSyncModelBase, useArSyncFetch as useArSyncFetchBase } from 'ar_sync/#{mode}/hooks' - export function useArSyncModel(request: R | null) { - return useArSyncModelBase>(request) + export function useArSyncModel(request: Q | null) { + return useArSyncModelBase>(request) } - export function useArSyncFetch(request: R | null) { - return useArSyncFetchBase>(request) + export function useArSyncFetch(request: Q | null) { + return useArSyncFetchBase>(request) } CODE end diff --git a/src/core/DataType.ts b/src/core/DataType.ts index 8397b1c..5d87cd3 100644 --- a/src/core/DataType.ts +++ b/src/core/DataType.ts @@ -28,21 +28,21 @@ type DataTypeExtractFromQueryHash = '*' extends keyof Query type _DataTypePickField = SubQuery extends { field: infer N, query?: infer Q } - ? ( - N extends keyof BaseType - ? ( - IsAnyCompareLeftType extends Q - ? DataTypeExtractField - : DataTypeFromQuery) - : ExtraFieldErrorType - ) - : ( + ? ( + N extends keyof BaseType + ? ( + IsAnyCompareLeftType extends Q + ? DataTypeExtractField + : DataTypeFromQuery) + : ExtraFieldErrorType + ) + : ( Key extends keyof BaseType - ? (SubQuery extends true - ? DataTypeExtractField - : DataTypeFromQuery) - : ExtraFieldErrorType - ) + ? (SubQuery extends true | { query?: true | never; params: any } + ? DataTypeExtractField + : DataTypeFromQuery) + : ExtraFieldErrorType + ) type _DataTypeFromQuery = QueryType extends keyof BaseType | '*' ? DataTypeExtractFieldsFromQuery @@ -50,12 +50,19 @@ type _DataTypeFromQuery = QueryType extends keyof BaseType ? DataTypeExtractFieldsFromQuery> : DataTypeExtractFromQueryHash -export type DataTypeFromQuery = BaseType extends any[] - ? CheckAttributesField[] - : null extends BaseType - ? CheckAttributesField | null +type CheckIsArray = BaseType extends (infer Type)[] + ? ( + null extends Type + ? (CheckAttributesField, QueryType> | null) + : CheckAttributesField + )[] : CheckAttributesField +type DataTypeFromQuery = + null extends BaseType + ? CheckIsArray, QueryType> | null + : CheckIsArray + type CheckAttributesField = Q extends { query: infer R } ? _DataTypeFromQuery : _DataTypeFromQuery @@ -74,14 +81,10 @@ type _CollectExtraFields = keyof (Type) extends never ? null : Values<{ [key in keyof Type]: CollectExtraFields }> -type SelectString = T extends string ? T : never -type _ValidateDataTypeExtraFileds = SelectString> extends never +type _ValidateDataTypeExtraFileds = Values extends never ? Type - : { error: { extraFields: SelectString> } } -type ValidateDataTypeExtraFileds = _ValidateDataTypeExtraFileds, Type> + : { error: { extraFields: Values } } + +type ValidateDataTypeExtraFileds = _ValidateDataTypeExtraFileds, null>, Type> -type RequestBase = { field: string; query?: any; params?: any; _meta?: { data: any } } -type DataTypeBaseFromRequestType = R extends { _meta?: { data: infer DataType } } ? DataType : never -export type DataTypeFromRequest = ValidateDataTypeExtraFileds< - DataTypeFromQuery, R['query']> -> +export type DataTypeFromQueryPair = ValidateDataTypeExtraFileds> From dbec9f33aa3c1c39982718c8c515796a9b889c65 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:10:32 +0900 Subject: [PATCH 18/22] npm run build --- core/ArSyncModelBase.d.ts | 4 +- core/DataType.d.ts | 46 ++++---- core/hooksBase.d.ts | 4 +- core/parseRequest.d.ts | 1 + core/parseRequest.js | 43 ++++++++ graph/ArSyncStore.js | 139 +++++++++--------------- tree/ArSyncStore.d.ts | 14 ++- tree/ArSyncStore.js | 218 +++++++++++++++++--------------------- 8 files changed, 221 insertions(+), 248 deletions(-) create mode 100644 core/parseRequest.d.ts create mode 100644 core/parseRequest.js diff --git a/core/ArSyncModelBase.d.ts b/core/ArSyncModelBase.d.ts index c234b41..a1c4043 100644 --- a/core/ArSyncModelBase.d.ts +++ b/core/ArSyncModelBase.d.ts @@ -1,7 +1,7 @@ interface Request { - api: string; - query: any; + field: string; params?: any; + query?: any; } declare type Path = (string | number)[]; interface Change { diff --git a/core/DataType.d.ts b/core/DataType.d.ts index f391957..5a6088b 100644 --- a/core/DataType.d.ts +++ b/core/DataType.d.ts @@ -16,18 +16,24 @@ interface ExtraFieldErrorType { error: 'extraFieldError'; } declare type DataTypeExtractFromQueryHash = '*' extends keyof QueryType ? { - [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '_params' | '*'>]: (key extends keyof BaseType ? (key extends keyof QueryType ? (QueryType[key] extends true ? DataTypeExtractField : DataTypeFromQuery) : DataTypeExtractField) : ExtraFieldErrorType); + [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '*'>]: (key extends keyof QueryType ? _DataTypePickField : key extends keyof BaseType ? DataTypeExtractField : ExtraFieldErrorType); } : { - [key in keyof QueryType]: (key extends keyof BaseType ? (QueryType[key] extends true ? DataTypeExtractField : DataTypeFromQuery) : ExtraFieldErrorType); + [key in keyof QueryType]: _DataTypePickField; }; -declare type _DataTypeFromQuery = QueryType extends keyof BaseType | '*' ? DataTypeExtractFieldsFromQuery : QueryType extends Readonly<(keyof BaseType | '*')[]> ? DataTypeExtractFieldsFromQuery> : QueryType extends { - as: string; -} ? { - error: 'type for alias field is not supported'; -} | undefined : DataTypeExtractFromQueryHash; -export declare type DataTypeFromQuery = BaseType extends any[] ? CheckAttributesField[] : null extends BaseType ? CheckAttributesField | null : CheckAttributesField; +declare type _DataTypePickField = SubQuery extends { + field: infer N; + query?: infer Q; +} ? (N extends keyof BaseType ? (IsAnyCompareLeftType extends Q ? DataTypeExtractField : DataTypeFromQuery) : ExtraFieldErrorType) : (Key extends keyof BaseType ? (SubQuery extends true | { + query?: true | never; + params: any; +} ? DataTypeExtractField : DataTypeFromQuery) : ExtraFieldErrorType); +declare type _DataTypeFromQuery = QueryType extends keyof BaseType | '*' ? DataTypeExtractFieldsFromQuery : QueryType extends Readonly<(keyof BaseType | '*')[]> ? DataTypeExtractFieldsFromQuery> : DataTypeExtractFromQueryHash; +declare type CheckIsArray = BaseType extends (infer Type)[] ? (null extends Type ? (CheckAttributesField, QueryType> | null) : CheckAttributesField)[] : CheckAttributesField; +declare type DataTypeFromQuery = null extends BaseType ? CheckIsArray, QueryType> | null : CheckIsArray; declare type CheckAttributesField = Q extends { - attributes: infer R; + query: infer R; } ? _DataTypeFromQuery : _DataTypeFromQuery; declare type IsAnyCompareLeftType = { __any: never; @@ -36,25 +42,11 @@ declare type CollectExtraFields = IsAnyCompareLeftType extends Type declare type _CollectExtraFields = keyof (Type) extends never ? null : Values<{ [key in keyof Type]: CollectExtraFields; }>; -declare type SelectString = T extends string ? T : never; -declare type _ValidateDataTypeExtraFileds = SelectString> extends never ? Type : { +declare type _ValidateDataTypeExtraFileds = Values extends never ? Type : { error: { - extraFields: SelectString>; + extraFields: Values; }; }; -declare type ValidateDataTypeExtraFileds = _ValidateDataTypeExtraFileds, Type>; -declare type RequestBase = { - api: string; - query: any; - params?: any; - _meta?: { - data: any; - }; -}; -declare type DataTypeBaseFromRequestType = R extends { - _meta?: { - data: infer DataType; - }; -} ? DataType : never; -export declare type DataTypeFromRequest = ValidateDataTypeExtraFileds, R['query']>>; +declare type ValidateDataTypeExtraFileds = _ValidateDataTypeExtraFileds, null>, Type>; +export declare type DataTypeFromQueryPair = ValidateDataTypeExtraFileds>; export {}; diff --git a/core/hooksBase.d.ts b/core/hooksBase.d.ts index 0d6bf64..feab79b 100644 --- a/core/hooksBase.d.ts +++ b/core/hooksBase.d.ts @@ -5,9 +5,9 @@ interface ModelStatus { } export declare type DataAndStatus = [T | null, ModelStatus]; export interface Request { - api: string; + field: string; params?: any; - query: any; + query?: any; } interface ArSyncModel { data: T | null; diff --git a/core/parseRequest.d.ts b/core/parseRequest.d.ts new file mode 100644 index 0000000..98f4223 --- /dev/null +++ b/core/parseRequest.d.ts @@ -0,0 +1 @@ +export declare function parseRequest(request: any, attrsonly?: any): {}; diff --git a/core/parseRequest.js b/core/parseRequest.js new file mode 100644 index 0000000..421cb9f --- /dev/null +++ b/core/parseRequest.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function parseRequest(request, attrsonly) { + const query = {}; + let field = null; + let params = null; + if (!request) + request = []; + if (request.constructor !== Array) + request = [request]; + for (const arg of request) { + if (typeof (arg) === 'string') { + query[arg] = {}; + } + else if (typeof (arg) === 'object') { + for (const key in arg) { + const value = arg[key]; + if (attrsonly) { + query[key] = parseRequest(value); + continue; + } + if (key === 'query') { + const child = parseRequest(value, true); + for (const k in child) + query[k] = child[k]; + } + else if (key === 'field') { + field = value; + } + else if (key === 'params') { + params = value; + } + else { + query[key] = parseRequest(value); + } + } + } + } + if (attrsonly) + return query; + return { query, field, params }; +} +exports.parseRequest = parseRequest; diff --git a/graph/ArSyncStore.js b/graph/ArSyncStore.js index 35c8a8b..0d6b4bd 100644 --- a/graph/ArSyncStore.js +++ b/graph/ArSyncStore.js @@ -1,14 +1,15 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ArSyncApi_1 = require("../core/ArSyncApi"); +const parseRequest_1 = require("../core/parseRequest"); const ModelBatchRequest = { timer: null, apiRequests: {}, - fetch(api, query, id) { + fetch(field, query, id) { this.setTimer(); return new Promise(resolve => { const queryJSON = JSON.stringify(query); - const apiRequest = this.apiRequests[api] = this.apiRequests[api] || {}; + const apiRequest = this.apiRequests[field] = this.apiRequests[field] || {}; const queryRequests = apiRequest[queryJSON] = apiRequest[queryJSON] || { query, requests: {} }; const request = queryRequests.requests[id] = queryRequests.requests[id] || { id, callbacks: [] }; request.callbacks.push(resolve); @@ -16,11 +17,11 @@ const ModelBatchRequest = { }, batchFetch() { const { apiRequests } = this; - for (const api in apiRequests) { - const apiRequest = apiRequests[api]; + for (const field in apiRequests) { + const apiRequest = apiRequests[field]; for (const { query, requests } of Object.values(apiRequest)) { const ids = Object.values(requests).map(({ id }) => id); - ArSyncApi_1.default.syncFetch({ api, query, params: { ids } }).then((models) => { + ArSyncApi_1.default.syncFetch({ field, query, params: { ids } }).then((models) => { for (const model of models) requests[model.id].model = model; for (const { model, callbacks } of Object.values(requests)) { @@ -87,56 +88,16 @@ class ArSyncContainerBase { l.unsubscribe(); this.listeners = []; } - static parseQuery(query, attrsonly = false) { - const attributes = {}; - let column = null; - let params = null; - if (!query) - query = []; - if (query.constructor !== Array) - query = [query]; - for (const arg of query) { - if (typeof (arg) === 'string') { - attributes[arg] = {}; - } - else if (typeof (arg) === 'object') { - for (const key in arg) { - const value = arg[key]; - if (attrsonly) { - attributes[key] = this.parseQuery(value); - continue; - } - if (key === 'attributes') { - const child = this.parseQuery(value, true); - for (const k in child) - attributes[k] = child[k]; - } - else if (key === 'as') { - column = value; - } - else if (key === 'params') { - params = value; - } - else { - attributes[key] = this.parseQuery(value); - } - } - } - } - if (attrsonly) - return attributes; - return { attributes, as: column, params }; - } - static _load({ api, id, params, query }, root) { - const parsedQuery = ArSyncRecord.parseQuery(query); + static _load({ field, id, params, query }, root) { + const parsedQuery = parseRequest_1.parseRequest(query, true); if (id) { - return ModelBatchRequest.fetch(api, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)); + return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)); } else { - const request = { api, query, params }; + const request = { field, query, params }; return ArSyncApi_1.default.syncFetch(request).then((response) => { if (response.collection && response.order) { - return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, response, request, root); + return new ArSyncCollection(response.sync_keys, 'collection', parsedQuery, params, response, request, root); } else { return new ArSyncRecord(parsedQuery, response, request, root); @@ -162,11 +123,11 @@ class ArSyncContainerBase { } } class ArSyncRecord extends ArSyncContainerBase { - constructor(query, data, request, root) { + constructor(query, data, initialRequest, root) { super(); this.root = root; - if (request) - this.initForReload(request); + if (initialRequest) + this.initForReload(initialRequest); this.query = query; this.data = {}; this.children = {}; @@ -186,49 +147,49 @@ class ArSyncRecord extends ArSyncContainerBase { this.data.id = data.id; } this.paths = []; - for (const key in this.query.attributes) { - const subQuery = this.query.attributes[key]; - const aliasName = subQuery.as || key; - const subData = data[aliasName]; + for (const key in this.query) { + const queryField = this.query[key]; + const aliasName = queryField.field || key; + const subData = data[key]; if (key === 'sync_keys') continue; - if (subQuery.attributes && (subData instanceof Array || (subData && subData.collection && subData.order))) { - if (this.children[aliasName]) { - this.children[aliasName].replaceData(subData, this.sync_keys); + if (queryField.query && (subData instanceof Array || (subData && subData.collection && subData.order))) { + if (this.children[key]) { + this.children[key].replaceData(subData, this.sync_keys); } else { - const collection = new ArSyncCollection(this.sync_keys, key, subQuery, subData, null, this.root); + const collection = new ArSyncCollection(this.sync_keys, aliasName, queryField.query, queryField.params, subData, null, this.root); this.mark(); - this.children[aliasName] = collection; - this.data[aliasName] = collection.data; + this.children[key] = collection; + this.data[key] = collection.data; collection.parentModel = this; - collection.parentKey = aliasName; + collection.parentKey = key; } } else { - if (subQuery.attributes && Object.keys(subQuery.attributes).length > 0) + if (queryField.query && Object.keys(queryField.query).length > 0) this.paths.push(key); if (subData && subData.sync_keys) { - if (this.children[aliasName]) { - this.children[aliasName].replaceData(subData); + if (this.children[key]) { + this.children[key].replaceData(subData); } else { - const model = new ArSyncRecord(subQuery, subData, null, this.root); + const model = new ArSyncRecord(queryField.query, subData, null, this.root); this.mark(); - this.children[aliasName] = model; - this.data[aliasName] = model.data; + this.children[key] = model; + this.data[key] = model.data; model.parentModel = this; - model.parentKey = aliasName; + model.parentKey = key; } } else { - if (this.children[aliasName]) { - this.children[aliasName].release(); - delete this.children[aliasName]; + if (this.children[key]) { + this.children[key].release(); + delete this.children[key]; } - if (this.data[aliasName] !== subData) { + if (this.data[key] !== subData) { this.mark(); - this.data[aliasName] = subData; + this.data[key] = subData; } } } @@ -255,7 +216,7 @@ class ArSyncRecord extends ArSyncContainerBase { else if (action === 'add') { if (this.data.id === id) return; - const query = this.query.attributes[path]; + const query = this.query[path].query; ModelBatchRequest.fetch(class_name, query, id).then(data => { if (!data) return; @@ -290,16 +251,16 @@ class ArSyncRecord extends ArSyncContainerBase { reloadQuery() { if (this.reloadQueryCache) return this.reloadQueryCache; - const reloadQuery = this.reloadQueryCache = { attributes: [] }; - for (const key in this.query.attributes) { + const reloadQuery = this.reloadQueryCache = { query: [] }; + for (const key in this.query) { if (key === 'sync_keys') continue; - const val = this.query.attributes[key]; - if (!val || !val.attributes) { - reloadQuery.attributes.push(key); + const val = this.query[key]; + if (!val || !val.query) { + reloadQuery.query.push(key); } - else if (!val.params && Object.keys(val.attributes).length === 0) { - reloadQuery.attributes.push({ [key]: val }); + else if (!val.params && Object.keys(val.query).length === 0) { + reloadQuery.query.push({ [key]: val }); } } return reloadQuery; @@ -334,14 +295,14 @@ class ArSyncRecord extends ArSyncContainerBase { } } class ArSyncCollection extends ArSyncContainerBase { - constructor(sync_keys, path, query, data, request, root) { + constructor(sync_keys, path, query, params, data, initialRequest, root) { super(); this.root = root; this.path = path; - if (request) - this.initForReload(request); - if (query.params && (query.params.order || query.params.limit)) { - this.order = { limit: query.params.limit, mode: query.params.order || 'asc' }; + if (initialRequest) + this.initForReload(initialRequest); + if (params && (params.order || params.limit)) { + this.order = { limit: params.limit, mode: params.order || 'asc' }; } else { this.order = { limit: null, mode: 'asc' }; diff --git a/tree/ArSyncStore.d.ts b/tree/ArSyncStore.d.ts index c28e267..fbfd5fe 100644 --- a/tree/ArSyncStore.d.ts +++ b/tree/ArSyncStore.d.ts @@ -1,8 +1,8 @@ export default class ArSyncStore { data: any; - query: any; + request: any; immutable: any; - constructor(query: any, data: any, option?: { + constructor(request: any, data: any, option?: { immutable?: boolean | undefined; }); replaceData(data: any): void; @@ -14,8 +14,12 @@ export default class ArSyncStore { changes: any; events: never[]; }; - _slicePatch(patchData: any, query: any): {}; + _slicePatch(patchData: any, query: any): any; _applyPatch(data: any, accessKeys: any, actualPath: any, updator: any, query: any, patchData: any): void; - _update(patch: any, updator: any, events: any): void; - static parseQuery(query: any, attrsonly?: any): {}; + _update(patch: { + action: string; + path: (string | number)[]; + ordering: any; + data: any; + }, updator: any, events: any): void; } diff --git a/tree/ArSyncStore.js b/tree/ArSyncStore.js index e66325b..1da59c3 100644 --- a/tree/ArSyncStore.js +++ b/tree/ArSyncStore.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +const parseRequest_1 = require("../core/parseRequest"); class Updator { constructor(immutable) { this.changes = []; @@ -191,9 +192,9 @@ class Updator { } } class ArSyncStore { - constructor(query, data, option = {}) { + constructor(request, data, option = {}) { this.data = option.immutable ? Updator.createFrozenObject(data) : data; - this.query = ArSyncStore.parseQuery(query); + this.request = parseRequest_1.parseRequest(request); this.immutable = option.immutable; } replaceData(data) { @@ -210,28 +211,24 @@ class ArSyncStore { return this.batchUpdate([patch]); } _slicePatch(patchData, query) { - const obj = {}; - for (const key in patchData) { - if (key === 'id' || query.attributes['*']) { - obj[key] = patchData[key]; - } - else { - const subq = query.attributes[key]; - if (subq) { - obj[subq.column || key] = patchData[key]; - } - } + const obj = query && query['*'] ? Object.assign({}, patchData) : {}; + for (const key in query) { + const fieldQuery = query[key]; + const field = (fieldQuery && fieldQuery.field) || key; + if (field in patchData) + obj[key] = patchData[field]; } + if (patchData.id) + obj.id = patchData.id; return obj; } _applyPatch(data, accessKeys, actualPath, updator, query, patchData) { for (const key in patchData) { - const subq = query.attributes[key]; - const value = patchData[key]; - if (subq || query.attributes['*']) { - const subcol = (subq && subq.column) || key; - if (data[subcol] !== value) { - this.data = updator.add(this.data, accessKeys, actualPath, subcol, value); + const subq = query[key]; + const value = patchData[(subq && subq.field) || key]; + if (subq || query['*']) { + if (data[key] !== value) { + this.data = updator.add(this.data, accessKeys, actualPath, key, value); } } } @@ -239,127 +236,102 @@ class ArSyncStore { _update(patch, updator, events) { const { action, path } = patch; const patchData = patch.data; - let query = this.query; + let request = this.request; let data = this.data; - const actualPath = []; - const accessKeys = []; - for (let i = 0; i < path.length - 1; i++) { + const trace = (i, actualPath, accessKeys, query, data) => { const nameOrId = path[i]; + const lastStep = i === path.length - 1; if (typeof (nameOrId) === 'number') { const idx = data.findIndex(o => o.id === nameOrId); - if (idx < 0) - return; - actualPath.push(nameOrId); - accessKeys.push(idx); - data = data[idx]; + if (lastStep) { + apply(accessKeys, actualPath, query, null, idx, data[idx]); + } + else { + if (idx < 0) + return; + actualPath.push(nameOrId); + accessKeys.push(idx); + const data2 = data[idx]; + trace(i + 1, actualPath, accessKeys, query, data2); + } } else { - const { attributes } = query; - if (!attributes[nameOrId]) - return; - const column = attributes[nameOrId].column || nameOrId; - query = attributes[nameOrId]; - actualPath.push(column); - accessKeys.push(column); - data = data[column]; + const matchedKeys = []; + for (const key in query) { + const field = query[key].field || key; + if (field === nameOrId) + matchedKeys.push(key); + } + const fork = matchedKeys.length > 1; + for (const key of matchedKeys) { + const queryField = query[key]; + if (lastStep) { + if (!queryField) + return; + apply(accessKeys, actualPath, queryField.query, key, null, data[key]); + } + else { + const data2 = data[key]; + if (!data2) + return; + const actualPath2 = fork ? [...actualPath] : actualPath; + const accessKeys2 = fork ? [...accessKeys] : accessKeys; + actualPath2.push(key); + accessKeys2.push(key); + trace(i + 1, actualPath2, accessKeys2, queryField.query, data2); + } + } } - if (!data) + }; + const apply = (accessKeys, actualPath, query, column, idx, target) => { + if (action === 'create') { + const obj = this._slicePatch(patchData, query); + if (column) { + this.data = updator.add(this.data, accessKeys, actualPath, column, obj); + } + else if (!target) { + const ordering = Object.assign({}, patch.ordering); + const limitOverride = request.params && request.params.limit; + ordering.order = request.params && request.params.order || ordering.order; + if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) + ordering.limit = limitOverride; + this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering); + } return; - } - const nameOrId = path[path.length - 1]; - let id, idx, column, target = data; - if (typeof (nameOrId) === 'number') { - id = nameOrId; - idx = data.findIndex(o => o.id === id); - target = data[idx]; - } - else if (nameOrId) { - const { attributes } = query; - if (!attributes[nameOrId]) + } + if (action === 'destroy') { + if (column) { + this.data = updator.remove(this.data, accessKeys, actualPath, column); + } + else if (idx != null) { + this.data = updator.remove(this.data, accessKeys, actualPath, idx); + } + return; + } + if (!target) return; - column = attributes[nameOrId].column || nameOrId; - query = attributes[nameOrId]; - target = data[column]; - } - if (action === 'create') { - const obj = this._slicePatch(patchData, query); if (column) { - this.data = updator.add(this.data, accessKeys, actualPath, column, obj); + actualPath.push(column); + accessKeys.push(column); } - else if (!target) { - const ordering = Object.assign({}, patch.ordering); - const limitOverride = query.params && query.params.limit; - ordering.order = query.params && query.params.order || ordering.order; - if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) - ordering.limit = limitOverride; - this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering); + else if (idx != null && patchData.id) { + actualPath.push(patchData.id); + accessKeys.push(idx); } - return; - } - if (action === 'destroy') { - if (column) { - this.data = updator.remove(this.data, accessKeys, actualPath, column); + if (action === 'update') { + this._applyPatch(target, accessKeys, actualPath, updator, query, patchData); } - else if (idx >= 0) { - this.data = updator.remove(this.data, accessKeys, actualPath, idx); + else { + const eventData = { target, path: actualPath, data: patchData.data }; + events.push({ type: patchData.type, data: eventData }); } - return; - } - if (!target) - return; - if (column) { - actualPath.push(column); - accessKeys.push(column); - } - else if (id) { - actualPath.push(id); - accessKeys.push(idx); - } - if (action === 'update') { - this._applyPatch(target, accessKeys, actualPath, updator, query, patchData); + }; + if (path.length === 0) { + apply([], [], request.query, null, null, data); } else { - const eventData = { target, path: actualPath, data: patchData.data }; - events.push({ type: patchData.type, data: eventData }); - } - } - static parseQuery(query, attrsonly) { - const attributes = {}; - let column = null; - let params = null; - if (query.constructor !== Array) - query = [query]; - for (const arg of query) { - if (typeof (arg) === 'string') { - attributes[arg] = {}; - } - else if (typeof (arg) === 'object') { - for (const key in arg) { - const value = arg[key]; - if (attrsonly) { - attributes[key] = this.parseQuery(value); - continue; - } - if (key === 'attributes') { - const child = this.parseQuery(value, true); - for (const k in child) - attributes[k] = child[k]; - } - else if (key === 'as') { - column = value; - } - else if (key === 'params') { - params = value; - } - else { - attributes[key] = this.parseQuery(value); - } - } - } + trace(0, [], [], request.query, data); } - if (attrsonly) - return attributes; - return { attributes, column, params }; } } exports.default = ArSyncStore; From fd24313894b92a65738d8ee0cdcd035943856766 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:18:53 +0900 Subject: [PATCH 19/22] use ts 3.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 000f454..b3cfa0a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "actioncable": "^5.2.0", "@types/actioncable": "^5.2.0", "eslint": "^5.16.0", - "typescript": "^3.4.5", + "typescript": "^3.5.1", "@typescript-eslint/eslint-plugin": "^1.6.0", "@typescript-eslint/parser": "^1.6.0" } From 5aeb6ca4c2d82b40dee67411735cbbb8c4824d68 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:29:14 +0900 Subject: [PATCH 20/22] load with id bugfix --- src/graph/ArSyncStore.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/graph/ArSyncStore.ts b/src/graph/ArSyncStore.ts index 9e93b41..48911b3 100644 --- a/src/graph/ArSyncStore.ts +++ b/src/graph/ArSyncStore.ts @@ -2,7 +2,7 @@ import ArSyncAPI from '../core/ArSyncApi' import { parseRequest } from '../core/parseRequest' const ModelBatchRequest = { - timer: null, + timer: null as null | number, apiRequests: {} as { [key: string]: { [queryJSON: string]: { @@ -28,7 +28,7 @@ const ModelBatchRequest = { }) }, batchFetch() { - const { apiRequests } = this as typeof ModelBatchRequest + const { apiRequests } = ModelBatchRequest for (const field in apiRequests) { const apiRequest = apiRequests[field] for (const { query, requests } of Object.values(apiRequest)) { @@ -41,13 +41,13 @@ const ModelBatchRequest = { }) } } - this.apiRequests = {} + ModelBatchRequest.apiRequests = {} }, setTimer() { - if (this.timer) return - this.timer = setTimeout(() => { - this.timer = null - this.batchFetch() + if (ModelBatchRequest.timer) return + ModelBatchRequest.timer = setTimeout(() => { + ModelBatchRequest.timer = null + ModelBatchRequest.batchFetch() }, 20) } } @@ -100,7 +100,7 @@ class ArSyncContainerBase { static _load({ field, id, params, query }, root) { const parsedQuery = parseRequest(query, true) if (id) { - return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)) + return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data, null, root)) } else { const request = { field, query, params } return ArSyncAPI.syncFetch(request).then((response: any) => { From ba9ac07d5564ceb11d3cbc2ac881b4c77b77bf2d Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 15:29:48 +0900 Subject: [PATCH 21/22] npm runb build (bugfix&ts3.5 --- core/ArSyncApi.d.ts | 4 ++-- core/ArSyncModelBase.d.ts | 2 +- graph/ArSyncStore.js | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/ArSyncApi.d.ts b/core/ArSyncApi.d.ts index df58603..3308124 100644 --- a/core/ArSyncApi.d.ts +++ b/core/ArSyncApi.d.ts @@ -1,5 +1,5 @@ declare const _default: { - fetch: (request: object) => Promise<{}>; - syncFetch: (request: object) => Promise<{}>; + fetch: (request: object) => Promise; + syncFetch: (request: object) => Promise; }; export default _default; diff --git a/core/ArSyncModelBase.d.ts b/core/ArSyncModelBase.d.ts index a1c4043..8e4c343 100644 --- a/core/ArSyncModelBase.d.ts +++ b/core/ArSyncModelBase.d.ts @@ -66,6 +66,6 @@ export default abstract class ArSyncModelBase { static _detach(ref: any): void; private static _attach; static setConnectionAdapter(_adapter: Adapter): void; - static waitForLoad(...models: ArSyncModelBase<{}>[]): Promise<{}>; + static waitForLoad(...models: ArSyncModelBase<{}>[]): Promise; } export {}; diff --git a/graph/ArSyncStore.js b/graph/ArSyncStore.js index 0d6b4bd..c686afa 100644 --- a/graph/ArSyncStore.js +++ b/graph/ArSyncStore.js @@ -16,7 +16,7 @@ const ModelBatchRequest = { }); }, batchFetch() { - const { apiRequests } = this; + const { apiRequests } = ModelBatchRequest; for (const field in apiRequests) { const apiRequest = apiRequests[field]; for (const { query, requests } of Object.values(apiRequest)) { @@ -31,14 +31,14 @@ const ModelBatchRequest = { }); } } - this.apiRequests = {}; + ModelBatchRequest.apiRequests = {}; }, setTimer() { - if (this.timer) + if (ModelBatchRequest.timer) return; - this.timer = setTimeout(() => { - this.timer = null; - this.batchFetch(); + ModelBatchRequest.timer = setTimeout(() => { + ModelBatchRequest.timer = null; + ModelBatchRequest.batchFetch(); }, 20); } }; @@ -91,7 +91,7 @@ class ArSyncContainerBase { static _load({ field, id, params, query }, root) { const parsedQuery = parseRequest_1.parseRequest(query, true); if (id) { - return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data[0], null, root)); + return ModelBatchRequest.fetch(field, query, id).then(data => new ArSyncRecord(parsedQuery, data, null, root)); } else { const request = { field, query, params }; From 73d0ce2dd7fb39a7e5c1aac8f02df6139a066692 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 9 Jun 2019 16:04:12 +0900 Subject: [PATCH 22/22] strict type compare test --- test/tsconfig.json | 10 +++++++ test/type_test.ts | 70 ++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 test/tsconfig.json diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..8957991 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "strictNullChecks": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["*"] +} diff --git a/test/type_test.ts b/test/type_test.ts index 8ad692b..120ae0a 100644 --- a/test/type_test.ts +++ b/test/type_test.ts @@ -1,50 +1,58 @@ import ArSyncModel from './generated_typed_files/ArSyncModel' import { useArSyncModel, useArSyncFetch } from './generated_typed_files/hooks' +type IsEqual = [T, U] extends [U, T] ? true : false +function isOK(): T | undefined { return } +type IsStrictMode = string | null extends string ? false : true +isOK() + const [hooksData1] = useArSyncModel({ field: 'currentUser', query: 'id' }) -hooksData1!.id +isOK>() const [hooksData2] = useArSyncModel({ field: 'currentUser', query: { '*': true, foo: true } }) -hooksData2!.error.extraFields = 'foo' +isOK>() const [hooksData3] = useArSyncFetch({ field: 'currentUser', query: 'id' }) -hooksData3!.id +isOK>() const [hooksData4] = useArSyncFetch({ field: 'currentUser', query: { '*': true, foo: true } }) -hooksData4!.error.extraFields = 'foo' +isOK>() const data1 = new ArSyncModel({ field: 'currentUser', query: 'id' }).data! -data1.id +isOK>() const data2 = new ArSyncModel({ field: 'currentUser', query: ['id', 'name'] }).data! -data2.id; data2.name +isOK>() const data3 = new ArSyncModel({ field: 'currentUser', query: '*' }).data! -data3.id; data3.name; data3.posts +isOK>() const data4 = new ArSyncModel({ field: 'currentUser', query: { posts: 'id' } }).data! -data4.posts[0].id +isOK>() const data5 = new ArSyncModel({ field: 'currentUser', query: { posts: '*' } }).data! -data5.posts[0].id; data5.posts[0].user; data5.posts[0].body -const data6 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, comments: 'user' } } }).data! -data6.posts[0].id; data6.posts[0].user; data6.posts[0].comments[0].user +isOK>() +const data6 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, comments: 'user' } } }).data!.posts[0].comments[0] +isOK>() const data7 = new ArSyncModel({ field: 'currentUser', query: { name: true, poosts: true } }).data! -data7.error.extraFields = 'poosts' +isOK>() const data8 = new ArSyncModel({ field: 'currentUser', query: { posts: { id: true, commmments: true, titllle: true } } }).data! -data8.error.extraFields = 'commmments' -data8.error.extraFields = 'titllle' +isOK>() const data9 = new ArSyncModel({ field: 'currentUser', query: { '*': true, posts: { id: true, commmments: true } } }).data! -data9.error.extraFields = 'commmments' -const data10 = new ArSyncModel({ field: 'users', query: { '*': true, posts: { id: true, comments: '*' } } }).data! -data10[0].posts[0].comments[0].id +isOK>() +const data10 = new ArSyncModel({ field: 'users', query: { '*': true, posts: { id: true, comments: '*' } } }).data![0].posts[0].comments[0].id +isOK>() const data11 = new ArSyncModel({ field: 'users', query: { '*': true, posts: { id: true, comments: '*', commmments: true } } }).data! -data11.error.extraFields = 'commmments' -const data12 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: 'title' } } }).data! -data12.posts[0].title -const data13 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: ['id', 'title'] } } }).data! -data13.posts[0].title -const data14 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: { id: true, title: true } } } }).data! -data14.posts[0].title -const data15 = new ArSyncModel({ field: 'currentUser', query: { posts: ['id', 'title'] } } as const).data! -data15.posts[0].title +isOK>() +const data12 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: 'title' } } }).data!.posts[0] +isOK>() +const data13 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: ['id', 'title'] } } }).data!.posts[0] +isOK>() +const data14 = new ArSyncModel({ field: 'currentUser', query: { posts: { params: { limit: 4 }, query: { id: true, title: true } } } }).data!.posts[0] +isOK>() +const data15 = new ArSyncModel({ field: 'currentUser', query: { posts: ['id', 'title'] } } as const).data!.posts[0] +isOK>() const data16 = new ArSyncModel({ field: 'currentUser', query: { id: { field: 'name' }, name: { field: 'id' }, id2: { field: 'id' }, name2: { field: 'name' } } }).data! -data16.name = data16.id2 = 0 -data16.id = data16.name2 = 'name' -const data17 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, hoge: { field: 'comments', query: 'id' }, comments: { field: 'title' } } } }).data! -data17.posts[0].comments = 'title' -data17.posts[0].hoge[0].id = 0 +isOK>() +const data17 = new ArSyncModel({ field: 'currentUser', query: { posts: { '*': true, hoge: { field: 'comments', query: 'id' }, comments: { field: 'title' } } } }).data!.posts[0] +isOK>() +isOK>()