@seedprotocol/sdk 0.1.47 → 0.1.48

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.
Files changed (141) hide show
  1. package/dist/bin.js.map +1 -1
  2. package/dist/constants-BLctWkrn.js.map +1 -1
  3. package/dist/{index-DWf9Ls94.js → index-DMIKRod-.js} +3995 -3995
  4. package/dist/index-DMIKRod-.js.map +1 -0
  5. package/dist/{index-B2WbNudj.js → index-wKss7188.js} +10 -10
  6. package/dist/index-wKss7188.js.map +1 -0
  7. package/dist/main.js +7 -7
  8. package/dist/{seed.schema.config-mBqth17w.js → seed.schema.config-C0M8Rcti.js} +7 -7
  9. package/dist/seed.schema.config-C0M8Rcti.js.map +1 -0
  10. package/dist/src/AppStateSchema.ts +10 -0
  11. package/dist/src/Attestation.ts +21 -0
  12. package/dist/src/ConfigSchema.ts +15 -0
  13. package/dist/src/ItemProperty.ts +383 -0
  14. package/dist/src/MetadataSchema.ts +28 -0
  15. package/dist/src/ModelSchema.ts +46 -0
  16. package/dist/src/ModelUidSchema.ts +16 -0
  17. package/dist/src/PropertyUidSchema.ts +16 -0
  18. package/dist/src/Schema.ts +17 -0
  19. package/dist/src/SeedSchema.ts +29 -0
  20. package/dist/src/VersionSchema.ts +16 -0
  21. package/dist/src/actors.ts +10 -0
  22. package/dist/src/addModelsToDb.ts +150 -0
  23. package/dist/src/allItems.ts +23 -0
  24. package/dist/src/arweave.ts +37 -0
  25. package/dist/src/browser.app.db.config.ts +27 -0
  26. package/dist/src/browser.seed.db.config.ts +33 -0
  27. package/dist/src/browser.ts +30 -0
  28. package/dist/src/checkStatus.ts +50 -0
  29. package/dist/src/client.ts +85 -0
  30. package/dist/src/configureFs.ts +81 -0
  31. package/dist/src/connectToDb.ts +74 -0
  32. package/dist/src/connectionManager.ts +67 -0
  33. package/dist/src/constants.ts +118 -0
  34. package/dist/src/create.ts +39 -0
  35. package/dist/src/createItem.ts +15 -0
  36. package/dist/src/createItemMachine.ts +37 -0
  37. package/dist/src/createPublishAttempt.ts +16 -0
  38. package/dist/src/createSeeds.ts +24 -0
  39. package/dist/src/createVersion.ts +33 -0
  40. package/dist/src/db.ts +247 -0
  41. package/dist/src/dbMachine.ts +181 -0
  42. package/dist/src/deleteItem.ts +19 -0
  43. package/dist/src/download.ts +289 -0
  44. package/dist/src/drizzle.ts +82 -0
  45. package/dist/src/environment.ts +15 -0
  46. package/dist/src/eventBus.ts +5 -0
  47. package/dist/src/events.ts +14 -0
  48. package/dist/src/fetchDataFromEas.ts +90 -0
  49. package/dist/src/fetchDbData.ts +16 -0
  50. package/dist/src/fetchRelatedItems.ts +179 -0
  51. package/dist/src/fetchSeeds.ts +44 -0
  52. package/dist/src/fetchVersions.ts +41 -0
  53. package/dist/src/files.ts +16 -0
  54. package/dist/src/fragment-masking.ts +87 -0
  55. package/dist/src/fsProxy.ts +36 -0
  56. package/dist/src/getItem.ts +189 -0
  57. package/dist/src/getItemProperties.ts +162 -0
  58. package/dist/src/getItems.ts +75 -0
  59. package/dist/src/getMetadata.ts +40 -0
  60. package/dist/src/getModelSchemas.ts +88 -0
  61. package/dist/src/getSchemaForModel.ts +42 -0
  62. package/dist/src/getSeedData.ts +34 -0
  63. package/dist/src/getVersionData.ts +58 -0
  64. package/dist/src/getVersionsForVersionUids.ts +39 -0
  65. package/dist/src/globalMachine.ts +259 -0
  66. package/dist/src/gql.ts +113 -0
  67. package/dist/src/graphql.ts +3201 -0
  68. package/dist/src/helpers.ts +150 -0
  69. package/dist/src/hydrateExistingItem.ts +137 -0
  70. package/dist/src/hydrateFromDb.ts +254 -0
  71. package/dist/src/hydrateNewItem.ts +34 -0
  72. package/dist/src/index.d.ts +5 -0
  73. package/dist/src/index.ts +21 -0
  74. package/dist/src/init.ts +61 -0
  75. package/dist/src/initialize.ts +127 -0
  76. package/dist/src/internalMachine.ts +220 -0
  77. package/dist/src/item.ts +324 -0
  78. package/dist/src/itemMachineAll.ts +158 -0
  79. package/dist/src/itemMachineSingle.ts +175 -0
  80. package/dist/src/loadAppDb.ts +47 -0
  81. package/dist/src/logger.ts +33 -0
  82. package/dist/src/machine.ts +55 -0
  83. package/dist/src/machines.ts +58 -0
  84. package/dist/src/migrate.ts +288 -0
  85. package/dist/src/model.ts +71 -0
  86. package/dist/src/modelClass.ts +19 -0
  87. package/dist/src/node.app.db.config.ts +40 -0
  88. package/dist/src/prepareDb.ts +34 -0
  89. package/dist/src/preparePublishRequestData.ts +81 -0
  90. package/dist/src/processItems.ts +71 -0
  91. package/dist/src/property.ts +161 -0
  92. package/dist/src/propertyMachine.ts +154 -0
  93. package/dist/src/publish.ts +31 -0
  94. package/dist/src/publishMachine.ts +77 -0
  95. package/dist/src/queries.ts +13 -0
  96. package/dist/src/read.ts +174 -0
  97. package/dist/src/recoverDeletedItem.ts +14 -0
  98. package/dist/src/request.ts +54 -0
  99. package/dist/src/requestAll.ts +157 -0
  100. package/dist/src/resolveRelatedValue.ts +348 -0
  101. package/dist/src/resolveRemoteStorage.ts +87 -0
  102. package/dist/src/save.ts +183 -0
  103. package/dist/src/saveConfig.ts +79 -0
  104. package/dist/src/saveDataToDb.ts +145 -0
  105. package/dist/src/saveMetadata.ts +18 -0
  106. package/dist/src/saveValueToDb.ts +94 -0
  107. package/dist/src/seed.schema.config.ts +25 -0
  108. package/dist/src/seed.ts +37 -0
  109. package/dist/src/seedData.ts +0 -0
  110. package/dist/src/seedProtocol.ts +17 -0
  111. package/dist/src/services.ts +359 -0
  112. package/dist/src/sqlWasmClient.ts +88 -0
  113. package/dist/src/syncDbWithEas.ts +686 -0
  114. package/dist/src/trash.ts +29 -0
  115. package/dist/src/ts-to-proto.ts +101 -0
  116. package/dist/src/types.ts +12 -0
  117. package/dist/src/upload.ts +86 -0
  118. package/dist/src/validate.ts +42 -0
  119. package/dist/src/validateInput.ts +33 -0
  120. package/dist/src/validateItemData.ts +20 -0
  121. package/dist/src/waitForDb.ts +23 -0
  122. package/dist/src/wasm.d.ts +8300 -0
  123. package/dist/src/write.ts +366 -0
  124. package/dist/types/src/browser/db/read/getModelSchemas.d.ts.map +1 -1
  125. package/dist/types/src/browser/item/single/actors/hydrateExistingItem.d.ts +3 -1
  126. package/dist/types/src/browser/item/single/actors/hydrateExistingItem.d.ts.map +1 -1
  127. package/dist/types/src/browser/item/single/actors/saveDataToDb.d.ts +3 -3
  128. package/dist/types/src/browser/item/single/itemMachineSingle.d.ts +3 -3
  129. package/dist/types/src/browser/property/ItemProperty.d.ts +7 -7
  130. package/dist/types/src/browser/property/ItemProperty.d.ts.map +1 -1
  131. package/dist/types/src/browser/property/actors/resolveRemoteStorage.d.ts +3 -3
  132. package/dist/types/src/browser/react/trash.d.ts +1 -1
  133. package/dist/types/src/browser/react/trash.d.ts.map +1 -1
  134. package/dist/types/src/browser/services/db/dbMachine.d.ts +6 -6
  135. package/dist/types/src/browser/services/publish/publishMachine.d.ts +17 -17
  136. package/dist/types/src/types/machines.d.ts +4 -0
  137. package/dist/types/src/types/machines.d.ts.map +1 -1
  138. package/package.json +1 -1
  139. package/dist/index-B2WbNudj.js.map +0 -1
  140. package/dist/index-DWf9Ls94.js.map +0 -1
  141. package/dist/seed.schema.config-mBqth17w.js.map +0 -1
