@seedprotocol/sdk 0.7.10 → 0.7.11-next.1384.g8b47246

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 (132) hide show
  1. package/README.md +1 -1
  2. package/dist/{Item-oPnmIehk.js → Item-Ld1iPicQ.js} +2051 -412
  3. package/dist/Item-Ld1iPicQ.js.map +1 -0
  4. package/dist/{ModelProperty-D2vJozmW.js → ModelProperty-C8gTx4pO.js} +32 -33
  5. package/dist/ModelProperty-C8gTx4pO.js.map +1 -0
  6. package/dist/{Schema-BMXRF4j9.js → Schema-BzeBUZxi.js} +19 -19
  7. package/dist/{Schema-BMXRF4j9.js.map → Schema-BzeBUZxi.js.map} +1 -1
  8. package/dist/{SchemaValidationService-CieGrj5c.js → SchemaValidationService-CW0s7V7R.js} +5 -6
  9. package/dist/{SchemaValidationService-CieGrj5c.js.map → SchemaValidationService-CW0s7V7R.js.map} +1 -1
  10. package/dist/arweaveContentTags-Bh4wCTal.js +51 -0
  11. package/dist/arweaveContentTags-Bh4wCTal.js.map +1 -0
  12. package/dist/dist/{index.node-BfiBriEq.d.ts → index.node-DV1OXCp0.d.ts} +25 -29
  13. package/dist/dist/{index.node-BfiBriEq.d.ts.map → index.node-DV1OXCp0.d.ts.map} +1 -1
  14. package/dist/dist/platform.node.d.ts +1 -1
  15. package/dist/download-Cvac_VGu.js +394 -0
  16. package/dist/download-Cvac_VGu.js.map +1 -0
  17. package/dist/{eas-FZPPqxqe.js → eas-CdhfODXL.js} +10 -11
  18. package/dist/{eas-FZPPqxqe.js.map → eas-CdhfODXL.js.map} +1 -1
  19. package/dist/ensureImageLocal-DNM6c2yD.js +176 -0
  20. package/dist/ensureImageLocal-DNM6c2yD.js.map +1 -0
  21. package/dist/{errors-CnT0fSMK.js → errors-DKxFSvb3.js} +179 -87
  22. package/dist/errors-DKxFSvb3.js.map +1 -0
  23. package/dist/{getPublishPayload-DKuEaq96.js → getPublishPayload-CLrAcZwr.js} +7 -7
  24. package/dist/{getPublishPayload-DKuEaq96.js.map → getPublishPayload-CLrAcZwr.js.map} +1 -1
  25. package/dist/{getPublishPendingDiff-CH5xLMjU.js → getPublishPendingDiff-DiPff8pS.js} +12 -13
  26. package/dist/{getPublishPendingDiff-CH5xLMjU.js.map → getPublishPendingDiff-DiPff8pS.js.map} +1 -1
  27. package/dist/{getPublishUploads-DivYpgNw.js → getPublishUploads-DFcgc27c.js} +6 -5
  28. package/dist/{getPublishUploads-DivYpgNw.js.map → getPublishUploads-DFcgc27c.js.map} +1 -1
  29. package/dist/index-9udoYObJ.js +25 -0
  30. package/dist/index-9udoYObJ.js.map +1 -0
  31. package/dist/{index-Gh42_47r.js → index-Cdm7Q7el.js} +17 -15
  32. package/dist/index-Cdm7Q7el.js.map +1 -0
  33. package/dist/{index-DXRhFg1N.js → index-CghAYA-z.js} +6 -7
  34. package/dist/index-CghAYA-z.js.map +1 -0
  35. package/dist/{index.browser-DQstVvRV.js → index.browser-D9oC-KKx.js} +41 -40
  36. package/dist/index.browser-D9oC-KKx.js.map +1 -0
  37. package/dist/{index.node-BfiBriEq.js → index.node-DV1OXCp0.js} +7 -31
  38. package/dist/index.node-DV1OXCp0.js.map +1 -0
  39. package/dist/main.js +19 -16
  40. package/dist/main.js.map +1 -1
  41. package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js → modelPropertyRecordSchemas-se4QMXSi.js} +9 -12
  42. package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js.map → modelPropertyRecordSchemas-se4QMXSi.js.map} +1 -1
  43. package/dist/node.js +6 -7
  44. package/dist/node.js.map +1 -1
  45. package/dist/platform.browser.js +7 -8
  46. package/dist/platform.browser.js.map +1 -1
  47. package/dist/platform.node.js +1 -1
  48. package/dist/{property-iNW1N3vm.js → property-Broz_CWj.js} +8 -11
  49. package/dist/property-Broz_CWj.js.map +1 -0
  50. package/dist/{publishGraph-CRn_N3NT.js → publishGraph-DnWoWZRP.js} +4 -4
  51. package/dist/{publishGraph-CRn_N3NT.js.map → publishGraph-DnWoWZRP.js.map} +1 -1
  52. package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js → readHtmlBodyForStorageSeed-FvwYPn4m.js} +9 -11
  53. package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js.map → readHtmlBodyForStorageSeed-FvwYPn4m.js.map} +1 -1
  54. package/dist/src/Item/Item.d.ts.map +1 -1
  55. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  56. package/dist/src/ItemProperty/service/actors/initialize.d.ts.map +1 -1
  57. package/dist/src/Model/index.d.ts +0 -2
  58. package/dist/src/Model/index.d.ts.map +1 -1
  59. package/dist/src/browser/helpers/FileManager.d.ts +1 -3
  60. package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
  61. package/dist/src/browser/workers/FileDownloader.d.ts +1 -0
  62. package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
  63. package/dist/src/browser/workers/ImageResizer.d.ts +1 -0
  64. package/dist/src/browser/workers/ImageResizer.d.ts.map +1 -1
  65. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  66. package/dist/src/client/actors/saveAppState.d.ts.map +1 -1
  67. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  68. package/dist/src/db/read/metadataProperty.d.ts +1 -1
  69. package/dist/src/db/read/metadataProperty.d.ts.map +1 -1
  70. package/dist/src/db/read/resolveModelRecord.d.ts +0 -20
  71. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
  72. package/dist/src/db/write/createMetadata.d.ts +1 -8
  73. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  74. package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts +0 -5
  75. package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts.map +1 -1
  76. package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
  77. package/dist/src/events/files/download.d.ts.map +1 -1
  78. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  79. package/dist/src/helpers/easPropertyCanonical.d.ts +1 -1
  80. package/dist/src/helpers/easPropertyCanonical.d.ts.map +1 -1
  81. package/dist/src/helpers/entity/entityLifecycle.d.ts +0 -2
  82. package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -1
  83. package/dist/src/helpers/environment.d.ts +0 -13
  84. package/dist/src/helpers/environment.d.ts.map +1 -1
  85. package/dist/src/helpers/index.d.ts +0 -4
  86. package/dist/src/helpers/index.d.ts.map +1 -1
  87. package/dist/src/helpers/itemStorageFile.d.ts +0 -11
  88. package/dist/src/helpers/itemStorageFile.d.ts.map +1 -1
  89. package/dist/src/helpers/propertySchemaUid.d.ts +7 -5
  90. package/dist/src/helpers/propertySchemaUid.d.ts.map +1 -1
  91. package/dist/src/helpers/storageDir.d.ts +0 -2
  92. package/dist/src/helpers/storageDir.d.ts.map +1 -1
  93. package/dist/src/node/db/Db.d.ts +0 -2
  94. package/dist/src/node/db/Db.d.ts.map +1 -1
  95. package/dist/src/node/helpers/FileManager.d.ts +0 -2
  96. package/dist/src/node/helpers/FileManager.d.ts.map +1 -1
  97. package/dist/src/node/index.d.ts +2 -2
  98. package/dist/src/node/index.d.ts.map +1 -1
  99. package/dist/src/outbox/index.d.ts +5 -5
  100. package/dist/src/outbox/index.d.ts.map +1 -1
  101. package/dist/{syncDbWithEas-FuyqoEKk.js → syncDbWithEas-Dj_8VqM7.js} +24 -23
  102. package/dist/syncDbWithEas-Dj_8VqM7.js.map +1 -0
  103. package/dist/tabEvents-BnBzIdEi.js +1408 -0
  104. package/dist/tabEvents-BnBzIdEi.js.map +1 -0
  105. package/dist/{tolerantWebAccess-zEseJ2zT.js → tolerantWebAccess-CNfvmR3m.js} +8 -9
  106. package/dist/{tolerantWebAccess-zEseJ2zT.js.map → tolerantWebAccess-CNfvmR3m.js.map} +1 -1
  107. package/dist/{updateSeedRevokedAt-B9JRwr5T.js → updateSeedRevokedAt-D-3SLW-D.js} +3 -2
  108. package/dist/{updateSeedRevokedAt-B9JRwr5T.js.map → updateSeedRevokedAt-D-3SLW-D.js.map} +1 -1
  109. package/package.json +7 -12
  110. package/dist/Item-oPnmIehk.js.map +0 -1
  111. package/dist/ModelProperty-D2vJozmW.js.map +0 -1
  112. package/dist/errors-CnT0fSMK.js.map +0 -1
  113. package/dist/index-CZMWKXLM.js +0 -27
  114. package/dist/index-CZMWKXLM.js.map +0 -1
  115. package/dist/index-DXRhFg1N.js.map +0 -1
  116. package/dist/index-Gh42_47r.js.map +0 -1
  117. package/dist/index.browser-DQstVvRV.js.map +0 -1
  118. package/dist/index.node-BfiBriEq.js.map +0 -1
  119. package/dist/property-iNW1N3vm.js.map +0 -1
  120. package/dist/src/db/read/getItemProperty.d.ts +0 -6
  121. package/dist/src/db/read/getItemProperty.d.ts.map +0 -1
  122. package/dist/src/db/read/isSeedRevoked.d.ts +0 -7
  123. package/dist/src/db/read/isSeedRevoked.d.ts.map +0 -1
  124. package/dist/src/db/write/saveMetadata.d.ts +0 -3
  125. package/dist/src/db/write/saveMetadata.d.ts.map +0 -1
  126. package/dist/src/db/write/updateMetadata.d.ts +0 -13
  127. package/dist/src/db/write/updateMetadata.d.ts.map +0 -1
  128. package/dist/src/types/fileManager.d.ts +0 -13
  129. package/dist/src/types/fileManager.d.ts.map +0 -1
  130. package/dist/syncDbWithEas-FuyqoEKk.js.map +0 -1
  131. package/dist/tabEvents-DT5ez6io.js +0 -3830
  132. package/dist/tabEvents-DT5ez6io.js.map +0 -1
