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 +329 -43
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +329 -43
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/behaviors/body-only.js +15 -5
- package/src/behaviors/body-overflow.js +9 -0
- package/src/behaviors/user-managed.js +8 -1
- package/src/concerns/typescript-generator.js +12 -2
- package/src/plugins/api/utils/openapi-generator.js +21 -2
- package/src/plugins/replicators/bigquery-replicator.class.js +9 -1
- package/src/plugins/replicators/mysql-replicator.class.js +9 -1
- package/src/plugins/replicators/planetscale-replicator.class.js +9 -1
- package/src/plugins/replicators/postgres-replicator.class.js +9 -1
- package/src/plugins/replicators/schema-sync.helper.js +19 -0
- package/src/plugins/replicators/turso-replicator.class.js +9 -1
- package/src/plugins/vector.plugin.js +3 -3
- package/src/resource.class.js +203 -4
- package/src/schema.class.js +223 -33
package/dist/s3db.cjs.js
CHANGED
|
@@ -13693,7 +13693,11 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
13693
13693
|
}
|
|
13694
13694
|
continue;
|
|
13695
13695
|
}
|
|
13696
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
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
|
-
...
|
|
17588
|
+
...cleanValue,
|
|
17463
17589
|
type: "number",
|
|
17464
|
-
min:
|
|
17465
|
-
max:
|
|
17590
|
+
min: cleanValue.min !== void 0 ? cleanValue.min : -90,
|
|
17591
|
+
max: cleanValue.max !== void 0 ? cleanValue.max : 90
|
|
17466
17592
|
};
|
|
17467
|
-
} else if (
|
|
17593
|
+
} else if (cleanValue.type === "geo:lon" || cleanValue.type === "geo-lon") {
|
|
17468
17594
|
processed[key] = {
|
|
17469
|
-
...
|
|
17595
|
+
...cleanValue,
|
|
17470
17596
|
type: "number",
|
|
17471
|
-
min:
|
|
17472
|
-
max:
|
|
17597
|
+
min: cleanValue.min !== void 0 ? cleanValue.min : -180,
|
|
17598
|
+
max: cleanValue.max !== void 0 ? cleanValue.max : 180
|
|
17473
17599
|
};
|
|
17474
|
-
} else if (
|
|
17475
|
-
processed[key] = { ...
|
|
17476
|
-
} else if (
|
|
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
|
-
...
|
|
17479
|
-
properties: this.preprocessAttributesForValidation(
|
|
17604
|
+
...cleanValue,
|
|
17605
|
+
properties: this.preprocessAttributesForValidation(cleanValue.properties)
|
|
17480
17606
|
};
|
|
17481
17607
|
} else {
|
|
17482
|
-
processed[key] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
38028
|
+
const allAttributes = resource.config?.attributes || resource.attributes || {};
|
|
37747
38029
|
const timestamps = resource.config?.timestamps || false;
|
|
37748
|
-
const
|
|
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,
|