@@ -0,0 +1,127 @@
1
+ import { EventObject, fromCallback, Subscription } from 'xstate'
2
+ import { isNode, isReactNative } from '@/shared'
3
+ import {
4
+ GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES,
5
+ GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY,
6
+ GLOBAL_INITIALIZING_SEND_CONFIG,
7
+ } from '@/browser/services/internal/constants'
8
+ import debug from 'debug'
9
+ import { FromCallbackInput, GlobalMachineContext } from '@/types'
10
+ import { getAppDb } from '@/browser/db/sqlWasmClient'
11
+ import { appState } from '@/shared/seedSchema'
12
+ import { like } from 'drizzle-orm'
13
+
14
+ const logger = debug('app:services:global:actors:initialize')
15
+
16
+ export const initialize = fromCallback<
17
+ EventObject,
18
+ FromCallbackInput<GlobalMachineContext>
19
+ >(({ sendBack, input: { event, context } }) => {
20
+ const { internalService, models, endpoints } = context
21
+
22
+ const { addresses } = event
23
+
24
+ let environment = 'browser'
25
+
26
+ if (isNode()) {
27
+ environment = 'node'
28
+ }
29
+
30
+ if (isReactNative()) {
31
+ environment = 'react-native'
32
+ }
33
+
34
+ let internalSubscription: Subscription | undefined
35
+ let easSubscription: Subscription | undefined
36
+
37
+ if (environment === 'browser' && models) {
38
+ const _initFileSystem = async (): Promise<void> => {
39
+ return
40
+ // return new Promise((resolve) => {
41
+ // })
42
+ }
43
+
44
+ const _initInternal = async (): Promise<void> => {
45
+ return new Promise((resolve) => {
46
+ internalSubscription = internalService.subscribe((snapshot) => {
47
+ logger('[sdk] [internal] snapshot', snapshot)
48
+ if (snapshot.value === 'ready') {
49
+ resolve()
50
+ }
51
+ })
52
+ internalService.send({ type: 'init', endpoints, addresses })
53
+ })
54
+ }
55
+
56
+ const _initAllItemsServices = async (): Promise<void> => {
57
+ const appDb = getAppDb()
58
+
59
+ const rows = await appDb
60
+ .select()
61
+ .from(appState)
62
+ .where(like(appState.key, 'snapshot__%'))
63
+
64
+ const payloadObj = {
65
+ create: {},
66
+ restore: {},
67
+ }
68
+
69
+ const modelNamesRestored: string[] = []
70
+
71
+ if (rows && rows.length > 0) {
72
+ for (const row of rows) {
73
+ const modelName = row.key.replace('snapshot__', '')
74
+ payloadObj.restore[modelName] = JSON.parse(row.value)
75
+ modelNamesRestored.push(modelName)
76
+ }
77
+ }
78
+ for (const [modelName, ModelClass] of Object.entries(models)) {
79
+ if (!modelNamesRestored.includes(modelName)) {
80
+ payloadObj.create[modelName] = ModelClass
81
+ }
82
+ }
83
+ sendBack({
84
+ type: GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES,
85
+ ...payloadObj,
86
+ })
87
+ }
88
+
89
+ const _initEas = async (): Promise<void> => {
90
+ // const { easService } = await import('@/browser/eas')
91
+ // easService.send({ type: 'init', endpoints, models })
92
+ // return new Promise((resolve) => {
93
+ // easSubscription = easService.subscribe((snapshot) => {
94
+ // if (snapshot.value === 'ready') {
95
+ // resolve()
96
+ // }
97
+ // })
98
+ // easService.send({ type: 'init', endpoints, models })
99
+ // })
100
+ }
101
+
102
+ _initFileSystem().then(() => {
103
+ logger('[global/actors] File system initialized')
104
+ })
105
+
106
+ _initInternal()
107
+ .then(() => {
108
+ return _initAllItemsServices()
109
+ })
110
+ .then(() => {
111
+ logger('[global/actors] Internal initialized')
112
+ sendBack({ type: GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY })
113
+ internalSubscription?.unsubscribe()
114
+ })
115
+
116
+ // _initEas().then(() => {
117
+ // logger('EAS initialized')
118
+ // })
119
+ }
120
+
121
+ sendBack({ type: GLOBAL_INITIALIZING_SEND_CONFIG, environment })
122
+
123
+ return () => {
124
+ internalSubscription?.unsubscribe()
125
+ easSubscription?.unsubscribe()
126
+ }
127
+ })
@@ -0,0 +1,220 @@
1
+ import { assign, setup } from 'xstate'
2
+ import { createBrowserInspector } from '@statelyai/inspect'
3
+ import {
4
+ DB_NAME_APP,
5
+ INTERNAL_CONFIGURING_FS_SUCCESS,
6
+ INTERNAL_LOADING_APP_DB_SUCCESS,
7
+ INTERNAL_SAVING_CONFIG_SUCCESS,
8
+ INTERNAL_VALIDATING_INPUT_SUCCESS,
9
+ InternalState,
10
+ MachineIds,
11
+ } from './constants'
12
+ import { dbMachine } from '@/browser/services/db/dbMachine'
13
+ import debug from 'debug'
14
+ import { validateInput } from '@/browser/services/internal/actors/validateInput'
15
+ import { prepareDb } from '@/browser/services/internal/actors/prepareDb'
16
+ import { configureFs } from '@/browser/services/internal/actors/configureFs'
17
+ import { saveConfig } from '@/browser/services/internal/actors/saveConfig'
18
+ import { loadAppDb } from '@/browser/services/internal/actors/loadAppDb'
19
+ import { InternalMachineContext } from '@/types'
20
+
21
+ const logger = debug('app:services:internal:machine')
22
+
23
+ const { inspect } = createBrowserInspector({
24
+ autoStart: false,
25
+ })
26
+
27
+ const {
28
+ IDLE,
29
+ VALIDATING_INPUT,
30
+ SAVING_CONFIG,
31
+ CONFIGURING_FS,
32
+ LOADING_APP_DB,
33
+ } = InternalState
34
+
35
+ // Create the state machine
36
+ export const internalMachine = setup({
37
+ types: {
38
+ context: {} as Partial<InternalMachineContext>,
39
+ input: {} as Partial<InternalMachineContext> | undefined,
40
+ },
41
+ actors: {
42
+ prepareDb,
43
+ validateInput,
44
+ configureFs,
45
+ loadAppDb,
46
+ saveConfig,
47
+ },
48
+ }).createMachine({
49
+ id: MachineIds.INTERNAL,
50
+ initial: IDLE,
51
+ context: ({ input }) => {
52
+ return {
53
+ ...input,
54
+ error: undefined,
55
+ hasFiles: false,
56
+ }
57
+ },
58
+ states: {
59
+ [IDLE]: {
60
+ on: {
61
+ reValidate: VALIDATING_INPUT,
62
+ init: {
63
+ target: VALIDATING_INPUT,
64
+ actions: [
65
+ assign({
66
+ appDbService: ({ spawn }) =>
67
+ spawn(dbMachine, {
68
+ input: {
69
+ dbName: DB_NAME_APP,
70
+ },
71
+ }),
72
+ }),
73
+ ],
74
+ },
75
+ },
76
+ meta: {
77
+ displayText: 'Waiting for something to happen ...',
78
+ percentComplete: 0,
79
+ },
80
+ },
81
+ [VALIDATING_INPUT]: {
82
+ on: {
83
+ [INTERNAL_VALIDATING_INPUT_SUCCESS]: {
84
+ target: 'preparingDb',
85
+ actions: assign({
86
+ endpoints: ({ event }) => event.endpoints,
87
+ addresses: ({ event }) => event.addresses,
88
+ }),
89
+ },
90
+ },
91
+ invoke: {
92
+ src: 'validateInput',
93
+ input: ({ context, event }) => ({ context, event }),
94
+ },
95
+ meta: {
96
+ displayText: 'Validating input',
97
+ percentComplete: 20,
98
+ },
99
+ tags: ['loading'],
100
+ },
101
+ preparingDb: {
102
+ on: {
103
+ prepareDbSuccess: {
104
+ target: CONFIGURING_FS,
105
+ },
106
+ },
107
+ invoke: {
108
+ src: 'prepareDb',
109
+ input: ({ context, event }) => ({ context, event }),
110
+ },
111
+ },
112
+ [CONFIGURING_FS]: {
113
+ on: {
114
+ [INTERNAL_CONFIGURING_FS_SUCCESS]: {
115
+ target: LOADING_APP_DB,
116
+ actions: assign({ hasFiles: true }),
117
+ },
118
+ },
119
+ invoke: {
120
+ src: 'configureFs',
121
+ input: ({ context, event }) => ({ context, event }),
122
+ },
123
+ meta: {
124
+ displayText: 'Downloading app files',
125
+ percentComplete: 30,
126
+ },
127
+ tags: ['loading'],
128
+ },
129
+ [LOADING_APP_DB]: {
130
+ on: {
131
+ [INTERNAL_LOADING_APP_DB_SUCCESS]: {
132
+ target: SAVING_CONFIG,
133
+ actions: () => {
134
+ logger('[sdk] [internal/index] App DB loaded!')
135
+ },
136
+ },
137
+ },
138
+ invoke: {
139
+ src: 'loadAppDb',
140
+ input: ({ context, event }) => ({ context, event }),
141
+ },
142
+ },
143
+ // Save developer's config to DB
144
+ [SAVING_CONFIG]: {
145
+ on: {
146
+ [INTERNAL_SAVING_CONFIG_SUCCESS]: 'ready',
147
+ },
148
+ invoke: {
149
+ src: 'saveConfig',
150
+ input: ({ context, event }) => ({ context, event }),
151
+ },
152
+ meta: {
153
+ displayText: 'Saving configuration',
154
+ percentComplete: 80,
155
+ },
156
+ tags: ['loading'],
157
+ },
158
+ ready: {
159
+ entry: () => {
160
+ logger('[sdk] [internal/index] Ready!')
161
+ },
162
+ meta: {
163
+ displayText: "Crossing the t's ...",
164
+ percentComplete: 90,
165
+ },
166
+ },
167
+ error: {
168
+ on: {
169
+ retry: {
170
+ target: CONFIGURING_FS,
171
+ actions: assign({ error: undefined }),
172
+ },
173
+ },
174
+ entry: () => {
175
+ logger('[sdk] [internal/index] Error!')
176
+ },
177
+ meta: {
178
+ displayText: 'Whoops! Something went wrong.',
179
+ percentComplete: null,
180
+ },
181
+ tags: ['error'],
182
+ },
183
+ },
184
+ })
185
+
186
+ // const internalService = createActor(internalMachine, {
187
+ // input: {},
188
+ // inspect: (inspEvent) => {
189
+ // if (inspEvent.type === '@xstate.snapshot') {
190
+ // if (
191
+ // inspEvent.event &&
192
+ // inspEvent.event.snapshot &&
193
+ // inspEvent.event.snapshot.value
194
+ // ) {
195
+ // logger(
196
+ // `[internalService] ${inspEvent.event.snapshot.value}`,
197
+ // inspEvent,
198
+ // )
199
+ // return
200
+ // }
201
+ //
202
+ // if (inspEvent.snapshot && inspEvent.snapshot.value) {
203
+ // logger(`[internalService] ${inspEvent.snapshot.value}`, inspEvent)
204
+ // return
205
+ // }
206
+ //
207
+ // // logger(`[internalService] Uncaught event`, inspEvent)
208
+ // }
209
+ // },
210
+ // })
211
+
212
+ // internalService.subscribe((snapshot) => {
213
+ // globalService.send({ type: INTERNAL_SERVICE_SNAPSHOT, snapshot })
214
+ // })
215
+ //
216
+ // internalService.on(CHILD_SNAPSHOT, (emitted) => {
217
+ // globalService.send({ ...emitted })
218
+ // })
219
+
220
+ // internalService.start()
@@ -0,0 +1,324 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { createNewItem } from '@/browser/db/write'
3
+ import { Item } from '@/browser/item'
4
+ import { eventEmitter } from '@/eventBus'
5
+ import { useImmer } from 'use-immer'
6
+ import { orderBy } from 'lodash-es'
7
+ import { getAreItemEventHandlersReady } from '@/browser/events'
8
+ import { Subscription } from 'xstate'
9
+ import { useSelector } from '@xstate/react'
10
+ import debug from 'debug'
11
+ import { useGlobalServiceStatus, useIsDbReady } from '@/browser/react/services'
12
+ import { ModelValues } from '@/types'
13
+
14
+ const logger = debug('app:react:item')
15
+
16
+ type UseItemProps = {
17
+ modelName: string
18
+ seedLocalId?: string
19
+ seedUid?: string
20
+ }
21
+
22
+ type UseItemReturn<T extends ModelValues<T>> = {
23
+ item: Item<T> | undefined
24
+ itemData: ItemData<T>
25
+ itemStatus: string | Record<string, unknown> | undefined
26
+ }
27
+
28
+ type UseItem<T> = (props: UseItemProps) => UseItemReturn<T>
29
+
30
+ type ItemData<T> = Record<string, Partial<T>>
31
+
32
+ export const useItem: UseItem<any> = ({ modelName, seedLocalId, seedUid }) => {
33
+ const [itemData, setItemData] = useImmer<ItemData<any>>({})
34
+ const [item, setItem] = useState<Item<any> | undefined>()
35
+ const [itemSubscription, setItemSubscription] = useState<
36
+ Subscription | undefined
37
+ >()
38
+
39
+ const { status, internalStatus } = useGlobalServiceStatus()
40
+
41
+ const isReadingDb = useRef(false)
42
+
43
+ const itemStatus = useSelector(
44
+ item?.getService(),
45
+ (snapshot) => snapshot?.value,
46
+ )
47
+
48
+ const updateItem = useCallback((newItem: Item<any>) => {
49
+ setItemData((draft) => {
50
+ Object.keys(newItem.properties).forEach((propertyName) => {
51
+ const value = newItem[propertyName]
52
+ draft[propertyName] = value
53
+ })
54
+ })
55
+ }, [])
56
+
57
+ const readFromDb = useCallback(async () => {
58
+ if (
59
+ isReadingDb.current ||
60
+ internalStatus !== 'ready' ||
61
+ (!seedUid && !seedLocalId)
62
+ ) {
63
+ return
64
+ }
65
+ isReadingDb.current = true
66
+ const foundItem = await Item.find({
67
+ modelName,
68
+ seedLocalId,
69
+ seedUid,
70
+ })
71
+ if (!foundItem) {
72
+ logger('[useItem] [getItemFromDb] no item found', modelName, seedLocalId)
73
+ return
74
+ }
75
+ setItem(foundItem)
76
+ updateItem(foundItem)
77
+ isReadingDb.current = false
78
+ }, [internalStatus])
79
+
80
+ const listenerRef = useRef(readFromDb)
81
+
82
+ useEffect(() => {
83
+ listenerRef.current = readFromDb
84
+ }, [readFromDb])
85
+
86
+ useEffect(() => {
87
+ if (internalStatus === 'ready') {
88
+ listenerRef.current()
89
+ }
90
+ }, [internalStatus, status])
91
+
92
+ useEffect(() => {
93
+ if (item && !itemSubscription) {
94
+ const subscription = item.subscribe(async (_) => {
95
+ const newItem = await Item.find({ modelName, seedLocalId, seedUid })
96
+ if (!newItem) {
97
+ logger(
98
+ '[useItem] [itemSubscription] no item found',
99
+ modelName,
100
+ seedLocalId,
101
+ )
102
+ return
103
+ }
104
+ updateItem(newItem)
105
+ setItem(newItem)
106
+ })
107
+ setItemSubscription(subscription)
108
+ }
109
+
110
+ return () => {
111
+ itemSubscription?.unsubscribe()
112
+ }
113
+ }, [item, itemSubscription])
114
+
115
+ useEffect(() => {
116
+ const seedId = seedUid || seedLocalId
117
+
118
+ eventEmitter.addListener(`item.${modelName}.${seedId}.update`, () => {
119
+ listenerRef.current()
120
+ })
121
+
122
+ return () => {
123
+ eventEmitter.removeListener(
124
+ `item.${modelName}.${seedId}.update`,
125
+ readFromDb,
126
+ )
127
+ }
128
+ }, [])
129
+
130
+ return {
131
+ item,
132
+ itemData,
133
+ itemStatus,
134
+ }
135
+ }
136
+ type UseItemsReturn = {
137
+ items: Item<any>[]
138
+ isReadingDb: boolean
139
+ isInitialized: boolean
140
+ }
141
+ type UseItemsProps = {
142
+ modelName?: string
143
+ deleted?: boolean
144
+ }
145
+ type UseItems = (props: UseItemsProps) => UseItemsReturn
146
+ export const useItems: UseItems = ({ modelName, deleted }) => {
147
+ const [items, setItems] = useImmer<Item<any>[]>([])
148
+ const [isReadingDb, setIsReadingDb] = useState(false)
149
+ const [isInitialized, setIsInitialized] = useState(false)
150
+
151
+ const isDbReady = useIsDbReady()
152
+
153
+ const modelNameRef = useRef<string | undefined>(modelName)
154
+
155
+ const readFromDb = useCallback(
156
+ async (event) => {
157
+ if (
158
+ !event ||
159
+ !event.modelName ||
160
+ event.modelName !== modelNameRef.current ||
161
+ isReadingDb
162
+ ) {
163
+ return
164
+ }
165
+ setIsReadingDb(true)
166
+ const allItems = await Item.all(modelNameRef.current, deleted)
167
+ setItems(() => allItems)
168
+ setIsReadingDb(false)
169
+ },
170
+ [modelName, isReadingDb],
171
+ )
172
+
173
+ useEffect(() => {
174
+ if (isDbReady && !isInitialized) {
175
+ const _fetchItems = async (): Promise<void> => {
176
+ await readFromDb({ modelName })
177
+ setIsInitialized(true)
178
+ }
179
+
180
+ _fetchItems()
181
+ }
182
+ }, [isInitialized, isDbReady])
183
+
184
+ useEffect(() => {
185
+ eventEmitter.addListener('item.requestAll', readFromDb)
186
+
187
+ return () => {
188
+ eventEmitter.removeListener('item.requestAll')
189
+ }
190
+ }, [])
191
+
192
+ return {
193
+ items: orderBy(
194
+ items,
195
+ [
196
+ (item) =>
197
+ item.lastVersionPublishedAt ||
198
+ item.attestationCreatedAt ||
199
+ item.createdAt,
200
+ ],
201
+ ['desc'],
202
+ ).slice(0, 10),
203
+ isReadingDb,
204
+ isInitialized,
205
+ }
206
+ }
207
+ export const useItemIsReady = () => {
208
+ const [itemListenersReady, setItemListenersReady] = useState(false)
209
+
210
+ const itemEventListenersHandler = useCallback((_) => {
211
+ setItemListenersReady(true)
212
+ }, [])
213
+
214
+ useEffect(() => {
215
+ const areReady = getAreItemEventHandlersReady()
216
+
217
+ if (areReady) {
218
+ itemEventListenersHandler(true)
219
+ }
220
+
221
+ eventEmitter.addListener(
222
+ 'item.events.setupAllItemsEventHandlers',
223
+ itemEventListenersHandler,
224
+ )
225
+
226
+ return () => {
227
+ eventEmitter.removeListener('item.events.setupAllItemsEventHandlers')
228
+ }
229
+ }, [])
230
+
231
+ return {
232
+ isReady: itemListenersReady,
233
+ }
234
+ }
235
+ export const useCreateItem = <T>(modelName: string) => {
236
+ const [isCreatingItem, setIsCreatingItem] = useState(false)
237
+
238
+ const { isReady } = useItemIsReady()
239
+
240
+ const createItem = useCallback(
241
+ async (itemData) => {
242
+ if (!isReady) {
243
+ console.error(
244
+ `[useCreateItem] [createItem] called before listeners are ready`,
245
+ itemData,
246
+ )
247
+ return
248
+ }
249
+ if (isCreatingItem) {
250
+ // TODO: should we setup a queue for this?
251
+ console.error(
252
+ `[useCreateItem] [createItem] already creating item`,
253
+ itemData,
254
+ )
255
+ return
256
+ }
257
+
258
+ setIsCreatingItem(true)
259
+
260
+ const { seedLocalId } = await createNewItem({ modelName, ...itemData })
261
+
262
+ const newItem = await Item.find({ modelName, seedLocalId })
263
+
264
+ eventEmitter.emit('item.requestAll', { modelName })
265
+
266
+ setIsCreatingItem(false)
267
+ },
268
+ [isCreatingItem, isReady],
269
+ )
270
+
271
+ return {
272
+ createItem,
273
+ isCreatingItem,
274
+ }
275
+ }
276
+
277
+ type PublishItemResult = Error | undefined | void
278
+
279
+ type UsePublishItemReturn = {
280
+ publishItem: (
281
+ item: Item<any> | undefined,
282
+ callback: (result: PublishItemResult) => any,
283
+ ) => void
284
+ isPublishing: boolean
285
+ }
286
+
287
+ type PublishItemProps = [
288
+ item: Item<any> | undefined,
289
+ callback?: (result: PublishItemResult) => any | undefined,
290
+ ]
291
+
292
+ type PublishItem = (...props: PublishItemProps) => Promise<any>
293
+
294
+ export const usePublishItem = (): UsePublishItemReturn => {
295
+ const [isPublishing, setIsPublishing] = useState(false)
296
+
297
+ const isLocked = useRef(false)
298
+
299
+ const publishItem: PublishItem = useCallback(async (item, callback?) => {
300
+ if (!item || isLocked.current) {
301
+ return
302
+ }
303
+ isLocked.current = true
304
+ console.log('Publishing item', item)
305
+ setIsPublishing(true)
306
+ try {
307
+ await item.publish()
308
+ if (callback) {
309
+ callback()
310
+ }
311
+ } catch (e) {
312
+ if (callback) {
313
+ callback(e as Error)
314
+ }
315
+ }
316
+ setIsPublishing(false)
317
+ isLocked.current = false
318
+ }, [])
319
+
320
+ return {
321
+ publishItem,
322
+ isPublishing,
323
+ }
324
+ }