@jd-data-limited/easy-fm 5.0.0-beta.2 → 5.0.0-beta.4

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.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -91
  3. package/dist/bin/cli.js +34 -5
  4. package/dist/bin/generateClient.d.ts +1 -0
  5. package/dist/bin/generateClient.js +48 -0
  6. package/dist/bin/generateTypes.js +19 -18
  7. package/dist/bin/init.d.ts +6 -0
  8. package/dist/bin/init.js +46 -0
  9. package/dist/bin/stressSearch.js +12 -12
  10. package/dist/connection/DataApiProvider.d.ts +15 -0
  11. package/dist/connection/DataApiProvider.js +246 -0
  12. package/dist/connection/dataApiDatabaseSession.d.ts +11 -0
  13. package/dist/connection/dataApiDatabaseSession.js +139 -0
  14. package/dist/connection/database.d.ts +22 -56
  15. package/dist/connection/database.js +175 -111
  16. package/dist/connection/databaseBase.d.ts +9 -22
  17. package/dist/connection/databaseBase.js +0 -3
  18. package/dist/connection/databaseProvider.d.ts +196 -0
  19. package/dist/connection/databaseProvider.js +6 -0
  20. package/dist/connection/databaseSessionPool.d.ts +8 -7
  21. package/dist/connection/databaseSessionPool.js +110 -106
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +3 -3
  24. package/dist/layouts/layout.d.ts +0 -2
  25. package/dist/layouts/layout.js +6 -16
  26. package/dist/layouts/layoutBase.d.ts +0 -1
  27. package/dist/layouts/layoutInterface.d.ts +4 -2
  28. package/dist/records/fields/baseField.d.ts +4 -1
  29. package/dist/records/fields/containerField.d.ts +6 -23
  30. package/dist/records/fields/containerField.js +34 -68
  31. package/dist/records/fields/valueField.js +6 -6
  32. package/dist/records/getOperations/recordGetOperation.js +19 -18
  33. package/dist/records/layoutRecord.d.ts +8 -2
  34. package/dist/records/layoutRecord.js +45 -74
  35. package/dist/records/portalRecord.d.ts +4 -1
  36. package/dist/records/portalRecord.js +12 -2
  37. package/dist/records/recordBase.d.ts +6 -3
  38. package/dist/records/recordBase.js +6 -5
  39. package/dist/schema.d.ts +54 -0
  40. package/dist/schema.js +87 -0
  41. package/dist/typegen/dataApiSchemaSource.d.ts +3 -0
  42. package/dist/typegen/dataApiSchemaSource.js +32 -0
  43. package/dist/typegen/dependencies.d.ts +11 -0
  44. package/dist/typegen/dependencies.js +67 -0
  45. package/dist/typegen/generate.d.ts +5 -0
  46. package/dist/typegen/generate.js +85 -0
  47. package/dist/typegen/index.d.ts +6 -0
  48. package/dist/typegen/index.js +6 -0
  49. package/dist/typegen/init.d.ts +13 -0
  50. package/dist/typegen/init.js +113 -0
  51. package/dist/typegen/types.d.ts +40 -0
  52. package/dist/typegen/types.js +1 -0
  53. package/dist/typegen/zod.d.ts +3 -0
  54. package/dist/typegen/zod.js +32 -0
  55. package/package.json +26 -4
  56. package/dist/layouts/recordFieldsMap.d.ts +0 -2
  57. package/dist/layouts/recordFieldsMap.js +0 -4
  58. package/dist/records/field.d.ts +0 -77
  59. package/dist/records/field.js +0 -192
