s3db.js 12.2.4 → 12.3.0

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/dist/s3db.es.js CHANGED
@@ -13670,7 +13670,11 @@ class BigqueryReplicator extends BaseReplicator {
13670
13670
  }
13671
13671
  continue;
13672
13672
  }
13673
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
13673
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
13674
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
13675
+ const attributes = Object.fromEntries(
13676
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
13677
+ );
13674
13678
  for (const tableConfig of tableConfigs) {
13675
13679
  const tableName = tableConfig.table;
13676
13680
  const [okSync, errSync] = await tryFn(async () => {
@@ -14688,7 +14692,11 @@ class MySQLReplicator extends BaseReplicator {
14688
14692
  }
14689
14693
  continue;
14690
14694
  }
14691
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
14695
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
14696
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
14697
+ const attributes = Object.fromEntries(
14698
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
14699
+ );
14692
14700
  for (const tableConfig of tableConfigs) {
14693
14701
  const tableName = tableConfig.table;
14694
14702
  const [okSync, errSync] = await tryFn(async () => {
@@ -15067,7 +15075,11 @@ class PlanetScaleReplicator extends BaseReplicator {
15067
15075
  }
15068
15076
  continue;
15069
15077
  }
15070
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15078
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15079
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
15080
+ const attributes = Object.fromEntries(
15081
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
15082
+ );
15071
15083
  for (const tableConfig of tableConfigs) {
15072
15084
  const tableName = tableConfig.table;
15073
15085
  const [okSync, errSync] = await tryFn(async () => {
@@ -15388,7 +15400,11 @@ class PostgresReplicator extends BaseReplicator {
15388
15400
  }
15389
15401
  continue;
15390
15402
  }
15391
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15403
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15404
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
15405
+ const attributes = Object.fromEntries(
15406
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
15407
+ );
15392
15408
  for (const tableConfig of tableConfigs) {
15393
15409
  const tableName = tableConfig.table;
15394
15410
  const [okSync, errSync] = await tryFn(async () => {
@@ -16510,6 +16526,32 @@ function generateBase62Mapping(keys) {
16510
16526
  });
16511
16527
  return { mapping, reversedMapping };
16512
16528
  }
16529
+ function generatePluginAttributeHash(pluginName, attributeName) {
16530
+ const input = `${pluginName}:${attributeName}`;
16531
+ const hash = createHash("sha256").update(input).digest();
16532
+ const num = hash.readUInt32BE(0);
16533
+ const base62Hash = encode(num);
16534
+ const paddedHash = base62Hash.padStart(3, "0").substring(0, 3);
16535
+ return "p" + paddedHash.toLowerCase();
16536
+ }
16537
+ function generatePluginMapping(attributes) {
16538
+ const mapping = {};
16539
+ const reversedMapping = {};
16540
+ const usedHashes = /* @__PURE__ */ new Set();
16541
+ for (const { key, pluginName } of attributes) {
16542
+ let hash = generatePluginAttributeHash(pluginName, key);
16543
+ let counter = 1;
16544
+ let finalHash = hash;
16545
+ while (usedHashes.has(finalHash)) {
16546
+ finalHash = `${hash}${counter}`;
16547
+ counter++;
16548
+ }
16549
+ usedHashes.add(finalHash);
16550
+ mapping[key] = finalHash;
16551
+ reversedMapping[finalHash] = key;
16552
+ }
16553
+ return { mapping, reversedMapping };
16554
+ }
16513
16555
  const SchemaActions = {
16514
16556
  trim: (value) => value == null ? value : value.trim(),
16515
16557
  encrypt: async (value, { passphrase }) => {
@@ -16900,11 +16942,14 @@ class Schema {
16900
16942
  constructor(args) {
16901
16943
  const {
16902
16944
  map,
16945
+ pluginMap,
16903
16946
  name,
16904
16947
  attributes,
16905
16948
  passphrase,
16906
16949
  version = 1,
16907
- options = {}
16950
+ options = {},
16951
+ _pluginAttributeMetadata,
16952
+ _pluginAttributes
16908
16953
  } = args;
16909
16954
  this.name = name;
16910
16955
  this.version = version;
@@ -16912,6 +16957,8 @@ class Schema {
16912
16957
  this.passphrase = passphrase ?? "secret";
16913
16958
  this.options = merge({}, this.defaultOptions(), options);
16914
16959
  this.allNestedObjectsOptional = this.options.allNestedObjectsOptional ?? false;
16960
+ this._pluginAttributeMetadata = _pluginAttributeMetadata || {};
16961
+ this._pluginAttributes = _pluginAttributes || {};
16915
16962
  const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
16916
16963
  this.validator = new ValidatorManager({ autoEncrypt: false }).compile(merge(
16917
16964
  { $$async: true, $$strict: false },
@@ -16926,9 +16973,43 @@ class Schema {
16926
16973
  const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
16927
16974
  const objectKeys = this.extractObjectKeys(this.attributes);
16928
16975
  const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
16929
- const { mapping, reversedMapping } = generateBase62Mapping(allKeys);
16976
+ const userKeys = [];
16977
+ const pluginAttributes = [];
16978
+ for (const key of allKeys) {
16979
+ const attrDef = this.getAttributeDefinition(key);
16980
+ if (typeof attrDef === "object" && attrDef !== null && attrDef.__plugin__) {
16981
+ pluginAttributes.push({ key, pluginName: attrDef.__plugin__ });
16982
+ } else if (typeof attrDef === "string" && this._pluginAttributeMetadata && this._pluginAttributeMetadata[key]) {
16983
+ const pluginName = this._pluginAttributeMetadata[key].__plugin__;
16984
+ pluginAttributes.push({ key, pluginName });
16985
+ } else {
16986
+ userKeys.push(key);
16987
+ }
16988
+ }
16989
+ const { mapping, reversedMapping } = generateBase62Mapping(userKeys);
16930
16990
  this.map = mapping;
16931
16991
  this.reversedMap = reversedMapping;
16992
+ const { mapping: pMapping, reversedMapping: pReversedMapping } = generatePluginMapping(pluginAttributes);
16993
+ this.pluginMap = pMapping;
16994
+ this.reversedPluginMap = pReversedMapping;
16995
+ this._pluginAttributes = {};
16996
+ for (const { key, pluginName } of pluginAttributes) {
16997
+ if (!this._pluginAttributes[pluginName]) {
16998
+ this._pluginAttributes[pluginName] = [];
16999
+ }
17000
+ this._pluginAttributes[pluginName].push(key);
17001
+ }
17002
+ }
17003
+ if (!isEmpty(pluginMap)) {
17004
+ this.pluginMap = pluginMap;
17005
+ this.reversedPluginMap = invert(pluginMap);
17006
+ }
17007
+ if (!this.pluginMap) {
17008
+ this.pluginMap = {};
17009
+ this.reversedPluginMap = {};
17010
+ }
17011
+ if (!this._pluginAttributes) {
17012
+ this._pluginAttributes = {};
16932
17013
  }
16933
17014
  }
16934
17015
  defaultOptions() {
@@ -17157,6 +17238,8 @@ class Schema {
17157
17238
  static import(data) {
17158
17239
  let {
17159
17240
  map,
17241
+ pluginMap,
17242
+ _pluginAttributeMetadata,
17160
17243
  name,
17161
17244
  options,
17162
17245
  version,
@@ -17167,11 +17250,15 @@ class Schema {
17167
17250
  attributes = attrs;
17168
17251
  const schema = new Schema({
17169
17252
  map,
17253
+ pluginMap: pluginMap || {},
17170
17254
  name,
17171
17255
  options,
17172
17256
  version,
17173
17257
  attributes
17174
17258
  });
17259
+ if (_pluginAttributeMetadata) {
17260
+ schema._pluginAttributeMetadata = _pluginAttributeMetadata;
17261
+ }
17175
17262
  return schema;
17176
17263
  }
17177
17264
  /**
@@ -17209,7 +17296,10 @@ class Schema {
17209
17296
  name: this.name,
17210
17297
  options: this.options,
17211
17298
  attributes: this._exportAttributes(this.attributes),
17212
- map: this.map
17299
+ map: this.map,
17300
+ pluginMap: this.pluginMap || {},
17301
+ _pluginAttributeMetadata: this._pluginAttributeMetadata || {},
17302
+ _pluginAttributes: this._pluginAttributes || {}
17213
17303
  };
17214
17304
  return data;
17215
17305
  }
@@ -17262,7 +17352,7 @@ class Schema {
17262
17352
  const flattenedObj = flatten(obj, { safe: true });
17263
17353
  const rest = { "_v": this.version + "" };
17264
17354
  for (const [key, value] of Object.entries(flattenedObj)) {
17265
- const mappedKey = this.map[key] || key;
17355
+ const mappedKey = this.pluginMap[key] || this.map[key] || key;
17266
17356
  const attrDef = this.getAttributeDefinition(key);
17267
17357
  if (typeof value === "number" && typeof attrDef === "string" && attrDef.includes("number")) {
17268
17358
  rest[mappedKey] = encode(value);
@@ -17283,14 +17373,18 @@ class Schema {
17283
17373
  await this.applyHooksActions(rest, "afterMap");
17284
17374
  return rest;
17285
17375
  }
17286
- async unmapper(mappedResourceItem, mapOverride) {
17376
+ async unmapper(mappedResourceItem, mapOverride, pluginMapOverride) {
17287
17377
  let obj = cloneDeep(mappedResourceItem);
17288
17378
  delete obj._v;
17289
17379
  obj = await this.applyHooksActions(obj, "beforeUnmap");
17290
17380
  const reversedMap = mapOverride ? invert(mapOverride) : this.reversedMap;
17381
+ const reversedPluginMap = pluginMapOverride ? invert(pluginMapOverride) : this.reversedPluginMap;
17291
17382
  const rest = {};
17292
17383
  for (const [key, value] of Object.entries(obj)) {
17293
- const originalKey = reversedMap && reversedMap[key] ? reversedMap[key] : key;
17384
+ let originalKey = reversedPluginMap[key] || reversedMap[key] || key;
17385
+ if (!originalKey) {
17386
+ originalKey = key;
17387
+ }
17294
17388
  let parsedValue = value;
17295
17389
  const attrDef = this.getAttributeDefinition(originalKey);
17296
17390
  const hasAfterUnmapHook = this.options.hooks?.afterUnmap?.[originalKey];
@@ -17357,6 +17451,37 @@ class Schema {
17357
17451
  }
17358
17452
  return def;
17359
17453
  }
17454
+ /**
17455
+ * Regenerate plugin attribute mapping
17456
+ * Called when plugin attributes are added or removed
17457
+ * @returns {void}
17458
+ */
17459
+ regeneratePluginMapping() {
17460
+ const flatAttrs = flatten(this.attributes, { safe: true });
17461
+ const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
17462
+ const objectKeys = this.extractObjectKeys(this.attributes);
17463
+ const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
17464
+ const pluginAttributes = [];
17465
+ for (const key of allKeys) {
17466
+ const attrDef = this.getAttributeDefinition(key);
17467
+ if (typeof attrDef === "object" && attrDef !== null && attrDef.__plugin__) {
17468
+ pluginAttributes.push({ key, pluginName: attrDef.__plugin__ });
17469
+ } else if (typeof attrDef === "string" && this._pluginAttributeMetadata && this._pluginAttributeMetadata[key]) {
17470
+ const pluginName = this._pluginAttributeMetadata[key].__plugin__;
17471
+ pluginAttributes.push({ key, pluginName });
17472
+ }
17473
+ }
17474
+ const { mapping, reversedMapping } = generatePluginMapping(pluginAttributes);
17475
+ this.pluginMap = mapping;
17476
+ this.reversedPluginMap = reversedMapping;
17477
+ this._pluginAttributes = {};
17478
+ for (const { key, pluginName } of pluginAttributes) {
17479
+ if (!this._pluginAttributes[pluginName]) {
17480
+ this._pluginAttributes[pluginName] = [];
17481
+ }
17482
+ this._pluginAttributes[pluginName].push(key);
17483
+ }
17484
+ }
17360
17485
  /**
17361
17486
  * Preprocess attributes to convert nested objects into validator-compatible format
17362
17487
  * @param {Object} attributes - Original attributes
@@ -17426,37 +17551,38 @@ class Schema {
17426
17551
  } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
17427
17552
  const hasValidatorType = value.type !== void 0 && key !== "$$type";
17428
17553
  if (hasValidatorType) {
17429
- if (value.type === "ip4") {
17430
- processed[key] = { ...value, type: "string" };
17431
- } else if (value.type === "ip6") {
17432
- processed[key] = { ...value, type: "string" };
17433
- } else if (value.type === "money" || value.type === "crypto") {
17434
- processed[key] = { ...value, type: "number", min: value.min !== void 0 ? value.min : 0 };
17435
- } else if (value.type === "decimal") {
17436
- processed[key] = { ...value, type: "number" };
17437
- } else if (value.type === "geo:lat" || value.type === "geo-lat") {
17554
+ const { __plugin__, __pluginCreated__, ...cleanValue } = value;
17555
+ if (cleanValue.type === "ip4") {
17556
+ processed[key] = { ...cleanValue, type: "string" };
17557
+ } else if (cleanValue.type === "ip6") {
17558
+ processed[key] = { ...cleanValue, type: "string" };
17559
+ } else if (cleanValue.type === "money" || cleanValue.type === "crypto") {
17560
+ processed[key] = { ...cleanValue, type: "number", min: cleanValue.min !== void 0 ? cleanValue.min : 0 };
17561
+ } else if (cleanValue.type === "decimal") {
17562
+ processed[key] = { ...cleanValue, type: "number" };
17563
+ } else if (cleanValue.type === "geo:lat" || cleanValue.type === "geo-lat") {
17438
17564
  processed[key] = {
17439
- ...value,
17565
+ ...cleanValue,
17440
17566
  type: "number",
17441
- min: value.min !== void 0 ? value.min : -90,
17442
- max: value.max !== void 0 ? value.max : 90
17567
+ min: cleanValue.min !== void 0 ? cleanValue.min : -90,
17568
+ max: cleanValue.max !== void 0 ? cleanValue.max : 90
17443
17569
  };
17444
- } else if (value.type === "geo:lon" || value.type === "geo-lon") {
17570
+ } else if (cleanValue.type === "geo:lon" || cleanValue.type === "geo-lon") {
17445
17571
  processed[key] = {
17446
- ...value,
17572
+ ...cleanValue,
17447
17573
  type: "number",
17448
- min: value.min !== void 0 ? value.min : -180,
17449
- max: value.max !== void 0 ? value.max : 180
17574
+ min: cleanValue.min !== void 0 ? cleanValue.min : -180,
17575
+ max: cleanValue.max !== void 0 ? cleanValue.max : 180
17450
17576
  };
17451
- } else if (value.type === "geo:point" || value.type === "geo-point") {
17452
- processed[key] = { ...value, type: "any" };
17453
- } else if (value.type === "object" && value.properties) {
17577
+ } else if (cleanValue.type === "geo:point" || cleanValue.type === "geo-point") {
17578
+ processed[key] = { ...cleanValue, type: "any" };
17579
+ } else if (cleanValue.type === "object" && cleanValue.properties) {
17454
17580
  processed[key] = {
17455
- ...value,
17456
- properties: this.preprocessAttributesForValidation(value.properties)
17581
+ ...cleanValue,
17582
+ properties: this.preprocessAttributesForValidation(cleanValue.properties)
17457
17583
  };
17458
17584
  } else {
17459
- processed[key] = value;
17585
+ processed[key] = cleanValue;
17460
17586
  }
17461
17587
  } else {
17462
17588
  const isExplicitRequired = value.$$type && value.$$type.includes("required");
@@ -17774,7 +17900,11 @@ async function handleInsert$3({ resource, data, mappedData, originalData }) {
17774
17900
  excess: totalSize - 2047,
17775
17901
  data: originalData || data
17776
17902
  });
17777
- return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
17903
+ const metadataOnly = { _v: mappedData._v };
17904
+ if (resource.schema?.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
17905
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
17906
+ }
17907
+ return { mappedData: metadataOnly, body: JSON.stringify(mappedData) };
17778
17908
  }
17779
17909
  return { mappedData, body: "" };
17780
17910
  }
@@ -17977,6 +18107,12 @@ async function handleInsert$1({ resource, data, mappedData, originalData }) {
17977
18107
  metadataFields._v = mappedData._v;
17978
18108
  currentSize += attributeSizes._v;
17979
18109
  }
18110
+ if (resource.schema?.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18111
+ const pluginMapStr = JSON.stringify(resource.schema.pluginMap);
18112
+ const pluginMapSize = calculateUTF8Bytes("_pluginMap") + calculateUTF8Bytes(pluginMapStr);
18113
+ metadataFields._pluginMap = pluginMapStr;
18114
+ currentSize += pluginMapSize;
18115
+ }
17980
18116
  let reservedLimit = effectiveLimit;
17981
18117
  for (const [fieldName, size] of sortedFields) {
17982
18118
  if (fieldName === "_v") continue;
@@ -18036,6 +18172,9 @@ async function handleInsert({ resource, data, mappedData }) {
18036
18172
  "_v": mappedData._v || String(resource.version)
18037
18173
  };
18038
18174
  metadataOnly._map = JSON.stringify(resource.schema.map);
18175
+ if (resource.schema.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18176
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
18177
+ }
18039
18178
  const body = JSON.stringify(mappedData);
18040
18179
  return { mappedData: metadataOnly, body };
18041
18180
  }
@@ -18044,6 +18183,9 @@ async function handleUpdate({ resource, id, data, mappedData }) {
18044
18183
  "_v": mappedData._v || String(resource.version)
18045
18184
  };
18046
18185
  metadataOnly._map = JSON.stringify(resource.schema.map);
18186
+ if (resource.schema.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18187
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
18188
+ }
18047
18189
  const body = JSON.stringify(mappedData);
18048
18190
  return { mappedData: metadataOnly, body };
18049
18191
  }
@@ -18424,6 +18566,118 @@ ${errorDetails}`,
18424
18566
  this.applyConfiguration();
18425
18567
  return { oldAttributes, newAttributes };
18426
18568
  }
18569
+ /**
18570
+ * Add a plugin-created attribute to the resource schema
18571
+ * This ensures plugin attributes don't interfere with user-defined attributes
18572
+ * by using a separate mapping namespace (p0, p1, p2, ...)
18573
+ *
18574
+ * @param {string} name - Attribute name (e.g., '_hasEmbedding', 'clusterId')
18575
+ * @param {Object|string} definition - Attribute definition
18576
+ * @param {string} pluginName - Name of plugin adding the attribute
18577
+ * @returns {void}
18578
+ *
18579
+ * @example
18580
+ * // VectorPlugin adding tracking field
18581
+ * resource.addPluginAttribute('_hasEmbedding', {
18582
+ * type: 'boolean',
18583
+ * optional: true,
18584
+ * default: false
18585
+ * }, 'VectorPlugin');
18586
+ *
18587
+ * // Shorthand notation
18588
+ * resource.addPluginAttribute('clusterId', 'string|optional', 'VectorPlugin');
18589
+ */
18590
+ addPluginAttribute(name, definition, pluginName) {
18591
+ if (!pluginName) {
18592
+ throw new ResourceError(
18593
+ "Plugin name is required when adding plugin attributes",
18594
+ { resource: this.name, attribute: name }
18595
+ );
18596
+ }
18597
+ const existingDef = this.schema.getAttributeDefinition(name);
18598
+ if (existingDef && (!existingDef.__plugin__ || existingDef.__plugin__ !== pluginName)) {
18599
+ throw new ResourceError(
18600
+ `Attribute '${name}' already exists and is not from plugin '${pluginName}'`,
18601
+ { resource: this.name, attribute: name, plugin: pluginName }
18602
+ );
18603
+ }
18604
+ let defObject = definition;
18605
+ if (typeof definition === "object" && definition !== null) {
18606
+ defObject = { ...definition };
18607
+ }
18608
+ if (typeof defObject === "object" && defObject !== null) {
18609
+ defObject.__plugin__ = pluginName;
18610
+ defObject.__pluginCreated__ = Date.now();
18611
+ }
18612
+ this.schema.attributes[name] = defObject;
18613
+ this.attributes[name] = defObject;
18614
+ if (typeof defObject === "string") {
18615
+ if (!this.schema._pluginAttributeMetadata) {
18616
+ this.schema._pluginAttributeMetadata = {};
18617
+ }
18618
+ this.schema._pluginAttributeMetadata[name] = {
18619
+ __plugin__: pluginName,
18620
+ __pluginCreated__: Date.now()
18621
+ };
18622
+ }
18623
+ this.schema.regeneratePluginMapping();
18624
+ if (this.schema.options.generateAutoHooks) {
18625
+ this.schema.generateAutoHooks();
18626
+ }
18627
+ const processedAttributes = this.schema.preprocessAttributesForValidation(this.schema.attributes);
18628
+ this.schema.validator = new ValidatorManager({ autoEncrypt: false }).compile(merge(
18629
+ { $$async: true, $$strict: false },
18630
+ processedAttributes
18631
+ ));
18632
+ if (this.database) {
18633
+ this.database.emit("plugin-attribute-added", {
18634
+ resource: this.name,
18635
+ attribute: name,
18636
+ plugin: pluginName,
18637
+ definition: defObject
18638
+ });
18639
+ }
18640
+ }
18641
+ /**
18642
+ * Remove a plugin-created attribute from the resource schema
18643
+ * Called when a plugin is uninstalled or no longer needs the attribute
18644
+ *
18645
+ * @param {string} name - Attribute name to remove
18646
+ * @param {string} [pluginName] - Optional plugin name for safety check
18647
+ * @returns {boolean} True if attribute was removed, false if not found
18648
+ *
18649
+ * @example
18650
+ * resource.removePluginAttribute('_hasEmbedding', 'VectorPlugin');
18651
+ */
18652
+ removePluginAttribute(name, pluginName = null) {
18653
+ const attrDef = this.schema.getAttributeDefinition(name);
18654
+ const metadata = this.schema._pluginAttributeMetadata?.[name];
18655
+ const isPluginAttr = typeof attrDef === "object" && attrDef?.__plugin__ || metadata;
18656
+ if (!attrDef || !isPluginAttr) {
18657
+ return false;
18658
+ }
18659
+ const actualPlugin = attrDef?.__plugin__ || metadata?.__plugin__;
18660
+ if (pluginName && actualPlugin !== pluginName) {
18661
+ throw new ResourceError(
18662
+ `Attribute '${name}' belongs to plugin '${actualPlugin}', not '${pluginName}'`,
18663
+ { resource: this.name, attribute: name, actualPlugin, requestedPlugin: pluginName }
18664
+ );
18665
+ }
18666
+ delete this.schema.attributes[name];
18667
+ delete this.attributes[name];
18668
+ if (this.schema._pluginAttributeMetadata?.[name]) {
18669
+ delete this.schema._pluginAttributeMetadata[name];
18670
+ }
18671
+ this.schema.regeneratePluginMapping();
18672
+ if (this.database) {
18673
+ this.database.emit("plugin-attribute-removed", {
18674
+ resource: this.name,
18675
+ attribute: name,
18676
+ plugin: actualPlugin
18677
+ });
18678
+ }
18679
+ return true;
18680
+ }
18427
18681
  /**
18428
18682
  * Add a hook function for a specific event
18429
18683
  * @param {string} event - Hook event (beforeInsert, afterInsert, etc.)
@@ -20725,8 +20979,9 @@ ${errorDetails}`,
20725
20979
  const filterInternalFields = (obj) => {
20726
20980
  if (!obj || typeof obj !== "object") return obj;
20727
20981
  const filtered2 = {};
20982
+ const pluginAttrNames = this.schema._pluginAttributes ? Object.values(this.schema._pluginAttributes).flat() : [];
20728
20983
  for (const [key, value] of Object.entries(obj)) {
20729
- if (!key.startsWith("_") || key === "_geohash" || key.startsWith("_geohash_zoom")) {
20984
+ if (!key.startsWith("_") || key === "_geohash" || key.startsWith("_geohash_zoom") || pluginAttrNames.includes(key)) {
20730
20985
  filtered2[key] = value;
20731
20986
  }
20732
20987
  }
@@ -20752,7 +21007,16 @@ ${errorDetails}`,
20752
21007
  if (hasOverflow && body) {
20753
21008
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(JSON.parse(body)));
20754
21009
  if (okBody) {
20755
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody));
21010
+ let pluginMapFromMeta = null;
21011
+ if (metadata && metadata._pluginmap) {
21012
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(
21013
+ () => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap)
21014
+ );
21015
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21016
+ }
21017
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(
21018
+ () => this.schema.unmapper(parsedBody, void 0, pluginMapFromMeta)
21019
+ );
20756
21020
  bodyData = okUnmap ? unmappedBody : {};
20757
21021
  }
20758
21022
  }
@@ -20769,11 +21033,16 @@ ${errorDetails}`,
20769
21033
  if (behavior === "body-only") {
20770
21034
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(body ? JSON.parse(body) : {}));
20771
21035
  let mapFromMeta = this.schema.map;
21036
+ let pluginMapFromMeta = null;
20772
21037
  if (metadata && metadata._map) {
20773
21038
  const [okMap, errMap, parsedMap] = await tryFn(() => Promise.resolve(typeof metadata._map === "string" ? JSON.parse(metadata._map) : metadata._map));
20774
21039
  mapFromMeta = okMap ? parsedMap : this.schema.map;
20775
21040
  }
20776
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody, mapFromMeta));
21041
+ if (metadata && metadata._pluginmap) {
21042
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(() => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap));
21043
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21044
+ }
21045
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody, mapFromMeta, pluginMapFromMeta));
20777
21046
  const result2 = okUnmap ? { ...unmappedBody, id } : { id };
20778
21047
  Object.keys(result2).forEach((k) => {
20779
21048
  result2[k] = fixValue(result2[k]);
@@ -20783,7 +21052,16 @@ ${errorDetails}`,
20783
21052
  if (behavior === "user-managed" && body && body.trim() !== "") {
20784
21053
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(JSON.parse(body)));
20785
21054
  if (okBody) {
20786
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody));
21055
+ let pluginMapFromMeta = null;
21056
+ if (metadata && metadata._pluginmap) {
21057
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(
21058
+ () => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap)
21059
+ );
21060
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21061
+ }
21062
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(
21063
+ () => this.schema.unmapper(parsedBody, void 0, pluginMapFromMeta)
21064
+ );
20787
21065
  const bodyData = okUnmap ? unmappedBody : {};
20788
21066
  const merged = { ...bodyData, ...unmappedMetadata, id };
20789
21067
  Object.keys(merged).forEach((k) => {
@@ -21031,7 +21309,7 @@ class Database extends EventEmitter {
21031
21309
  this.id = idGenerator(7);
21032
21310
  this.version = "1";
21033
21311
  this.s3dbVersion = (() => {
21034
- const [ok, err, version] = tryFn(() => true ? "12.2.4" : "latest");
21312
+ const [ok, err, version] = tryFn(() => true ? "12.3.0" : "latest");
21035
21313
  return ok ? version : "latest";
21036
21314
  })();
21037
21315
  this._resourcesMap = {};
@@ -23021,7 +23299,11 @@ class TursoReplicator extends BaseReplicator {
23021
23299
  }
23022
23300
  continue;
23023
23301
  }
23024
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
23302
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
23303
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
23304
+ const attributes = Object.fromEntries(
23305
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
23306
+ );
23025
23307
  for (const tableConfig of tableConfigs) {
23026
23308
  const tableName = tableConfig.table;
23027
23309
  const [okSync, errSync] = await tryFn(async () => {
@@ -36930,11 +37212,11 @@ class VectorPlugin extends Plugin {
36930
37212
  }
36931
37213
  };
36932
37214
  if (!resource.schema.attributes[trackingFieldName]) {
36933
- resource.schema.attributes[trackingFieldName] = {
37215
+ resource.addPluginAttribute(trackingFieldName, {
36934
37216
  type: "boolean",
36935
37217
  optional: true,
36936
37218
  default: false
36937
- };
37219
+ }, "VectorPlugin");
36938
37220
  }
36939
37221
  this.emit("vector:partition-created", {
36940
37222
  resource: resource.name,
@@ -37720,9 +38002,13 @@ async function generateTypes(database, options = {}) {
37720
38002
  }
37721
38003
  const resourceInterfaces = [];
37722
38004
  for (const [name, resource] of Object.entries(database.resources)) {
37723
- const attributes = resource.config?.attributes || resource.attributes || {};
38005
+ const allAttributes = resource.config?.attributes || resource.attributes || {};
37724
38006
  const timestamps = resource.config?.timestamps || false;
37725
- const interfaceDef = generateResourceInterface(name, attributes, timestamps);
38007
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
38008
+ const userAttributes = Object.fromEntries(
38009
+ Object.entries(allAttributes).filter(([name2]) => !pluginAttrNames.includes(name2))
38010
+ );
38011
+ const interfaceDef = generateResourceInterface(name, userAttributes, timestamps);
37726
38012
  lines.push(interfaceDef);
37727
38013
  resourceInterfaces.push({
37728
38014
  name,