gip-remote 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +47 -7
- package/index.js +110 -38
- package/lib/drive.js +19 -10
- package/lib/git.js +32 -1
- package/package.json +1 -1
- package/schema/hyperdb/db.json +36 -8
- package/schema/hyperdb/index.js +144 -64
- package/schema/hyperdb/messages.js +129 -19
- package/schema/hyperschema/index.js +63 -7
- package/schema/hyperschema/schema.json +57 -1
package/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Readable } from 'streamx'
|
|
2
2
|
|
|
3
3
|
interface RemoteOpts {
|
|
4
|
-
name?: string
|
|
5
|
-
store: any
|
|
6
|
-
swarm: any
|
|
7
|
-
key?: Buffer
|
|
8
4
|
timeout?: number
|
|
9
|
-
blind?:
|
|
5
|
+
blind?: any
|
|
10
6
|
}
|
|
11
7
|
|
|
8
|
+
type RemoteLink =
|
|
9
|
+
| string
|
|
10
|
+
| { name: string; key?: Buffer }
|
|
11
|
+
| { drive: { key: Buffer }; pathname?: string }
|
|
12
|
+
|
|
12
13
|
interface GitObject {
|
|
13
14
|
oid: string
|
|
14
15
|
type: string
|
|
@@ -19,6 +20,7 @@ interface GitObject {
|
|
|
19
20
|
interface Ref {
|
|
20
21
|
ref: string
|
|
21
22
|
oid: string
|
|
23
|
+
symref?: string
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
interface RefObject extends GitObject {
|
|
@@ -26,13 +28,14 @@ interface RefObject extends GitObject {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
declare class Remote {
|
|
29
|
-
constructor(
|
|
31
|
+
constructor(store: any, link: RemoteLink, opts?: RemoteOpts)
|
|
30
32
|
|
|
31
33
|
readonly name: string
|
|
32
34
|
readonly core: any
|
|
33
35
|
readonly key: Buffer
|
|
34
36
|
readonly discoveryKey: Buffer
|
|
35
37
|
readonly availablePeers: number
|
|
38
|
+
readonly url: string
|
|
36
39
|
|
|
37
40
|
ready(): Promise<void>
|
|
38
41
|
close(): Promise<void>
|
|
@@ -42,11 +45,20 @@ declare class Remote {
|
|
|
42
45
|
commitOid: string,
|
|
43
46
|
objects: Map<string, { type: string; size: number; data: Buffer }>
|
|
44
47
|
): Promise<void>
|
|
48
|
+
|
|
49
|
+
getHead(): Promise<string | null>
|
|
50
|
+
setHead(branch: string): Promise<void>
|
|
51
|
+
|
|
45
52
|
getAllRefs(): Promise<Ref[]>
|
|
46
53
|
getBranchRef(branch: string): Promise<Ref | null>
|
|
54
|
+
deleteBranch(branchName: string): Promise<boolean>
|
|
55
|
+
|
|
47
56
|
getObject(oid: string): Promise<GitObject | null>
|
|
48
57
|
getRefObjects(commitOid: string, onLoad?: (size: number) => void): Promise<RefObject[]>
|
|
58
|
+
|
|
49
59
|
toDrive(branch: string): Promise<RemoteDrive | null>
|
|
60
|
+
|
|
61
|
+
update(): Promise<void>
|
|
50
62
|
waitForPeers(): Promise<void>
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -73,6 +85,22 @@ declare class RemoteDrive {
|
|
|
73
85
|
mirror(out: any, opts?: any): any
|
|
74
86
|
}
|
|
75
87
|
|
|
88
|
+
interface ParsedGitPearLink {
|
|
89
|
+
protocol: string
|
|
90
|
+
origin: string
|
|
91
|
+
pathname?: string
|
|
92
|
+
drive: {
|
|
93
|
+
key: Buffer
|
|
94
|
+
length: number
|
|
95
|
+
fork?: number | null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const GitPearLink: {
|
|
100
|
+
parse(link: string): ParsedGitPearLink
|
|
101
|
+
serialize(o: ParsedGitPearLink): string
|
|
102
|
+
}
|
|
103
|
+
|
|
76
104
|
interface ToDiskOpts {
|
|
77
105
|
gitDir: string
|
|
78
106
|
objects: Array<{ type: string; id: string; size: number; data: Buffer }>
|
|
@@ -107,4 +135,16 @@ declare function walkTree(
|
|
|
107
135
|
prefix: string
|
|
108
136
|
): FileEntry[]
|
|
109
137
|
|
|
110
|
-
export { Remote, RemoteDrive, toDisk, parseCommit, walkTree }
|
|
138
|
+
export { Remote, RemoteDrive, GitPearLink, toDisk, parseCommit, walkTree }
|
|
139
|
+
export type {
|
|
140
|
+
RemoteOpts,
|
|
141
|
+
RemoteLink,
|
|
142
|
+
GitObject,
|
|
143
|
+
Ref,
|
|
144
|
+
RefObject,
|
|
145
|
+
DriveEntry,
|
|
146
|
+
ParsedGitPearLink,
|
|
147
|
+
ToDiskOpts,
|
|
148
|
+
Commit,
|
|
149
|
+
FileEntry
|
|
150
|
+
}
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const z32 = require('z32')
|
|
|
5
5
|
const def = require('./schema/hyperdb/index')
|
|
6
6
|
const RemoteDrive = require('./lib/drive')
|
|
7
7
|
const GitPearLink = require('./lib/link')
|
|
8
|
-
const { parseCommit, walkTree } = require('./lib/git')
|
|
8
|
+
const { parseCommit, parseTag, walkTree } = require('./lib/git')
|
|
9
9
|
|
|
10
10
|
class Remote extends ReadyResource {
|
|
11
11
|
_swarm = null
|
|
@@ -22,7 +22,7 @@ class Remote extends ReadyResource {
|
|
|
22
22
|
let config
|
|
23
23
|
if (typeof this._link === 'string') {
|
|
24
24
|
this._name = this._link
|
|
25
|
-
config = {}
|
|
25
|
+
config = { core: store.get({ name: this._link }) }
|
|
26
26
|
} else if (this._link.drive) {
|
|
27
27
|
this._name = this._link.pathname?.split('/').slice(1)[0]
|
|
28
28
|
config = { key: this._link.drive.key }
|
|
@@ -91,13 +91,18 @@ class Remote extends ReadyResource {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async getAllRefs() {
|
|
94
|
-
const branches = this._db.find('@gip/branches')
|
|
95
94
|
const refs = []
|
|
96
95
|
|
|
96
|
+
const branches = this._db.find('@gip/branches')
|
|
97
97
|
for await (const b of branches) {
|
|
98
98
|
refs.push({ ref: `refs/heads/${b.name}`, oid: b.commitOid })
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const tags = this._db.find('@gip/tags')
|
|
102
|
+
for await (const t of tags) {
|
|
103
|
+
refs.push({ ref: `refs/tags/${t.name}`, oid: t.oid })
|
|
104
|
+
}
|
|
105
|
+
|
|
101
106
|
const headBranch = await this.getHead()
|
|
102
107
|
if (headBranch) {
|
|
103
108
|
const head = refs.find((r) => r.ref === `refs/heads/${headBranch}`)
|
|
@@ -129,28 +134,58 @@ class Remote extends ReadyResource {
|
|
|
129
134
|
return true
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
async deleteTag(tagName) {
|
|
138
|
+
const tag = await this._db.get('@gip/tags', { name: tagName })
|
|
139
|
+
if (!tag) return false
|
|
140
|
+
|
|
141
|
+
await this._db.delete('@gip/tags', { name: tagName })
|
|
133
142
|
|
|
134
|
-
|
|
143
|
+
// Remove file records for this tag
|
|
144
|
+
const filesBranch = 'tags/' + tagName
|
|
145
|
+
const files = this._db.find('@gip/files', { branch: filesBranch })
|
|
146
|
+
for await (const file of files) {
|
|
147
|
+
await this._db.delete('@gip/files', { branch: filesBranch, path: file.path })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await this._db.flush()
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- Push: store objects + index branch/tag + files ---
|
|
155
|
+
|
|
156
|
+
async push(refName, oid, objects) {
|
|
135
157
|
// 1. Store all git objects
|
|
136
|
-
for (const [
|
|
137
|
-
const existing = await this.getObject(
|
|
158
|
+
for (const [objOid, obj] of objects) {
|
|
159
|
+
const existing = await this.getObject(objOid)
|
|
138
160
|
if (existing) continue
|
|
139
161
|
|
|
140
162
|
await this._db.insert('@gip/objects', {
|
|
141
|
-
oid,
|
|
163
|
+
oid: objOid,
|
|
142
164
|
type: obj.type,
|
|
143
165
|
size: obj.size,
|
|
144
166
|
data: obj.data
|
|
145
167
|
})
|
|
146
168
|
}
|
|
147
169
|
|
|
148
|
-
// 2.
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
// 2. Dereference tag objects to find the commit
|
|
171
|
+
let resolvedOid = oid
|
|
172
|
+
let obj = objects.get(resolvedOid)
|
|
173
|
+
if (!obj) throw new Error('Object not found: ' + resolvedOid)
|
|
174
|
+
|
|
175
|
+
let tagMeta = null
|
|
176
|
+
while (obj.type === 'tag') {
|
|
177
|
+
const tag = parseTag(obj.data)
|
|
178
|
+
if (!tagMeta) tagMeta = tag
|
|
179
|
+
if (!tag.object) throw new Error('Tag has no object: ' + resolvedOid)
|
|
180
|
+
resolvedOid = tag.object
|
|
181
|
+
obj = objects.get(resolvedOid)
|
|
182
|
+
if (!obj) throw new Error('Object not found: ' + resolvedOid)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (obj.type !== 'commit') throw new Error('Expected commit, got ' + obj.type + ': ' + resolvedOid)
|
|
151
186
|
|
|
152
|
-
const commit = parseCommit(
|
|
153
|
-
if (!commit.tree) throw new Error('Commit has no tree: ' +
|
|
187
|
+
const commit = parseCommit(obj.data)
|
|
188
|
+
if (!commit.tree) throw new Error('Commit has no tree: ' + resolvedOid)
|
|
154
189
|
|
|
155
190
|
// 3. Walk tree to enumerate files
|
|
156
191
|
const files = walkTree(objects, commit.tree, '')
|
|
@@ -158,7 +193,7 @@ class Remote extends ReadyResource {
|
|
|
158
193
|
// 4. Insert file records
|
|
159
194
|
for (const file of files) {
|
|
160
195
|
await this._db.insert('@gip/files', {
|
|
161
|
-
branch:
|
|
196
|
+
branch: refName,
|
|
162
197
|
path: file.path,
|
|
163
198
|
oid: file.oid,
|
|
164
199
|
mode: file.mode,
|
|
@@ -169,21 +204,38 @@ class Remote extends ReadyResource {
|
|
|
169
204
|
})
|
|
170
205
|
}
|
|
171
206
|
|
|
172
|
-
|
|
173
|
-
await this._db.insert('@gip/branches', {
|
|
174
|
-
name: branchName,
|
|
175
|
-
commitOid,
|
|
176
|
-
treeOid: commit.tree,
|
|
177
|
-
author: commit.author,
|
|
178
|
-
message: commit.message,
|
|
179
|
-
timestamp: commit.timestamp,
|
|
180
|
-
objects: [...objects.keys()]
|
|
181
|
-
})
|
|
207
|
+
const isTag = refName.startsWith('tags/')
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await this._db.insert('@gip/
|
|
209
|
+
if (isTag) {
|
|
210
|
+
// 5a. Insert tag record
|
|
211
|
+
const tagName = refName.slice(5) // strip 'tags/'
|
|
212
|
+
await this._db.insert('@gip/tags', {
|
|
213
|
+
name: tagName,
|
|
214
|
+
oid,
|
|
215
|
+
commitOid: resolvedOid,
|
|
216
|
+
treeOid: commit.tree,
|
|
217
|
+
tagger: tagMeta ? tagMeta.tagger : null,
|
|
218
|
+
message: tagMeta ? tagMeta.message : null,
|
|
219
|
+
timestamp: tagMeta ? tagMeta.timestamp : 0,
|
|
220
|
+
objects: [...objects.keys()]
|
|
221
|
+
})
|
|
222
|
+
} else {
|
|
223
|
+
// 5b. Insert branch record
|
|
224
|
+
await this._db.insert('@gip/branches', {
|
|
225
|
+
name: refName,
|
|
226
|
+
commitOid: oid,
|
|
227
|
+
treeOid: commit.tree,
|
|
228
|
+
author: commit.author,
|
|
229
|
+
message: commit.message,
|
|
230
|
+
timestamp: commit.timestamp,
|
|
231
|
+
objects: [...objects.keys()]
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// 6. Set HEAD to first branch pushed (like git init)
|
|
235
|
+
const currentHead = await this.getHead()
|
|
236
|
+
if (!currentHead) {
|
|
237
|
+
await this._db.insert('@gip/head', { branch: refName })
|
|
238
|
+
}
|
|
187
239
|
}
|
|
188
240
|
|
|
189
241
|
// 7. Flush
|
|
@@ -192,29 +244,41 @@ class Remote extends ReadyResource {
|
|
|
192
244
|
|
|
193
245
|
// --- Fetch support ---
|
|
194
246
|
|
|
195
|
-
async getRefObjects(
|
|
196
|
-
|
|
197
|
-
let branch = null
|
|
247
|
+
async getRefObjects(oid, onLoad) {
|
|
248
|
+
let record = null
|
|
198
249
|
|
|
250
|
+
// Check branches first
|
|
251
|
+
const branches = this._db.find('@gip/branches')
|
|
199
252
|
for await (const b of branches) {
|
|
200
|
-
if (b.commitOid ===
|
|
201
|
-
|
|
253
|
+
if (b.commitOid === oid) {
|
|
254
|
+
record = b
|
|
202
255
|
break
|
|
203
256
|
}
|
|
204
257
|
}
|
|
205
258
|
|
|
206
|
-
|
|
259
|
+
// Then check tags
|
|
260
|
+
if (!record) {
|
|
261
|
+
const tags = this._db.find('@gip/tags')
|
|
262
|
+
for await (const t of tags) {
|
|
263
|
+
if (t.oid === oid) {
|
|
264
|
+
record = t
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!record) return []
|
|
207
271
|
|
|
208
272
|
const results = []
|
|
209
|
-
for (const
|
|
210
|
-
const obj = await this.getObject(
|
|
273
|
+
for (const objOid of record.objects) {
|
|
274
|
+
const obj = await this.getObject(objOid)
|
|
211
275
|
if (!obj) continue
|
|
212
276
|
|
|
213
277
|
// Empty blobs have null data after round-tripping through compact-encoding
|
|
214
278
|
const data = obj.data || Buffer.alloc(0)
|
|
215
279
|
|
|
216
280
|
if (onLoad) onLoad(obj.size)
|
|
217
|
-
results.push({ ...obj, data, id:
|
|
281
|
+
results.push({ ...obj, data, id: objOid })
|
|
218
282
|
}
|
|
219
283
|
|
|
220
284
|
return results
|
|
@@ -223,8 +287,16 @@ class Remote extends ReadyResource {
|
|
|
223
287
|
// --- Drive ---
|
|
224
288
|
|
|
225
289
|
async toDrive(branch) {
|
|
290
|
+
// Check branches first, then tags
|
|
226
291
|
const b = await this._db.get('@gip/branches', { name: branch })
|
|
227
|
-
if (!b)
|
|
292
|
+
if (!b) {
|
|
293
|
+
const t = await this._db.get('@gip/tags', { name: branch })
|
|
294
|
+
if (!t) return null
|
|
295
|
+
// For tags, files are stored under 'tags/<name>'
|
|
296
|
+
const drive = new RemoteDrive(this._db, { branch: 'tags/' + branch })
|
|
297
|
+
await drive.ready()
|
|
298
|
+
return drive
|
|
299
|
+
}
|
|
228
300
|
|
|
229
301
|
const drive = new RemoteDrive(this._db, { branch })
|
|
230
302
|
await drive.ready()
|
package/lib/drive.js
CHANGED
|
@@ -26,15 +26,7 @@ class RemoteDrive extends ReadyResource {
|
|
|
26
26
|
})
|
|
27
27
|
if (!record) return null
|
|
28
28
|
|
|
29
|
-
return
|
|
30
|
-
key,
|
|
31
|
-
value: {
|
|
32
|
-
executable: record.mode === '100755',
|
|
33
|
-
linkname: null,
|
|
34
|
-
blob: { byteLength: record.size },
|
|
35
|
-
metadata: null
|
|
36
|
-
}
|
|
37
|
-
}
|
|
29
|
+
return toEntry(key, record)
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
async get(nameOrEntry) {
|
|
@@ -89,7 +81,7 @@ class RemoteDrive extends ReadyResource {
|
|
|
89
81
|
try {
|
|
90
82
|
for await (const record of stream) {
|
|
91
83
|
if (ignore && ignore(record.path)) continue
|
|
92
|
-
this.push(record.path)
|
|
84
|
+
this.push(toEntry(record.path, record))
|
|
93
85
|
}
|
|
94
86
|
this.push(null)
|
|
95
87
|
cb(null)
|
|
@@ -130,4 +122,21 @@ class RemoteDrive extends ReadyResource {
|
|
|
130
122
|
}
|
|
131
123
|
}
|
|
132
124
|
|
|
125
|
+
function toEntry(key, record) {
|
|
126
|
+
return {
|
|
127
|
+
key,
|
|
128
|
+
value: {
|
|
129
|
+
executable: record.mode === '100755',
|
|
130
|
+
linkname: null,
|
|
131
|
+
blob: {
|
|
132
|
+
blockOffset: 0,
|
|
133
|
+
blockLength: 0,
|
|
134
|
+
byteOffset: 0,
|
|
135
|
+
byteLength: record.size
|
|
136
|
+
},
|
|
137
|
+
metadata: null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
133
142
|
module.exports = RemoteDrive
|
package/lib/git.js
CHANGED
|
@@ -35,6 +35,37 @@ function parseCommit(data) {
|
|
|
35
35
|
return result
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// --- Git tag parser ---
|
|
39
|
+
|
|
40
|
+
function parseTag(data) {
|
|
41
|
+
const text = data.toString('utf8')
|
|
42
|
+
const lines = text.split('\n')
|
|
43
|
+
const result = { object: null, type: null, tag: null, tagger: null, timestamp: 0, message: '' }
|
|
44
|
+
|
|
45
|
+
let i = 0
|
|
46
|
+
for (; i < lines.length; i++) {
|
|
47
|
+
const line = lines[i]
|
|
48
|
+
if (line === '') {
|
|
49
|
+
i++
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (line.startsWith('object ')) result.object = line.slice(7)
|
|
54
|
+
else if (line.startsWith('type ')) result.type = line.slice(5)
|
|
55
|
+
else if (line.startsWith('tag ')) result.tag = line.slice(4)
|
|
56
|
+
else if (line.startsWith('tagger ')) {
|
|
57
|
+
const match = line.match(/^tagger (.+) <.+> (\d+) [+-]\d+$/)
|
|
58
|
+
if (match) {
|
|
59
|
+
result.tagger = match[1]
|
|
60
|
+
result.timestamp = parseInt(match[2])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
result.message = lines.slice(i).join('\n').trim()
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
// --- Tree walker: extracts all file paths from git tree objects ---
|
|
39
70
|
|
|
40
71
|
function walkTree(objects, treeOid, prefix) {
|
|
@@ -136,4 +167,4 @@ async function toDisk(opts) {
|
|
|
136
167
|
}
|
|
137
168
|
}
|
|
138
169
|
|
|
139
|
-
module.exports = { parseCommit, walkTree, toDisk }
|
|
170
|
+
module.exports = { parseCommit, parseTag, walkTree, toDisk }
|
package/package.json
CHANGED
package/schema/hyperdb/db.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version":
|
|
2
|
+
"version": 3,
|
|
3
3
|
"offset": 0,
|
|
4
4
|
"schema": [
|
|
5
5
|
{
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"indexes": [],
|
|
13
13
|
"schema": "@gip/repos",
|
|
14
14
|
"derived": false,
|
|
15
|
-
"key": [
|
|
15
|
+
"key": [
|
|
16
|
+
"name"
|
|
17
|
+
],
|
|
16
18
|
"trigger": null
|
|
17
19
|
},
|
|
18
20
|
{
|
|
@@ -25,7 +27,9 @@
|
|
|
25
27
|
"indexes": [],
|
|
26
28
|
"schema": "@gip/branches",
|
|
27
29
|
"derived": false,
|
|
28
|
-
"key": [
|
|
30
|
+
"key": [
|
|
31
|
+
"name"
|
|
32
|
+
],
|
|
29
33
|
"trigger": null
|
|
30
34
|
},
|
|
31
35
|
{
|
|
@@ -35,10 +39,15 @@
|
|
|
35
39
|
"type": 1,
|
|
36
40
|
"version": 1,
|
|
37
41
|
"versionField": null,
|
|
38
|
-
"indexes": [
|
|
42
|
+
"indexes": [
|
|
43
|
+
"@gip/files-by-branch"
|
|
44
|
+
],
|
|
39
45
|
"schema": "@gip/files",
|
|
40
46
|
"derived": false,
|
|
41
|
-
"key": [
|
|
47
|
+
"key": [
|
|
48
|
+
"branch",
|
|
49
|
+
"path"
|
|
50
|
+
],
|
|
42
51
|
"trigger": null
|
|
43
52
|
},
|
|
44
53
|
{
|
|
@@ -51,7 +60,9 @@
|
|
|
51
60
|
"indexes": [],
|
|
52
61
|
"schema": "@gip/objects",
|
|
53
62
|
"derived": false,
|
|
54
|
-
"key": [
|
|
63
|
+
"key": [
|
|
64
|
+
"oid"
|
|
65
|
+
],
|
|
55
66
|
"trigger": null
|
|
56
67
|
},
|
|
57
68
|
{
|
|
@@ -63,7 +74,9 @@
|
|
|
63
74
|
"collection": "@gip/files",
|
|
64
75
|
"unique": false,
|
|
65
76
|
"deprecated": false,
|
|
66
|
-
"key": [
|
|
77
|
+
"key": [
|
|
78
|
+
"branch"
|
|
79
|
+
]
|
|
67
80
|
},
|
|
68
81
|
{
|
|
69
82
|
"name": "head",
|
|
@@ -77,6 +90,21 @@
|
|
|
77
90
|
"derived": false,
|
|
78
91
|
"key": [],
|
|
79
92
|
"trigger": null
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "tags",
|
|
96
|
+
"namespace": "gip",
|
|
97
|
+
"id": 6,
|
|
98
|
+
"type": 1,
|
|
99
|
+
"version": 3,
|
|
100
|
+
"versionField": null,
|
|
101
|
+
"indexes": [],
|
|
102
|
+
"schema": "@gip/tags",
|
|
103
|
+
"derived": false,
|
|
104
|
+
"key": [
|
|
105
|
+
"name"
|
|
106
|
+
],
|
|
107
|
+
"trigger": null
|
|
80
108
|
}
|
|
81
109
|
]
|
|
82
|
-
}
|
|
110
|
+
}
|