@@ -0,0 +1,67 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { dirname, parse, resolve } from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ const ZOD_VERSION = '^4.6.5';
5
+ /** Ensures generated code can resolve its direct `zod` import. */
6
+ export async function ensureZodInstalled(start) {
7
+ const plan = await getZodInstallPlan(start);
8
+ if (!plan)
9
+ return false;
10
+ await runInstall(plan);
11
+ return true;
12
+ }
13
+ /** Returns null when Zod is already declared. Exported for alternate CLIs. */
14
+ export async function getZodInstallPlan(start) {
15
+ const packagePath = await findUp(resolve(start), 'package.json');
16
+ if (!packagePath)
17
+ throw new Error(`Cannot install Zod: no package.json found above ${resolve(start)}`);
18
+ const manifest = JSON.parse(await readFile(packagePath, 'utf8'));
19
+ const dependencyGroups = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
20
+ if (dependencyGroups.some(group => hasDependency(manifest[group], 'zod')))
21
+ return null;
22
+ const cwd = dirname(packagePath);
23
+ const manager = await detectPackageManager(cwd, manifest.packageManager);
24
+ return { cwd, command: manager, args: installArguments(manager) };
25
+ }
26
+ function hasDependency(value, name) {
27
+ return typeof value === 'object' && value !== null && name in value;
28
+ }
29
+ async function detectPackageManager(cwd, declared) {
30
+ if (typeof declared === 'string') {
31
+ const name = declared.split('@')[0];
32
+ if (name === 'pnpm' || name === 'npm' || name === 'yarn' || name === 'bun')
33
+ return name;
34
+ }
35
+ const locks = [['pnpm-lock.yaml', 'pnpm'], ['yarn.lock', 'yarn'], ['bun.lock', 'bun'], ['bun.lockb', 'bun'], ['package-lock.json', 'npm']];
36
+ for (const [file, manager] of locks)
37
+ if (await findUp(cwd, file))
38
+ return manager;
39
+ return 'npm';
40
+ }
41
+ function installArguments(manager) {
42
+ if (manager === 'npm')
43
+ return ['install', `zod@${ZOD_VERSION}`];
44
+ return ['add', `zod@${ZOD_VERSION}`];
45
+ }
46
+ async function runInstall({ cwd, command, args }) {
47
+ await new Promise((resolvePromise, reject) => {
48
+ const child = spawn(command, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });
49
+ child.once('error', reject);
50
+ child.once('exit', code => code === 0 ? resolvePromise() : reject(new Error(`${command} ${args.join(' ')} exited with code ${code ?? 'unknown'}`)));
51
+ });
52
+ }
53
+ async function findUp(start, name) {
54
+ let directory = start;
55
+ while (true) {
56
+ const candidate = resolve(directory, name);
57
+ try {
58
+ await access(candidate);
59
+ return candidate;
60
+ }
61
+ catch { }
62
+ const parent = dirname(directory);
63
+ if (parent === directory || directory === parse(directory).root)
64
+ return undefined;
65
+ directory = parent;
66
+ }
67
+ }
@@ -0,0 +1,5 @@
1
+ import type { TypegenConfig } from './types.js';
2
+ export declare function generate(config: TypegenConfig): Promise<{
3
+ output: string;
4
+ layouts: number;
5
+ }>;
@@ -0,0 +1,85 @@
1
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { zodTypegenValidator } from './zod.js';
4
+ export async function generate(config) {
5
+ const discovered = await config.source.introspect();
6
+ const selected = config.layouts ? discovered.layouts.filter(layout => config.layouts.includes(layout.name)) : discovered.layouts;
7
+ const missing = config.layouts?.filter(name => !selected.some(layout => layout.name === name)) ?? [];
8
+ if (missing.length)
9
+ throw new Error(`Layouts not found: ${missing.join(', ')}`);
10
+ const schema = { version: 1, layouts: [...selected].sort((a, b) => a.name.localeCompare(b.name)) };
11
+ const output = resolve(config.output);
12
+ const temporary = `${output}.tmp-${process.pid}`;
13
+ await rm(temporary, { recursive: true, force: true });
14
+ await mkdir(temporary, { recursive: true });
15
+ await writeFile(resolve(temporary, 'client.ts'), renderClient(schema, config.validator ?? zodTypegenValidator()), 'utf8');
16
+ await writeFile(resolve(temporary, 'schema.json'), `${JSON.stringify({ ...schema, transports: config.transports ?? ['data-api', 'odata'] }, null, 2)}\n`, 'utf8');
17
+ await mkdir(dirname(output), { recursive: true });
18
+ await rm(output, { recursive: true, force: true });
19
+ await rename(temporary, output);
20
+ return { output, layouts: schema.layouts.length };
21
+ }
22
+ function renderClient(schema, validator) {
23
+ const interfaces = schema.layouts.map(renderLayout).join('\n\n');
24
+ const structure = schema.layouts.map(layout => `${quote(layout.name)}: ${layoutTypeName(layout)}`).join(',\n ');
25
+ const validators = schema.layouts.map(layout => `${quote(layout.name)}: {\n record: ${validator.renderRecord(layout)},\n metadata: ${renderMetadata(layout)}\n }`).join(',\n ');
26
+ const properties = schema.layouts.map(layout => `${safeProperty(layout.clientName ?? layout.name)}: database.layout<${layoutTypeName(layout)}>(${quote(layout.name)})`).join(',\n ');
27
+ return `/* Generated by easyfm typegen. Do not edit. */
28
+ import {Database, withSchemaValidation, ${validator.runtimeValidator}, type DatabaseProvider, type DatabaseStructure, type LayoutInterface, type Portal, type ReadonlyField, type TextField, type NumberField, type DateField, type TimeField, type TimeStampField, type ContainerField} from '@jd-data-limited/easy-fm'
29
+ ${validator.imports.join('\n')}
30
+
31
+ ${interfaces}
32
+
33
+ export interface GeneratedDatabase extends DatabaseStructure {
34
+ layouts: {
35
+ ${structure}
36
+ }
37
+ }
38
+
39
+ export const easyFMSchema = {
40
+ validator: ${validator.runtimeValidator},
41
+ layouts: {
42
+ ${validators}
43
+ }
44
+ } satisfies Parameters<typeof withSchemaValidation>[1]
45
+
46
+ export function createEasyFMClient (provider: DatabaseProvider) {
47
+ const database = Database.create<GeneratedDatabase>({provider: withSchemaValidation(provider, easyFMSchema)})
48
+ return {
49
+ database,
50
+ ${properties}
51
+ }
52
+ }
53
+ `;
54
+ }
55
+ function renderLayout(layout) {
56
+ const fields = layout.fields.map(field => `${quote(field.name)}: ${fieldType(field)}`).join('\n ');
57
+ const portals = Object.entries(layout.portals).map(([name, portalFields]) => `${quote(name)}: Portal<{\n ${portalFields.map(field => `${quote(field.name)}: ${fieldType(field)}`).join('\n ')}\n }>`).join('\n ');
58
+ return `export interface ${layoutTypeName(layout)} extends LayoutInterface {
59
+ fields: {
60
+ ${fields}
61
+ }
62
+ portals: {
63
+ ${portals}
64
+ }
65
+ }`;
66
+ }
67
+ function fieldType(field) {
68
+ const type = { text: 'TextField', number: 'NumberField', date: 'DateField', time: 'TimeField', timeStamp: 'TimeStampField', container: 'ContainerField' }[field.result];
69
+ return field.writable ? type : `ReadonlyField<${type}>`;
70
+ }
71
+ function renderMetadata(layout) {
72
+ const fields = renderMetadataFields(layout.fields);
73
+ const portals = Object.entries(layout.portals).map(([name, portalFields]) => `${quote(name)}: ${renderMetadataFields(portalFields)}`).join(', ');
74
+ return `{fields: ${fields}, portals: {${portals}}}`;
75
+ }
76
+ function renderMetadataFields(fields) {
77
+ return `{${fields.map(field => `${quote(field.name)}: {result: ${quote(field.result)}, type: ${quote(field.type)}}`).join(', ')}}`;
78
+ }
79
+ function layoutTypeName(layout) { return `${identifier(layout.clientName ?? layout.name, 'Layout')}Layout`; }
80
+ function safeProperty(value) { return identifier(value, 'layout'); }
81
+ function identifier(value, fallback) {
82
+ const cleaned = value.replace(/[^a-zA-Z0-9_$]/g, '_').replace(/^[^a-zA-Z_$]+/, '');
83
+ return cleaned || fallback;
84
+ }
85
+ function quote(value) { return JSON.stringify(value); }
@@ -0,0 +1,6 @@
1
+ export { generate } from './generate.js';
2
+ export { dataApiSchemaSource } from './dataApiSchemaSource.js';
3
+ export { zodTypegenValidator } from './zod.js';
4
+ export { configExists, readEnvironment, writeDataApiInitialization, type DataApiInitAnswers } from './init.js';
5
+ export { ensureZodInstalled, getZodInstallPlan, type InstallPlan } from './dependencies.js';
6
+ export { defineTypegenConfig, type SchemaSource, type TypegenConfig, type TypegenField, type TypegenLayout, type TypegenSchema, type TypegenTransport, type TypegenValidator } from './types.js';
@@ -0,0 +1,6 @@
1
+ export { generate } from './generate.js';
2
+ export { dataApiSchemaSource } from './dataApiSchemaSource.js';
3
+ export { zodTypegenValidator } from './zod.js';
4
+ export { configExists, readEnvironment, writeDataApiInitialization } from './init.js';
5
+ export { ensureZodInstalled, getZodInstallPlan } from './dependencies.js';
6
+ export { defineTypegenConfig } from './types.js';
@@ -0,0 +1,13 @@
1
+ export interface DataApiInitAnswers {
2
+ provider: 'data-api/odata';
3
+ transports: 'data-api' | 'odata' | 'both';
4
+ hostname: string;
5
+ database: string;
6
+ username: string;
7
+ password: string;
8
+ output: string;
9
+ }
10
+ export declare function configExists(path: string): Promise<boolean>;
11
+ /** Non-interactive write step, usable by alternate setup UIs. */
12
+ export declare function writeDataApiInitialization(config: string, answers: DataApiInitAnswers): Promise<void>;
13
+ export declare function readEnvironment(path: string): Promise<Record<string, string>>;
@@ -0,0 +1,113 @@
1
+ import { mkdir, open, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ export async function configExists(path) {
4
+ try {
5
+ await readFile(path);
6
+ return true;
7
+ }
8
+ catch (error) {
9
+ if (error.code === 'ENOENT')
10
+ return false;
11
+ throw error;
12
+ }
13
+ }
14
+ /** Non-interactive write step, usable by alternate setup UIs. */
15
+ export async function writeDataApiInitialization(config, answers) {
16
+ const configPath = resolve(config);
17
+ const directory = dirname(configPath);
18
+ const envPath = resolve(directory, '.env');
19
+ await mkdir(directory, { recursive: true });
20
+ const file = await open(configPath, 'wx');
21
+ try {
22
+ await file.writeFile(renderConfig(answers), 'utf8');
23
+ }
24
+ finally {
25
+ await file.close();
26
+ }
27
+ await updateEnvironment(envPath, { FM_HOST: answers.hostname, FM_DATABASE: answers.database, FM_USERNAME: answers.username, FM_PASSWORD: answers.password });
28
+ await ensureEnvironmentIgnored(directory);
29
+ }
30
+ export async function readEnvironment(path) {
31
+ try {
32
+ const contents = await readFile(path, 'utf8');
33
+ return Object.fromEntries(contents.split(/\r?\n/).map(line => line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/)).filter(match => match).map(match => [match[1], decodeEnvironmentValue(match[2])]));
34
+ }
35
+ catch (error) {
36
+ if (error.code === 'ENOENT')
37
+ return {};
38
+ throw error;
39
+ }
40
+ }
41
+ function renderConfig(answers) {
42
+ return `import {dataApiSchemaSource, defineTypegenConfig, zodTypegenValidator} from '@jd-data-limited/easy-fm/typegen'
43
+
44
+ function required (name) {
45
+ const value = process.env[name]
46
+ if (!value) throw new Error(\`Missing required environment variable \${name}\`)
47
+ return value
48
+ }
49
+
50
+ export default defineTypegenConfig({
51
+ output: ${JSON.stringify(answers.output)},
52
+ transports: ${JSON.stringify(answers.transports === 'both' ? ['data-api', 'odata'] : [answers.transports])},
53
+ validator: zodTypegenValidator(),
54
+ source: dataApiSchemaSource({
55
+ hostname: required('FM_HOST'),
56
+ database: required('FM_DATABASE'),
57
+ credentials: {method: 'filemaker', username: required('FM_USERNAME'), password: required('FM_PASSWORD')},
58
+ externalSources: []
59
+ })
60
+ })
61
+ `;
62
+ }
63
+ async function updateEnvironment(path, values) {
64
+ let contents = '';
65
+ try {
66
+ contents = await readFile(path, 'utf8');
67
+ }
68
+ catch (error) {
69
+ if (error.code !== 'ENOENT')
70
+ throw error;
71
+ }
72
+ const lines = contents ? contents.split(/\r?\n/) : [];
73
+ for (const [name, value] of Object.entries(values)) {
74
+ const replacement = `${name}=${JSON.stringify(value)}`;
75
+ const index = lines.findIndex(line => line.startsWith(`${name}=`));
76
+ if (index >= 0)
77
+ lines[index] = replacement;
78
+ else
79
+ lines.push(replacement);
80
+ }
81
+ await writeFile(path, `${lines.filter((line, index) => line || index < lines.length - 1).join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
82
+ }
83
+ function decodeEnvironmentValue(value) {
84
+ const trimmed = value.trim();
85
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
86
+ try {
87
+ return JSON.parse(trimmed);
88
+ }
89
+ catch {
90
+ return trimmed.slice(1, -1);
91
+ }
92
+ }
93
+ return trimmed;
94
+ }
95
+ async function ensureEnvironmentIgnored(directory) {
96
+ const path = resolve(directory, '.gitignore');
97
+ let contents = '';
98
+ try {
99
+ contents = await readFile(path, 'utf8');
100
+ }
101
+ catch (error) {
102
+ if (error.code !== 'ENOENT')
103
+ throw error;
104
+ }
105
+ const alreadyIgnored = contents.split(/\r?\n/).some(line => {
106
+ const rule = line.trim();
107
+ return rule === '.env' || rule === '/.env' || rule === '.env*' || rule === '/.env*';
108
+ });
109
+ if (alreadyIgnored)
110
+ return;
111
+ const prefix = contents && !contents.endsWith('\n') ? '\n' : '';
112
+ await writeFile(path, `${contents}${prefix}.env\n`, 'utf8');
113
+ }
@@ -0,0 +1,40 @@
1
+ import type { ApiFieldResultTypes, ApiFieldTypes } from '../models/apiResults.js';
2
+ export interface TypegenField {
3
+ name: string;
4
+ result: typeof ApiFieldResultTypes[keyof typeof ApiFieldResultTypes];
5
+ type: typeof ApiFieldTypes[keyof typeof ApiFieldTypes];
6
+ writable: boolean;
7
+ required: boolean;
8
+ }
9
+ export interface TypegenLayout {
10
+ name: string;
11
+ clientName?: string;
12
+ fields: TypegenField[];
13
+ portals: Record<string, TypegenField[]>;
14
+ }
15
+ export interface TypegenSchema {
16
+ version: 1;
17
+ layouts: TypegenLayout[];
18
+ }
19
+ export type TypegenTransport = 'data-api' | 'odata';
20
+ export interface SchemaSource {
21
+ introspect(): Promise<TypegenSchema>;
22
+ }
23
+ /** Code-generation plugin for a runtime validation library. */
24
+ export interface TypegenValidator {
25
+ readonly imports: readonly string[];
26
+ readonly runtimeValidator: string;
27
+ /** Render a validator for the complete canonical ProviderRecord. */
28
+ renderRecord(layout: TypegenLayout): string;
29
+ }
30
+ export interface TypegenConfig {
31
+ source: SchemaSource;
32
+ output: string;
33
+ /** Placeholder describing intended runtime transports. */
34
+ transports?: TypegenTransport[];
35
+ /** Restrict output to these FileMaker layout names. */
36
+ layouts?: string[];
37
+ /** Defaults to the built-in strict Zod validator. */
38
+ validator?: TypegenValidator;
39
+ }
40
+ export declare function defineTypegenConfig(config: TypegenConfig): TypegenConfig;
@@ -0,0 +1 @@
1
+ export function defineTypegenConfig(config) { return config; }
@@ -0,0 +1,3 @@
1
+ import type { TypegenValidator } from './types.js';
2
+ /** Strict Zod generator. Extra, missing, and wrongly typed fields fail validation. */
3
+ export declare function zodTypegenValidator(): TypegenValidator;
@@ -0,0 +1,32 @@
1
+ /** Strict Zod generator. Extra, missing, and wrongly typed fields fail validation. */
2
+ export function zodTypegenValidator() {
3
+ return {
4
+ imports: ["import {z} from 'zod'"],
5
+ runtimeValidator: 'zodValidator',
6
+ renderRecord: renderProviderRecord
7
+ };
8
+ }
9
+ function renderProviderRecord(layout) {
10
+ const fieldData = objectSchema(layout.fields);
11
+ const portals = Object.entries(layout.portals).map(([name, fields]) => `${quote(name)}: z.array(${portalRowSchema(fields)})`).join(', ');
12
+ const portalData = `z.object({${portals}}).partial().strict().optional()`;
13
+ return `z.object({recordId: z.string(), modId: z.string(), fieldData: ${fieldData}, portalData: ${portalData}}).strict()`;
14
+ }
15
+ function objectSchema(fields) {
16
+ return `z.object({${fields.map(field => `${quote(field.name)}: ${fieldSchema(field)}`).join(', ')}}).strict()`;
17
+ }
18
+ function portalRowSchema(fields) {
19
+ const entries = fields.map(field => `${quote(field.name)}: ${fieldSchema(field)}`);
20
+ entries.push('recordId: z.union([z.string(), z.number()])', 'modId: z.union([z.string(), z.number()])');
21
+ return `z.object({${entries.join(', ')}}).strict()`;
22
+ }
23
+ function fieldSchema(field) {
24
+ if (field.result === 'number')
25
+ return 'z.number().nullable()';
26
+ if (field.result === 'date' || field.result === 'time' || field.result === 'timeStamp')
27
+ return 'z.string().nullable()';
28
+ if (field.result === 'container')
29
+ return 'z.string().nullable()';
30
+ return 'z.string()';
31
+ }
32
+ function quote(value) { return JSON.stringify(value); }
package/package.json CHANGED
@@ -1,13 +1,27 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0-beta.4",
4
4
  "dependencies": {
5
+ "chalk": "^5.6.2",
5
6
  "commander": "^15.0.0",
7
+ "inquirer": "^14.2.2",
6
8
  "temporal-polyfill": "^1.0.5",
7
9
  "zod": "^4.6.5"
8
10
  },
9
11
  "type": "module",
10
12
  "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "sideEffects": false,
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./typegen": {
21
+ "types": "./dist/typegen/index.d.ts",
22
+ "import": "./dist/typegen/index.js"
23
+ }
24
+ },
11
25
  "module": "es2022",
12
26
  "homepage": "https://jd-data.com/project/easyfm/",
13
27
  "url": "https://github.com/JD-Data-Limited/easy-fm",
@@ -44,6 +58,7 @@
44
58
  "@babel/core": "^8.0.6",
45
59
  "@babel/preset-env": "^8.0.6",
46
60
  "@babel/preset-typescript": "^8.0.1",
61
+ "@cloudflare/vitest-plugin": "^1.2.2",
47
62
  "@types/btoa": "^1.2.5",
48
63
  "@types/events": "^3.0.3",
49
64
  "@types/inquirer": "^9.0.10",
@@ -57,17 +72,20 @@
57
72
  "eslint-plugin-import": "^2.32.0",
58
73
  "eslint-plugin-n": "^18.3.0",
59
74
  "eslint-plugin-promise": "^7.3.0",
60
- "inquirer": "^14.2.2",
61
75
  "jest": "^30.5.2",
62
76
  "ts-jest": "^29.4.12",
63
77
  "ts-morph": "^28.0.0",
64
78
  "tsx": "^4.23.15",
65
79
  "typedoc": "^0.28.20",
66
- "typescript": "^6.0.3"
80
+ "typescript": "^6.0.3",
81
+ "vitest": "^4.1.11"
67
82
  },
68
83
  "bin": {
69
84
  "easyfm": "dist/bin/cli.js"
70
85
  },
86
+ "publishConfig": {
87
+ "access": "public"
88
+ },
71
89
  "scripts": {
72
90
  "build": "tsc",
73
91
  "test": "npm run build && jest",
@@ -75,7 +93,11 @@
75
93
  "test:coverage": "jest --coverage --testPathIgnorePatterns='/__tests__/database.test.ts$'",
76
94
  "test:integration": "npm run build && jest __tests__/database.test.ts --runInBand",
77
95
  "test:unit": "jest __tests__/query.test.ts __tests__/CookieJar.test.ts --runInBand",
96
+ "test:runtime": "pnpm build && pnpm test:runtime:bun && pnpm test:runtime:deno && pnpm test:runtime:workers",
97
+ "test:runtime:bun": "bun test ./runtime-tests/bun.test.ts",
98
+ "test:runtime:deno": "deno test --no-check --allow-read --node-modules-dir=manual ./runtime-tests/deno.test.ts",
99
+ "test:runtime:workers": "vitest run --config ./runtime-tests/workers/vitest.config.ts",
78
100
  "stress:search": "tsx src/bin/stressSearch.ts",
79
- "docs:build": "typedoc --options ./docs/theme.json"
101
+ "docs:build": "typedoc --options ../../docs/theme.json"
80
102
  }
81
103
  }
@@ -1,2 +0,0 @@
1
- import { type Field, type FieldValue } from '../records/field.js';
2
- export type RecordFieldsMap = Record<string, Field<FieldValue>>;
@@ -1,4 +0,0 @@
1
- /*
2
- * Copyright (c) 2023-2024. See LICENSE file for more information
3
- */
4
- export {};
@@ -1,77 +0,0 @@
1
- import { RecordTypes } from '../types.js';
2
- import { type ApiFieldMetadata } from '../models/apiResults.js';
3
- import { type LayoutBase } from '../layouts/layoutBase.js';
4
- import { type Moment } from 'moment';
5
- import { Readable } from 'node:stream';
6
- import { type z } from 'zod';
7
- export type FieldValue = string | number | Moment | Container;
8
- export type Container = null;
9
- export interface Parentable {
10
- layout: LayoutBase;
11
- type: RecordTypes;
12
- endpoint: string;
13
- portal?: {
14
- name: string;
15
- };
16
- }
17
- interface StreamOptions {
18
- abortSignal?: AbortSignal;
19
- }
20
- /**
21
- * A class representing a field in a record.
22
- *
23
- * @template T - The type of the field value.
24
- */
25
- export declare class Field<T extends FieldValue> {
26
- #private;
27
- parent: Parentable;
28
- id: string;
29
- protected _value: T | null;
30
- static firstContainerDownload: Promise<any> | null;
31
- constructor(record: Parentable, id: string, originalContents: T);
32
- /** `true` if this field has changed since it was last loaded or saved. */
33
- get edited(): boolean;
34
- /**
35
- * Resets the 'edited' state of this field, without changing its value
36
- */
37
- updateOriginalContents(): void;
38
- /**
39
- * Sets the value of the field.
40
- *
41
- * @param {T | null} content - The new content value to be set. Can be either the type T or null.
42
- * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
43
- */
44
- set(content: T | null): void;
45
- /** Returns the FileMaker metadata for this field. */
46
- get metadata(): z.infer<typeof ApiFieldMetadata>;
47
- /** Gets or sets the current field value. */
48
- get value(): T | null;
49
- set value(value: T);
50
- /** Returns this field value as a string. Throws if the value is not a string. */
51
- get string(): string;
52
- /**
53
- * Uploads a file to the container field.
54
- *
55
- * @param {Buffer} file - The file content as a buffer.
56
- *
57
- * @throws {Error} - Cannot upload a file to the field if it's not a container field.
58
- * @throws {Error} - Upload failed with HTTP error.
59
- *
60
- * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
61
- */
62
- upload(file: File): Promise<void>;
63
- stream(): Promise<{
64
- data: Readable;
65
- mime: string;
66
- }>;
67
- /**
68
- * Returns the container download as a Web `Response`.
69
- */
70
- webStream(options: StreamOptions): Promise<Response>;
71
- /** Downloads the full container contents into memory. */
72
- arrayBuffer(): Promise<{
73
- data: ArrayBuffer;
74
- mime: string;
75
- }>;
76
- }
77
- export {};