@@ -0,0 +1,1408 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import debug from 'debug';
3
+ import { customAlphabet } from 'nanoid';
4
+ import { alphanumeric } from 'nanoid-dictionary';
5
+ import { w as BaseFileManager, g as entityChanges } from './BaseFileManager-DLou1TZS.js';
6
+ import { sql, gt, lt } from 'drizzle-orm';
7
+ import { VERSION_SCHEMA_UID } from '@seedprotocol/eas';
8
+ import '@seedprotocol/arweave';
9
+
10
+ // Define zero constants ourselves since they're not exported from eas-sdk in newer versions
11
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
12
+ const ZERO_BYTES = '0x';
13
+ const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
14
+ const INTERNAL_DATA_TYPES = {
15
+ Text: {
16
+ eas: 'string',
17
+ },
18
+ // ADR 0010: decimal strings, so negatives, fractions and values above 255 publish.
19
+ Number: {
20
+ eas: 'string',
21
+ },
22
+ Image: {
23
+ eas: 'bytes32',
24
+ },
25
+ Relation: {
26
+ eas: 'bytes32',
27
+ },
28
+ List: {
29
+ eas: 'bytes32[]',
30
+ },
31
+ File: {
32
+ eas: 'bytes32',
33
+ },
34
+ Json: {
35
+ eas: 'bytes32',
36
+ },
37
+ Blob: {
38
+ eas: 'bytes32',
39
+ },
40
+ Boolean: {
41
+ eas: 'bool',
42
+ },
43
+ Date: {
44
+ eas: 'uint256',
45
+ },
46
+ Html: {
47
+ eas: 'bytes32',
48
+ },
49
+ };
50
+ /**
51
+ * Internal property names ending in `Id` that are names in their own right: never stripped to a
52
+ * base name, and never given an `Id` suffix in metadata.
53
+ */
54
+ const PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP = new Set(['storageTransactionId', 'transactionId']);
55
+ const INTERNAL_PROPERTY_NAMES = [
56
+ 'localId',
57
+ 'uid',
58
+ 'seedLocalId',
59
+ 'seedUid',
60
+ 'schemaUid',
61
+ 'attestationCreatedAt',
62
+ 'attestationRaw',
63
+ 'createdAt',
64
+ 'updatedAt',
65
+ 'lastVersionPublishedAt',
66
+ 'latestVersionLocalId',
67
+ 'latestVersionUid',
68
+ 'lastLocalUpdateAt',
69
+ 'modelName',
70
+ 'refSeedType',
71
+ 'refValueType',
72
+ 'refResolvedValue',
73
+ 'refResolvedDisplayValue',
74
+ 'type',
75
+ // Image
76
+ 'src',
77
+ 'alt',
78
+ //
79
+ 'versionLocalId',
80
+ 'versionsCount',
81
+ 'versionUid',
82
+ '_markedForDeletion',
83
+ // SDK-internal: wallet address of attester; never publish as property attestation
84
+ 'publisher',
85
+ // SDK-internal: Unix timestamp when attestations were revoked; never a schema field
86
+ 'revokedAt',
87
+ ];
88
+ /** @deprecated The UID is chain-independent. Use {@link VERSION_SCHEMA_UID}. */
89
+ const VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA = VERSION_SCHEMA_UID;
90
+ const defaultAttestationData = {
91
+ recipient: ZERO_ADDRESS,
92
+ revocable: true,
93
+ value: BigInt(0),
94
+ refUID: ZERO_BYTES32,
95
+ expirationTime: BigInt(0),
96
+ data: ZERO_BYTES,
97
+ };
98
+ var ImageSize;
99
+ (function (ImageSize) {
100
+ ImageSize[ImageSize["EXTRA_SMALL"] = 480] = "EXTRA_SMALL";
101
+ ImageSize[ImageSize["SMALL"] = 760] = "SMALL";
102
+ ImageSize[ImageSize["MEDIUM"] = 1024] = "MEDIUM";
103
+ ImageSize[ImageSize["LARGE"] = 1440] = "LARGE";
104
+ ImageSize[ImageSize["EXTRA_LARGE"] = 1920] = "EXTRA_LARGE";
105
+ })(ImageSize || (ImageSize = {}));
106
+ const CLIENT_NOT_INITIALIZED = 'ClientManager is not initialized. Please call init() first.';
107
+ var SeedModels;
108
+ (function (SeedModels) {
109
+ SeedModels["Seed"] = "Seed";
110
+ SeedModels["Metadata"] = "Metadata";
111
+ SeedModels["Version"] = "Version";
112
+ })(SeedModels || (SeedModels = {}));
113
+ // Internal SDK schema that should not be created in app's files directory
114
+ const SEED_PROTOCOL_SCHEMA_NAME = 'Seed Protocol';
115
+ const INTERNAL_SCHEMA_IDS = ['SEEDPROTOCOL'];
116
+ /** Fired, in every tab, after `syncDbWithEas` persists EAS seeds, versions and metadata to SQLite. */
117
+ const EAS_SEED_DATA_SYNCED_TO_DB_EVENT = 'easSeedDataSyncedToDb';
118
+ /**
119
+ * Check if a schema is an internal SDK schema that should not be created in app files
120
+ */
121
+ function isInternalSchema(schemaName, schemaId) {
122
+ return schemaName === SEED_PROTOCOL_SCHEMA_NAME ||
123
+ (schemaId !== undefined && INTERNAL_SCHEMA_IDS.includes(schemaId));
124
+ }
125
+ /**
126
+ * Model ids (schemaFileId) of the internal Seed Protocol storage models. Storage seeds are always
127
+ * these models, even if a user schema also defines a model named Image/File/Html.
128
+ */
129
+ const INTERNAL_STORAGE_MODEL_FILE_IDS = {
130
+ image: 'SEEDPROTOCOL_IMAGE',
131
+ file: 'SEEDPROTOCOL_FILE',
132
+ html: 'SEEDPROTOCOL_HTML',
133
+ };
134
+
135
+ /** Default maximum length for Text properties when not explicitly set. Html holds arbitrary-length content and does not use this limit. */
136
+ const DEFAULT_TEXT_MAX_LENGTH = 255;
137
+ /**
138
+ * TypeBox schema for ValidationRules
139
+ */
140
+ const TValidationRules = Type.Object({
141
+ pattern: Type.Optional(Type.String()),
142
+ minLength: Type.Optional(Type.Integer({ minimum: 0 })),
143
+ maxLength: Type.Optional(Type.Integer({ minimum: 1 })),
144
+ enum: Type.Optional(Type.Array(Type.Any())),
145
+ custom: Type.Optional(Type.String()),
146
+ }, { additionalProperties: true });
147
+
148
+ /**
149
+ * Enum for all property data types
150
+ */
151
+ var ModelPropertyDataTypes;
152
+ (function (ModelPropertyDataTypes) {
153
+ ModelPropertyDataTypes["Text"] = "Text";
154
+ ModelPropertyDataTypes["Number"] = "Number";
155
+ ModelPropertyDataTypes["List"] = "List";
156
+ ModelPropertyDataTypes["Relation"] = "Relation";
157
+ ModelPropertyDataTypes["Image"] = "Image";
158
+ ModelPropertyDataTypes["Json"] = "Json";
159
+ ModelPropertyDataTypes["File"] = "File";
160
+ ModelPropertyDataTypes["Boolean"] = "Boolean";
161
+ ModelPropertyDataTypes["Date"] = "Date";
162
+ ModelPropertyDataTypes["Html"] = "Html";
163
+ })(ModelPropertyDataTypes || (ModelPropertyDataTypes = {}));
164
+ /** Known data types in lowercase for normalization */
165
+ const DATA_TYPE_LOWER_MAP = {
166
+ text: 'Text',
167
+ string: 'Text', // JSON Schema convention
168
+ number: 'Number',
169
+ list: 'List',
170
+ relation: 'Relation',
171
+ image: 'Image',
172
+ json: 'Json',
173
+ file: 'File',
174
+ boolean: 'Boolean',
175
+ date: 'Date',
176
+ html: 'Html',
177
+ blob: 'Blob',
178
+ };
179
+ /**
180
+ * Normalize dataType to canonical PascalCase form.
181
+ * Accepts lowercase (e.g. 'text') or mixed case and returns 'Text'.
182
+ * Use when ingesting dataType from JSON schema files.
183
+ */
184
+ function normalizeDataType(value) {
185
+ if (!value || typeof value !== 'string')
186
+ return value ?? '';
187
+ const lower = value.toLowerCase().trim();
188
+ return DATA_TYPE_LOWER_MAP[lower] ?? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
189
+ }
190
+ /**
191
+ * Case-insensitive dataType check. Use instead of `dataType === 'List'` so schemas
192
+ * (or existing DB rows) with lowercase types like "list" still match.
193
+ */
194
+ function isDataType(value, expected) {
195
+ if (!value)
196
+ return false;
197
+ return normalizeDataType(value) === expected;
198
+ }
199
+ /**
200
+ * Normalize a property definition to the internal shape ItemProperty reads (`dataType`,
201
+ * `refValueType`, `ref`, `storageType`, ...). Accepts schema-file shape (`type`, `model`,
202
+ * `items`, `storage`) and any casing. Mirrors the conversion in imports/json createModelFromJson
203
+ * for the fallback paths that read schema-file properties directly.
204
+ */
205
+ function normalizePropertyRecordSchema(def) {
206
+ if (!def || typeof def !== 'object')
207
+ return def;
208
+ const src = def;
209
+ const out = { ...src };
210
+ const rawType = src.dataType ?? src.type;
211
+ if (typeof rawType === 'string' && rawType)
212
+ out.dataType = normalizeDataType(rawType);
213
+ const ref = src.ref ?? src.refModelName ?? src.items?.model ?? src.model;
214
+ if (ref && !out.ref)
215
+ out.ref = ref;
216
+ if (ref && !out.refModelName)
217
+ out.refModelName = ref;
218
+ const rawRefValueType = src.refValueType ?? src.items?.type;
219
+ if (typeof rawRefValueType === 'string' && rawRefValueType) {
220
+ out.refValueType = normalizeDataType(rawRefValueType);
221
+ }
222
+ else if (out.dataType === ModelPropertyDataTypes.List && ref) {
223
+ out.refValueType = ModelPropertyDataTypes.Relation;
224
+ }
225
+ if (src.storage && typeof src.storage === 'object' && !out.storageType) {
226
+ out.storageType = src.storage.type === 'ItemStorage' ? 'ItemStorage' : 'PropertyStorage';
227
+ if (src.storage.path !== undefined && out.localStorageDir === undefined)
228
+ out.localStorageDir = src.storage.path;
229
+ if (src.storage.extension !== undefined && out.filenameSuffix === undefined)
230
+ out.filenameSuffix = src.storage.extension;
231
+ }
232
+ return out;
233
+ }
234
+ // String literals, not enum members: PropertyDataType must accept plain strings
235
+ // such as `{ dataType: 'Text' }`. ModelPropertyDataTypes members still fit.
236
+ const TPropertyDataType = Type.Union([
237
+ Type.Literal('Text'),
238
+ Type.Literal('Number'),
239
+ Type.Literal('List'),
240
+ Type.Literal('Relation'),
241
+ Type.Literal('Image'),
242
+ Type.Literal('Json'),
243
+ Type.Literal('File'),
244
+ Type.Literal('Boolean'),
245
+ Type.Literal('Date'),
246
+ Type.Literal('Html'),
247
+ ]);
248
+ const TStorageType = Type.Union([
249
+ Type.Literal('ItemStorage'), // Looks for a storageTransactionId property on the item
250
+ Type.Literal('PropertyStorage'), // Looks for a storageTransactionId value on the property
251
+ ]);
252
+ /** How publish handles `data:image/*` URIs inside Html storage. Only applies when `dataType === 'Html'`. */
253
+ const THtmlEmbeddedDataUriPolicy = Type.Union([
254
+ Type.Literal('materialize'),
255
+ Type.Literal('preserve'),
256
+ ]);
257
+ const TProperty = Type.Object({
258
+ id: Type.Optional(Type.String()), // schemaFileId (string) - public ID
259
+ _dbId: Type.Optional(Type.Number()), // Database integer ID - internal only
260
+ name: Type.Optional(Type.String()),
261
+ dataType: TPropertyDataType,
262
+ ref: Type.Optional(Type.String()),
263
+ // modelId: string (modelFileId) in code; number only for DB foreign keys when resolved
264
+ modelId: Type.Optional(Type.Union([Type.Number(), Type.String()])),
265
+ modelName: Type.Optional(Type.String()),
266
+ refModelId: Type.Optional(Type.Number()),
267
+ refModelName: Type.Optional(Type.String()),
268
+ refValueType: Type.Optional(TPropertyDataType),
269
+ storageType: Type.Optional(TStorageType),
270
+ localStorageDir: Type.Optional(Type.String()),
271
+ filenameSuffix: Type.Optional(Type.String()),
272
+ validation: Type.Optional(TValidationRules),
273
+ required: Type.Optional(Type.Boolean()),
274
+ /** When set on an Html property: `materialize` uploads embedded images as Image seeds and rewrites src; `preserve` leaves data URIs. */
275
+ htmlEmbeddedDataUriPolicy: Type.Optional(THtmlEmbeddedDataUriPolicy),
276
+ });
277
+ const TPropertyConstructor = Type.Function([
278
+ Type.Optional(Type.Union([Type.String(), TStorageType, Type.Undefined()])),
279
+ Type.Optional(Type.Union([Type.String(), TPropertyDataType])),
280
+ Type.Optional(Type.Union([Type.String(), Type.Boolean()])),
281
+ ], TProperty);
282
+ const TPropertyDefs = Type.Record(TPropertyDataType, TPropertyConstructor);
283
+ function htmlPropertyFactory(opts) {
284
+ return {
285
+ dataType: ModelPropertyDataTypes.Html,
286
+ ...(opts?.htmlEmbeddedDataUriPolicy
287
+ ? { htmlEmbeddedDataUriPolicy: opts.htmlEmbeddedDataUriPolicy }
288
+ : {}),
289
+ };
290
+ }
291
+ const Property = {
292
+ Text: (storageType, localStorageDir, filenameSuffix) => ({
293
+ dataType: ModelPropertyDataTypes.Text,
294
+ storageType,
295
+ localStorageDir,
296
+ filenameSuffix,
297
+ TObject: Type.String(),
298
+ }),
299
+ Json: () => ({ dataType: ModelPropertyDataTypes.Json }),
300
+ File: () => ({ dataType: ModelPropertyDataTypes.File }),
301
+ Number: () => ({ dataType: ModelPropertyDataTypes.Number }),
302
+ /**
303
+ * Create a List property. refValueType is required; ref is required only for list of relations.
304
+ * @param refValueType - Element type: 'Text', 'Number', 'Relation', etc.
305
+ * @param ref - Model name when refValueType is 'Relation' (optional for primitives)
306
+ * @example List('Text') - list of strings
307
+ * @example List('Relation', 'Tag') - list of relations to Tag model
308
+ */
309
+ List: (refValueType, ref) => ({
310
+ dataType: ModelPropertyDataTypes.List,
311
+ ref,
312
+ refValueType,
313
+ }),
314
+ Relation: (ref, refValueType, required) => ({
315
+ dataType: ModelPropertyDataTypes.Relation,
316
+ ref,
317
+ refValueType,
318
+ required,
319
+ }),
320
+ Image: () => ({ dataType: ModelPropertyDataTypes.Image }),
321
+ Boolean: () => ({ dataType: ModelPropertyDataTypes.Boolean }),
322
+ Date: () => ({ dataType: ModelPropertyDataTypes.Date }),
323
+ Html: htmlPropertyFactory,
324
+ /** `Html` opts are not representable in `TPropertyConstructor`; keep runtime shape via double assert. */
325
+ };
326
+
327
+ /**
328
+ * Phase timings for item creates, finds and property saves, and ready waits that timed out but
329
+ * were allowed to continue, under `DEBUG=seedSdk:timing`. Timing lines are `<label> <ms>ms` plus
330
+ * any details, so an app can report where a slow call spent its time.
331
+ */
332
+ const timingLogger = debug('seedSdk:timing');
333
+ const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
334
+ /** Runs `fn` and logs how long it took (also when it throws). Costs nothing when the namespace is off. */
335
+ async function timed(label, fn, details) {
336
+ if (!timingLogger.enabled)
337
+ return fn();
338
+ const start = now();
339
+ let failed = false;
340
+ try {
341
+ return await fn();
342
+ }
343
+ catch (error) {
344
+ failed = true;
345
+ throw error;
346
+ }
347
+ finally {
348
+ const extra = details?.();
349
+ timingLogger(`${label} ${Math.round(now() - start)}ms${failed ? ' (failed)' : ''}${extra ? ` ${extra}` : ''}`);
350
+ }
351
+ }
352
+ /**
353
+ * Database work counted while the namespace is on, so a timing line can say how much of its time
354
+ * was the SQLite worker. Statements are the ones the SDK sends through the driver; their time runs
355
+ * from send to result, so it includes the wait behind everything queued before them on the one
356
+ * worker. Live-query runs are SQLocal reactive-query results (the first run and every re-run after
357
+ * a write to a watched table), which go through the same worker but not through the driver.
358
+ */
359
+ const dbTimingCounters = {
360
+ statements: 0,
361
+ statementMs: 0,
362
+ inFlight: 0,
363
+ liveQueryRuns: 0,
364
+ liveQueryFirstRuns: 0,
365
+ liveQueryFirstRunMs: 0,
366
+ };
367
+ const snapshotCounters = () => ({ ...dbTimingCounters });
368
+ /** Wraps a driver function so each call is counted while timing is on. */
369
+ function countDbStatements(driver) {
370
+ return async (...args) => {
371
+ if (!timingLogger.enabled)
372
+ return driver(...args);
373
+ const start = now();
374
+ dbTimingCounters.inFlight += 1;
375
+ try {
376
+ return await driver(...args);
377
+ }
378
+ finally {
379
+ dbTimingCounters.inFlight -= 1;
380
+ dbTimingCounters.statements += 1;
381
+ dbTimingCounters.statementMs += now() - start;
382
+ }
383
+ };
384
+ }
385
+ /** Starts the clock for one live query; call the result on each of its runs. */
386
+ function countLiveQueryRuns() {
387
+ if (!timingLogger.enabled)
388
+ return () => { };
389
+ const start = now();
390
+ let first = true;
391
+ return () => {
392
+ dbTimingCounters.liveQueryRuns += 1;
393
+ if (!first)
394
+ return;
395
+ first = false;
396
+ dbTimingCounters.liveQueryFirstRuns += 1;
397
+ dbTimingCounters.liveQueryFirstRunMs += now() - start;
398
+ };
399
+ }
400
+ const describeDbDelta = (before) => {
401
+ const after = dbTimingCounters;
402
+ const statements = after.statements - before.statements;
403
+ const statementMs = after.statementMs - before.statementMs;
404
+ const liveRuns = after.liveQueryRuns - before.liveQueryRuns;
405
+ const firstRuns = after.liveQueryFirstRuns - before.liveQueryFirstRuns;
406
+ const firstMs = after.liveQueryFirstRunMs - before.liveQueryFirstRunMs;
407
+ const parts = [`queued ${before.inFlight}`];
408
+ if (statements)
409
+ parts.push(`${statements} stmts avg ${Math.round(statementMs / statements)}ms`);
410
+ if (liveRuns)
411
+ parts.push(`${liveRuns} live runs`);
412
+ if (firstRuns)
413
+ parts.push(`${firstRuns} new live queries, first run avg ${Math.round(firstMs / firstRuns)}ms`);
414
+ return `[db: ${parts.join(', ')}]`;
415
+ };
416
+ /**
417
+ * Like `timed`, and the line also says how many statements and live-query runs went through the
418
+ * worker meanwhile (from any caller, since they share the queue) and how many statements were
419
+ * already queued when it started.
420
+ */
421
+ async function timedDb(label, fn, details) {
422
+ if (!timingLogger.enabled)
423
+ return fn();
424
+ const before = snapshotCounters();
425
+ return timed(label, fn, () => [details?.(), describeDbDelta(before)].filter(Boolean).join(' '));
426
+ }
427
+ /** Milliseconds since `start` (a `timingNow()` value), rounded. */
428
+ const timingSince = (start) => Math.round(now() - start);
429
+ const timingNow = now;
430
+ /**
431
+ * Split timer for one multi-step call: `mark(step)` records the time since the previous mark, and
432
+ * `log(label)` writes one line with the total and every step. Does nothing when timing is off.
433
+ */
434
+ function timingMarks() {
435
+ if (!timingLogger.enabled)
436
+ return { mark: () => { }, log: () => { } };
437
+ const start = now();
438
+ const before = snapshotCounters();
439
+ let last = start;
440
+ const steps = [];
441
+ return {
442
+ mark: (step) => {
443
+ const t = now();
444
+ steps.push(`${step} ${Math.round(t - last)}`);
445
+ last = t;
446
+ },
447
+ log: (label) => {
448
+ timingLogger(`${label} ${Math.round(now() - start)}ms: ${steps.join(', ')} ${describeDbDelta(before)}`);
449
+ },
450
+ };
451
+ }
452
+
453
+ const randomId = customAlphabet(alphanumeric, 10);
454
+ /**
455
+ * A 10-character local id. Never starts with "0x" (about 1 in 3,844 random ids would), so code that
456
+ * tells local ids from EAS uids by that prefix can't mistake one for the other.
457
+ */
458
+ const generateId = () => {
459
+ let id = randomId();
460
+ while (id.startsWith('0x'))
461
+ id = randomId();
462
+ return id;
463
+ };
464
+
465
+ var generateId$1 = /*#__PURE__*/Object.freeze({
466
+ __proto__: null,
467
+ generateId: generateId
468
+ });
469
+
470
+ const isNode = () => {
471
+ return (typeof process !== 'undefined' &&
472
+ process.versions != null &&
473
+ process.versions.node != null);
474
+ };
475
+ const isBrowser = () => {
476
+ return !isElectron() && typeof document !== 'undefined' && typeof window !== 'undefined';
477
+ };
478
+ const isElectron = () => {
479
+ return typeof process !== 'undefined' && process.versions != null && process.versions.electron != null;
480
+ };
481
+ /**
482
+ * Detects if code is running in an Electron renderer process.
483
+ *
484
+ * Uses multiple detection methods for robustness:
485
+ * 1. Checks process.type === 'renderer' (most reliable)
486
+ * 2. Falls back to checking if Electron is present and window/document exist
487
+ *
488
+ * @returns true if running in Electron renderer process, false otherwise
489
+ */
490
+ const isElectronRenderer = () => {
491
+ // Method 1: Check process.type (most reliable - official Electron API)
492
+ if (typeof process !== 'undefined' &&
493
+ process.type === 'renderer') {
494
+ return true;
495
+ }
496
+ // Method 2: Fallback - Electron + browser-like environment
497
+ // Renderer processes have window/document, main process doesn't (in modern Electron)
498
+ if (isElectron() &&
499
+ typeof window !== 'undefined' &&
500
+ typeof document !== 'undefined') {
501
+ return true;
502
+ }
503
+ return false;
504
+ };
505
+ /**
506
+ * True where OPFS-backed Arweave file downloads should run: normal browsers and
507
+ * Electron renderer. {@link isBrowser} is false for every Electron build (including renderer).
508
+ */
509
+ const supportsOpfsFileDownloads = () => {
510
+ return isBrowser() || isElectronRenderer();
511
+ };
512
+
513
+ /**
514
+ * Cross-tab exclusive sections, using the Web Locks API. All tabs of an origin share one SQLite
515
+ * database and OPFS tree; the browser releases a tab's locks when it closes or crashes, so a lock
516
+ * can't go stale. See docs/MULTI_TAB.md.
517
+ *
518
+ * Node has Web Locks from 24.5, scoped to the process. Where they're missing (older Node, old
519
+ * browsers), sections still run one at a time within this JS realm (see `withProcessLock`), which
520
+ * covers a single Node process but not other tabs or worker threads.
521
+ * Not re-entrant: requesting a name the caller already holds waits forever, so take each lock
522
+ * only at the outermost call site.
523
+ */
524
+ /** Long enough for a slow init in another tab; short enough that a hung tab doesn't block forever. */
525
+ const TAB_LOCK_WAIT_TIMEOUT_MS = 60000;
526
+ function seedDbLockName(scope, filesDir) {
527
+ return `seed:${scope}:${filesDir}/db/seed.db`;
528
+ }
529
+ /** `withTabLock` on the database under `filesDir`; runs `fn` directly when there's no `filesDir` yet. */
530
+ function withSeedDbLock(scope, filesDir, fn) {
531
+ return filesDir ? withTabLock(seedDbLockName(scope, filesDir), fn) : fn();
532
+ }
533
+ class TabLockTimeoutError extends Error {
534
+ constructor(lockName, timeoutMs) {
535
+ super(`Timed out after ${timeoutMs}ms waiting for another tab to release "${lockName}".`);
536
+ this.lockName = lockName;
537
+ this.code = 'TAB_LOCK_TIMEOUT';
538
+ this.name = 'TabLockTimeoutError';
539
+ }
540
+ }
541
+ async function withTabLock(name, fn, { timeoutMs = TAB_LOCK_WAIT_TIMEOUT_MS } = {}) {
542
+ const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined;
543
+ if (!locks)
544
+ return withProcessLock(name, fn, { timeoutMs });
545
+ try {
546
+ // The timeout bounds the wait only; once granted, fn runs to completion. Infinity waits for as
547
+ // long as the holder runs (it can't outlive its tab).
548
+ const signal = Number.isFinite(timeoutMs) ? AbortSignal.timeout(timeoutMs) : undefined;
549
+ return await locks.request(name, { mode: 'exclusive', signal }, fn);
550
+ }
551
+ catch (error) {
552
+ if (error?.name === 'TimeoutError') {
553
+ throw new TabLockTimeoutError(name, timeoutMs);
554
+ }
555
+ throw error;
556
+ }
557
+ }
558
+ /**
559
+ * Runs `fn` holding lock `name` only if no one holds it or waits for it now; otherwise runs nothing
560
+ * and returns `{ acquired: false }`. For work that has something else to do meanwhile (the outbox
561
+ * drainer).
562
+ */
563
+ async function tryWithTabLock(name, fn) {
564
+ const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined;
565
+ if (!locks) {
566
+ if (processLockTails.has(name))
567
+ return { acquired: false };
568
+ return { acquired: true, value: await withProcessLock(name, fn) };
569
+ }
570
+ return locks.request(name, { mode: 'exclusive', ifAvailable: true }, async (lock) => lock ? { acquired: true, value: await fn() } : { acquired: false });
571
+ }
572
+ /** Tail of each name's queue: resolves once every section queued so far has finished or given up. */
573
+ const processLockTails = new Map();
574
+ /**
575
+ * The `withTabLock` fallback without Web Locks: an exclusive, first-come-first-served lock per name
576
+ * within this JS realm. Like Web Locks, `timeoutMs` bounds the wait only, and a waiter that times
577
+ * out doesn't let later waiters in before the current holder finishes.
578
+ */
579
+ async function withProcessLock(name, fn, { timeoutMs = TAB_LOCK_WAIT_TIMEOUT_MS } = {}) {
580
+ const previous = processLockTails.get(name) ?? Promise.resolve();
581
+ let release;
582
+ const done = new Promise((resolve) => (release = resolve));
583
+ const tail = previous.then(() => done);
584
+ processLockTails.set(name, tail);
585
+ void tail.then(() => {
586
+ if (processLockTails.get(name) === tail)
587
+ processLockTails.delete(name);
588
+ });
589
+ try {
590
+ await waitForTurn(previous, name, timeoutMs);
591
+ }
592
+ catch (error) {
593
+ release();
594
+ throw error;
595
+ }
596
+ try {
597
+ return await fn();
598
+ }
599
+ finally {
600
+ release();
601
+ }
602
+ }
603
+ function waitForTurn(previous, name, timeoutMs) {
604
+ if (!Number.isFinite(timeoutMs))
605
+ return previous;
606
+ let timer;
607
+ const timedOut = new Promise((_, reject) => {
608
+ timer = setTimeout(() => reject(new TabLockTimeoutError(name, timeoutMs)), timeoutMs);
609
+ });
610
+ return Promise.race([previous, timedOut]).finally(() => clearTimeout(timer));
611
+ }
612
+
613
+ const logger$3 = debug('seedSdk:helpers:tabCoordinator');
614
+ /** Identifies this tab (this page's SDK instance) in the messages it sends. */
615
+ const TAB_ID = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
616
+ ? crypto.randomUUID()
617
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
618
+ let state;
619
+ let leader = false;
620
+ let leaderWaiters = [];
621
+ const messageHandlers = new Set();
622
+ /** Starts once per page until stopTabCoordination; later calls (re-init) keep the first database. */
623
+ function startTabCoordination({ filesDir, mode = 'coordinate' }) {
624
+ if (state)
625
+ return;
626
+ const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined;
627
+ const dbKey = `${filesDir}/db/seed.db`;
628
+ const coordinating = mode === 'coordinate' && isBrowser() && !!locks && typeof BroadcastChannel !== 'undefined';
629
+ state = { dbKey, coordinating };
630
+ if (!coordinating) {
631
+ becomeLeader();
632
+ return;
633
+ }
634
+ const channel = new BroadcastChannel(`seed:tabs:${dbKey}`);
635
+ channel.onmessage = (event) => {
636
+ // A BroadcastChannel never hears itself, but another channel object in this page would (a
637
+ // second channel opened by a test, or the same module loaded twice): skip our own messages.
638
+ if (event.data?.from === TAB_ID)
639
+ return;
640
+ for (const handler of messageHandlers)
641
+ handler(event.data);
642
+ };
643
+ state.channel = channel;
644
+ const current = state;
645
+ void locks.request(`seed:leader:${dbKey}`, () => {
646
+ // Reset (tests) before the grant: hand the lock straight back.
647
+ if (state !== current)
648
+ return undefined;
649
+ becomeLeader();
650
+ // Held until the tab closes (or a test resets coordination).
651
+ return new Promise((release) => {
652
+ current.releaseLeadership = release;
653
+ });
654
+ });
655
+ }
656
+ function becomeLeader() {
657
+ if (leader)
658
+ return;
659
+ leader = true;
660
+ logger$3(`this tab leads ${state?.dbKey}`);
661
+ const waiters = leaderWaiters;
662
+ leaderWaiters = [];
663
+ for (const resolve of waiters)
664
+ resolve();
665
+ }
666
+ function isLeaderTab() {
667
+ return !state || leader;
668
+ }
669
+ /** Resolves when this tab leads (at once if it already does). */
670
+ function whenLeaderTab() {
671
+ if (isLeaderTab())
672
+ return Promise.resolve();
673
+ return new Promise((resolve) => leaderWaiters.push(resolve));
674
+ }
675
+ /** Sends to the other tabs of this database; a no-op when not coordinating. */
676
+ function postTabMessage(message) {
677
+ state?.channel?.postMessage({ ...message, from: TAB_ID });
678
+ }
679
+ /** Whether postTabMessage reaches other tabs (a browser tab coordinating with Web Locks). */
680
+ function isPostingTabMessages() {
681
+ return !!state?.channel;
682
+ }
683
+ function onTabMessage(handler) {
684
+ messageHandlers.add(handler);
685
+ return () => messageHandlers.delete(handler);
686
+ }
687
+ /** `withTabLock` named for this database (`seed:<scope>:<db>`); runs `fn` directly before init. */
688
+ function withDbTabLock(scope, fn, options) {
689
+ return state ? withTabLock(`seed:${scope}:${state.dbKey}`, fn, options) : fn();
690
+ }
691
+ /**
692
+ * Ends this tab's coordination (client.stop): hands leadership to another tab and closes the
693
+ * channel. The next startTabCoordination starts over, for whatever database init then opens.
694
+ * Keeps message handlers (modules register theirs once per page) and whenLeaderTab waiters, which
695
+ * resolve once this tab leads again.
696
+ */
697
+ function stopTabCoordination() {
698
+ state?.releaseLeadership?.();
699
+ state?.channel?.close();
700
+ state = undefined;
701
+ leader = false;
702
+ }
703
+
704
+ const logger$2 = debug('seedSdk:helpers:localImageBlobUrl');
705
+ /**
706
+ * A new blob: URL for `images/<txId>` in the files directory, or undefined when the file isn't
707
+ * there. Each call creates a URL; the caller owns it and revokes it with `URL.revokeObjectURL`
708
+ * once nothing displays it.
709
+ */
710
+ const convertTxIdToImage = async (txId) => {
711
+ const imageFilePath = BaseFileManager.getFilesPath('images', txId);
712
+ const fileExists = await BaseFileManager.pathExists(imageFilePath);
713
+ if (!fileExists) {
714
+ logger$2(`${imageFilePath} does not exist`);
715
+ return;
716
+ }
717
+ const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
718
+ // Convert Blob to ArrayBuffer if needed, or use Buffer directly
719
+ let arrayBuffer;
720
+ if (buffer instanceof Blob) {
721
+ arrayBuffer = await buffer.arrayBuffer();
722
+ }
723
+ else if (buffer instanceof ArrayBuffer) {
724
+ arrayBuffer = buffer;
725
+ }
726
+ else {
727
+ // Node.js Buffer
728
+ const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
729
+ // Handle SharedArrayBuffer case by creating a new ArrayBuffer
730
+ if (sliced instanceof SharedArrayBuffer) {
731
+ const uint8Array = new Uint8Array(sliced);
732
+ arrayBuffer = uint8Array.buffer.slice(0);
733
+ }
734
+ else {
735
+ arrayBuffer = sliced;
736
+ }
737
+ }
738
+ return URL.createObjectURL(new Blob([new Uint8Array(arrayBuffer)]));
739
+ };
740
+
741
+ /**
742
+ * File extension <-> MIME type, shared by the upload side (`getPublishUploads.ts`, which sets the
743
+ * `Content-Type` tag) and the download worker (`browser/workers/filesDownload.ts`, which picks the
744
+ * extension of a file saved under `files/`). The worker runs from a string and cannot import this
745
+ * module, so {@link MIME_TO_EXTENSION} is injected into its source as JSON.
746
+ */
747
+ /**
748
+ * Lowercase extension (no dot) to MIME type. Includes the odd extensions the SDK itself produces:
749
+ * `saveImage` names a file `<id>.<subtype>` (e.g. `.svg+xml`, `.x-icon`), so subtypes appear here too.
750
+ */
751
+ const EXTENSION_TO_MIME = {
752
+ // images
753
+ png: 'image/png',
754
+ apng: 'image/apng',
755
+ jpg: 'image/jpeg',
756
+ jpeg: 'image/jpeg',
757
+ jfif: 'image/jpeg',
758
+ gif: 'image/gif',
759
+ webp: 'image/webp',
760
+ avif: 'image/avif',
761
+ heic: 'image/heic',
762
+ heif: 'image/heif',
763
+ svg: 'image/svg+xml',
764
+ 'svg+xml': 'image/svg+xml',
765
+ bmp: 'image/bmp',
766
+ ico: 'image/x-icon',
767
+ 'x-icon': 'image/x-icon',
768
+ 'vnd.microsoft.icon': 'image/x-icon',
769
+ tif: 'image/tiff',
770
+ tiff: 'image/tiff',
771
+ // text / documents
772
+ html: 'text/html',
773
+ htm: 'text/html',
774
+ xhtml: 'application/xhtml+xml',
775
+ json: 'application/json',
776
+ txt: 'text/plain',
777
+ plain: 'text/plain',
778
+ md: 'text/markdown',
779
+ markdown: 'text/markdown',
780
+ csv: 'text/csv',
781
+ tsv: 'text/tab-separated-values',
782
+ xml: 'application/xml',
783
+ css: 'text/css',
784
+ js: 'text/javascript',
785
+ mjs: 'text/javascript',
786
+ pdf: 'application/pdf',
787
+ rtf: 'application/rtf',
788
+ epub: 'application/epub+zip',
789
+ zip: 'application/zip',
790
+ gz: 'application/gzip',
791
+ doc: 'application/msword',
792
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
793
+ xls: 'application/vnd.ms-excel',
794
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
795
+ ppt: 'application/vnd.ms-powerpoint',
796
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
797
+ odt: 'application/vnd.oasis.opendocument.text',
798
+ // audio
799
+ mp3: 'audio/mpeg',
800
+ mpeg: 'audio/mpeg',
801
+ wav: 'audio/wav',
802
+ ogg: 'audio/ogg',
803
+ oga: 'audio/ogg',
804
+ opus: 'audio/opus',
805
+ m4a: 'audio/mp4',
806
+ aac: 'audio/aac',
807
+ flac: 'audio/flac',
808
+ weba: 'audio/webm',
809
+ // video
810
+ mp4: 'video/mp4',
811
+ m4v: 'video/mp4',
812
+ webm: 'video/webm',
813
+ mov: 'video/quicktime',
814
+ quicktime: 'video/quicktime',
815
+ ogv: 'video/ogg',
816
+ mkv: 'video/x-matroska',
817
+ avi: 'video/x-msvideo',
818
+ // fonts
819
+ woff: 'font/woff',
820
+ woff2: 'font/woff2',
821
+ ttf: 'font/ttf',
822
+ otf: 'font/otf',
823
+ };
824
+ /**
825
+ * MIME type to the extension a downloaded file gets. One canonical extension per type (the
826
+ * first one listed here wins over {@link EXTENSION_TO_MIME}'s aliases), plus a few common
827
+ * non-canonical spellings a gateway may send.
828
+ */
829
+ const MIME_TO_EXTENSION = (() => {
830
+ const preferred = {
831
+ 'image/jpeg': 'jpg',
832
+ 'image/svg+xml': 'svg',
833
+ 'image/x-icon': 'ico',
834
+ 'image/tiff': 'tiff',
835
+ 'text/html': 'html',
836
+ 'text/plain': 'txt',
837
+ 'text/markdown': 'md',
838
+ 'text/javascript': 'js',
839
+ 'audio/mpeg': 'mp3',
840
+ 'audio/ogg': 'ogg',
841
+ 'audio/mp4': 'm4a',
842
+ 'video/mp4': 'mp4',
843
+ 'video/quicktime': 'mov',
844
+ 'video/ogg': 'ogv',
845
+ };
846
+ const out = { ...preferred };
847
+ for (const [ext, mime] of Object.entries(EXTENSION_TO_MIME)) {
848
+ if (!(mime in out))
849
+ out[mime] = ext;
850
+ }
851
+ // Non-canonical names some servers use for the same formats.
852
+ Object.assign(out, {
853
+ 'audio/mp3': 'mp3',
854
+ 'audio/x-wav': 'wav',
855
+ 'audio/wave': 'wav',
856
+ 'audio/vnd.wave': 'wav',
857
+ 'audio/x-m4a': 'm4a',
858
+ 'audio/x-flac': 'flac',
859
+ 'application/x-zip-compressed': 'zip',
860
+ 'image/vnd.microsoft.icon': 'ico',
861
+ });
862
+ return out;
863
+ })();
864
+ /**
865
+ * Content types that say nothing about the content. A Blob carrying one of these is treated as
866
+ * untyped.
867
+ */
868
+ const GENERIC_MIME_TYPES = [
869
+ '',
870
+ 'application/octet-stream',
871
+ 'binary/octet-stream',
872
+ ];
873
+ /** Lowercase `type/subtype` without parameters; `undefined` when empty or generic. */
874
+ const normalizeMimeType = (mimeType) => {
875
+ const t = (mimeType ?? '').split(';')[0].trim().toLowerCase();
876
+ if (!t || GENERIC_MIME_TYPES.includes(t) || !t.includes('/'))
877
+ return undefined;
878
+ return t;
879
+ };
880
+ /** MIME type for a path or file name by its extension, if it has a known one. */
881
+ const getMimeTypeFromPath = (filePathOrName) => {
882
+ const name = filePathOrName.split('/').pop() ?? '';
883
+ const dot = name.lastIndexOf('.');
884
+ if (dot <= 0 || dot === name.length - 1)
885
+ return undefined;
886
+ return EXTENSION_TO_MIME[name.slice(dot + 1).toLowerCase()];
887
+ };
888
+ /** Extension (no dot) for a MIME type, if it has a well-known one. */
889
+ const getExtensionForMimeType = (mimeType) => {
890
+ const t = normalizeMimeType(mimeType);
891
+ return t ? MIME_TO_EXTENSION[t] : undefined;
892
+ };
893
+ /**
894
+ * MIME type from the leading bytes, for formats with an unambiguous signature. Used when a local
895
+ * file has no (known) extension, e.g. a file seed named after its id. Self-contained: the download
896
+ * and resize workers get it as source text.
897
+ */
898
+ const sniffMimeType = (bytes) => {
899
+ const at = (offset, ...sig) => bytes.length >= offset + sig.length && sig.every((b, i) => bytes[offset + i] === b);
900
+ const ascii = (offset, s) => at(offset, ...Array.from(s).map((c) => c.charCodeAt(0)));
901
+ if (at(0, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a))
902
+ return 'image/png';
903
+ if (at(0, 0xff, 0xd8, 0xff))
904
+ return 'image/jpeg';
905
+ if (ascii(0, 'GIF87a') || ascii(0, 'GIF89a'))
906
+ return 'image/gif';
907
+ if (ascii(0, 'RIFF') && ascii(8, 'WEBP'))
908
+ return 'image/webp';
909
+ if (ascii(0, 'RIFF') && ascii(8, 'WAVE'))
910
+ return 'audio/wav';
911
+ if (ascii(0, 'RIFF') && ascii(8, 'AVI '))
912
+ return 'video/x-msvideo';
913
+ if (ascii(0, 'BM') && bytes.length > 14)
914
+ return 'image/bmp';
915
+ if (at(0, 0x49, 0x49, 0x2a, 0x00) || at(0, 0x4d, 0x4d, 0x00, 0x2a))
916
+ return 'image/tiff';
917
+ if (ascii(0, '%PDF-'))
918
+ return 'application/pdf';
919
+ if (ascii(0, 'OggS'))
920
+ return 'audio/ogg';
921
+ if (ascii(0, 'fLaC'))
922
+ return 'audio/flac';
923
+ if (ascii(0, 'ID3'))
924
+ return 'audio/mpeg';
925
+ if (at(0, 0x1a, 0x45, 0xdf, 0xa3))
926
+ return 'video/webm';
927
+ if (ascii(4, 'ftyp')) {
928
+ const brand = String.fromCharCode(...Array.from(bytes.slice(8, 12)));
929
+ if (brand === 'avif' || brand === 'avis')
930
+ return 'image/avif';
931
+ if (['heic', 'heix', 'heif', 'mif1', 'msf1'].includes(brand))
932
+ return 'image/heic';
933
+ if (brand === 'qt ')
934
+ return 'video/quicktime';
935
+ if (brand === 'M4A ')
936
+ return 'audio/mp4';
937
+ return 'video/mp4';
938
+ }
939
+ if (at(0, 0x00, 0x00, 0x01, 0x00))
940
+ return 'image/x-icon';
941
+ return undefined;
942
+ };
943
+ /**
944
+ * Best MIME type for upload bytes: the source's own type (a Blob/File `type`) when it is specific,
945
+ * then the file name's extension, then the bytes' signature.
946
+ */
947
+ const resolveUploadMimeType = (params) => normalizeMimeType(params.sourceType) ??
948
+ (params.fileName ? getMimeTypeFromPath(params.fileName) : undefined) ??
949
+ (params.bytes ? sniffMimeType(params.bytes) : undefined);
950
+
951
+ class SeedEventEmitter {
952
+ constructor() {
953
+ this.listeners = new Map();
954
+ }
955
+ on(event, listener) {
956
+ const set = this.listeners.get(event) ?? new Set();
957
+ set.add(listener);
958
+ this.listeners.set(event, set);
959
+ return this;
960
+ }
961
+ addListener(event, listener) {
962
+ return this.on(event, listener);
963
+ }
964
+ once(event, listener) {
965
+ const wrapped = (...args) => {
966
+ this.off(event, wrapped);
967
+ listener(...args);
968
+ };
969
+ return this.on(event, wrapped);
970
+ }
971
+ off(event, listener) {
972
+ const set = this.listeners.get(event);
973
+ if (!set)
974
+ return this;
975
+ set.delete(listener);
976
+ if (set.size === 0)
977
+ this.listeners.delete(event);
978
+ return this;
979
+ }
980
+ removeListener(event, listener) {
981
+ return this.off(event, listener);
982
+ }
983
+ removeAllListeners(event) {
984
+ if (typeof event === 'undefined') {
985
+ this.listeners.clear();
986
+ return this;
987
+ }
988
+ this.listeners.delete(event);
989
+ return this;
990
+ }
991
+ /**
992
+ * Calls every listener. A listener that throws is logged and skipped: the others still
993
+ * run, and the error never reaches the SDK code that emitted the event (which may be
994
+ * midway through a save).
995
+ */
996
+ emit(event, ...args) {
997
+ const set = this.listeners.get(event);
998
+ if (!set || set.size === 0)
999
+ return false;
1000
+ for (const listener of [...set]) {
1001
+ try {
1002
+ listener(...args);
1003
+ }
1004
+ catch (error) {
1005
+ console.error(`[seed] listener for "${String(event)}" threw:`, error);
1006
+ }
1007
+ }
1008
+ return true;
1009
+ }
1010
+ }
1011
+ const eventEmitter = new SeedEventEmitter();
1012
+
1013
+ const logger$1 = debug('seedSdk:browser:db:changeFeed');
1014
+ /** Log rows older than this are pruned by the leader tab. */
1015
+ const ENTITY_CHANGES_RETENTION_MS = 10 * 60000;
1016
+ /** The leader prunes at most this often. */
1017
+ const ENTITY_CHANGES_PRUNE_EVERY_MS = 60000;
1018
+ const byLocalId = new Map();
1019
+ const byUid = new Map();
1020
+ const registrations = new Set();
1021
+ let feed;
1022
+ let cursor = 0;
1023
+ let reading;
1024
+ let readAgain = false;
1025
+ let lastPrunedAt = 0;
1026
+ const addTo = (map, key, registration) => {
1027
+ let set = map.get(key);
1028
+ if (!set)
1029
+ map.set(key, (set = new Set()));
1030
+ set.add(registration);
1031
+ };
1032
+ const removeFrom = (map, key, registration) => {
1033
+ const set = map.get(key);
1034
+ if (!set)
1035
+ return;
1036
+ set.delete(registration);
1037
+ if (set.size === 0)
1038
+ map.delete(key);
1039
+ };
1040
+ /**
1041
+ * Calls `listener` after a committed write to one of `watch.tables` for the watched seed, matched
1042
+ * by local id or (case-insensitively) by uid. A read of the log that has several matching rows
1043
+ * calls it once. When the tab missed rows (its cursor is older than the oldest kept row), every
1044
+ * listener is called once. Returns the unregister function.
1045
+ */
1046
+ function watchEntityChanges(watch, listener) {
1047
+ const registration = {
1048
+ watch,
1049
+ tables: new Set(watch.tables),
1050
+ propertyNames: watch.propertyNames ? new Set(watch.propertyNames) : undefined,
1051
+ listener,
1052
+ };
1053
+ const localId = watch.seedLocalId || undefined;
1054
+ const uid = watch.seedUid ? watch.seedUid.toLowerCase() : undefined;
1055
+ registrations.add(registration);
1056
+ if (localId)
1057
+ addTo(byLocalId, localId, registration);
1058
+ if (uid)
1059
+ addTo(byUid, uid, registration);
1060
+ return () => {
1061
+ registrations.delete(registration);
1062
+ if (localId)
1063
+ removeFrom(byLocalId, localId, registration);
1064
+ if (uid)
1065
+ removeFrom(byUid, uid, registration);
1066
+ };
1067
+ }
1068
+ /**
1069
+ * Watches `watch` and runs `read` once per dispatch, handing its result to `onRows` when it differs
1070
+ * from the last one handed over. A read that finishes after a newer one started is dropped, so an
1071
+ * older result never replaces a newer one. Pass `immediate` to also run it once right away.
1072
+ */
1073
+ function watchEntityRows(watch, read, onRows, options = {}) {
1074
+ let latest = 0;
1075
+ let closed = false;
1076
+ let lastJson;
1077
+ const run = () => {
1078
+ const token = ++latest;
1079
+ read().then((rows) => {
1080
+ if (closed || token !== latest)
1081
+ return;
1082
+ // A write that left these rows as they were (this entity's own write, read back) doesn't
1083
+ // reach the handler again.
1084
+ const json = JSON.stringify(rows);
1085
+ if (json === lastJson)
1086
+ return;
1087
+ lastJson = json;
1088
+ onRows(rows);
1089
+ }, (error) => {
1090
+ if (closed || token !== latest)
1091
+ return;
1092
+ if (options.onError)
1093
+ options.onError(error);
1094
+ else
1095
+ logger$1('entity read failed', error);
1096
+ });
1097
+ };
1098
+ const unregister = watchEntityChanges(watch, run);
1099
+ if (options.immediate)
1100
+ run();
1101
+ return {
1102
+ unsubscribe: () => {
1103
+ closed = true;
1104
+ unregister();
1105
+ },
1106
+ };
1107
+ }
1108
+ /** Whether this tab's feed runs (a browser database is open); Node has none. */
1109
+ function isChangeFeedRunning() {
1110
+ return feed !== undefined;
1111
+ }
1112
+ const callAll = (targets) => {
1113
+ for (const registration of targets) {
1114
+ try {
1115
+ registration.listener();
1116
+ }
1117
+ catch (error) {
1118
+ logger$1('listener threw', error);
1119
+ }
1120
+ }
1121
+ };
1122
+ const dispatch = (rows) => {
1123
+ const targets = new Set();
1124
+ for (const row of rows) {
1125
+ const candidates = [
1126
+ ...(row.seedLocalId ? byLocalId.get(row.seedLocalId) ?? [] : []),
1127
+ ...(row.seedUid ? byUid.get(row.seedUid.toLowerCase()) ?? [] : []),
1128
+ ];
1129
+ for (const registration of candidates) {
1130
+ if (!registration.tables.has(row.tbl))
1131
+ continue;
1132
+ if (row.tbl === 'metadata' &&
1133
+ registration.propertyNames &&
1134
+ row.propertyName != null &&
1135
+ !registration.propertyNames.has(row.propertyName)) {
1136
+ continue;
1137
+ }
1138
+ targets.add(registration);
1139
+ }
1140
+ }
1141
+ callAll(targets);
1142
+ };
1143
+ const readNewRows = async (db) => {
1144
+ const rows = await db
1145
+ .select({
1146
+ seq: entityChanges.seq,
1147
+ tbl: entityChanges.tbl,
1148
+ seedLocalId: entityChanges.seedLocalId,
1149
+ seedUid: entityChanges.seedUid,
1150
+ propertyName: entityChanges.propertyName,
1151
+ })
1152
+ .from(entityChanges)
1153
+ .where(gt(entityChanges.seq, cursor))
1154
+ .orderBy(entityChanges.seq);
1155
+ if (feed?.db !== db || rows.length === 0)
1156
+ return;
1157
+ // Writers are serialized, so committed seqs have no holes: a missing first row was pruned.
1158
+ const missed = rows[0].seq > cursor + 1;
1159
+ cursor = rows[rows.length - 1].seq;
1160
+ if (missed) {
1161
+ logger$1(`cursor fell behind the log; notifying all ${registrations.size} watches`);
1162
+ callAll([...registrations]);
1163
+ }
1164
+ else {
1165
+ dispatch(rows);
1166
+ }
1167
+ await maybePrune(db);
1168
+ };
1169
+ const maybePrune = async (db) => {
1170
+ const now = Date.now();
1171
+ if (!isLeaderTab() || now - lastPrunedAt < ENTITY_CHANGES_PRUNE_EVERY_MS)
1172
+ return;
1173
+ lastPrunedAt = now;
1174
+ try {
1175
+ await pruneEntityChanges(db, now);
1176
+ }
1177
+ catch (error) {
1178
+ logger$1('prune failed', error);
1179
+ }
1180
+ };
1181
+ /** Deletes log rows older than ENTITY_CHANGES_RETENTION_MS. Deleting doesn't raise max(seq), so it dispatches nothing. */
1182
+ async function pruneEntityChanges(db, now = Date.now()) {
1183
+ await db.delete(entityChanges).where(lt(entityChanges.at, now - ENTITY_CHANGES_RETENTION_MS));
1184
+ }
1185
+ /** Reads once at a time; an emission during a read makes it read once more. */
1186
+ const scheduleRead = (db) => {
1187
+ if (reading) {
1188
+ readAgain = true;
1189
+ return;
1190
+ }
1191
+ reading = (async () => {
1192
+ try {
1193
+ do {
1194
+ readAgain = false;
1195
+ await readNewRows(db);
1196
+ } while (readAgain && feed?.db === db);
1197
+ }
1198
+ catch (error) {
1199
+ logger$1('read failed', error);
1200
+ }
1201
+ finally {
1202
+ reading = undefined;
1203
+ }
1204
+ })();
1205
+ };
1206
+ /**
1207
+ * Starts this tab's feed on `db` (once its migrations ran), at the newest seq allocated so far:
1208
+ * entities do their own initial read. A second call replaces the first feed.
1209
+ */
1210
+ async function startChangeFeed(db, { liveQuery }) {
1211
+ stopChangeFeed();
1212
+ // sqlite_sequence keeps the last seq even when pruning emptied the table.
1213
+ const [row] = await db.all(sql `SELECT seq FROM sqlite_sequence WHERE name = 'entity_changes'`);
1214
+ cursor = Number(row?.seq ?? 0);
1215
+ const subscription = liveQuery((tag) => tag `SELECT max(seq) AS seq FROM entity_changes`).subscribe({
1216
+ next: ([latest]) => {
1217
+ if (latest?.seq != null && Number(latest.seq) > cursor)
1218
+ scheduleRead(db);
1219
+ },
1220
+ error: (error) => logger$1('feed query failed', error),
1221
+ });
1222
+ feed = { db, unsubscribe: () => subscription.unsubscribe() };
1223
+ logger$1(`started at seq ${cursor}`);
1224
+ }
1225
+ function stopChangeFeed() {
1226
+ feed?.unsubscribe();
1227
+ feed = undefined;
1228
+ readAgain = false;
1229
+ }
1230
+
1231
+ /**
1232
+ * Blob URLs the SDK mints for property values (File and Image display URLs). Only these are revoked
1233
+ * by `ItemProperty`: a blob URL the app passed in as a value stays the app's to revoke.
1234
+ */
1235
+ const minted = new Set();
1236
+ const createSessionObjectUrl = (blob) => {
1237
+ const url = URL.createObjectURL(blob);
1238
+ minted.add(url);
1239
+ return url;
1240
+ };
1241
+ const isSessionObjectUrl = (value) => typeof value === 'string' && minted.has(value);
1242
+ const revokeSessionObjectUrl = (url) => {
1243
+ if (!minted.delete(url))
1244
+ return;
1245
+ URL.revokeObjectURL(url);
1246
+ };
1247
+
1248
+ /** Emitted by the client manager after `app_state.addresses` is written (post-`setAddresses`). */
1249
+ const ADDRESSES_PERSISTED_EVENT = 'addresses.persisted';
1250
+ /** Emitted after hard-deleting local on-chain seed copies for publisher addresses. */
1251
+ const LOCAL_COPIES_REMOVED_EVENT = 'localCopies.removed';
1252
+ function parseAddressesPersistedPayload(value) {
1253
+ if (!value || typeof value !== 'object') {
1254
+ return { owned: [], watched: [] };
1255
+ }
1256
+ const v = value;
1257
+ const owned = Array.isArray(v.owned)
1258
+ ? v.owned.filter((a) => typeof a === 'string')
1259
+ : [];
1260
+ const watched = Array.isArray(v.watched)
1261
+ ? v.watched.filter((a) => typeof a === 'string')
1262
+ : [];
1263
+ return { owned, watched };
1264
+ }
1265
+
1266
+ const logger = debug('seedSdk:helpers:tabEvents');
1267
+ /**
1268
+ * Keeps other tabs current (docs/MULTI_TAB.md). The event bus is per tab, so events that mean "the
1269
+ * shared database or OPFS changed" are also posted to the other tabs, which refresh their caches
1270
+ * and emit the same event locally. Events are posted only where they originate, so a relayed event
1271
+ * is never relayed again.
1272
+ */
1273
+ /** Emitted with the changed paths when files change outside this tab's file-system cache. */
1274
+ const FILES_CHANGED_EVENT = 'fs.files.changed';
1275
+ /** Emits on this tab's event bus and in every other tab of this database. */
1276
+ function emitAcrossTabs(name, payload) {
1277
+ if (payload === undefined) {
1278
+ eventEmitter.emit(name);
1279
+ }
1280
+ else {
1281
+ eventEmitter.emit(name, payload);
1282
+ }
1283
+ postTabMessage({ type: 'event', name, payload });
1284
+ }
1285
+ /**
1286
+ * A file saved through this tab's file system: emits `file-saved` here. Other tabs hear about it
1287
+ * from the file system itself (see queueFilesChangedNotice), once the write has reached OPFS.
1288
+ */
1289
+ function notifyFileSaved(filePath) {
1290
+ eventEmitter.emit('file-saved', filePath);
1291
+ }
1292
+ /**
1293
+ * Files written straight to OPFS by a worker: refreshes this tab's file-system cache, emits
1294
+ * FILES_CHANGED_EVENT, and tells other tabs to do the same.
1295
+ */
1296
+ async function notifyFilesWrittenOutsideCache(paths) {
1297
+ if (paths.length === 0)
1298
+ return;
1299
+ await BaseFileManager.invalidateCachedPaths(paths);
1300
+ eventEmitter.emit(FILES_CHANGED_EVENT, paths);
1301
+ queueFilesChangedNotice(paths);
1302
+ }
1303
+ /**
1304
+ * How long changed paths gather before one `files-changed` message goes out. Counted from the
1305
+ * first queued path, so a steady stream of writes still sends once per window, never later.
1306
+ */
1307
+ const FILES_CHANGED_FLUSH_MS = 25;
1308
+ /** Keeps a message for a huge batch (a bulk download) from being one giant structured clone. */
1309
+ const FILES_CHANGED_MAX_PATHS = 500;
1310
+ let pendingChangedPaths = new Set();
1311
+ let flushTimer;
1312
+ let flushOnPageHide = false;
1313
+ /**
1314
+ * Tells other tabs that these absolute ZenFS paths changed in OPFS, so they drop and re-read their
1315
+ * cached copies (the sync API's in-memory copy in particular). The browser file system calls this
1316
+ * for every file it writes, truncates or removes (`TolerantWebAccessFS.onOpfsChange`, wired in
1317
+ * `BrowserFileManager.initializeFileSystem`), after the change reached OPFS. Paths are coalesced
1318
+ * for FILES_CHANGED_FLUSH_MS, so bulk writes (migrations, schema processing, downloads) send a few
1319
+ * messages rather than one per write.
1320
+ *
1321
+ * Defense in depth only: a tab that misses a message still sees the change on its next async
1322
+ * stat() (see TolerantWebAccessFS.stat). A no-op unless this tab coordinates with others.
1323
+ */
1324
+ function queueFilesChangedNotice(paths) {
1325
+ if (!isPostingTabMessages())
1326
+ return;
1327
+ for (const path of paths)
1328
+ pendingChangedPaths.add(path);
1329
+ if (flushTimer !== undefined || pendingChangedPaths.size === 0)
1330
+ return;
1331
+ flushTimer = setTimeout(flushFilesChangedNotices, FILES_CHANGED_FLUSH_MS);
1332
+ if (!flushOnPageHide && typeof addEventListener === 'function') {
1333
+ flushOnPageHide = true;
1334
+ // A tab closing inside the window still sends what it wrote.
1335
+ addEventListener('pagehide', flushFilesChangedNotices);
1336
+ }
1337
+ }
1338
+ /** Sends the queued paths now (page hide, tests). */
1339
+ function flushFilesChangedNotices() {
1340
+ if (flushTimer !== undefined)
1341
+ clearTimeout(flushTimer);
1342
+ flushTimer = undefined;
1343
+ if (pendingChangedPaths.size === 0)
1344
+ return;
1345
+ const paths = [...pendingChangedPaths];
1346
+ pendingChangedPaths = new Set();
1347
+ for (let i = 0; i < paths.length; i += FILES_CHANGED_MAX_PATHS) {
1348
+ postTabMessage({ type: 'files-changed', paths: paths.slice(i, i + FILES_CHANGED_MAX_PATHS) });
1349
+ }
1350
+ }
1351
+ /** Refreshes this tab's in-memory state for an event another tab emitted, before re-emitting it. */
1352
+ async function catchUpOn(name, payload) {
1353
+ if (name === LOCAL_COPIES_REMOVED_EVENT) {
1354
+ const { removedSeedLocalIds = [], removedSeedUids = [] } = (payload ?? {});
1355
+ const _mod_0 = await import('./Item-Ld1iPicQ.js');
1356
+ const _ns_0 = _mod_0.bX;
1357
+ const { Item } = _ns_0;
1358
+ Item.dropCachedInstancesForSeedIds([...removedSeedLocalIds, ...removedSeedUids]);
1359
+ }
1360
+ }
1361
+ const RELAYED_EVENTS = new Set([
1362
+ EAS_SEED_DATA_SYNCED_TO_DB_EVENT,
1363
+ ADDRESSES_PERSISTED_EVENT,
1364
+ LOCAL_COPIES_REMOVED_EVENT,
1365
+ ]);
1366
+ let listening = false;
1367
+ /** Applies other tabs' events and file changes in this tab. */
1368
+ function listenForOtherTabsEvents() {
1369
+ if (listening)
1370
+ return;
1371
+ listening = true;
1372
+ onTabMessage((message) => {
1373
+ if (message.type === 'event' && RELAYED_EVENTS.has(message.name)) {
1374
+ void catchUpOn(message.name, message.payload)
1375
+ .catch((error) => logger(`catching up on ${message.name} failed`, error))
1376
+ .then(() => {
1377
+ if (message.payload === undefined) {
1378
+ eventEmitter.emit(message.name);
1379
+ }
1380
+ else {
1381
+ eventEmitter.emit(message.name, message.payload);
1382
+ }
1383
+ });
1384
+ }
1385
+ else if (message.type === 'files-changed') {
1386
+ // Only refreshes caches: never writes, so never sends files-changed back.
1387
+ void BaseFileManager.invalidateCachedPaths(message.paths)
1388
+ .catch((error) => logger('refreshing changed files failed', error))
1389
+ .then(() => eventEmitter.emit(FILES_CHANGED_EVENT, message.paths));
1390
+ }
1391
+ });
1392
+ }
1393
+
1394
+ var tabEvents = /*#__PURE__*/Object.freeze({
1395
+ __proto__: null,
1396
+ FILES_CHANGED_EVENT: FILES_CHANGED_EVENT,
1397
+ FILES_CHANGED_FLUSH_MS: FILES_CHANGED_FLUSH_MS,
1398
+ FILES_CHANGED_MAX_PATHS: FILES_CHANGED_MAX_PATHS,
1399
+ emitAcrossTabs: emitAcrossTabs,
1400
+ flushFilesChangedNotices: flushFilesChangedNotices,
1401
+ listenForOtherTabsEvents: listenForOtherTabsEvents,
1402
+ notifyFileSaved: notifyFileSaved,
1403
+ notifyFilesWrittenOutsideCache: notifyFilesWrittenOutsideCache,
1404
+ queueFilesChangedNotice: queueFilesChangedNotice
1405
+ });
1406
+
1407
+ export { stopChangeFeed as $, ADDRESSES_PERSISTED_EVENT as A, isChangeFeedRunning as B, isLeaderTab as C, DEFAULT_TEXT_MAX_LENGTH as D, EAS_SEED_DATA_SYNCED_TO_DB_EVENT as E, FILES_CHANGED_EVENT as F, parseAddressesPersistedPayload as G, watchEntityChanges as H, ImageSize as I, watchEntityRows as J, whenLeaderTab as K, LOCAL_COPIES_REMOVED_EVENT as L, ModelPropertyDataTypes as M, withDbTabLock as N, MIME_TO_EXTENSION as O, Property as P, sniffMimeType as Q, notifyFilesWrittenOutsideCache as R, SEED_PROTOCOL_SCHEMA_NAME as S, THtmlEmbeddedDataUriPolicy as T, createSessionObjectUrl as U, VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA as V, withSeedDbLock as W, countDbStatements as X, startChangeFeed as Y, ZERO_BYTES32 as Z, countLiveQueryRuns as _, TProperty as a, postTabMessage as a0, onTabMessage as a1, listenForOtherTabsEvents as a2, startTabCoordination as a3, tryWithTabLock as a4, notifyFileSaved as a5, CLIENT_NOT_INITIALIZED as a6, stopTabCoordination as a7, PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP as a8, TAB_ID as a9, normalizeMimeType as aa, timingMarks as ab, withTabLock as ac, revokeSessionObjectUrl as ad, timed as ae, isSessionObjectUrl as af, timingNow as ag, timingSince as ah, timingLogger as ai, timedDb as aj, generateId$1 as ak, tabEvents as al, TPropertyConstructor as b, TPropertyDataType as c, TPropertyDefs as d, TStorageType as e, TValidationRules as f, normalizePropertyRecordSchema as g, eventEmitter as h, isDataType as i, INTERNAL_SCHEMA_IDS as j, generateId as k, isInternalSchema as l, INTERNAL_DATA_TYPES as m, normalizeDataType as n, isBrowser as o, emitAcrossTabs as p, getExtensionForMimeType as q, INTERNAL_STORAGE_MODEL_FILE_IDS as r, supportsOpfsFileDownloads as s, resolveUploadMimeType as t, isNode as u, INTERNAL_PROPERTY_NAMES as v, defaultAttestationData as w, convertTxIdToImage as x, SeedModels as y, TabLockTimeoutError as z };
1408
+ //# sourceMappingURL=tabEvents-BnBzIdEi.js.map