@dokebi/search 0.0.0-stage → 0.1.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/AI.md +134 -0
- package/LICENSE +21 -0
- package/README.md +8 -2
- package/dist/api.d.ts +78 -0
- package/dist/api.js +1 -0
- package/dist/cursor.d.ts +19 -0
- package/dist/cursor.js +68 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +13 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +58 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/indexes.d.ts +27 -0
- package/dist/indexes.js +87 -0
- package/dist/install.d.ts +37 -0
- package/dist/install.js +56 -0
- package/dist/options.d.ts +23 -0
- package/dist/options.js +73 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +708 -0
- package/dist/sql.d.ts +37 -0
- package/dist/sql.js +114 -0
- package/dist/store.d.ts +77 -0
- package/dist/store.js +164 -0
- package/dist/terms.d.ts +31 -0
- package/dist/terms.js +58 -0
- package/options.schema.json +95 -0
- package/package.json +86 -4
- package/templates/indexes.ts +10 -0
- package/templates/migrations/dokebi_search.ts +92 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Install lifecycle module (`dokebi.installModule`). It runs isolated in the CLI, so it imports
|
|
2
|
+
// nothing and only reads the plain options it is given. Invalid options are left to the schema.
|
|
3
|
+
const DB_PROVIDER = 'dokebi-db';
|
|
4
|
+
/** dokebi-db connection names (the same rule as its options). */
|
|
5
|
+
const CONNECTION = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
6
|
+
const MIGRATION_SOURCE = 'templates/migrations/dokebi_search.ts';
|
|
7
|
+
const DEFAULT_CONNECTION = 'main';
|
|
8
|
+
/** The cursor signing secrets: `dokebi add` writes a generated one into dokebi.yaml. */
|
|
9
|
+
const SECRETS_OPTION = 'secrets';
|
|
10
|
+
/** The user-owned index declaration (D-0170 §2), the plugin's entry. */
|
|
11
|
+
const ENTRY = Object.freeze({
|
|
12
|
+
source: 'templates/indexes.ts',
|
|
13
|
+
target: 'src/infra/search/indexes.ts',
|
|
14
|
+
});
|
|
15
|
+
function name(value, fallback) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return fallback;
|
|
18
|
+
return typeof value === 'string' && CONNECTION.test(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
/** UTC yyyyMMddHHmmss, the @dokebi/migrate file name prefix. */
|
|
21
|
+
function stamp(now) {
|
|
22
|
+
return now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The index declaration (src/infra/search/indexes.ts), the table migration in the migrations
|
|
26
|
+
* folder of `connection` (default main, the database of the application's rows) and a generated
|
|
27
|
+
* cursor secret in dokebi.yaml (a CLI without `configSecrets` leaves it to
|
|
28
|
+
* `npx dokebi config secret dokebi-search.secrets`, which the start asks for).
|
|
29
|
+
*/
|
|
30
|
+
export function planInstall(context) {
|
|
31
|
+
const connection = name(context.options.connection, DEFAULT_CONNECTION);
|
|
32
|
+
const secrets = Array.isArray(context.planKeys) && context.planKeys.includes('configSecrets')
|
|
33
|
+
? { configSecrets: [{ path: [SECRETS_OPTION] }] }
|
|
34
|
+
: {};
|
|
35
|
+
return {
|
|
36
|
+
requires: [DB_PROVIDER],
|
|
37
|
+
entry: ENTRY,
|
|
38
|
+
...secrets,
|
|
39
|
+
...(connection === undefined
|
|
40
|
+
? {}
|
|
41
|
+
: {
|
|
42
|
+
files: [
|
|
43
|
+
{
|
|
44
|
+
source: MIGRATION_SOURCE,
|
|
45
|
+
target: `src/infra/db/migrations/${connection}/${stamp(new Date())}_dokebi_search.ts`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function planUpgrade() {
|
|
52
|
+
return { requires: [DB_PROVIDER], entry: ENTRY };
|
|
53
|
+
}
|
|
54
|
+
export function check() {
|
|
55
|
+
return { requires: [DB_PROVIDER], diagnostics: [] };
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type OptionsOf } from '@dokebi/core';
|
|
2
|
+
/** dokebi-db connection names (the same rule as its options). */
|
|
3
|
+
export declare const CONNECTION_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$";
|
|
4
|
+
export declare const MIN_SECRET_LENGTH = 32;
|
|
5
|
+
export declare const DEFAULT_SECRETS_ENV = "DOKEBI_SEARCH_SECRETS";
|
|
6
|
+
export declare const options: import("@dokebi/core").OptionsDeclaration<{
|
|
7
|
+
readonly connection: string;
|
|
8
|
+
readonly counts: {
|
|
9
|
+
readonly flushMs: number;
|
|
10
|
+
readonly slots: number;
|
|
11
|
+
};
|
|
12
|
+
readonly examineLimit: number;
|
|
13
|
+
readonly list: {
|
|
14
|
+
readonly maxLimit: number;
|
|
15
|
+
};
|
|
16
|
+
readonly pool: {
|
|
17
|
+
readonly max: number;
|
|
18
|
+
};
|
|
19
|
+
readonly searchLimit: number;
|
|
20
|
+
readonly secrets?: readonly string[];
|
|
21
|
+
readonly secretsEnv?: string;
|
|
22
|
+
}>;
|
|
23
|
+
export type SearchOptions = OptionsOf<typeof options>;
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { defineOptions, option as o } from '@dokebi/core';
|
|
2
|
+
/*
|
|
3
|
+
* dokebi-search options (D-0170). options.schema.json is generated from this declaration at build
|
|
4
|
+
* time (D-0063 §3). The limits are rows of the term table a search reads (@dokebi/db AI.md 6.10).
|
|
5
|
+
*/
|
|
6
|
+
/** dokebi-db connection names (the same rule as its options). */
|
|
7
|
+
export const CONNECTION_PATTERN = '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$';
|
|
8
|
+
export const MIN_SECRET_LENGTH = 32;
|
|
9
|
+
export const DEFAULT_SECRETS_ENV = 'DOKEBI_SEARCH_SECRETS';
|
|
10
|
+
const ENV_NAME = '^[A-Za-z_][A-Za-z0-9_]{0,127}$';
|
|
11
|
+
/** A settings object whose fields all have defaults (the parser fills them). */
|
|
12
|
+
const EMPTY = {};
|
|
13
|
+
export const options = defineOptions({
|
|
14
|
+
connection: o.string({
|
|
15
|
+
pattern: CONNECTION_PATTERN,
|
|
16
|
+
default: 'main',
|
|
17
|
+
description: "The dokebi-db connection of the database that holds the application's rows (the terms share their transaction); dokebi add writes the table migration into its migrations folder.",
|
|
18
|
+
}),
|
|
19
|
+
pool: o.object({
|
|
20
|
+
max: o.integer({
|
|
21
|
+
minimum: 0,
|
|
22
|
+
maximum: 64,
|
|
23
|
+
default: 4,
|
|
24
|
+
description: 'Connections of the pool derived from the connection for find, so searches never take the request pool. 0 uses the connection pool.',
|
|
25
|
+
}),
|
|
26
|
+
}, { default: EMPTY }),
|
|
27
|
+
examineLimit: o.integer({
|
|
28
|
+
minimum: 100,
|
|
29
|
+
maximum: 100_000,
|
|
30
|
+
default: 2000,
|
|
31
|
+
description: 'Most rows of the leading word one find call examines when a search has several words; reached before the page is full, it answers the matches found with next and partial: true.',
|
|
32
|
+
}),
|
|
33
|
+
searchLimit: o.integer({
|
|
34
|
+
minimum: 100,
|
|
35
|
+
maximum: 10_000_000,
|
|
36
|
+
default: 50_000,
|
|
37
|
+
description: 'Most rows one search of several words examines over all its pages (kept in the signed cursor); reached, it ends with stopped: SEARCH_TOO_BROAD. At least examineLimit.',
|
|
38
|
+
}),
|
|
39
|
+
list: o.object({
|
|
40
|
+
maxLimit: o.integer({
|
|
41
|
+
minimum: 1,
|
|
42
|
+
maximum: 1000,
|
|
43
|
+
default: 100,
|
|
44
|
+
description: 'Most items one find call returns.',
|
|
45
|
+
}),
|
|
46
|
+
}, { default: EMPTY }),
|
|
47
|
+
counts: o.object({
|
|
48
|
+
slots: o.integer({
|
|
49
|
+
minimum: 1,
|
|
50
|
+
maximum: 64,
|
|
51
|
+
default: 4,
|
|
52
|
+
description: "Rows a word's item count is split into (a flush adds to one at random, a read sums them), so the workers' flushes of common words do not wait on one row.",
|
|
53
|
+
}),
|
|
54
|
+
flushMs: o.integer({
|
|
55
|
+
minimum: 100,
|
|
56
|
+
maximum: 60_000,
|
|
57
|
+
default: 1000,
|
|
58
|
+
description: "How often each worker adds its buffered word counts to the table, outside every transaction (the counts only choose a search's leading word); also at drain and when 2000 words are buffered.",
|
|
59
|
+
}),
|
|
60
|
+
}, { default: EMPTY }),
|
|
61
|
+
secrets: o.array(o.string({ minLength: MIN_SECRET_LENGTH, maxLength: 512, secret: true }), {
|
|
62
|
+
minItems: 1,
|
|
63
|
+
maxItems: 8,
|
|
64
|
+
uniqueItems: true,
|
|
65
|
+
description: 'Cursor signing secrets (32+ characters); the first signs, all verify. dokebi add writes a generated one (npx dokebi config secret dokebi-search.secrets makes one later); keep dokebi.yaml out of version control',
|
|
66
|
+
}),
|
|
67
|
+
secretsEnv: o.string({
|
|
68
|
+
pattern: ENV_NAME,
|
|
69
|
+
// The name of an environment variable, never a secret itself.
|
|
70
|
+
plain: true,
|
|
71
|
+
description: `Environment variable that, when set and not empty, replaces secrets (comma-separated, the first signs): an optional production override (default ${DEFAULT_SECRETS_ENV})`,
|
|
72
|
+
}),
|
|
73
|
+
});
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Describer } from '@dokebi/core';
|
|
2
|
+
import type { SearchApi } from './api.js';
|
|
3
|
+
/** The `core:describe` point (core 0.4.2), declared by name. */
|
|
4
|
+
export declare const describePoint: import("@dokebi/core").ContributionPoint<Describer>;
|
|
5
|
+
/** Factory input: `{ app, options?, entry }`: `entry` is the module of src/infra/search/indexes.ts. */
|
|
6
|
+
export declare const createPlugin: import("@dokebi/core").PluginFactory<SearchApi>;
|