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.es.js
CHANGED
|
@@ -13670,7 +13670,11 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
13670
13670
|
}
|
|
13671
13671
|
continue;
|
|
13672
13672
|
}
|
|
13673
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
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
|
-
...
|
|
17565
|
+
...cleanValue,
|
|
17440
17566
|
type: "number",
|
|
17441
|
-
min:
|
|
17442
|
-
max:
|
|
17567
|
+
min: cleanValue.min !== void 0 ? cleanValue.min : -90,
|
|
17568
|
+
max: cleanValue.max !== void 0 ? cleanValue.max : 90
|
|
17443
17569
|
};
|
|
17444
|
-
} else if (
|
|
17570
|
+
} else if (cleanValue.type === "geo:lon" || cleanValue.type === "geo-lon") {
|
|
17445
17571
|
processed[key] = {
|
|
17446
|
-
...
|
|
17572
|
+
...cleanValue,
|
|
17447
17573
|
type: "number",
|
|
17448
|
-
min:
|
|
17449
|
-
max:
|
|
17574
|
+
min: cleanValue.min !== void 0 ? cleanValue.min : -180,
|
|
17575
|
+
max: cleanValue.max !== void 0 ? cleanValue.max : 180
|
|
17450
17576
|
};
|
|
17451
|
-
} else if (
|
|
17452
|
-
processed[key] = { ...
|
|
17453
|
-
} else if (
|
|
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
|
-
...
|
|
17456
|
-
properties: this.preprocessAttributesForValidation(
|
|
17581
|
+
...cleanValue,
|
|
17582
|
+
properties: this.preprocessAttributesForValidation(cleanValue.properties)
|
|
17457
17583
|
};
|
|
17458
17584
|
} else {
|
|
17459
|
-
processed[key] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
38005
|
+
const allAttributes = resource.config?.attributes || resource.attributes || {};
|
|
37724
38006
|
const timestamps = resource.config?.timestamps || false;
|
|
37725
|
-
const
|
|
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,
|