s3db.js 11.3.2 → 12.0.1
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/README.md +102 -8
- package/dist/s3db.cjs.js +36945 -15510
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +66 -1
- package/dist/s3db.es.js +36914 -15534
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +35 -15
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +79 -49
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +97 -47
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +544 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +354 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicator.plugin.js +2 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +315 -36
- package/src/s3db.d.ts +66 -1
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
package/src/plugins/index.js
CHANGED
|
@@ -3,16 +3,27 @@ export * from './plugin.obj.js'
|
|
|
3
3
|
export { default as Plugin } from './plugin.class.js'
|
|
4
4
|
|
|
5
5
|
// plugins:
|
|
6
|
+
export * from './api/index.js'
|
|
6
7
|
export * from './audit.plugin.js'
|
|
7
8
|
export * from './backup.plugin.js'
|
|
8
9
|
export * from './cache.plugin.js'
|
|
9
10
|
export * from './costs.plugin.js'
|
|
10
11
|
export * from './eventual-consistency/index.js'
|
|
11
12
|
export * from './fulltext.plugin.js'
|
|
13
|
+
export * from './geo.plugin.js'
|
|
12
14
|
export * from './metrics.plugin.js'
|
|
13
15
|
export * from './queue-consumer.plugin.js'
|
|
16
|
+
export * from './relation.plugin.js'
|
|
14
17
|
export * from './replicator.plugin.js'
|
|
15
18
|
export * from './s3-queue.plugin.js'
|
|
16
19
|
export * from './scheduler.plugin.js'
|
|
17
20
|
export * from './state-machine.plugin.js'
|
|
21
|
+
export * from './tfstate/index.js'
|
|
22
|
+
export * from './ttl.plugin.js'
|
|
18
23
|
export * from './vector.plugin.js'
|
|
24
|
+
|
|
25
|
+
// plugin drivers & utilities:
|
|
26
|
+
export * from './backup/index.js'
|
|
27
|
+
export * from './cache/index.js'
|
|
28
|
+
export * from './replicators/index.js'
|
|
29
|
+
export * from './consumers/index.js'
|
|
@@ -10,9 +10,19 @@ export class MetricsPlugin extends Plugin {
|
|
|
10
10
|
collectUsage: options.collectUsage !== false,
|
|
11
11
|
retentionDays: options.retentionDays || 30,
|
|
12
12
|
flushInterval: options.flushInterval || 60000, // 1 minute
|
|
13
|
+
|
|
14
|
+
// Prometheus configuration
|
|
15
|
+
prometheus: {
|
|
16
|
+
enabled: options.prometheus?.enabled !== false, // Enabled by default
|
|
17
|
+
mode: options.prometheus?.mode || 'auto', // 'auto' | 'integrated' | 'standalone'
|
|
18
|
+
port: options.prometheus?.port || 9090, // Standalone server port
|
|
19
|
+
path: options.prometheus?.path || '/metrics', // Metrics endpoint path
|
|
20
|
+
includeResourceLabels: options.prometheus?.includeResourceLabels !== false
|
|
21
|
+
},
|
|
22
|
+
|
|
13
23
|
...options
|
|
14
24
|
};
|
|
15
|
-
|
|
25
|
+
|
|
16
26
|
this.metrics = {
|
|
17
27
|
operations: {
|
|
18
28
|
insert: { count: 0, totalTime: 0, errors: 0 },
|
|
@@ -27,8 +37,9 @@ export class MetricsPlugin extends Plugin {
|
|
|
27
37
|
performance: [],
|
|
28
38
|
startTime: new Date().toISOString()
|
|
29
39
|
};
|
|
30
|
-
|
|
40
|
+
|
|
31
41
|
this.flushTimer = null;
|
|
42
|
+
this.metricsServer = null; // Standalone HTTP server (if needed)
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
async onInstall() {
|
|
@@ -113,7 +124,8 @@ export class MetricsPlugin extends Plugin {
|
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
async start() {
|
|
116
|
-
//
|
|
127
|
+
// Setup Prometheus metrics exporter
|
|
128
|
+
await this._setupPrometheusExporter();
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
async stop() {
|
|
@@ -122,7 +134,18 @@ export class MetricsPlugin extends Plugin {
|
|
|
122
134
|
clearInterval(this.flushTimer);
|
|
123
135
|
this.flushTimer = null;
|
|
124
136
|
}
|
|
125
|
-
|
|
137
|
+
|
|
138
|
+
// Stop standalone metrics server if running
|
|
139
|
+
if (this.metricsServer) {
|
|
140
|
+
await new Promise((resolve) => {
|
|
141
|
+
this.metricsServer.close(() => {
|
|
142
|
+
console.log('[Metrics Plugin] Standalone metrics server stopped');
|
|
143
|
+
this.metricsServer = null;
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
// Remove database hooks
|
|
127
150
|
this.removeDatabaseHooks();
|
|
128
151
|
}
|
|
@@ -672,6 +695,142 @@ export class MetricsPlugin extends Plugin {
|
|
|
672
695
|
}
|
|
673
696
|
}
|
|
674
697
|
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Get metrics in Prometheus format
|
|
701
|
+
* @returns {Promise<string>} Prometheus metrics text
|
|
702
|
+
*/
|
|
703
|
+
async getPrometheusMetrics() {
|
|
704
|
+
const { formatPrometheusMetrics } = await import('./concerns/prometheus-formatter.js');
|
|
705
|
+
return formatPrometheusMetrics(this);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Setup Prometheus metrics exporter
|
|
710
|
+
* Chooses mode based on configuration and API Plugin availability
|
|
711
|
+
* @private
|
|
712
|
+
*/
|
|
713
|
+
async _setupPrometheusExporter() {
|
|
714
|
+
if (!this.config.prometheus.enabled) {
|
|
715
|
+
return; // Prometheus export disabled
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const mode = this.config.prometheus.mode;
|
|
719
|
+
const apiPlugin = this.database.plugins?.api || this.database.plugins?.ApiPlugin;
|
|
720
|
+
|
|
721
|
+
// AUTO mode: detect API Plugin
|
|
722
|
+
if (mode === 'auto') {
|
|
723
|
+
if (apiPlugin && apiPlugin.server) {
|
|
724
|
+
await this._setupIntegratedMetrics(apiPlugin);
|
|
725
|
+
} else {
|
|
726
|
+
await this._setupStandaloneMetrics();
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// INTEGRATED mode: requires API Plugin
|
|
731
|
+
else if (mode === 'integrated') {
|
|
732
|
+
if (!apiPlugin || !apiPlugin.server) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
'[Metrics Plugin] prometheus.mode=integrated requires API Plugin to be active'
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
await this._setupIntegratedMetrics(apiPlugin);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// STANDALONE mode: always separate server
|
|
741
|
+
else if (mode === 'standalone') {
|
|
742
|
+
await this._setupStandaloneMetrics();
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
else {
|
|
746
|
+
console.warn(
|
|
747
|
+
`[Metrics Plugin] Unknown prometheus.mode="${mode}". Valid modes: auto, integrated, standalone`
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Setup integrated metrics (uses API Plugin's server)
|
|
754
|
+
* @param {ApiPlugin} apiPlugin - API Plugin instance
|
|
755
|
+
* @private
|
|
756
|
+
*/
|
|
757
|
+
async _setupIntegratedMetrics(apiPlugin) {
|
|
758
|
+
const app = apiPlugin.getApp();
|
|
759
|
+
const path = this.config.prometheus.path;
|
|
760
|
+
|
|
761
|
+
if (!app) {
|
|
762
|
+
console.error('[Metrics Plugin] Failed to get Hono app from API Plugin');
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Add /metrics route to Hono app
|
|
767
|
+
app.get(path, async (c) => {
|
|
768
|
+
try {
|
|
769
|
+
const metrics = await this.getPrometheusMetrics();
|
|
770
|
+
return c.text(metrics, 200, {
|
|
771
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8'
|
|
772
|
+
});
|
|
773
|
+
} catch (err) {
|
|
774
|
+
console.error('[Metrics Plugin] Error generating Prometheus metrics:', err);
|
|
775
|
+
return c.text('Internal Server Error', 500);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
const port = apiPlugin.config?.port || 3000;
|
|
780
|
+
console.log(
|
|
781
|
+
`[Metrics Plugin] Prometheus metrics available at http://localhost:${port}${path} (integrated mode)`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Setup standalone metrics server (separate HTTP server)
|
|
787
|
+
* @private
|
|
788
|
+
*/
|
|
789
|
+
async _setupStandaloneMetrics() {
|
|
790
|
+
const { createServer } = await import('http');
|
|
791
|
+
const port = this.config.prometheus.port;
|
|
792
|
+
const path = this.config.prometheus.path;
|
|
793
|
+
|
|
794
|
+
this.metricsServer = createServer(async (req, res) => {
|
|
795
|
+
// CORS headers to allow scraping from anywhere
|
|
796
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
797
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET');
|
|
798
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
799
|
+
|
|
800
|
+
if (req.url === path && req.method === 'GET') {
|
|
801
|
+
try {
|
|
802
|
+
const metrics = await this.getPrometheusMetrics();
|
|
803
|
+
res.writeHead(200, {
|
|
804
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
|
|
805
|
+
'Content-Length': Buffer.byteLength(metrics, 'utf8')
|
|
806
|
+
});
|
|
807
|
+
res.end(metrics);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
console.error('[Metrics Plugin] Error generating Prometheus metrics:', err);
|
|
810
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
811
|
+
res.end('Internal Server Error');
|
|
812
|
+
}
|
|
813
|
+
} else if (req.method === 'OPTIONS') {
|
|
814
|
+
// Handle preflight requests
|
|
815
|
+
res.writeHead(204);
|
|
816
|
+
res.end();
|
|
817
|
+
} else {
|
|
818
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
819
|
+
res.end('Not Found');
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
this.metricsServer.listen(port, '0.0.0.0', () => {
|
|
824
|
+
console.log(
|
|
825
|
+
`[Metrics Plugin] Prometheus metrics available at http://0.0.0.0:${port}${path} (standalone mode)`
|
|
826
|
+
);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Handle server errors
|
|
830
|
+
this.metricsServer.on('error', (err) => {
|
|
831
|
+
console.error('[Metrics Plugin] Standalone metrics server error:', err);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
675
834
|
}
|
|
676
835
|
|
|
677
836
|
export default MetricsPlugin;
|
|
@@ -35,42 +35,21 @@ export class QueueConsumerPlugin extends Plugin {
|
|
|
35
35
|
|
|
36
36
|
for (const driverDef of this.driversConfig) {
|
|
37
37
|
const { driver, config: driverConfig = {}, consumers: consumerDefs = [] } = driverDef;
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const { resources, driver: defDriver, config: nestedConfig, ...directConfig } = driverDef;
|
|
38
|
+
|
|
39
|
+
// Structured format: { driver: 'sqs', config: {...}, consumers: [{ resources: 'users', ... }] }
|
|
40
|
+
for (const consumerDef of consumerDefs) {
|
|
41
|
+
const { resources, ...consumerConfig } = consumerDef;
|
|
43
42
|
const resourceList = Array.isArray(resources) ? resources : [resources];
|
|
44
|
-
|
|
45
|
-
// Flatten config - prioritize nested config if it exists, otherwise use direct config
|
|
46
|
-
const flatConfig = nestedConfig ? { ...directConfig, ...nestedConfig } : directConfig;
|
|
47
|
-
|
|
48
43
|
for (const resource of resourceList) {
|
|
44
|
+
const mergedConfig = { ...driverConfig, ...consumerConfig };
|
|
49
45
|
const consumer = createConsumer(driver, {
|
|
50
|
-
...
|
|
46
|
+
...mergedConfig,
|
|
51
47
|
onMessage: (msg) => this._handleMessage(msg, resource),
|
|
52
48
|
onError: (err, raw) => this._handleError(err, raw, resource)
|
|
53
49
|
});
|
|
54
|
-
|
|
55
50
|
await consumer.start();
|
|
56
51
|
this.consumers.push(consumer);
|
|
57
52
|
}
|
|
58
|
-
} else {
|
|
59
|
-
// New format: { driver: 'sqs', config: {...}, consumers: [{ resources: 'users', ... }] }
|
|
60
|
-
for (const consumerDef of consumerDefs) {
|
|
61
|
-
const { resources, ...consumerConfig } = consumerDef;
|
|
62
|
-
const resourceList = Array.isArray(resources) ? resources : [resources];
|
|
63
|
-
for (const resource of resourceList) {
|
|
64
|
-
const mergedConfig = { ...driverConfig, ...consumerConfig };
|
|
65
|
-
const consumer = createConsumer(driver, {
|
|
66
|
-
...mergedConfig,
|
|
67
|
-
onMessage: (msg) => this._handleMessage(msg, resource),
|
|
68
|
-
onError: (err, raw) => this._handleError(err, raw, resource)
|
|
69
|
-
});
|
|
70
|
-
await consumer.start();
|
|
71
|
-
this.consumers.push(consumer);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
53
|
}
|
|
75
54
|
}
|
|
76
55
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationPlugin Error Classes
|
|
3
|
+
* Custom errors for relation operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base error for all relation operations
|
|
8
|
+
*/
|
|
9
|
+
export class RelationError extends Error {
|
|
10
|
+
constructor(message, context = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'RelationError';
|
|
13
|
+
this.context = context;
|
|
14
|
+
Error.captureStackTrace(this, this.constructor);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when relation configuration is invalid
|
|
20
|
+
*/
|
|
21
|
+
export class RelationConfigError extends RelationError {
|
|
22
|
+
constructor(message, context = {}) {
|
|
23
|
+
super(message, context);
|
|
24
|
+
this.name = 'RelationConfigError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when a relation type is not supported
|
|
30
|
+
*/
|
|
31
|
+
export class UnsupportedRelationTypeError extends RelationError {
|
|
32
|
+
constructor(type, context = {}) {
|
|
33
|
+
super(`Unsupported relation type: ${type}. Supported types: hasOne, hasMany, belongsTo, belongsToMany`, context);
|
|
34
|
+
this.name = 'UnsupportedRelationTypeError';
|
|
35
|
+
this.relationType = type;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Thrown when a related resource is not found
|
|
41
|
+
*/
|
|
42
|
+
export class RelatedResourceNotFoundError extends RelationError {
|
|
43
|
+
constructor(resourceName, context = {}) {
|
|
44
|
+
super(`Related resource "${resourceName}" not found`, context);
|
|
45
|
+
this.name = 'RelatedResourceNotFoundError';
|
|
46
|
+
this.resourceName = resourceName;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Thrown when a junction table is missing for belongsToMany
|
|
52
|
+
*/
|
|
53
|
+
export class JunctionTableNotFoundError extends RelationError {
|
|
54
|
+
constructor(junctionTable, context = {}) {
|
|
55
|
+
super(`Junction table "${junctionTable}" not found for belongsToMany relation`, context);
|
|
56
|
+
this.name = 'JunctionTableNotFoundError';
|
|
57
|
+
this.junctionTable = junctionTable;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when cascade operation fails
|
|
63
|
+
*/
|
|
64
|
+
export class CascadeError extends RelationError {
|
|
65
|
+
constructor(operation, resourceName, recordId, originalError, context = {}) {
|
|
66
|
+
super(
|
|
67
|
+
`Cascade ${operation} failed for resource "${resourceName}" record "${recordId}": ${originalError.message}`,
|
|
68
|
+
context
|
|
69
|
+
);
|
|
70
|
+
this.name = 'CascadeError';
|
|
71
|
+
this.operation = operation;
|
|
72
|
+
this.resourceName = resourceName;
|
|
73
|
+
this.recordId = recordId;
|
|
74
|
+
this.originalError = originalError;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Thrown when foreign key is missing
|
|
80
|
+
*/
|
|
81
|
+
export class MissingForeignKeyError extends RelationError {
|
|
82
|
+
constructor(foreignKey, resourceName, context = {}) {
|
|
83
|
+
super(`Foreign key "${foreignKey}" not found in resource "${resourceName}"`, context);
|
|
84
|
+
this.name = 'MissingForeignKeyError';
|
|
85
|
+
this.foreignKey = foreignKey;
|
|
86
|
+
this.resourceName = resourceName;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Thrown when trying to load relations on non-existent record
|
|
92
|
+
*/
|
|
93
|
+
export class RecordNotFoundError extends RelationError {
|
|
94
|
+
constructor(recordId, resourceName, context = {}) {
|
|
95
|
+
super(`Record "${recordId}" not found in resource "${resourceName}"`, context);
|
|
96
|
+
this.name = 'RecordNotFoundError';
|
|
97
|
+
this.recordId = recordId;
|
|
98
|
+
this.resourceName = resourceName;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Thrown when circular relation is detected
|
|
104
|
+
*/
|
|
105
|
+
export class CircularRelationError extends RelationError {
|
|
106
|
+
constructor(path, context = {}) {
|
|
107
|
+
super(`Circular relation detected in path: ${path.join(' -> ')}`, context);
|
|
108
|
+
this.name = 'CircularRelationError';
|
|
109
|
+
this.relationPath = path;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Thrown when include path is invalid
|
|
115
|
+
*/
|
|
116
|
+
export class InvalidIncludePathError extends RelationError {
|
|
117
|
+
constructor(path, reason, context = {}) {
|
|
118
|
+
super(`Invalid include path "${path}": ${reason}`, context);
|
|
119
|
+
this.name = 'InvalidIncludePathError';
|
|
120
|
+
this.includePath = path;
|
|
121
|
+
this.reason = reason;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Thrown when batch loading fails
|
|
127
|
+
*/
|
|
128
|
+
export class BatchLoadError extends RelationError {
|
|
129
|
+
constructor(relation, batchSize, failedCount, context = {}) {
|
|
130
|
+
super(
|
|
131
|
+
`Batch loading failed for relation "${relation}". Failed ${failedCount} out of ${batchSize} records`,
|
|
132
|
+
context
|
|
133
|
+
);
|
|
134
|
+
this.name = 'BatchLoadError';
|
|
135
|
+
this.relation = relation;
|
|
136
|
+
this.batchSize = batchSize;
|
|
137
|
+
this.failedCount = failedCount;
|
|
138
|
+
}
|
|
139
|
+
}
|