@harperfast/harper-pro 5.0.6 → 5.0.8
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/core/components/EntryHandler.ts +2 -4
- package/core/components/OptionsWatcher.ts +9 -1
- package/core/components/Scope.ts +1 -1
- package/core/components/componentLoader.ts +4 -11
- package/core/components/requestRestart.ts +2 -17
- package/core/package-lock.json +67 -975
- package/core/resources/RecordEncoder.ts +9 -1
- package/core/resources/Table.ts +30 -14
- package/core/resources/databases.ts +1 -0
- package/core/resources/graphql.ts +168 -158
- package/core/resources/indexes/HierarchicalNavigableSmallWorld.ts +3 -14
- package/core/resources/indexes/vector.ts +0 -17
- package/core/resources/loadEnv.ts +16 -20
- package/core/resources/login.ts +3 -4
- package/core/resources/roles.ts +65 -60
- package/core/security/auth.ts +14 -15
- package/core/security/keys.js +1 -1
- package/core/server/DurableSubscriptionsSession.ts +1 -0
- package/core/server/REST.ts +11 -10
- package/core/server/fastifyRoutes.ts +29 -30
- package/core/server/graphqlQuerying.ts +3 -4
- package/core/server/http.ts +1 -175
- package/core/server/mqtt.ts +2 -8
- package/core/server/threads/threadServer.js +2 -30
- package/core/server/throttle.ts +0 -18
- package/core/utility/environment/environmentManager.js +4 -10
- package/core/utility/hdbTerms.ts +0 -1
- package/dist/core/components/EntryHandler.js +2 -4
- package/dist/core/components/EntryHandler.js.map +1 -1
- package/dist/core/components/OptionsWatcher.js +8 -1
- package/dist/core/components/OptionsWatcher.js.map +1 -1
- package/dist/core/components/Scope.js +1 -1
- package/dist/core/components/Scope.js.map +1 -1
- package/dist/core/components/componentLoader.js +3 -11
- package/dist/core/components/componentLoader.js.map +1 -1
- package/dist/core/components/requestRestart.js +1 -12
- package/dist/core/components/requestRestart.js.map +1 -1
- package/dist/core/resources/RecordEncoder.js +10 -1
- package/dist/core/resources/RecordEncoder.js.map +1 -1
- package/dist/core/resources/Table.js +31 -16
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/core/resources/graphql.js +12 -5
- package/dist/core/resources/graphql.js.map +1 -1
- package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js +2 -14
- package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/core/resources/indexes/vector.js +0 -14
- package/dist/core/resources/indexes/vector.js.map +1 -1
- package/dist/core/resources/loadEnv.js +17 -20
- package/dist/core/resources/loadEnv.js.map +1 -1
- package/dist/core/resources/login.js +4 -4
- package/dist/core/resources/login.js.map +1 -1
- package/dist/core/resources/roles.js +68 -64
- package/dist/core/resources/roles.js.map +1 -1
- package/dist/core/security/auth.js +15 -17
- package/dist/core/security/auth.js.map +1 -1
- package/dist/core/security/keys.js +1 -1
- package/dist/core/security/keys.js.map +1 -1
- package/dist/core/server/DurableSubscriptionsSession.js +2 -0
- package/dist/core/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/core/server/REST.js +11 -11
- package/dist/core/server/REST.js.map +1 -1
- package/dist/core/server/fastifyRoutes.js +29 -30
- package/dist/core/server/fastifyRoutes.js.map +1 -1
- package/dist/core/server/graphqlQuerying.js +4 -5
- package/dist/core/server/graphqlQuerying.js.map +1 -1
- package/dist/core/server/http.js +0 -179
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/mqtt.js +3 -5
- package/dist/core/server/mqtt.js.map +1 -1
- package/dist/core/server/threads/threadServer.js +2 -26
- package/dist/core/server/threads/threadServer.js.map +1 -1
- package/dist/core/server/throttle.js +0 -17
- package/dist/core/server/throttle.js.map +1 -1
- package/dist/core/utility/environment/environmentManager.js +4 -9
- package/dist/core/utility/environment/environmentManager.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +0 -1
- package/dist/core/utility/hdbTerms.js.map +1 -1
- package/dist/replication/replicationConnection.js +13 -6
- package/dist/replication/replicationConnection.js.map +1 -1
- package/dist/security/certificate.js +1 -1
- package/dist/security/certificate.js.map +1 -1
- package/npm-shrinkwrap.json +58 -978
- package/package.json +2 -3
- package/replication/replicationConnection.ts +24 -18
- package/security/certificate.ts +1 -1
- package/studio/web/assets/{index-qbLPhOzw.js → index-BftP-yQ8.js} +2 -2
- package/studio/web/assets/{index-qbLPhOzw.js.map → index-BftP-yQ8.js.map} +1 -1
- package/studio/web/index.html +1 -1
|
@@ -470,6 +470,7 @@ export function handleLocalTimeForGets(store, rootStore) {
|
|
|
470
470
|
};
|
|
471
471
|
Txn.prototype.done = function () {
|
|
472
472
|
done.call(this);
|
|
473
|
+
this.openTimer = 0; // reset so idle pool time doesn't accumulate toward the stale-open threshold
|
|
473
474
|
if (this.isDone) {
|
|
474
475
|
for (let i = 0; i < trackedTxns.length; i++) {
|
|
475
476
|
const txn = trackedTxns[i].deref();
|
|
@@ -498,7 +499,14 @@ setInterval(() => {
|
|
|
498
499
|
'Read transaction detected that has been open too long (over 15 minutes), ending transaction',
|
|
499
500
|
txn
|
|
500
501
|
);
|
|
501
|
-
|
|
502
|
+
trackedTxns.splice(i--, 1);
|
|
503
|
+
txn.timerTracked = false;
|
|
504
|
+
txn.openTimer = 0;
|
|
505
|
+
try {
|
|
506
|
+
txn.done();
|
|
507
|
+
} catch (error) {
|
|
508
|
+
harperLogger.warn('Unexpected error force-closing stale LMDB read transaction', error);
|
|
509
|
+
}
|
|
502
510
|
} else
|
|
503
511
|
harperLogger.error(
|
|
504
512
|
'Read transaction detected that has been open too long (over one minute), make sure read transactions are quickly closed',
|
package/core/resources/Table.ts
CHANGED
|
@@ -91,7 +91,6 @@ const NULL_WITH_TIMESTAMP = new Uint8Array(9);
|
|
|
91
91
|
NULL_WITH_TIMESTAMP[8] = 0xc0; // null
|
|
92
92
|
const UNCACHEABLE_TIMESTAMP = Infinity; // we use this when dynamic content is accessed that we can't safely cache, and this prevents earlier timestamps from change the "last" modification
|
|
93
93
|
const RECORD_PRUNING_INTERVAL = 60000; // one minute
|
|
94
|
-
const CACHEABLE_STATUS_CODES = new Set([200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501]);
|
|
95
94
|
envMngr.initSync();
|
|
96
95
|
const LMDB_PREFETCH_WRITES = envMngr.get(CONFIG_PARAMS.STORAGE_PREFETCHWRITES);
|
|
97
96
|
const LOCK_TIMEOUT = 10000;
|
|
@@ -364,6 +363,10 @@ export function makeTable(options) {
|
|
|
364
363
|
// we listen for events by iterating through the async iterator provided by the subscription
|
|
365
364
|
for await (const event of subscription) {
|
|
366
365
|
try {
|
|
366
|
+
if (!event || typeof event !== 'object') {
|
|
367
|
+
logger.error?.('Bad subscription event', event);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
367
370
|
const firstWrite = event.type === 'transaction' ? event.writes[0] : event;
|
|
368
371
|
if (!firstWrite) {
|
|
369
372
|
logger.error?.('Bad subscription event', event);
|
|
@@ -1426,7 +1429,8 @@ export function makeTable(options) {
|
|
|
1426
1429
|
*/
|
|
1427
1430
|
static evict(id, existingRecord, existingVersion) {
|
|
1428
1431
|
let entry;
|
|
1429
|
-
|
|
1432
|
+
const lmdbTransaction = txnForContext({ transaction: new DatabaseTransaction() });
|
|
1433
|
+
let transaction = lmdbTransaction.getReadTxn();
|
|
1430
1434
|
let options = { transaction };
|
|
1431
1435
|
try {
|
|
1432
1436
|
if (hasSourceGet || audit) {
|
|
@@ -1453,7 +1457,13 @@ export function makeTable(options) {
|
|
|
1453
1457
|
return removeEntry(primaryStore, entry ?? primaryStore.getEntry(id), options);
|
|
1454
1458
|
}
|
|
1455
1459
|
} finally {
|
|
1456
|
-
|
|
1460
|
+
if (primaryStore.ifVersion) {
|
|
1461
|
+
// LMDB: committing the wrapper calls doneReadTxn(), removing it from trackedTxns
|
|
1462
|
+
return lmdbTransaction.commit();
|
|
1463
|
+
}
|
|
1464
|
+
// RocksDB: eviction writes went directly into the raw transaction via options;
|
|
1465
|
+
// commit it directly, as DatabaseTransaction.commit() would abort it (no tracked writes)
|
|
1466
|
+
return transaction?.commit();
|
|
1457
1467
|
}
|
|
1458
1468
|
}
|
|
1459
1469
|
/**
|
|
@@ -4039,14 +4049,15 @@ export function makeTable(options) {
|
|
|
4039
4049
|
if (typeof updatedRecord !== 'object') throw new Error('Only objects can be cached and stored in tables');
|
|
4040
4050
|
if (updatedRecord.status > 0 && updatedRecord.headers) {
|
|
4041
4051
|
// if the source has a status code and headers, treat it as a response
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4052
|
+
if (updatedRecord.status >= 300) {
|
|
4053
|
+
if (updatedRecord.status === 304) {
|
|
4054
|
+
// revalidation of our current cached record
|
|
4055
|
+
updatedRecord = existingRecord;
|
|
4056
|
+
version = existingVersion;
|
|
4057
|
+
} else {
|
|
4058
|
+
// if the source has an error status, we need to throw an error
|
|
4059
|
+
throw new ServerError(updatedRecord.body || 'Error from source', updatedRecord.status);
|
|
4060
|
+
} // there are definitely more status codes to handle
|
|
4050
4061
|
} else {
|
|
4051
4062
|
let headers: any;
|
|
4052
4063
|
const sourceHeaders = updatedRecord.headers;
|
|
@@ -4074,11 +4085,16 @@ export function makeTable(options) {
|
|
|
4074
4085
|
if (data !== undefined) {
|
|
4075
4086
|
// we have structured data that we have parsed
|
|
4076
4087
|
delete headers['content-type']; // don't store the content type if we have already parsed it
|
|
4077
|
-
updatedRecord = {
|
|
4088
|
+
updatedRecord = {
|
|
4089
|
+
headers,
|
|
4090
|
+
data,
|
|
4091
|
+
};
|
|
4078
4092
|
} else {
|
|
4079
|
-
updatedRecord = {
|
|
4093
|
+
updatedRecord = {
|
|
4094
|
+
headers,
|
|
4095
|
+
body: createBlob(updatedRecord.body),
|
|
4096
|
+
};
|
|
4080
4097
|
}
|
|
4081
|
-
if (status !== 200) updatedRecord.status = status;
|
|
4082
4098
|
}
|
|
4083
4099
|
}
|
|
4084
4100
|
if (typeof updatedRecord.toJSON === 'function') updatedRecord = updatedRecord.toJSON();
|
|
@@ -184,6 +184,7 @@ export function getDatabases(): Databases {
|
|
|
184
184
|
getConfigPath(CONFIG_PARAMS.STORAGE_PATH) ||
|
|
185
185
|
(databasePath && (existsSync(databasePath) ? databasePath : join(getHdbBasePath(), LEGACY_DATABASES_DIR_NAME)));
|
|
186
186
|
if (!databasePath) return;
|
|
187
|
+
|
|
187
188
|
if (existsSync(databasePath)) {
|
|
188
189
|
// First load all the databases from our main database folder
|
|
189
190
|
// TODO: Load any databases defined with explicit storage paths from the config
|
|
@@ -4,6 +4,7 @@ import { table } from './databases.ts';
|
|
|
4
4
|
import { getWorkerIndex } from '../server/threads/manageThreads.js';
|
|
5
5
|
import { Resources } from './Resources.ts';
|
|
6
6
|
import type { NamedTypeNode, StringValueNode } from 'graphql';
|
|
7
|
+
import { once } from 'node:events';
|
|
7
8
|
|
|
8
9
|
const PRIMITIVE_TYPES = ['ID', 'Int', 'Float', 'Long', 'String', 'Boolean', 'Date', 'Bytes', 'Any', 'BigInt', 'Blob'];
|
|
9
10
|
|
|
@@ -36,184 +37,193 @@ server.knownGraphQLDirectives.push(
|
|
|
36
37
|
* @param resources
|
|
37
38
|
*/
|
|
38
39
|
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
39
|
-
scope.handleEntry(async (entry) => {
|
|
40
|
+
const entryHandler = scope.handleEntry(async (entry) => {
|
|
40
41
|
if (entry.eventType === 'unlink') return;
|
|
42
|
+
if (entry.entryType === 'directory') {
|
|
43
|
+
scope.logger.warn?.('graphqlSchema currently does not handle directories. Specify file patterns only.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
await processGraphQLSchema(entry.contents, entry.urlPath, entry.absolutePath, scope.resources);
|
|
42
48
|
});
|
|
49
|
+
return once(entryHandler, 'initialLoadComplete');
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
async function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
52
|
+
async function handleFile(gqlContent, urlPath, filePath, resources) {
|
|
53
|
+
// lazy load the graphql package so we don't load it for users that don't use graphql
|
|
54
|
+
const { parse, Source, Kind } = await import('graphql');
|
|
55
|
+
const ast = parse(new Source(gqlContent.toString(), filePath));
|
|
56
|
+
const types = new Map();
|
|
57
|
+
const tables = [];
|
|
58
|
+
// we begin by iterating through the definitions in the AST to get the types and convert them
|
|
59
|
+
// to a friendly format for table attributes
|
|
60
|
+
for (const definition of ast.definitions) {
|
|
61
|
+
switch (definition.kind) {
|
|
62
|
+
case Kind.OBJECT_TYPE_DEFINITION:
|
|
63
|
+
const typeName = definition.name.value;
|
|
64
|
+
// use type name as the default table
|
|
65
|
+
const properties = [];
|
|
66
|
+
const typeDef = { table: null, database: null, properties };
|
|
67
|
+
types.set(typeName, typeDef);
|
|
68
|
+
resources.allTypes.set(typeName, typeDef);
|
|
69
|
+
for (const directive of definition.directives) {
|
|
70
|
+
const directiveName = directive.name.value;
|
|
71
|
+
if (directiveName === 'table') {
|
|
72
|
+
for (const arg of directive.arguments) {
|
|
73
|
+
typeDef[arg.name.value] = (arg.value as StringValueNode).value;
|
|
74
|
+
}
|
|
75
|
+
if (typeDef.schema) typeDef.database = typeDef.schema;
|
|
76
|
+
if (!typeDef.table) typeDef.table = typeName;
|
|
77
|
+
if (typeDef.audit) typeDef.audit = typeDef.audit !== 'false';
|
|
78
|
+
typeDef.attributes = typeDef.properties;
|
|
79
|
+
tables.push(typeDef);
|
|
67
80
|
}
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (directive.name.value === 'export') {
|
|
78
|
-
typeDef.export = true;
|
|
79
|
-
for (const arg of directive.arguments) {
|
|
80
|
-
if (typeof typeDef.export !== 'object') typeDef.export = {};
|
|
81
|
-
typeDef.export[arg.name.value] = (arg.value as StringValueNode).value;
|
|
81
|
+
if (directive.name.value === 'sealed') typeDef.sealed = true;
|
|
82
|
+
if (directive.name.value === 'splitSegments') typeDef.splitSegments = true;
|
|
83
|
+
if (directive.name.value === 'replicate') typeDef.replicate = true;
|
|
84
|
+
if (directive.name.value === 'export') {
|
|
85
|
+
typeDef.export = true;
|
|
86
|
+
for (const arg of directive.arguments) {
|
|
87
|
+
if (typeof typeDef.export !== 'object') typeDef.export = {};
|
|
88
|
+
typeDef.export[arg.name.value] = (arg.value as StringValueNode).value;
|
|
89
|
+
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
let hasPrimaryKey = false;
|
|
93
|
+
function getProperty(type) {
|
|
94
|
+
if (type.kind === 'NonNullType') {
|
|
95
|
+
const property = getProperty(type.type);
|
|
96
|
+
property.nullable = false;
|
|
97
|
+
return property;
|
|
98
|
+
}
|
|
99
|
+
if (type.kind === 'ListType') {
|
|
100
|
+
return {
|
|
101
|
+
type: 'array',
|
|
102
|
+
elements: getProperty(type.type),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const typeName = (type as NamedTypeNode).name?.value;
|
|
106
|
+
const property = { type: typeName };
|
|
107
|
+
Object.defineProperty(property, 'location', { value: type.loc.startToken });
|
|
90
108
|
return property;
|
|
91
109
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const property = getProperty(field.type);
|
|
106
|
-
property.name = field.name.value;
|
|
107
|
-
properties.push(property);
|
|
108
|
-
attributesObject[property.name] = undefined; // this is used as a backup scope for computed properties
|
|
109
|
-
for (const directive of field.directives) {
|
|
110
|
-
const directiveName = directive.name.value;
|
|
111
|
-
if (directiveName === 'primaryKey') {
|
|
112
|
-
if (hasPrimaryKey) console.warn('Can not define two attributes as a primary key at', directive.loc);
|
|
113
|
-
else {
|
|
114
|
-
property.isPrimaryKey = true;
|
|
115
|
-
hasPrimaryKey = true;
|
|
116
|
-
}
|
|
117
|
-
} else if (directiveName === 'indexed') {
|
|
118
|
-
const indexedDefinition = {};
|
|
119
|
-
// store indexed arguments for configurable indexes.
|
|
120
|
-
for (const arg of directive.arguments || []) {
|
|
121
|
-
indexedDefinition[arg.name.value] = (arg.value as StringValueNode).value;
|
|
122
|
-
}
|
|
123
|
-
property.indexed = indexedDefinition;
|
|
124
|
-
} else if (directiveName === 'computed') {
|
|
125
|
-
for (const arg of directive.arguments || []) {
|
|
126
|
-
if (arg.name.value === 'from') {
|
|
127
|
-
const computedFromExpression = (arg.value as StringValueNode).value;
|
|
128
|
-
property.computed = {
|
|
129
|
-
from: createComputedFrom(computedFromExpression, arg, attributesObject),
|
|
130
|
-
};
|
|
131
|
-
// if the version is not defined, we use the computed from expression as the version, any changes to the computed from expression will trigger a version change and reindex
|
|
132
|
-
if (property.version == undefined) property.version = computedFromExpression;
|
|
133
|
-
} else if (arg.name.value === 'version') {
|
|
134
|
-
property.version = (arg.value as StringValueNode).value;
|
|
110
|
+
const attributesObject = {};
|
|
111
|
+
for (const field of definition.fields) {
|
|
112
|
+
const property = getProperty(field.type);
|
|
113
|
+
property.name = field.name.value;
|
|
114
|
+
properties.push(property);
|
|
115
|
+
attributesObject[property.name] = undefined; // this is used as a backup scope for computed properties
|
|
116
|
+
for (const directive of field.directives) {
|
|
117
|
+
const directiveName = directive.name.value;
|
|
118
|
+
if (directiveName === 'primaryKey') {
|
|
119
|
+
if (hasPrimaryKey) console.warn('Can not define two attributes as a primary key at', directive.loc);
|
|
120
|
+
else {
|
|
121
|
+
property.isPrimaryKey = true;
|
|
122
|
+
hasPrimaryKey = true;
|
|
135
123
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (arg.name.value === 'role') {
|
|
156
|
-
authorizedRoles.push((arg.value as StringValueNode).value);
|
|
124
|
+
} else if (directiveName === 'indexed') {
|
|
125
|
+
const indexedDefinition = {};
|
|
126
|
+
// store indexed arguments for configurable indexes.
|
|
127
|
+
for (const arg of directive.arguments || []) {
|
|
128
|
+
indexedDefinition[arg.name.value] = (arg.value as StringValueNode).value;
|
|
129
|
+
}
|
|
130
|
+
property.indexed = indexedDefinition;
|
|
131
|
+
} else if (directiveName === 'computed') {
|
|
132
|
+
for (const arg of directive.arguments || []) {
|
|
133
|
+
if (arg.name.value === 'from') {
|
|
134
|
+
const computedFromExpression = (arg.value as StringValueNode).value;
|
|
135
|
+
property.computed = {
|
|
136
|
+
from: createComputedFrom(computedFromExpression, arg, attributesObject),
|
|
137
|
+
};
|
|
138
|
+
// if the version is not defined, we use the computed from expression as the version, any changes to the computed from expression will trigger a version change and reindex
|
|
139
|
+
if (property.version == undefined) property.version = computedFromExpression;
|
|
140
|
+
} else if (arg.name.value === 'version') {
|
|
141
|
+
property.version = (arg.value as StringValueNode).value;
|
|
142
|
+
}
|
|
157
143
|
}
|
|
144
|
+
property.computed = property.computed || true;
|
|
145
|
+
} else if (directiveName === 'relationship') {
|
|
146
|
+
const relationshipDefinition = {};
|
|
147
|
+
for (const arg of directive.arguments) {
|
|
148
|
+
relationshipDefinition[arg.name.value] = (arg.value as StringValueNode).value;
|
|
149
|
+
}
|
|
150
|
+
property.relationship = relationshipDefinition;
|
|
151
|
+
} else if (directiveName === 'createdTime') {
|
|
152
|
+
property.assignCreatedTime = true;
|
|
153
|
+
} else if (directiveName === 'updatedTime') {
|
|
154
|
+
property.assignUpdatedTime = true;
|
|
155
|
+
} else if (directiveName === 'expiresAt') {
|
|
156
|
+
property.expiresAt = true;
|
|
157
|
+
} else if (directiveName === 'enumerable') {
|
|
158
|
+
property.enumerable = true;
|
|
159
|
+
} else if (directiveName === 'allow') {
|
|
160
|
+
const authorizedRoles = (property.authorizedRoles = []);
|
|
161
|
+
for (const arg of directive.arguments) {
|
|
162
|
+
if (arg.name.value === 'role') {
|
|
163
|
+
authorizedRoles.push((arg.value as StringValueNode).value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else if (server.knownGraphQLDirectives.includes(directiveName)) {
|
|
167
|
+
console.warn(`@${directiveName} is an unknown directive, at`, directive.loc);
|
|
158
168
|
}
|
|
159
|
-
} else if (server.knownGraphQLDirectives.includes(directiveName)) {
|
|
160
|
-
console.warn(`@${directiveName} is an unknown directive, at`, directive.loc);
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
typeDef.type = typeName;
|
|
172
|
+
}
|
|
165
173
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
// check the types and if any types reference other types, fill those in.
|
|
175
|
+
function connectPropertyType(property) {
|
|
176
|
+
const targetTypeDef = types.get(property.type);
|
|
177
|
+
if (targetTypeDef) {
|
|
178
|
+
Object.defineProperty(property, 'properties', { value: targetTypeDef.properties });
|
|
179
|
+
Object.defineProperty(property, 'definition', { value: targetTypeDef });
|
|
180
|
+
} else if (property.type === 'array') connectPropertyType(property.elements);
|
|
181
|
+
else if (!PRIMITIVE_TYPES.includes(property.type)) {
|
|
182
|
+
if (getWorkerIndex() === 0)
|
|
183
|
+
console.error(
|
|
184
|
+
`The type ${property.type} is unknown at line ${property.location.line}, column ${property.location.column}, in ${filePath}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
179
187
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const property of typeDef.properties) connectPropertyType(property);
|
|
183
|
-
}
|
|
184
|
-
// any tables that are defined in the schema can now be registered
|
|
185
|
-
for (const typeDef of tables) {
|
|
186
|
-
// with graphql database definitions, this is a declaration that the table should exist and that it
|
|
187
|
-
// should be created if it does not exist
|
|
188
|
-
typeDef.tableClass = table(typeDef);
|
|
189
|
-
if (typeDef.export) {
|
|
190
|
-
// allow empty string to be used to declare a table on the root path
|
|
191
|
-
if (typeDef.export.name === '') resources.set(dirname(urlPath), typeDef.tableClass);
|
|
192
|
-
else
|
|
193
|
-
resources.set(
|
|
194
|
-
dirname(urlPath) + '/' + (typeDef.export.name || typeDef.type),
|
|
195
|
-
typeDef.tableClass,
|
|
196
|
-
typeDef.export
|
|
197
|
-
);
|
|
188
|
+
for (const typeDef of types.values()) {
|
|
189
|
+
for (const property of typeDef.properties) connectPropertyType(property);
|
|
198
190
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
191
|
+
// any tables that are defined in the schema can now be registered
|
|
192
|
+
for (const typeDef of tables) {
|
|
193
|
+
// with graphql database definitions, this is a declaration that the table should exist and that it
|
|
194
|
+
// should be created if it does not exist
|
|
195
|
+
typeDef.tableClass = ensureTable(typeDef);
|
|
196
|
+
if (typeDef.export) {
|
|
197
|
+
// allow empty string to be used to declare a table on the root path
|
|
198
|
+
if (typeDef.export.name === '') resources.set(dirname(urlPath), typeDef.tableClass);
|
|
199
|
+
else
|
|
200
|
+
resources.set(
|
|
201
|
+
dirname(urlPath) + '/' + (typeDef.export.name || typeDef.type),
|
|
202
|
+
typeDef.tableClass,
|
|
203
|
+
typeDef.export
|
|
204
|
+
);
|
|
212
205
|
}
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
}
|
|
207
|
+
function createComputedFrom(computedFrom: string, arg: any, attributes: any) {
|
|
208
|
+
// Create a function from a computed "from" directive. This can look like:
|
|
209
|
+
// @computed(from: "fieldOne + fieldTwo")
|
|
210
|
+
// We use Node's built-in Script class to compile the function and run it in the context of the record object, which allows us to specify the source
|
|
211
|
+
const script = new Script(
|
|
212
|
+
// we use the inner with statement to allow the computed function to access the record object's properties directly as top level names
|
|
213
|
+
// we use the outer with statement with attributes as a fallback so any access to an attribute that isn't defined on the record still returns undefined (instead of a ReferenceError)
|
|
214
|
+
`function computed(attributes) { return function(record) { with(attributes) { with (record) { return ${computedFrom}; } } } } computed;`,
|
|
215
|
+
{
|
|
216
|
+
filename: filePath, // specify the file path and line position for better error messages/debugging
|
|
217
|
+
lineOffset: arg.loc.startToken.line - 1,
|
|
218
|
+
columnOffset: arg.loc.startToken.column,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
return script.runInThisContext()(attributes); // run the script in the context of the current context/global and return the function we defined
|
|
222
|
+
}
|
|
215
223
|
}
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
export const startOnMainThread = start;
|
|
218
227
|
// useful for testing
|
|
219
|
-
export const loadGQLSchema = (content) =>
|
|
228
|
+
export const loadGQLSchema = (content) =>
|
|
229
|
+
start({ ensureTable: table }).handleFile(content, null, null, new Resources());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cosineDistance, euclideanDistance
|
|
1
|
+
import { cosineDistance, euclideanDistance } from './vector.ts';
|
|
2
2
|
import { FLOAT32_OPTIONS } from 'msgpackr';
|
|
3
3
|
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
4
4
|
import { ClientError } from '../../utility/errors/hdbError.js';
|
|
@@ -52,12 +52,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
52
52
|
// (we would actually like to use float16 if it were available)
|
|
53
53
|
this.indexStore.encoder.useFloat32 = FLOAT32_OPTIONS.ALWAYS;
|
|
54
54
|
}
|
|
55
|
-
this.distance =
|
|
56
|
-
options?.distance === 'euclidean'
|
|
57
|
-
? euclideanDistance
|
|
58
|
-
: options?.distance === 'dotProduct'
|
|
59
|
-
? dotProductDistance
|
|
60
|
-
: cosineDistance;
|
|
55
|
+
this.distance = options?.distance === 'euclidean' ? euclideanDistance : cosineDistance;
|
|
61
56
|
if (options) {
|
|
62
57
|
// allow all the HNSW parameters to be configured/tuned
|
|
63
58
|
if (options.M !== undefined) {
|
|
@@ -474,7 +469,6 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
474
469
|
let distanceFunction: (a: number[], b: number[]) => number;
|
|
475
470
|
if (distance === 'cosine') distanceFunction = cosineDistance;
|
|
476
471
|
else if (distance === 'euclidean') distanceFunction = euclideanDistance;
|
|
477
|
-
else if (distance === 'dotProduct') distanceFunction = dotProductDistance;
|
|
478
472
|
else if (distance) throw new ClientError('Unknown distance function');
|
|
479
473
|
else distanceFunction = this.distance;
|
|
480
474
|
if (!target) throw new ClientError('A target vector must be provided for an HNSW query');
|
|
@@ -664,12 +658,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
664
658
|
|
|
665
659
|
let distanceFunction = this.distance;
|
|
666
660
|
if (sortDefinition.type)
|
|
667
|
-
distanceFunction =
|
|
668
|
-
sortDefinition.distance === 'euclidean'
|
|
669
|
-
? euclideanDistance
|
|
670
|
-
: sortDefinition.distance === 'dotProduct'
|
|
671
|
-
? dotProductDistance
|
|
672
|
-
: cosineDistance;
|
|
661
|
+
distanceFunction = sortDefinition.distance === 'euclidean' ? euclideanDistance : cosineDistance;
|
|
673
662
|
const distance = distanceFunction(sortDefinition.target, vector);
|
|
674
663
|
vectorDistances.set(entry, distance);
|
|
675
664
|
return distance;
|
|
@@ -36,20 +36,3 @@ export function cosineDistance(a: number[], b: number[]): number {
|
|
|
36
36
|
|
|
37
37
|
return 1 - dotProduct / (magnitudeA * magnitudeB || 1);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
export function dotProductDistance(a: number[], b: number[]): number {
|
|
41
|
-
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
42
|
-
throw new Error('Inner product comparison requires an array');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let dotProduct = 0;
|
|
46
|
-
const length = Math.max(a.length, b.length);
|
|
47
|
-
|
|
48
|
-
for (let i = 0; i < length; i++) {
|
|
49
|
-
const va = a[i] || 0;
|
|
50
|
-
const vb = b[i] || 0;
|
|
51
|
-
dotProduct += va * vb;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return -dotProduct;
|
|
55
|
-
}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { parse } from 'dotenv';
|
|
2
2
|
import logger from '../utility/logging/harper_logger.js';
|
|
3
|
-
import { Scope } from '../components/Scope.ts';
|
|
4
3
|
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
logger.debug(`override option enabled. overriding environment variable: ${key}`);
|
|
18
|
-
} else {
|
|
19
|
-
continue;
|
|
4
|
+
export function start({ override }: { override: boolean }) {
|
|
5
|
+
return {
|
|
6
|
+
handleFile: (contents, _, filePath) => {
|
|
7
|
+
logger.debug(`Loading env file: ${filePath}`);
|
|
8
|
+
for (const [key, value] of Object.entries(parse(contents))) {
|
|
9
|
+
if (process.env[key] !== undefined) {
|
|
10
|
+
logger.warn(`Environment variable conflict: ${key} from ${filePath} is already set on process.env`);
|
|
11
|
+
if (override) {
|
|
12
|
+
logger.debug(`override option enabled. overriding environment variable: ${key}`);
|
|
13
|
+
} else {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
20
16
|
}
|
|
21
|
-
}
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
process.env[key] = value;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
26
22
|
}
|
package/core/resources/login.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Resource } from './Resource.ts';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
scope.resources.loginPath = (request) => {
|
|
2
|
+
export function start({ resources }) {
|
|
3
|
+
resources.set('login', Login);
|
|
4
|
+
resources.loginPath = (request) => {
|
|
6
5
|
return '/login?redirect=' + encodeURIComponent(request.url);
|
|
7
6
|
};
|
|
8
7
|
}
|