import { Key } from 'interface-datastore';
import { OpenFailedError } from 'interface-store';
import { BaseDatastore } from './base.js';
import { KeyTransformDatastore } from './keytransform.js';
import { readShardFun, SHARDING_FN } from './shard.js';
const shardKey = new Key(SHARDING_FN);
/**
 * Backend independent abstraction of go-ds-flatfs.
 *
 * Wraps another datastore such that all values are stored
 * sharded according to the given sharding function.
 */
export class ShardingDatastore extends BaseDatastore {
    child;
    shard;
    constructor(store, shard) {
        super();
        this.child = new KeyTransformDatastore(store, {
            convert: this._convertKey.bind(this),
            invert: this._invertKey.bind(this)
        });
        this.shard = shard;
    }
    async open() {
        this.shard = await ShardingDatastore.create(this.child, this.shard);
    }
    _convertKey(key) {
        const s = key.toString();
        if (s === shardKey.toString()) {
            return key;
        }
        const parent = new Key(this.shard.fun(s));
        return parent.child(key);
    }
    _invertKey(key) {
        const s = key.toString();
        if (s === shardKey.toString()) {
            return key;
        }
        return Key.withNamespaces(key.list().slice(1));
    }
    static async create(store, shard) {
        const hasShard = await store.has(shardKey);
        if (!hasShard) {
            if (shard == null) {
                throw new OpenFailedError('Shard is required when datastore doesn\'t have a shard key already');
            }
            await store.put(shardKey, new TextEncoder().encode(shard.toString() + '\n'));
        }
        if (shard == null) {
            shard = await readShardFun('/', store);
        }
        // test shards
        const diskShard = await readShardFun('/', store);
        const a = diskShard.toString();
        const b = shard.toString();
        if (a !== b) {
            throw new Error(`specified fun ${b} does not match repo shard fun ${a}`);
        }
        return diskShard;
    }
    async put(key, val, options) {
        await this.child.put(key, val, options);
        return key;
    }
    async get(key, options) {
        return this.child.get(key, options);
    }
    async has(key, options) {
        return this.child.has(key, options);
    }
    async delete(key, options) {
        await this.child.delete(key, options);
    }
    async *putMany(source, options = {}) {
        yield* this.child.putMany(source, options);
    }
    async *getMany(source, options = {}) {
        yield* this.child.getMany(source, options);
    }
    async *deleteMany(source, options = {}) {
        yield* this.child.deleteMany(source, options);
    }
    batch() {
        return this.child.batch();
    }
    query(q, options) {
        const omitShard = ({ key }) => key.toString() !== shardKey.toString();
        const tq = {
            ...q,
            filters: [
                omitShard
            ].concat(q.filters ?? [])
        };
        return this.child.query(tq, options);
    }
    queryKeys(q, options) {
        const omitShard = (key) => key.toString() !== shardKey.toString();
        const tq = {
            ...q,
            filters: [
                omitShard
            ].concat(q.filters ?? [])
        };
        return this.child.queryKeys(tq, options);
    }
}
//# sourceMappingURL=sharding.js.map