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.cjs.js CHANGED
@@ -13693,7 +13693,11 @@ class BigqueryReplicator extends BaseReplicator {
13693
13693
  }
13694
13694
  continue;
13695
13695
  }
13696
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
13696
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
13697
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
13698
+ const attributes = Object.fromEntries(
13699
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
13700
+ );
13697
13701
  for (const tableConfig of tableConfigs) {
13698
13702
  const tableName = tableConfig.table;
13699
13703
  const [okSync, errSync] = await tryFn(async () => {
@@ -14711,7 +14715,11 @@ class MySQLReplicator extends BaseReplicator {
14711
14715
  }
14712
14716
  continue;
14713
14717
  }
14714
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
14718
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
14719
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
14720
+ const attributes = Object.fromEntries(
14721
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
14722
+ );
14715
14723
  for (const tableConfig of tableConfigs) {
14716
14724
  const tableName = tableConfig.table;
14717
14725
  const [okSync, errSync] = await tryFn(async () => {
@@ -15090,7 +15098,11 @@ class PlanetScaleReplicator extends BaseReplicator {
15090
15098
  }
15091
15099
  continue;
15092
15100
  }
15093
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15101
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15102
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
15103
+ const attributes = Object.fromEntries(
15104
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
15105
+ );
15094
15106
  for (const tableConfig of tableConfigs) {
15095
15107
  const tableName = tableConfig.table;
15096
15108
  const [okSync, errSync] = await tryFn(async () => {
@@ -15411,7 +15423,11 @@ class PostgresReplicator extends BaseReplicator {
15411
15423
  }
15412
15424
  continue;
15413
15425
  }
15414
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15426
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
15427
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
15428
+ const attributes = Object.fromEntries(
15429
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
15430
+ );
15415
15431
  for (const tableConfig of tableConfigs) {
15416
15432
  const tableName = tableConfig.table;
15417
15433
  const [okSync, errSync] = await tryFn(async () => {
@@ -16533,6 +16549,32 @@ function generateBase62Mapping(keys) {
16533
16549
  });
16534
16550
  return { mapping, reversedMapping };
16535
16551
  }
16552
+ function generatePluginAttributeHash(pluginName, attributeName) {
16553
+ const input = `${pluginName}:${attributeName}`;
16554
+ const hash = crypto$1.createHash("sha256").update(input).digest();
16555
+ const num = hash.readUInt32BE(0);
16556
+ const base62Hash = encode(num);
16557
+ const paddedHash = base62Hash.padStart(3, "0").substring(0, 3);
16558
+ return "p" + paddedHash.toLowerCase();
16559
+ }
16560
+ function generatePluginMapping(attributes) {
16561
+ const mapping = {};
16562
+ const reversedMapping = {};
16563
+ const usedHashes = /* @__PURE__ */ new Set();
16564
+ for (const { key, pluginName } of attributes) {
16565
+ let hash = generatePluginAttributeHash(pluginName, key);
16566
+ let counter = 1;
16567
+ let finalHash = hash;
16568
+ while (usedHashes.has(finalHash)) {
16569
+ finalHash = `${hash}${counter}`;
16570
+ counter++;
16571
+ }
16572
+ usedHashes.add(finalHash);
16573
+ mapping[key] = finalHash;
16574
+ reversedMapping[finalHash] = key;
16575
+ }
16576
+ return { mapping, reversedMapping };
16577
+ }
16536
16578
  const SchemaActions = {
16537
16579
  trim: (value) => value == null ? value : value.trim(),
16538
16580
  encrypt: async (value, { passphrase }) => {
@@ -16923,11 +16965,14 @@ class Schema {
16923
16965
  constructor(args) {
16924
16966
  const {
16925
16967
  map,
16968
+ pluginMap,
16926
16969
  name,
16927
16970
  attributes,
16928
16971
  passphrase,
16929
16972
  version = 1,
16930
- options = {}
16973
+ options = {},
16974
+ _pluginAttributeMetadata,
16975
+ _pluginAttributes
16931
16976
  } = args;
16932
16977
  this.name = name;
16933
16978
  this.version = version;
@@ -16935,6 +16980,8 @@ class Schema {
16935
16980
  this.passphrase = passphrase ?? "secret";
16936
16981
  this.options = lodashEs.merge({}, this.defaultOptions(), options);
16937
16982
  this.allNestedObjectsOptional = this.options.allNestedObjectsOptional ?? false;
16983
+ this._pluginAttributeMetadata = _pluginAttributeMetadata || {};
16984
+ this._pluginAttributes = _pluginAttributes || {};
16938
16985
  const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
16939
16986
  this.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
16940
16987
  { $$async: true, $$strict: false },
@@ -16949,9 +16996,43 @@ class Schema {
16949
16996
  const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
16950
16997
  const objectKeys = this.extractObjectKeys(this.attributes);
16951
16998
  const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
16952
- const { mapping, reversedMapping } = generateBase62Mapping(allKeys);
16999
+ const userKeys = [];
17000
+ const pluginAttributes = [];
17001
+ for (const key of allKeys) {
17002
+ const attrDef = this.getAttributeDefinition(key);
17003
+ if (typeof attrDef === "object" && attrDef !== null && attrDef.__plugin__) {
17004
+ pluginAttributes.push({ key, pluginName: attrDef.__plugin__ });
17005
+ } else if (typeof attrDef === "string" && this._pluginAttributeMetadata && this._pluginAttributeMetadata[key]) {
17006
+ const pluginName = this._pluginAttributeMetadata[key].__plugin__;
17007
+ pluginAttributes.push({ key, pluginName });
17008
+ } else {
17009
+ userKeys.push(key);
17010
+ }
17011
+ }
17012
+ const { mapping, reversedMapping } = generateBase62Mapping(userKeys);
16953
17013
  this.map = mapping;
16954
17014
  this.reversedMap = reversedMapping;
17015
+ const { mapping: pMapping, reversedMapping: pReversedMapping } = generatePluginMapping(pluginAttributes);
17016
+ this.pluginMap = pMapping;
17017
+ this.reversedPluginMap = pReversedMapping;
17018
+ this._pluginAttributes = {};
17019
+ for (const { key, pluginName } of pluginAttributes) {
17020
+ if (!this._pluginAttributes[pluginName]) {
17021
+ this._pluginAttributes[pluginName] = [];
17022
+ }
17023
+ this._pluginAttributes[pluginName].push(key);
17024
+ }
17025
+ }
17026
+ if (!lodashEs.isEmpty(pluginMap)) {
17027
+ this.pluginMap = pluginMap;
17028
+ this.reversedPluginMap = lodashEs.invert(pluginMap);
17029
+ }
17030
+ if (!this.pluginMap) {
17031
+ this.pluginMap = {};
17032
+ this.reversedPluginMap = {};
17033
+ }
17034
+ if (!this._pluginAttributes) {
17035
+ this._pluginAttributes = {};
16955
17036
  }
16956
17037
  }
16957
17038
  defaultOptions() {
@@ -17180,6 +17261,8 @@ class Schema {
17180
17261
  static import(data) {
17181
17262
  let {
17182
17263
  map,
17264
+ pluginMap,
17265
+ _pluginAttributeMetadata,
17183
17266
  name,
17184
17267
  options,
17185
17268
  version,
@@ -17190,11 +17273,15 @@ class Schema {
17190
17273
  attributes = attrs;
17191
17274
  const schema = new Schema({
17192
17275
  map,
17276
+ pluginMap: pluginMap || {},
17193
17277
  name,
17194
17278
  options,
17195
17279
  version,
17196
17280
  attributes
17197
17281
  });
17282
+ if (_pluginAttributeMetadata) {
17283
+ schema._pluginAttributeMetadata = _pluginAttributeMetadata;
17284
+ }
17198
17285
  return schema;
17199
17286
  }
17200
17287
  /**
@@ -17232,7 +17319,10 @@ class Schema {
17232
17319
  name: this.name,
17233
17320
  options: this.options,
17234
17321
  attributes: this._exportAttributes(this.attributes),
17235
- map: this.map
17322
+ map: this.map,
17323
+ pluginMap: this.pluginMap || {},
17324
+ _pluginAttributeMetadata: this._pluginAttributeMetadata || {},
17325
+ _pluginAttributes: this._pluginAttributes || {}
17236
17326
  };
17237
17327
  return data;
17238
17328
  }
@@ -17285,7 +17375,7 @@ class Schema {
17285
17375
  const flattenedObj = flat.flatten(obj, { safe: true });
17286
17376
  const rest = { "_v": this.version + "" };
17287
17377
  for (const [key, value] of Object.entries(flattenedObj)) {
17288
- const mappedKey = this.map[key] || key;
17378
+ const mappedKey = this.pluginMap[key] || this.map[key] || key;
17289
17379
  const attrDef = this.getAttributeDefinition(key);
17290
17380
  if (typeof value === "number" && typeof attrDef === "string" && attrDef.includes("number")) {
17291
17381
  rest[mappedKey] = encode(value);
@@ -17306,14 +17396,18 @@ class Schema {
17306
17396
  await this.applyHooksActions(rest, "afterMap");
17307
17397
  return rest;
17308
17398
  }
17309
- async unmapper(mappedResourceItem, mapOverride) {
17399
+ async unmapper(mappedResourceItem, mapOverride, pluginMapOverride) {
17310
17400
  let obj = lodashEs.cloneDeep(mappedResourceItem);
17311
17401
  delete obj._v;
17312
17402
  obj = await this.applyHooksActions(obj, "beforeUnmap");
17313
17403
  const reversedMap = mapOverride ? lodashEs.invert(mapOverride) : this.reversedMap;
17404
+ const reversedPluginMap = pluginMapOverride ? lodashEs.invert(pluginMapOverride) : this.reversedPluginMap;
17314
17405
  const rest = {};
17315
17406
  for (const [key, value] of Object.entries(obj)) {
17316
- const originalKey = reversedMap && reversedMap[key] ? reversedMap[key] : key;
17407
+ let originalKey = reversedPluginMap[key] || reversedMap[key] || key;
17408
+ if (!originalKey) {
17409
+ originalKey = key;
17410
+ }
17317
17411
  let parsedValue = value;
17318
17412
  const attrDef = this.getAttributeDefinition(originalKey);
17319
17413
  const hasAfterUnmapHook = this.options.hooks?.afterUnmap?.[originalKey];
@@ -17380,6 +17474,37 @@ class Schema {
17380
17474
  }
17381
17475
  return def;
17382
17476
  }
17477
+ /**
17478
+ * Regenerate plugin attribute mapping
17479
+ * Called when plugin attributes are added or removed
17480
+ * @returns {void}
17481
+ */
17482
+ regeneratePluginMapping() {
17483
+ const flatAttrs = flat.flatten(this.attributes, { safe: true });
17484
+ const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
17485
+ const objectKeys = this.extractObjectKeys(this.attributes);
17486
+ const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
17487
+ const pluginAttributes = [];
17488
+ for (const key of allKeys) {
17489
+ const attrDef = this.getAttributeDefinition(key);
17490
+ if (typeof attrDef === "object" && attrDef !== null && attrDef.__plugin__) {
17491
+ pluginAttributes.push({ key, pluginName: attrDef.__plugin__ });
17492
+ } else if (typeof attrDef === "string" && this._pluginAttributeMetadata && this._pluginAttributeMetadata[key]) {
17493
+ const pluginName = this._pluginAttributeMetadata[key].__plugin__;
17494
+ pluginAttributes.push({ key, pluginName });
17495
+ }
17496
+ }
17497
+ const { mapping, reversedMapping } = generatePluginMapping(pluginAttributes);
17498
+ this.pluginMap = mapping;
17499
+ this.reversedPluginMap = reversedMapping;
17500
+ this._pluginAttributes = {};
17501
+ for (const { key, pluginName } of pluginAttributes) {
17502
+ if (!this._pluginAttributes[pluginName]) {
17503
+ this._pluginAttributes[pluginName] = [];
17504
+ }
17505
+ this._pluginAttributes[pluginName].push(key);
17506
+ }
17507
+ }
17383
17508
  /**
17384
17509
  * Preprocess attributes to convert nested objects into validator-compatible format
17385
17510
  * @param {Object} attributes - Original attributes
@@ -17449,37 +17574,38 @@ class Schema {
17449
17574
  } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
17450
17575
  const hasValidatorType = value.type !== void 0 && key !== "$$type";
17451
17576
  if (hasValidatorType) {
17452
- if (value.type === "ip4") {
17453
- processed[key] = { ...value, type: "string" };
17454
- } else if (value.type === "ip6") {
17455
- processed[key] = { ...value, type: "string" };
17456
- } else if (value.type === "money" || value.type === "crypto") {
17457
- processed[key] = { ...value, type: "number", min: value.min !== void 0 ? value.min : 0 };
17458
- } else if (value.type === "decimal") {
17459
- processed[key] = { ...value, type: "number" };
17460
- } else if (value.type === "geo:lat" || value.type === "geo-lat") {
17577
+ const { __plugin__, __pluginCreated__, ...cleanValue } = value;
17578
+ if (cleanValue.type === "ip4") {
17579
+ processed[key] = { ...cleanValue, type: "string" };
17580
+ } else if (cleanValue.type === "ip6") {
17581
+ processed[key] = { ...cleanValue, type: "string" };
17582
+ } else if (cleanValue.type === "money" || cleanValue.type === "crypto") {
17583
+ processed[key] = { ...cleanValue, type: "number", min: cleanValue.min !== void 0 ? cleanValue.min : 0 };
17584
+ } else if (cleanValue.type === "decimal") {
17585
+ processed[key] = { ...cleanValue, type: "number" };
17586
+ } else if (cleanValue.type === "geo:lat" || cleanValue.type === "geo-lat") {
17461
17587
  processed[key] = {
17462
- ...value,
17588
+ ...cleanValue,
17463
17589
  type: "number",
17464
- min: value.min !== void 0 ? value.min : -90,
17465
- max: value.max !== void 0 ? value.max : 90
17590
+ min: cleanValue.min !== void 0 ? cleanValue.min : -90,
17591
+ max: cleanValue.max !== void 0 ? cleanValue.max : 90
17466
17592
  };
17467
- } else if (value.type === "geo:lon" || value.type === "geo-lon") {
17593
+ } else if (cleanValue.type === "geo:lon" || cleanValue.type === "geo-lon") {
17468
17594
  processed[key] = {
17469
- ...value,
17595
+ ...cleanValue,
17470
17596
  type: "number",
17471
- min: value.min !== void 0 ? value.min : -180,
17472
- max: value.max !== void 0 ? value.max : 180
17597
+ min: cleanValue.min !== void 0 ? cleanValue.min : -180,
17598
+ max: cleanValue.max !== void 0 ? cleanValue.max : 180
17473
17599
  };
17474
- } else if (value.type === "geo:point" || value.type === "geo-point") {
17475
- processed[key] = { ...value, type: "any" };
17476
- } else if (value.type === "object" && value.properties) {
17600
+ } else if (cleanValue.type === "geo:point" || cleanValue.type === "geo-point") {
17601
+ processed[key] = { ...cleanValue, type: "any" };
17602
+ } else if (cleanValue.type === "object" && cleanValue.properties) {
17477
17603
  processed[key] = {
17478
- ...value,
17479
- properties: this.preprocessAttributesForValidation(value.properties)
17604
+ ...cleanValue,
17605
+ properties: this.preprocessAttributesForValidation(cleanValue.properties)
17480
17606
  };
17481
17607
  } else {
17482
- processed[key] = value;
17608
+ processed[key] = cleanValue;
17483
17609
  }
17484
17610
  } else {
17485
17611
  const isExplicitRequired = value.$$type && value.$$type.includes("required");
@@ -17797,7 +17923,11 @@ async function handleInsert$3({ resource, data, mappedData, originalData }) {
17797
17923
  excess: totalSize - 2047,
17798
17924
  data: originalData || data
17799
17925
  });
17800
- return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
17926
+ const metadataOnly = { _v: mappedData._v };
17927
+ if (resource.schema?.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
17928
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
17929
+ }
17930
+ return { mappedData: metadataOnly, body: JSON.stringify(mappedData) };
17801
17931
  }
17802
17932
  return { mappedData, body: "" };
17803
17933
  }
@@ -18000,6 +18130,12 @@ async function handleInsert$1({ resource, data, mappedData, originalData }) {
18000
18130
  metadataFields._v = mappedData._v;
18001
18131
  currentSize += attributeSizes._v;
18002
18132
  }
18133
+ if (resource.schema?.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18134
+ const pluginMapStr = JSON.stringify(resource.schema.pluginMap);
18135
+ const pluginMapSize = calculateUTF8Bytes("_pluginMap") + calculateUTF8Bytes(pluginMapStr);
18136
+ metadataFields._pluginMap = pluginMapStr;
18137
+ currentSize += pluginMapSize;
18138
+ }
18003
18139
  let reservedLimit = effectiveLimit;
18004
18140
  for (const [fieldName, size] of sortedFields) {
18005
18141
  if (fieldName === "_v") continue;
@@ -18059,6 +18195,9 @@ async function handleInsert({ resource, data, mappedData }) {
18059
18195
  "_v": mappedData._v || String(resource.version)
18060
18196
  };
18061
18197
  metadataOnly._map = JSON.stringify(resource.schema.map);
18198
+ if (resource.schema.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18199
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
18200
+ }
18062
18201
  const body = JSON.stringify(mappedData);
18063
18202
  return { mappedData: metadataOnly, body };
18064
18203
  }
@@ -18067,6 +18206,9 @@ async function handleUpdate({ resource, id, data, mappedData }) {
18067
18206
  "_v": mappedData._v || String(resource.version)
18068
18207
  };
18069
18208
  metadataOnly._map = JSON.stringify(resource.schema.map);
18209
+ if (resource.schema.pluginMap && Object.keys(resource.schema.pluginMap).length > 0) {
18210
+ metadataOnly._pluginMap = JSON.stringify(resource.schema.pluginMap);
18211
+ }
18070
18212
  const body = JSON.stringify(mappedData);
18071
18213
  return { mappedData: metadataOnly, body };
18072
18214
  }
@@ -18447,6 +18589,118 @@ ${errorDetails}`,
18447
18589
  this.applyConfiguration();
18448
18590
  return { oldAttributes, newAttributes };
18449
18591
  }
18592
+ /**
18593
+ * Add a plugin-created attribute to the resource schema
18594
+ * This ensures plugin attributes don't interfere with user-defined attributes
18595
+ * by using a separate mapping namespace (p0, p1, p2, ...)
18596
+ *
18597
+ * @param {string} name - Attribute name (e.g., '_hasEmbedding', 'clusterId')
18598
+ * @param {Object|string} definition - Attribute definition
18599
+ * @param {string} pluginName - Name of plugin adding the attribute
18600
+ * @returns {void}
18601
+ *
18602
+ * @example
18603
+ * // VectorPlugin adding tracking field
18604
+ * resource.addPluginAttribute('_hasEmbedding', {
18605
+ * type: 'boolean',
18606
+ * optional: true,
18607
+ * default: false
18608
+ * }, 'VectorPlugin');
18609
+ *
18610
+ * // Shorthand notation
18611
+ * resource.addPluginAttribute('clusterId', 'string|optional', 'VectorPlugin');
18612
+ */
18613
+ addPluginAttribute(name, definition, pluginName) {
18614
+ if (!pluginName) {
18615
+ throw new ResourceError(
18616
+ "Plugin name is required when adding plugin attributes",
18617
+ { resource: this.name, attribute: name }
18618
+ );
18619
+ }
18620
+ const existingDef = this.schema.getAttributeDefinition(name);
18621
+ if (existingDef && (!existingDef.__plugin__ || existingDef.__plugin__ !== pluginName)) {
18622
+ throw new ResourceError(
18623
+ `Attribute '${name}' already exists and is not from plugin '${pluginName}'`,
18624
+ { resource: this.name, attribute: name, plugin: pluginName }
18625
+ );
18626
+ }
18627
+ let defObject = definition;
18628
+ if (typeof definition === "object" && definition !== null) {
18629
+ defObject = { ...definition };
18630
+ }
18631
+ if (typeof defObject === "object" && defObject !== null) {
18632
+ defObject.__plugin__ = pluginName;
18633
+ defObject.__pluginCreated__ = Date.now();
18634
+ }
18635
+ this.schema.attributes[name] = defObject;
18636
+ this.attributes[name] = defObject;
18637
+ if (typeof defObject === "string") {
18638
+ if (!this.schema._pluginAttributeMetadata) {
18639
+ this.schema._pluginAttributeMetadata = {};
18640
+ }
18641
+ this.schema._pluginAttributeMetadata[name] = {
18642
+ __plugin__: pluginName,
18643
+ __pluginCreated__: Date.now()
18644
+ };
18645
+ }
18646
+ this.schema.regeneratePluginMapping();
18647
+ if (this.schema.options.generateAutoHooks) {
18648
+ this.schema.generateAutoHooks();
18649
+ }
18650
+ const processedAttributes = this.schema.preprocessAttributesForValidation(this.schema.attributes);
18651
+ this.schema.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
18652
+ { $$async: true, $$strict: false },
18653
+ processedAttributes
18654
+ ));
18655
+ if (this.database) {
18656
+ this.database.emit("plugin-attribute-added", {
18657
+ resource: this.name,
18658
+ attribute: name,
18659
+ plugin: pluginName,
18660
+ definition: defObject
18661
+ });
18662
+ }
18663
+ }
18664
+ /**
18665
+ * Remove a plugin-created attribute from the resource schema
18666
+ * Called when a plugin is uninstalled or no longer needs the attribute
18667
+ *
18668
+ * @param {string} name - Attribute name to remove
18669
+ * @param {string} [pluginName] - Optional plugin name for safety check
18670
+ * @returns {boolean} True if attribute was removed, false if not found
18671
+ *
18672
+ * @example
18673
+ * resource.removePluginAttribute('_hasEmbedding', 'VectorPlugin');
18674
+ */
18675
+ removePluginAttribute(name, pluginName = null) {
18676
+ const attrDef = this.schema.getAttributeDefinition(name);
18677
+ const metadata = this.schema._pluginAttributeMetadata?.[name];
18678
+ const isPluginAttr = typeof attrDef === "object" && attrDef?.__plugin__ || metadata;
18679
+ if (!attrDef || !isPluginAttr) {
18680
+ return false;
18681
+ }
18682
+ const actualPlugin = attrDef?.__plugin__ || metadata?.__plugin__;
18683
+ if (pluginName && actualPlugin !== pluginName) {
18684
+ throw new ResourceError(
18685
+ `Attribute '${name}' belongs to plugin '${actualPlugin}', not '${pluginName}'`,
18686
+ { resource: this.name, attribute: name, actualPlugin, requestedPlugin: pluginName }
18687
+ );
18688
+ }
18689
+ delete this.schema.attributes[name];
18690
+ delete this.attributes[name];
18691
+ if (this.schema._pluginAttributeMetadata?.[name]) {
18692
+ delete this.schema._pluginAttributeMetadata[name];
18693
+ }
18694
+ this.schema.regeneratePluginMapping();
18695
+ if (this.database) {
18696
+ this.database.emit("plugin-attribute-removed", {
18697
+ resource: this.name,
18698
+ attribute: name,
18699
+ plugin: actualPlugin
18700
+ });
18701
+ }
18702
+ return true;
18703
+ }
18450
18704
  /**
18451
18705
  * Add a hook function for a specific event
18452
18706
  * @param {string} event - Hook event (beforeInsert, afterInsert, etc.)
@@ -20748,8 +21002,9 @@ ${errorDetails}`,
20748
21002
  const filterInternalFields = (obj) => {
20749
21003
  if (!obj || typeof obj !== "object") return obj;
20750
21004
  const filtered2 = {};
21005
+ const pluginAttrNames = this.schema._pluginAttributes ? Object.values(this.schema._pluginAttributes).flat() : [];
20751
21006
  for (const [key, value] of Object.entries(obj)) {
20752
- if (!key.startsWith("_") || key === "_geohash" || key.startsWith("_geohash_zoom")) {
21007
+ if (!key.startsWith("_") || key === "_geohash" || key.startsWith("_geohash_zoom") || pluginAttrNames.includes(key)) {
20753
21008
  filtered2[key] = value;
20754
21009
  }
20755
21010
  }
@@ -20775,7 +21030,16 @@ ${errorDetails}`,
20775
21030
  if (hasOverflow && body) {
20776
21031
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(JSON.parse(body)));
20777
21032
  if (okBody) {
20778
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody));
21033
+ let pluginMapFromMeta = null;
21034
+ if (metadata && metadata._pluginmap) {
21035
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(
21036
+ () => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap)
21037
+ );
21038
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21039
+ }
21040
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(
21041
+ () => this.schema.unmapper(parsedBody, void 0, pluginMapFromMeta)
21042
+ );
20779
21043
  bodyData = okUnmap ? unmappedBody : {};
20780
21044
  }
20781
21045
  }
@@ -20792,11 +21056,16 @@ ${errorDetails}`,
20792
21056
  if (behavior === "body-only") {
20793
21057
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(body ? JSON.parse(body) : {}));
20794
21058
  let mapFromMeta = this.schema.map;
21059
+ let pluginMapFromMeta = null;
20795
21060
  if (metadata && metadata._map) {
20796
21061
  const [okMap, errMap, parsedMap] = await tryFn(() => Promise.resolve(typeof metadata._map === "string" ? JSON.parse(metadata._map) : metadata._map));
20797
21062
  mapFromMeta = okMap ? parsedMap : this.schema.map;
20798
21063
  }
20799
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody, mapFromMeta));
21064
+ if (metadata && metadata._pluginmap) {
21065
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(() => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap));
21066
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21067
+ }
21068
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody, mapFromMeta, pluginMapFromMeta));
20800
21069
  const result2 = okUnmap ? { ...unmappedBody, id } : { id };
20801
21070
  Object.keys(result2).forEach((k) => {
20802
21071
  result2[k] = fixValue(result2[k]);
@@ -20806,7 +21075,16 @@ ${errorDetails}`,
20806
21075
  if (behavior === "user-managed" && body && body.trim() !== "") {
20807
21076
  const [okBody, errBody, parsedBody] = await tryFn(() => Promise.resolve(JSON.parse(body)));
20808
21077
  if (okBody) {
20809
- const [okUnmap, errUnmap, unmappedBody] = await tryFn(() => this.schema.unmapper(parsedBody));
21078
+ let pluginMapFromMeta = null;
21079
+ if (metadata && metadata._pluginmap) {
21080
+ const [okPluginMap, errPluginMap, parsedPluginMap] = await tryFn(
21081
+ () => Promise.resolve(typeof metadata._pluginmap === "string" ? JSON.parse(metadata._pluginmap) : metadata._pluginmap)
21082
+ );
21083
+ pluginMapFromMeta = okPluginMap ? parsedPluginMap : null;
21084
+ }
21085
+ const [okUnmap, errUnmap, unmappedBody] = await tryFn(
21086
+ () => this.schema.unmapper(parsedBody, void 0, pluginMapFromMeta)
21087
+ );
20810
21088
  const bodyData = okUnmap ? unmappedBody : {};
20811
21089
  const merged = { ...bodyData, ...unmappedMetadata, id };
20812
21090
  Object.keys(merged).forEach((k) => {
@@ -21054,7 +21332,7 @@ class Database extends EventEmitter {
21054
21332
  this.id = idGenerator(7);
21055
21333
  this.version = "1";
21056
21334
  this.s3dbVersion = (() => {
21057
- const [ok, err, version] = tryFn(() => true ? "12.2.4" : "latest");
21335
+ const [ok, err, version] = tryFn(() => true ? "12.3.0" : "latest");
21058
21336
  return ok ? version : "latest";
21059
21337
  })();
21060
21338
  this._resourcesMap = {};
@@ -23044,7 +23322,11 @@ class TursoReplicator extends BaseReplicator {
23044
23322
  }
23045
23323
  continue;
23046
23324
  }
23047
- const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
23325
+ const allAttributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
23326
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
23327
+ const attributes = Object.fromEntries(
23328
+ Object.entries(allAttributes).filter(([name]) => !pluginAttrNames.includes(name))
23329
+ );
23048
23330
  for (const tableConfig of tableConfigs) {
23049
23331
  const tableName = tableConfig.table;
23050
23332
  const [okSync, errSync] = await tryFn(async () => {
@@ -36953,11 +37235,11 @@ class VectorPlugin extends Plugin {
36953
37235
  }
36954
37236
  };
36955
37237
  if (!resource.schema.attributes[trackingFieldName]) {
36956
- resource.schema.attributes[trackingFieldName] = {
37238
+ resource.addPluginAttribute(trackingFieldName, {
36957
37239
  type: "boolean",
36958
37240
  optional: true,
36959
37241
  default: false
36960
- };
37242
+ }, "VectorPlugin");
36961
37243
  }
36962
37244
  this.emit("vector:partition-created", {
36963
37245
  resource: resource.name,
@@ -37743,9 +38025,13 @@ async function generateTypes(database, options = {}) {
37743
38025
  }
37744
38026
  const resourceInterfaces = [];
37745
38027
  for (const [name, resource] of Object.entries(database.resources)) {
37746
- const attributes = resource.config?.attributes || resource.attributes || {};
38028
+ const allAttributes = resource.config?.attributes || resource.attributes || {};
37747
38029
  const timestamps = resource.config?.timestamps || false;
37748
- const interfaceDef = generateResourceInterface(name, attributes, timestamps);
38030
+ const pluginAttrNames = resource.schema?._pluginAttributes ? Object.values(resource.schema._pluginAttributes).flat() : [];
38031
+ const userAttributes = Object.fromEntries(
38032
+ Object.entries(allAttributes).filter(([name2]) => !pluginAttrNames.includes(name2))
38033
+ );
38034
+ const interfaceDef = generateResourceInterface(name, userAttributes, timestamps);
37749
38035
  lines.push(interfaceDef);
37750
38036
  resourceInterfaces.push({
37751
38037
  name,